diff --git a/packages/git/src/git-helpers.js b/packages/git/src/git-helpers.js index 75fe87fa..87e00862 100644 --- a/packages/git/src/git-helpers.js +++ b/packages/git/src/git-helpers.js @@ -70,3 +70,15 @@ export const find_repo_root = async (fs, pwd) => { throw new Error('not a git repository (or any of the parent directories): .git'); } + +/** + * Produce a shortened version of the given hash, which is still unique within the repo. + * TODO: Ensure that whatever we produce is unique within the repo. + * For now this is just a convenience function, so there's one place to change later. + * @param hash + * @returns {String} The shortened hash + */ +export const shorten_hash = (hash) => { + // TODO: Ensure that whatever we produce is unique within the repo + return hash.slice(0, 7); +} diff --git a/packages/git/src/subcommands/__exports__.js b/packages/git/src/subcommands/__exports__.js index 4e89b787..a2fb2741 100644 --- a/packages/git/src/subcommands/__exports__.js +++ b/packages/git/src/subcommands/__exports__.js @@ -18,6 +18,7 @@ */ // Generated by /tools/gen.js import module_add from './add.js' +import module_commit from './commit.js' import module_help from './help.js' import module_init from './init.js' import module_status from './status.js' @@ -25,6 +26,7 @@ import module_version from './version.js' export default { "add": module_add, + "commit": module_commit, "help": module_help, "init": module_init, "status": module_status, diff --git a/packages/git/src/subcommands/commit.js b/packages/git/src/subcommands/commit.js new file mode 100644 index 00000000..35ab9042 --- /dev/null +++ b/packages/git/src/subcommands/commit.js @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import path from 'path-browserify'; +import { ErrorCodes } from '@heyputer/puter-js-common/src/PosixError.js'; +import { find_repo_root, shorten_hash } from '../git-helpers.js'; + +export default { + name: 'commit', + usage: 'git commit [-m|--message ] [-a|--author ]', + description: 'Commit staged changes to the repository.', + args: { + allowPositionals: false, + options: { + message: { + description: 'Specify the commit message', + type: 'string', + short: 'm', + }, + author: { + description: 'Specify the commit author, as `A U Thor `', + type: 'string', + }, + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + if (!options.message) { + // TODO: Support opening a temporary file in an editor, + // where the user can edit the commit message if it's not specified. + stderr('You must specify a commit message with --message or -m'); + return 1; + } + + const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD); + + let user_name; + let user_email; + + if (options.author) { + const author_regex = /(.+?)\s+<(.+)>/; + const matches = options.author.match(author_regex); + if (!matches) + throw new Error('Failed to parse author string'); + user_name = matches[1]; + user_email = matches[2]; + } else { + user_name = await git.getConfig({ + fs, + dir: repository_dir, + gitdir: git_dir, + path: 'user.name', + }); + user_email = await git.getConfig({ + fs, + dir: repository_dir, + gitdir: git_dir, + path: 'user.email', + }); + } + + if (!user_name || !user_email) { + throw new Error('Missing author information. Either provide --author="A " or set user.name and user.email in the git config'); + } + + const commit_hash = await git.commit({ + fs, + dir: repository_dir, + gitdir: git_dir, + message: options.message, + author: { + name: user_name, + email: user_email, + }, + }); + + const branch = await git.currentBranch({ + fs, + dir: repository_dir, + gitdir: git_dir, + }); + const commit_title = options.message.split('\n')[0]; + const short_hash = shorten_hash(commit_hash); + let output = `[${branch} ${short_hash}] ${commit_title}\n`; + // TODO: --amend prints out the date of the original commit here, as: + // ` Date: Fri May 17 15:45:47 2024 +0100` + // TODO: Print out file change count, insertion count, and deletion count + // (Seems if insertions and deletions are both 0, we should print both. + // Otherwise we just print nonzero ones.) + // TODO: Print out each file created or deleted. eg: + // create mode 100644 bar + // delete mode 100644 foo.txt + stdout(output); + } +}