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);
+ }
+}