Skip to contentSkip to conversation

commit: add --committer option

PATCH Superseded ZheNing Hu via GitGitGadget 8 participants 44 messages Nov 9, 2025 – Nov 17, 2025 Documentation

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 commit: add --committer option

From: ZheNing Hu <[email protected]>

Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables.

---
commit: add --committer option

Currently, when users need to override the committer identity in git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables, which can be cumbersome in scripting scenarios or when frequently switching committer identities.

While git-commit already provides the --author option to conveniently override the author identity, there's no equivalent --committer option for the committer identity. This asymmetry creates an inconsistent user experience.

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1997

 Documentation/git-commit.adoc |  9 +++-
 builtin/commit.c              | 58 ++++++++++++++++++++++++-
 t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++
 3 files changed, 144 insertions(+), 3 deletions(-)
Documentation/git-commit.adoc +8 -1
index 54c207ad45..a015c8328e 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
 	   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+	   [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
 	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
 	   [--] [<pathspec>...]
@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.
 `--date=<date>`::
 	Override the author date used in the commit.
 
+`--committer=<committer>`::
+	Override the committer for the commit. Specify an explicit committer using the
+	standard `A U Thor <[email protected]>` format. Otherwise _<committer>_
+	is assumed to be a pattern and is used to search for an existing
+	commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
+	the commit author is then copied from the first such commit found.
+
 `-m <msg>`::
 `--message=<msg>`::
 	Use _<msg>_ as the commit message.
builtin/commit.c +56 -2

107 lines not on this page · raw message

t/t7509-commit-authorship.sh +80 -0

90 lines not on this page · raw message

base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84

Signature · 1 line
gitgitgadget
#1 lore raw
Quoted 5 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables.

Yeah, I can see how that's useful.

Documentation/git-commit.adoc git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] · line 18

Quoted 13 lines
Documentation/git-commit.adoc +1 -1
index 54c207ad45..a015c8328e 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
 	   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+	   [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
 	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
 	   [--] [<pathspec>...]

Nit: I'd move --committer before --date so that it comes directly after --author.

Documentation/git-commit.adoc See linkgit:git-rebase[1] for details. · line 189

Quoted 10 lines
Documentation/git-commit.adoc +6 -0
@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.
 `--date=<date>`::
 	Override the author date used in the commit.
 
+`--committer=<committer>`::
+	Override the committer for the commit. Specify an explicit committer using the
+	standard `A U Thor <[email protected]>` format. Otherwise _<committer>_
+	is assumed to be a pattern and is used to search for an existing
+	commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
+	the commit author is then copied from the first such commit found.

This matches the description of --author.

Quoted 43 lines
builtin/commit.c +35 -0
index 0243f17d53..88e77cbaab 100644
@@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)
 	free(date);
 }
 
+static void determine_committer_info(struct strbuf *committer_ident)
+{
+	char *name, *email, *date;
+	struct ident_split committer;
+
+	name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
+	email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
+	date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
+
+	if (force_committer) {
+		struct ident_split ident;
+
+		if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)
+			die(_("malformed --committer parameter"));
+		set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+		set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+
+		if (ident.date_begin) {
+			struct strbuf date_buf = STRBUF_INIT;
+			strbuf_addch(&date_buf, '@');
+			strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+			strbuf_addch(&date_buf, ' ');
+			strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+			set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+		}
+	}
+
+	if (force_date) {
+		struct strbuf date_buf = STRBUF_INIT;
+		if (parse_force_date(force_date, &date_buf))
+			die(_("invalid date format: %s"), force_date);
+		set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+	}
+
+	strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,

builtin/commit.c static void determine_author_info(struct strbuf *author_ident) · line 738

Quoted 10 lines
builtin/commit.c +7 -0
+				IDENT_STRICT));
+	assert_split_ident(&committer, committer_ident);
+	free(name);
+	free(email);
+	free(date);
+}
+
 static int author_date_is_interesting(void)
 {
 	return author_message || force_date;

A lot of the infra in this new function is shared with determine_author_info(). It would be great if we could refactor it so that the common parts are shared given that this all is quite non-trivial.

Maybe we could have something like determine_identity() that contains the common bits between both functions? It might ultimately not really be worth it, but at least the functionality in the force_committer condition feels like it should be pulled out.

builtin/commit.c static int parse_and_validate_options(int argc, const char *argv[], · line 1372

Quoted 10 lines
builtin/commit.c +3 -0
@@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
 	if (force_author && renew_authorship)
 		die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
 
+	if (force_committer && !strchr(force_committer, '>'))
+		force_committer = find_author_by_nickname(force_committer);
+
 	if (logfile || have_option_m || use_message)
 		use_editor = 0;
 

Is it the right thing to search by author here? Shouldn't we rather be searching by committer?

builtin/commit.c int cmd_commit(int argc, · line 1984

Quoted 8 lines
builtin/commit.c +4 -0
@@ -1930,8 +1978,13 @@ int cmd_commit(int argc,
 		append_merge_tag_headers(parents, &tail);
 	}
 
+	if (force_committer) {
+		determine_committer_info(&committer_ident);
+	}
+

Nit: we tend to not use braces around single-line bodies.

Thanks!

Patrick

#2 lore raw
Quoted 10 lines · Patrick Steinhardt <[email protected]> 于2025年11月10日周一 17:24写道:
Quoted 5 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables.

Yeah, I can see how that's useful.

I'm glad we're aligned on this.

Quoted 8 lines
Quoted 8 lines
Documentation/git-commit.adoc +0 -0
index 54c207ad45..a015c8328e 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
         [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
         [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
         [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
Quoted 9 lines

Documentation/git-commit.adoc git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] · line 18

Quoted 5 lines
Documentation/git-commit.adoc +1 -1
-        [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+        [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
         [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
         [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
         [--] [<pathspec>...]

Nit: I'd move --committer before --date so that it comes directly after --author.

Agreed, will fix.

Quoted 4 lines
Quoted 4 lines
Documentation/git-commit.adoc +0 -0
@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.
 `--date=<date>`::
      Override the author date used in the commit.
 
Quoted 9 lines

Documentation/git-commit.adoc See linkgit:git-rebase[1] for details. · line 189

Quoted 6 lines
Documentation/git-commit.adoc +6 -0
+`--committer=<committer>`::
+     Override the committer for the commit. Specify an explicit committer using the
+     standard `A U Thor <[email protected]>` format. Otherwise _<committer>_
+     is assumed to be a pattern and is used to search for an existing
+     commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
+     the commit author is then copied from the first such commit found.

This matches the description of --author.

Agreed, I will fix it to use committer description.

Quoted 59 lines
Quoted 43 lines
builtin/commit.c +35 -0
index 0243f17d53..88e77cbaab 100644
@@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)
      free(date);
 }
 
+static void determine_committer_info(struct strbuf *committer_ident)
+{
+     char *name, *email, *date;
+     struct ident_split committer;
+
+     name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
+     email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
+     date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
+
+     if (force_committer) {
+             struct ident_split ident;
+
+             if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)
+                     die(_("malformed --committer parameter"));
+             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+
+             if (ident.date_begin) {
+                     struct strbuf date_buf = STRBUF_INIT;
+                     strbuf_addch(&date_buf, '@');
+                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+                     strbuf_addch(&date_buf, ' ');
+                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+                     set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+             }
+     }
+
+     if (force_date) {
+             struct strbuf date_buf = STRBUF_INIT;
+             if (parse_force_date(force_date, &date_buf))
+                     die(_("invalid date format: %s"), force_date);
+             set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+     }
+
+     strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,

builtin/commit.c static void determine_author_info(struct strbuf *author_ident) · line 738

Quoted 10 lines
builtin/commit.c +7 -0
+                             IDENT_STRICT));
+     assert_split_ident(&committer, committer_ident);
+     free(name);
+     free(email);
+     free(date);
+}
+
 static int author_date_is_interesting(void)
 {
      return author_message || force_date;

A lot of the infra in this new function is shared with determine_author_info(). It would be great if we could refactor it so that the common parts are shared given that this all is quite non-trivial.

Quoted 5 lines

Maybe we could have something like determine_identity() that contains the common bits between both functions? It might ultimately not really be worth it, but at least the functionality in the force_committer condition feels like it should be pulled out.

Good suggestion, I will refactor them to use determine_identity(), which should be more generic, and it even caught that I missed updating GIT_COMMITTER_*.

Quoted 4 lines
Quoted 4 lines
builtin/commit.c +0 -0
@@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
      if (force_author && renew_authorship)
              die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
 
Quoted 10 lines

builtin/commit.c static int parse_and_validate_options(int argc, const char *argv[], · line 1372

Quoted 6 lines
builtin/commit.c +3 -0
+     if (force_committer && !strchr(force_committer, '>'))
+             force_committer = find_author_by_nickname(force_committer);
+
      if (logfile || have_option_m || use_message)
              use_editor = 0;
 

Is it the right thing to search by author here? Shouldn't we rather be searching by committer?

Yes, there should also be a find_identity_by_nickname() or find_committer_by_nickname() here.

Quoted 11 lines

builtin/commit.c int cmd_commit(int argc, · line 1984

Quoted 8 lines
builtin/commit.c +4 -0
@@ -1930,8 +1978,13 @@ int cmd_commit(int argc,
              append_merge_tag_headers(parents, &tail);
      }
 
+     if (force_committer) {
+             determine_committer_info(&committer_ident);
+     }
+

Nit: we tend to not use braces around single-line bodies.

Agree.

Quoted 3 lines

Thanks!

Patrick

#5 lore raw
In reply to ZheNing Hu via GitGitGadget

Hi ZheNing

Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

Thanks

Phillip

Quoted 256 lines

The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1997

  Documentation/git-commit.adoc |  9 +++-
  builtin/commit.c              | 58 ++++++++++++++++++++++++-
  t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++
  3 files changed, 144 insertions(+), 3 deletions(-)
Documentation/git-commit.adoc +8 -1
index 54c207ad45..a015c8328e 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
  	   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
  	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
  	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+	   [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
  	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
  	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
  	   [--] [<pathspec>...]
@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.
  `--date=<date>`::
  	Override the author date used in the commit.
 
+`--committer=<committer>`::
+	Override the committer for the commit. Specify an explicit committer using the
+	standard `A U Thor <[email protected]>` format. Otherwise _<committer>_
+	is assumed to be a pattern and is used to search for an existing
+	commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
+	the commit author is then copied from the first such commit found.
+
  `-m <msg>`::
  `--message=<msg>`::
  	Use _<msg>_ as the commit message.
builtin/commit.c +56 -2
index 0243f17d53..88e77cbaab 100644
@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {
  	   "           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n"
  	   "           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n"
  	   "           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n"
-	   "           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n"
+	   "           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n"
  	   "           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
  	   "           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n"
  	   "           [--] [<pathspec>...]"),
@@ -112,6 +112,7 @@ static enum {
  } commit_style;
 
  static const char *force_author;
+static const char *force_committer;
  static char *logfile;
  static char *template_file;
  /*
@@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)
  	free(date);
  }
 
+static void determine_committer_info(struct strbuf *committer_ident)
+{
+	char *name, *email, *date;
+	struct ident_split committer;
+
+	name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
+	email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
+	date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
+
+	if (force_committer) {
+		struct ident_split ident;
+
+		if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)
+			die(_("malformed --committer parameter"));
+		set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+		set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+
+		if (ident.date_begin) {
+			struct strbuf date_buf = STRBUF_INIT;
+			strbuf_addch(&date_buf, '@');
+			strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+			strbuf_addch(&date_buf, ' ');
+			strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+			set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+		}
+	}
+
+	if (force_date) {
+		struct strbuf date_buf = STRBUF_INIT;
+		if (parse_force_date(force_date, &date_buf))
+			die(_("invalid date format: %s"), force_date);
+		set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+	}
+
+	strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,
+				IDENT_STRICT));
+	assert_split_ident(&committer, committer_ident);
+	free(name);
+	free(email);
+	free(date);
+}
+
  static int author_date_is_interesting(void)
  {
  	return author_message || force_date;
@@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
  	if (force_author && renew_authorship)
  		die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
 
+	if (force_committer && !strchr(force_committer, '>'))
+		force_committer = find_author_by_nickname(force_committer);
+
  	if (logfile || have_option_m || use_message)
  		use_editor = 0;
 
@@ -1709,6 +1755,7 @@ int cmd_commit(int argc,
  		OPT_FILENAME('F', "file", &logfile, N_("read message from file")),
  		OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")),
  		OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")),
+		OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")),
  		OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
  		OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
  		OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
@@ -1785,6 +1832,7 @@ int cmd_commit(int argc,
 
  	struct strbuf sb = STRBUF_INIT;
  	struct strbuf author_ident = STRBUF_INIT;
+	struct strbuf committer_ident = STRBUF_INIT;
  	const char *index_file, *reflog_msg;
  	struct object_id oid;
  	struct commit_list *parents = NULL;
@@ -1930,8 +1978,13 @@ int cmd_commit(int argc,
  		append_merge_tag_headers(parents, &tail);
  	}
 
+	if (force_committer) {
+		determine_committer_info(&committer_ident);
+	}
+
  	if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,
-				 parents, &oid, author_ident.buf, NULL,
+				 parents, &oid, author_ident.buf,
+				 force_committer ? committer_ident.buf : NULL,
  				 sign_commit, extra)) {
  		rollback_index_files();
  		die(_("failed to write commit object"));
@@ -1980,6 +2033,7 @@ cleanup:
  	free_commit_extra_headers(extra);
  	free_commit_list(parents);
  	strbuf_release(&author_ident);
+	strbuf_release(&committer_ident);
  	strbuf_release(&err);
  	strbuf_release(&sb);
  	free(logfile);
t/t7509-commit-authorship.sh +80 -0
index 8e373b566b..45527f6a70 100755
@@ -12,6 +12,11 @@ author_header () {
  	sed -n -e '/^$/q' -e '/^author /p'
  }
 
+committer_header () {
+	git cat-file commit "$1" |
+	sed -n -e '/^$/q' -e '/^committer /p'
+}
+
  message_body () {
  	git cat-file commit "$1" |
  	sed -e '1,/^$/d'
@@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
  	test_cmp expect actual
  '
 
+test_expect_success '--committer option overrides committer' '
+	git checkout Initial &&
+	echo "Test --committer" >>foo &&
+	test_tick &&
+	git commit -a -m "test committer" --committer="Custom Committer <[email protected]>" &&
+	committer_header HEAD >actual &&
+	grep "Custom Committer <[email protected]>" actual
+'
+
+test_expect_success '--committer with pattern search' '
+	echo "Test committer pattern" >>foo &&
+	test_tick &&
+	git commit -a -m "test committer pattern" --committer="Frigate" &&
+	committer_header HEAD >actual &&
+	grep "Frigate <[email protected]>" actual
+'
+
+test_expect_success '--committer malformed parameter' '
+	echo "Test malformed" >>foo &&
+	test_tick &&
+	test_must_fail git commit -a -m "test malformed" --committer="malformed committer"
+'
+
+test_expect_success '--committer with --amend option' '
+	git checkout -f Initial &&
+	echo "Test committer with amend" >>foo &&
+	test_tick &&
+	git commit -a -m "initial commit for amend test" &&
+	echo "Modified for amend" >>foo &&
+	test_tick &&
+	git commit -a --amend --no-edit \
+		--author="Test Author <[email protected]>" \
+		--committer="Test Committer <[email protected]>" &&
+	author_header HEAD >actual_author &&
+	grep "Test Author <[email protected]>" actual_author &&
+	committer_header HEAD >actual_committer &&
+	grep "Test Committer <[email protected]>" actual_committer
+'
+
+test_expect_success 'GIT_COMMITTER_* environment variables' '
+	git checkout -f Initial &&
+	echo "Test env vars" >>foo &&
+	test_tick &&
+	GIT_COMMITTER_NAME="Env Committer" \
+	GIT_COMMITTER_EMAIL="[email protected]" \
+	git commit -a -m "test committer env vars" &&
+	committer_header HEAD >actual &&
+	grep "Env Committer <[email protected]>" actual
+'
+
+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '
+	echo "Test override" >>foo &&
+	test_tick &&
+	GIT_COMMITTER_NAME="Env Committer" \
+	GIT_COMMITTER_EMAIL="[email protected]" \
+	git commit -a -m "test override" \
+		--committer="Override Committer <[email protected]>" &&
+	committer_header HEAD >actual &&
+	grep "Override Committer <[email protected]>" actual
+'
+
+test_expect_success '--date with --committer changes both author and committer dates' '
+	git checkout -f Initial &&
+	echo "Test date override" >>foo &&
+	test_tick &&
+	git commit -a -m "test date" \
+		--author="Date Author <[email protected]>" \
+		--committer="Date Committer <[email protected]>" \
+		--date="2024-06-15 10:30:00 +0800" &&
+	git log -1 --format="%ai" >author_date &&
+	git log -1 --format="%ci" >committer_date &&
+	grep "2024-06-15 10:30:00 +0800" author_date &&
+	grep "2024-06-15 10:30:00 +0800" committer_date
+'
+
  test_done

base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84

#34 lore raw

PATCH v2 1/1 commit: add --committer option

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables.

Like --author, the --committer option supports two formats:
- Explicit identity: --committer="Name <[email protected]>"
- Pattern search: --committer="pattern" searches commit history for a matching committer and reuses that identity

To share code with the existing --author option, this patch refactors:

1. find_author_by_nickname() into find_identity_by_nickname() which handles both author and committer searches through an is_author parameter.

2. determine_author_info() into determine_identity() which handles identity parsing and setting for both author and committer through an is_author parameter.

---
commit: add --committer option

Currently, when users need to override the committer identity in git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables, which can be cumbersome in scripting scenarios or when frequently switching committer identities.

While git-commit already provides the --author option to conveniently override the author identity, there's no equivalent --committer option for the committer identity. This asymmetry creates an inconsistent user experience.

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1997

Range-diff vs v1:

1: 05e97b439f ! 1: 58e9e5c9d7 commit: add --committer option

     @@ Commit message
          committer identity similar to how --author works. This provides a more
          convenient alternative to setting GIT_COMMITTER_* environment variables.
      
     -    Signed-off-by: ZheNing Hu <[email protected]>
     +    Like --author, the --committer option supports two formats:
     +    - Explicit identity: --committer="Name <[email protected]>"
     +    - Pattern search: --committer="pattern" searches commit history for a
     +      matching committer and reuses that identity
     +
     +    To share code with the existing --author option, this patch refactors:
     +
     +    1. find_author_by_nickname() into find_identity_by_nickname() which
     +       handles both author and committer searches through an is_author
     +       parameter.
     +
     +    2. determine_author_info() into determine_identity() which handles
     +       identity parsing and setting for both author and committer through
     +       an is_author parameter.
      
     +    Signed-off-by: ZheNing Hu <[email protected]>
          Co-authored-by: Aone-Agent <[email protected]>
      
       ## Documentation/git-commit.adoc ##
     @@ Documentation/git-commit.adoc: git commit [-a | --interactive | --patch] [-s] [-
       	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
       	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
      -	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
     -+	   [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
     ++	   [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]
       	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
       	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
       	   [--] [<pathspec>...]
      @@ Documentation/git-commit.adoc: See linkgit:git-rebase[1] for details.
     - `--date=<date>`::
     - 	Override the author date used in the commit.
     + 	commit by that author (i.e. `git rev-list --all -i --author=<author>`);
     + 	the commit author is then copied from the first such commit found.
       
      +`--committer=<committer>`::
      +	Override the committer for the commit. Specify an explicit committer using the
     -+	standard `A U Thor <[email protected]>` format. Otherwise _<committer>_
     ++	standard `C O Mitter <[email protected]>` format. Otherwise _<committer>_
      +	is assumed to be a pattern and is used to search for an existing
     -+	commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
     -+	the commit author is then copied from the first such commit found.
     ++	commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);
     ++	the commit committer is then copied from the first such commit found.
      +
     - `-m <msg>`::
     - `--message=<msg>`::
     - 	Use _<msg>_ as the commit message.
     + `--date=<date>`::
     + 	Override the author date used in the commit.
     + 
      
       ## builtin/commit.c ##
      @@ builtin/commit.c: static const char * const builtin_commit_usage[] = {
     @@ builtin/commit.c: static const char * const builtin_commit_usage[] = {
       	   "           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n"
       	   "           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n"
      -	   "           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n"
     -+	   "           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n"
     ++	   "           [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n"
       	   "           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
       	   "           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n"
       	   "           [--] [<pathspec>...]"),
     @@ builtin/commit.c: static enum {
       static char *logfile;
       static char *template_file;
       /*
     -@@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)
     - 	free(date);
     +@@ builtin/commit.c: static void set_ident_var(char **buf, char *val)
     + 	*buf = val;
       }
       
     -+static void determine_committer_info(struct strbuf *committer_ident)
     -+{
     -+	char *name, *email, *date;
     -+	struct ident_split committer;
     -+
     -+	name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
     -+	email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
     -+	date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
     +-static void determine_author_info(struct strbuf *author_ident)
     ++static void determine_identity(struct strbuf *ident_str, int is_author)
     + {
     + 	char *name, *email, *date;
     +-	struct ident_split author;
     +-
     +-	name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME"));
     +-	email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL"));
     +-	date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE"));
     +-
     +-	if (author_message) {
     +-		struct ident_split ident;
     ++	struct ident_split ident;
     ++	const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME";
     ++	const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL";
     ++	const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE";
     ++	const char *force_ident = is_author ? force_author : force_committer;
     ++	const char *param_name = is_author ? "--author" : "--committer";
     ++	int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;
      +
     -+	if (force_committer) {
     -+		struct ident_split ident;
     ++	name = xstrdup_or_null(getenv(env_name));
     ++	email = xstrdup_or_null(getenv(env_email));
     ++	date = xstrdup_or_null(getenv(env_date));
      +
     -+		if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)
     -+			die(_("malformed --committer parameter"));
     -+		set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
     -+		set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
     ++	if (is_author && author_message) {
     ++		struct ident_split msg_ident;
     + 		size_t len;
     + 		const char *a;
     + 
     + 		a = find_commit_header(author_message_buffer, "author", &len);
     + 		if (!a)
     + 			die(_("commit '%s' lacks author header"), author_message);
     +-		if (split_ident_line(&ident, a, len) < 0)
     ++		if (split_ident_line(&msg_ident, a, len) < 0)
     + 			die(_("commit '%s' has malformed author line"), author_message);
     + 
     +-		set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
     +-		set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
     ++		set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));
     ++		set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));
     + 
     +-		if (ident.date_begin) {
     ++		if (msg_ident.date_begin) {
     + 			struct strbuf date_buf = STRBUF_INIT;
     + 			strbuf_addch(&date_buf, '@');
     +-			strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
     ++			strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);
     + 			strbuf_addch(&date_buf, ' ');
     +-			strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
     ++			strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);
     + 			set_ident_var(&date, strbuf_detach(&date_buf, NULL));
     + 		}
     + 	}
     + 
     +-	if (force_author) {
     +-		struct ident_split ident;
     ++	if (force_ident) {
     ++		struct ident_split force_ident_split;
      +
     -+		if (ident.date_begin) {
     ++		if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)
     ++			die(_("malformed %s parameter"), param_name);
     ++		set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));
     ++		set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));
     + 
     +-		if (split_ident_line(&ident, force_author, strlen(force_author)) < 0)
     +-			die(_("malformed --author parameter"));
     +-		set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
     +-		set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
     ++		if (!is_author && force_ident_split.date_begin) {
      +			struct strbuf date_buf = STRBUF_INIT;
      +			strbuf_addch(&date_buf, '@');
     -+			strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
     ++			strbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin);
      +			strbuf_addch(&date_buf, ' ');
     -+			strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
     ++			strbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin);
      +			set_ident_var(&date, strbuf_detach(&date_buf, NULL));
      +		}
     -+	}
     + 	}
     + 
     + 	if (force_date) {
     +@@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)
     + 		set_ident_var(&date, strbuf_detach(&date_buf, NULL));
     + 	}
     + 
     +-	strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,
     ++	strbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date,
     + 				IDENT_STRICT));
     +-	assert_split_ident(&author, author_ident);
     +-	export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
     +-	export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
     +-	export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
     ++	assert_split_ident(&ident, ident_str);
      +
     -+	if (force_date) {
     -+		struct strbuf date_buf = STRBUF_INIT;
     -+		if (parse_force_date(force_date, &date_buf))
     -+			die(_("invalid date format: %s"), force_date);
     -+		set_ident_var(&date, strbuf_detach(&date_buf, NULL));
     ++	if (is_author) {
     ++		export_one("GIT_AUTHOR_NAME", ident.name_begin, ident.name_end, 0);
     ++		export_one("GIT_AUTHOR_EMAIL", ident.mail_begin, ident.mail_end, 0);
     ++		export_one("GIT_AUTHOR_DATE", ident.date_begin, ident.tz_end, '@');
     ++	} else {
     ++		export_one("GIT_COMMITTER_NAME", ident.name_begin, ident.name_end, 0);
     ++		export_one("GIT_COMMITTER_EMAIL", ident.mail_begin, ident.mail_end, 0);
     ++		export_one("GIT_COMMITTER_DATE", ident.date_begin, ident.tz_end, '@');
      +	}
      +
     -+	strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,
     -+				IDENT_STRICT));
     -+	assert_split_ident(&committer, committer_ident);
     -+	free(name);
     -+	free(email);
     -+	free(date);
     + 	free(name);
     + 	free(email);
     + 	free(date);
     + }
     + 
     ++static void determine_author_info(struct strbuf *author_ident)
     ++{
     ++	determine_identity(author_ident, 1);
     ++}
     ++
     ++static void determine_committer_info(struct strbuf *committer_ident)
     ++{
     ++	determine_identity(committer_ident, 0);
      +}
      +
       static int author_date_is_interesting(void)
       {
       	return author_message || force_date;
     +@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,
     + 	return 1;
     + }
     + 
     +-static const char *find_author_by_nickname(const char *name)
     ++static const char *find_identity_by_nickname(const char *name, int is_author)
     + {
     + 	struct rev_info revs;
     + 	struct commit *commit;
     + 	struct strbuf buf = STRBUF_INIT;
     + 	const char *av[20];
     + 	int ac = 0;
     ++	const char *field = is_author ? "author" : "committer";
     ++	const char *format = is_author ? "%aN <%aE>" : "%cN <%cE>";
     + 
     + 	repo_init_revisions(the_repository, &revs, NULL);
     +-	strbuf_addf(&buf, "--author=%s", name);
     ++	strbuf_addf(&buf, "--%s=%s", field, name);
     + 	av[++ac] = "--all";
     + 	av[++ac] = "-i";
     + 	av[++ac] = buf.buf;
     +@@ builtin/commit.c: static const char *find_author_by_nickname(const char *name)
     + 		ctx.date_mode.type = DATE_NORMAL;
     + 		strbuf_release(&buf);
     + 		repo_format_commit_message(the_repository, commit,
     +-					   "%aN <%aE>", &buf, &ctx);
     ++					   format, &buf, &ctx);
     + 		release_revisions(&revs);
     + 		return strbuf_detach(&buf, NULL);
     + 	}
     +-	die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
     ++	die(_("--%s '%s' is not 'Name <email>' and matches no existing %s"),
     ++	    field, name, field);
     ++}
     ++
     ++static const char *find_author_by_nickname(const char *name)
     ++{
     ++	return find_identity_by_nickname(name, 1);
     ++}
     ++
     ++static const char *find_committer_by_nickname(const char *name)
     ++{
     ++	return find_identity_by_nickname(name, 0);
     + }
     + 
     + static void handle_ignored_arg(struct wt_status *s)
      @@ builtin/commit.c: static int parse_and_validate_options(int argc, const char *argv[],
       	if (force_author && renew_authorship)
       		die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
       
      +	if (force_committer && !strchr(force_committer, '>'))
     -+		force_committer = find_author_by_nickname(force_committer);
     ++		force_committer = find_committer_by_nickname(force_committer);
      +
       	if (logfile || have_option_m || use_message)
       		use_editor = 0;
     @@ builtin/commit.c: int cmd_commit(int argc,
       		append_merge_tag_headers(parents, &tail);
       	}
       
     -+	if (force_committer) {
     ++	if (force_committer)
      +		determine_committer_info(&committer_ident);
     -+	}
      +
       	if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,
      -				 parents, &oid, author_ident.buf, NULL,
     @@ t/t7509-commit-authorship.sh: author_header () {
       message_body () {
       	git cat-file commit "$1" |
       	sed -e '1,/^$/d'
     + }
     + 
     + test_expect_success '-C option copies authorship and message' '
     +-	test_commit --author Frigate\ \<[email protected]\> \
     ++	test_env GIT_COMMITTER_NAME="Frigate" \
     ++		GIT_COMMITTER_EMAIL="[email protected]" \
     ++		test_commit --author Frigate\ \<[email protected]\> \
     + 		"Initial Commit" foo Initial Initial &&
     + 	echo "Test 1" >>foo &&
     + 	test_tick &&
      @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
       	test_cmp expect actual
       '
     @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PI
      +	git checkout -f Initial &&
      +	echo "Test env vars" >>foo &&
      +	test_tick &&
     -+	GIT_COMMITTER_NAME="Env Committer" \
     -+	GIT_COMMITTER_EMAIL="[email protected]" \
     -+	git commit -a -m "test committer env vars" &&
     ++	test_env GIT_COMMITTER_NAME="Env Committer" \
     ++		GIT_COMMITTER_EMAIL="[email protected]" \
     ++		git commit -a -m "test committer env vars" &&
      +	committer_header HEAD >actual &&
      +	grep "Env Committer <[email protected]>" actual
      +'
     @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PI
      +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '
      +	echo "Test override" >>foo &&
      +	test_tick &&
     -+	GIT_COMMITTER_NAME="Env Committer" \
     -+	GIT_COMMITTER_EMAIL="[email protected]" \
     -+	git commit -a -m "test override" \
     ++	test_env GIT_COMMITTER_NAME="Env Committer" \
     ++		GIT_COMMITTER_EMAIL="[email protected]" \
     ++		git commit -a -m "test override" \
      +		--committer="Override Committer <[email protected]>" &&
      +	committer_header HEAD >actual &&
      +	grep "Override Committer <[email protected]>" actual
 Documentation/git-commit.adoc |   9 ++-
 builtin/commit.c              | 121 +++++++++++++++++++++++++---------
 t/t7509-commit-authorship.sh  |  84 ++++++++++++++++++++++-
 3 files changed, 180 insertions(+), 34 deletions(-)
Documentation/git-commit.adoc +8 -1
index 54c207ad45..ed4c54ae81 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
 	   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+	   [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
 	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
 	   [--] [<pathspec>...]
@@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details.
 	commit by that author (i.e. `git rev-list --all -i --author=<author>`);
 	the commit author is then copied from the first such commit found.
 
+`--committer=<committer>`::
+	Override the committer for the commit. Specify an explicit committer using the
+	standard `C O Mitter <[email protected]>` format. Otherwise _<committer>_
+	is assumed to be a pattern and is used to search for an existing
+	commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);
+	the commit committer is then copied from the first such commit found.
+
 `--date=<date>`::
 	Override the author date used in the commit.
 
builtin/commit.c +89 -32

224 lines not on this page · raw message

t/t7509-commit-authorship.sh +83 -1

100 lines not on this page · raw message

base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84

Signature · 1 line
gitgitgadget
#3 lore raw
In reply to Patrick Steinhardt
Quoted 8 lines · Patrick Steinhardt
Quoted 5 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables.

Yeah, I can see how that's useful.

Well, I don't. Naming somebody other than yourself as the author may be something that is needed from time to time by human users, but lying about the committer who made commits? Our tradition is to give long rope to let users hang themselves, but we already have the environment variable override specifically designed for scripted uses, where there may be very legit uses of recording arbitrary committer identity that has nothing to do with the identity the current user who is running the Git processes usually uses. I do not think it is a "useful" change to make it more it ergonomic to perform certain operations that we may not want to encourage.

So I dunno.

#6 lore raw
In reply to Phillip Wood
Quoted 9 lines · Phillip Wood
Quoted 7 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:
1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 6 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

Someone who works on the same project under both their personal and corporate identities. For instance, me working on the Git project.

Some open source projects also require a CLA and you have to use a particular address to match the one that's listed on the CLA. For example, Google requires an address with a Google account, so in the hypothetical state where I was going to contribute to one of their projects, I'd need to use a different committer identity with my Gmail address.

I've also kept business logs in Git when I had a small business and I might well need to log approving a profit distribution (with my corporate address) and log accepting a profit distribution (with my personal address). Those would need separate digital signatures from my two different email addresses.

Signature · 2 lines
brian m. carlson (they/them)
Toronto, Ontario, CA
#35 lore raw
In reply to ZheNing Hu via GitGitGadget
Quoted 2 lines · ZheNing Hu via GitGitGadget

What is this second author and how would its presence in the author list interact with your DCO obligation?

How did you make sure that whatever is in this patch were not copied by the "agent" from somewhere that we cannot copy the code from before deciding to send this patch? The "cannot copy from" may come in different shapes, from "their code is proprietary" to "their licensing terms are not compatible with GPLv2" to "they welcome us borrowing but we must give credit to them", any of which we should be careful to avoid.

#36 lore raw
Quoted 8 lines · Junio C Hamano
Quoted 2 lines · ZheNing Hu via GitGitGadget

What is this second author and how would its presence in the author list interact with your DCO obligation?

Quoted 7 lines

How did you make sure that whatever is in this patch were not copied by the "agent" from somewhere that we cannot copy the code from before deciding to send this patch? The "cannot copy from" may come in different shapes, from "their code is proprietary" to "their licensing terms are not compatible with GPLv2" to "they welcome us borrowing but we must give credit to them", any of which we should be careful to avoid.

Well, the last one is not something we should *avoid*. If their licensing terms are compatible with ours but they want to be credited, then we comply that request and credit them.

But I think readers got the idea.

Thanks.

#7 lore raw
In reply to brian m. carlson
Quoted 27 lines · brian m. carlson
Quoted 9 lines · Phillip Wood
Quoted 7 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:
1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 6 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

Someone who works on the same project under both their personal and corporate identities. For instance, me working on the Git project.

Some open source projects also require a CLA and you have to use a particular address to match the one that's listed on the CLA. For example, Google requires an address with a Google account, so in the hypothetical state where I was going to contribute to one of their projects, I'd need to use a different committer identity with my Gmail address.

Quoted 5 lines

I've also kept business logs in Git when I had a small business and I might well need to log approving a profit distribution (with my corporate address) and log accepting a profit distribution (with my personal address). Those would need separate digital signatures from my two different email addresses.

Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone.

That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them).

I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option.

-Peff

#8 lore raw
Quoted 8 lines · Jeff King

Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone.

That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them).

This happens to match my experience, but my use case may be rather skewed. The two sets of contents managed by my two clones that push into the same repository (of course to two different branches) are rather disjoint and they never merge into each other (in fact they do not even share the root commit).

If you are working under two identities on the same codebase, I would imagine that two repo arrangement may be more cumbersome than working in a single repository and switching between the identities, and may be preferrable as long as you are confident that you won't commit a change under the "other" (wrong) identity. Of course, your reflogs and notes would also follow the "then-current" committer identity, so if I were to flip between two identities while working on the same codebase in a single repository, I am very likely to export GIT_{AUTHOR,COMMITTER}_* environment variables, dedicate that shell/window to the work done under that identity, and switch the environment variables if/when I want to switch (or have another shell/window with the other identities exported---perhaps I'd do that in a secondary worktree). I cannot imagine myself keep giving --author and --committer between my two identities without mistakes.

Quoted 3 lines

I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option.

Same here. The "give them long enough rope" principle tells me that this may be worth having if even only to have symmetry with existing "--author" option, but these two are not inherently symmetric to begin with, and I am not sure if there is a scenario in which this new option is the best thing to use.

#9 lore raw
In reply to Jeff King
Quoted 39 lines · Jeff King
Quoted 27 lines · brian m. carlson
Quoted 9 lines · Phillip Wood
Quoted 7 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:
1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 6 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

Someone who works on the same project under both their personal and corporate identities. For instance, me working on the Git project.

Some open source projects also require a CLA and you have to use a particular address to match the one that's listed on the CLA. For example, Google requires an address with a Google account, so in the hypothetical state where I was going to contribute to one of their projects, I'd need to use a different committer identity with my Gmail address.

Quoted 5 lines

I've also kept business logs in Git when I had a small business and I might well need to log approving a profit distribution (with my corporate address) and log accepting a profit distribution (with my personal address). Those would need separate digital signatures from my two different email addresses.

Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone.

Quoted 8 lines

That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them).

I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option.

The reason why I find it useful is mostly scripted uses. Sure, you can already set environment variables there. But from my experience, environment variables tend to be a significantly worse API compared to command line options:

- They are harder to discover in the manual page.

- You don't have any "guarantees" that Git actually interprets them, as there won't be an error if you mistype the name.

- Cause and effect may be detached with environment variables, but with command line options that's never the case.

So I myself would prefer using "--committer" over its accompanying environment variable any point in time when I have a scripted use case for it.

Patrick

#23 lore raw
In reply to Phillip Wood
Quoted 14 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 00:50写道:

Hi ZheNing

Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 8 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

I frequently need to distinguish between different user.name and user.email configurations on our company's internal GitHub.

The current problems are:

When I misconfigure (which happens occasionally), git commit --author only fixes the author part, I still need to additionally set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables to fix the committer information These environment variables are painful to use, requiring manual setup every time

If a --committer option could be provided to align with --author, users wouldn't need to remember and use these additional environment variables. This would greatly simplify the workflow and reduce cognitive overhead.

Quoted 261 lines

Thanks

Phillip

Quoted 256 lines

The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1997

  Documentation/git-commit.adoc |  9 +++-
  builtin/commit.c              | 58 ++++++++++++++++++++++++-
  t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++
  3 files changed, 144 insertions(+), 3 deletions(-)
Documentation/git-commit.adoc +8 -1
index 54c207ad45..a015c8328e 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
         [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
         [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
         [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-        [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+        [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
         [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
         [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
         [--] [<pathspec>...]
@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.
  `--date=<date>`::
      Override the author date used in the commit.
 
+`--committer=<committer>`::
+     Override the committer for the commit. Specify an explicit committer using the
+     standard `A U Thor <[email protected]>` format. Otherwise _<committer>_
+     is assumed to be a pattern and is used to search for an existing
+     commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
+     the commit author is then copied from the first such commit found.
+
  `-m <msg>`::
  `--message=<msg>`::
      Use _<msg>_ as the commit message.
builtin/commit.c +56 -2
index 0243f17d53..88e77cbaab 100644
@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {
         "           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n"
         "           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n"
         "           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n"
-        "           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n"
+        "           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n"
         "           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
         "           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n"
         "           [--] [<pathspec>...]"),
@@ -112,6 +112,7 @@ static enum {
  } commit_style;
 
  static const char *force_author;
+static const char *force_committer;
  static char *logfile;
  static char *template_file;
  /*
@@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)
      free(date);
  }
 
+static void determine_committer_info(struct strbuf *committer_ident)
+{
+     char *name, *email, *date;
+     struct ident_split committer;
+
+     name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
+     email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
+     date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
+
+     if (force_committer) {
+             struct ident_split ident;
+
+             if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)
+                     die(_("malformed --committer parameter"));
+             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+
+             if (ident.date_begin) {
+                     struct strbuf date_buf = STRBUF_INIT;
+                     strbuf_addch(&date_buf, '@');
+                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+                     strbuf_addch(&date_buf, ' ');
+                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+                     set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+             }
+     }
+
+     if (force_date) {
+             struct strbuf date_buf = STRBUF_INIT;
+             if (parse_force_date(force_date, &date_buf))
+                     die(_("invalid date format: %s"), force_date);
+             set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+     }
+
+     strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,
+                             IDENT_STRICT));
+     assert_split_ident(&committer, committer_ident);
+     free(name);
+     free(email);
+     free(date);
+}
+
  static int author_date_is_interesting(void)
  {
      return author_message || force_date;
@@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
      if (force_author && renew_authorship)
              die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
 
+     if (force_committer && !strchr(force_committer, '>'))
+             force_committer = find_author_by_nickname(force_committer);
+
      if (logfile || have_option_m || use_message)
              use_editor = 0;
 
@@ -1709,6 +1755,7 @@ int cmd_commit(int argc,
              OPT_FILENAME('F', "file", &logfile, N_("read message from file")),
              OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")),
              OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")),
+             OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")),
              OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
              OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
              OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
@@ -1785,6 +1832,7 @@ int cmd_commit(int argc,
 
      struct strbuf sb = STRBUF_INIT;
      struct strbuf author_ident = STRBUF_INIT;
+     struct strbuf committer_ident = STRBUF_INIT;
      const char *index_file, *reflog_msg;
      struct object_id oid;
      struct commit_list *parents = NULL;
@@ -1930,8 +1978,13 @@ int cmd_commit(int argc,
              append_merge_tag_headers(parents, &tail);
      }
 
+     if (force_committer) {
+             determine_committer_info(&committer_ident);
+     }
+
      if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,
-                              parents, &oid, author_ident.buf, NULL,
+                              parents, &oid, author_ident.buf,
+                              force_committer ? committer_ident.buf : NULL,
                               sign_commit, extra)) {
              rollback_index_files();
              die(_("failed to write commit object"));
@@ -1980,6 +2033,7 @@ cleanup:
      free_commit_extra_headers(extra);
      free_commit_list(parents);
      strbuf_release(&author_ident);
+     strbuf_release(&committer_ident);
      strbuf_release(&err);
      strbuf_release(&sb);
      free(logfile);
t/t7509-commit-authorship.sh +80 -0
index 8e373b566b..45527f6a70 100755
@@ -12,6 +12,11 @@ author_header () {
      sed -n -e '/^$/q' -e '/^author /p'
  }
 
+committer_header () {
+     git cat-file commit "$1" |
+     sed -n -e '/^$/q' -e '/^committer /p'
+}
+
  message_body () {
      git cat-file commit "$1" |
      sed -e '1,/^$/d'
@@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
      test_cmp expect actual
  '
 
+test_expect_success '--committer option overrides committer' '
+     git checkout Initial &&
+     echo "Test --committer" >>foo &&
+     test_tick &&
+     git commit -a -m "test committer" --committer="Custom Committer <[email protected]>" &&
+     committer_header HEAD >actual &&
+     grep "Custom Committer <[email protected]>" actual
+'
+
+test_expect_success '--committer with pattern search' '
+     echo "Test committer pattern" >>foo &&
+     test_tick &&
+     git commit -a -m "test committer pattern" --committer="Frigate" &&
+     committer_header HEAD >actual &&
+     grep "Frigate <[email protected]>" actual
+'
+
+test_expect_success '--committer malformed parameter' '
+     echo "Test malformed" >>foo &&
+     test_tick &&
+     test_must_fail git commit -a -m "test malformed" --committer="malformed committer"
+'
+
+test_expect_success '--committer with --amend option' '
+     git checkout -f Initial &&
+     echo "Test committer with amend" >>foo &&
+     test_tick &&
+     git commit -a -m "initial commit for amend test" &&
+     echo "Modified for amend" >>foo &&
+     test_tick &&
+     git commit -a --amend --no-edit \
+             --author="Test Author <[email protected]>" \
+             --committer="Test Committer <[email protected]>" &&
+     author_header HEAD >actual_author &&
+     grep "Test Author <[email protected]>" actual_author &&
+     committer_header HEAD >actual_committer &&
+     grep "Test Committer <[email protected]>" actual_committer
+'
+
+test_expect_success 'GIT_COMMITTER_* environment variables' '
+     git checkout -f Initial &&
+     echo "Test env vars" >>foo &&
+     test_tick &&
+     GIT_COMMITTER_NAME="Env Committer" \
+     GIT_COMMITTER_EMAIL="[email protected]" \
+     git commit -a -m "test committer env vars" &&
+     committer_header HEAD >actual &&
+     grep "Env Committer <[email protected]>" actual
+'
+
+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '
+     echo "Test override" >>foo &&
+     test_tick &&
+     GIT_COMMITTER_NAME="Env Committer" \
+     GIT_COMMITTER_EMAIL="[email protected]" \
+     git commit -a -m "test override" \
+             --committer="Override Committer <[email protected]>" &&
+     committer_header HEAD >actual &&
+     grep "Override Committer <[email protected]>" actual
+'
+
+test_expect_success '--date with --committer changes both author and committer dates' '
+     git checkout -f Initial &&
+     echo "Test date override" >>foo &&
+     test_tick &&
+     git commit -a -m "test date" \
+             --author="Date Author <[email protected]>" \
+             --committer="Date Committer <[email protected]>" \
+             --date="2024-06-15 10:30:00 +0800" &&
+     git log -1 --format="%ai" >author_date &&
+     git log -1 --format="%ci" >committer_date &&
+     grep "2024-06-15 10:30:00 +0800" author_date &&
+     grep "2024-06-15 10:30:00 +0800" committer_date
+'
+
  test_done

base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84

#4 lore raw
In reply to Junio C Hamano
Quoted 23 lines · Junio C Hamano <[email protected]> 于2025年11月11日周二 01:38写道:
Quoted 8 lines · Patrick Steinhardt
Quoted 5 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables.

Yeah, I can see how that's useful.

Well, I don't. Naming somebody other than yourself as the author may be something that is needed from time to time by human users, but lying about the committer who made commits? Our tradition is to give long rope to let users hang themselves, but we already have the environment variable override specifically designed for scripted uses, where there may be very legit uses of recording arbitrary committer identity that has nothing to do with the identity the current user who is running the Git processes usually uses. I do not think it is a "useful" change to make it more it ergonomic to perform certain operations that we may not want to encourage.

Quoted 2 lines

So I dunno.

I understand your concern about not wanting to encourage users to misrepresent their committer identity. However, I'd like to point out that if someone truly wants to "lie" about the committer identity, there's nothing stopping them today. They can already do this through multiple methods.

My primary motivation for this patch is not to make it easier to falsify identity,
but rather to help legitimate users who work across different repositories with different identities. These users occasionally need to fix accidentally misconfigured committer information, and the current methods (setting environment variables or editing config files) can be cumbersome for this use case.

You can see from [1] how many people have struggled with modifying commits committer.

Quoted 1 line

[1]: https://stackoverflow.com/questions/750172/how-do-i-change-the-author-and-committer-name-email-for-multiple-commits?page=1&tab=scoredesc#tab-top

#37 lore raw
In reply to Junio C Hamano
Quoted 9 lines · Junio C Hamano <[email protected]> 于2025年11月11日周二 03:22写道:
Quoted 2 lines · ZheNing Hu via GitGitGadget

What is this second author and how would its presence in the author list interact with your DCO obligation?

Quoted 9 lines

How did you make sure that whatever is in this patch were not copied by the "agent" from somewhere that we cannot copy the code from before deciding to send this patch? The "cannot copy from" may come in different shapes, from "their code is proprietary" to "their licensing terms are not compatible with GPLv2" to "they welcome us borrowing but we must give credit to them", any of which we should be careful to avoid.

This was automatically added by some code assistance tools. I indeed forgot to consider its impact on the open source license. I'll remove it right away.

#12 lore raw
In reply to Jeff King
Quoted 40 lines · Jeff King <[email protected]> 于2025年11月11日周二 04:11写道:
Quoted 27 lines · brian m. carlson
Quoted 9 lines · Phillip Wood
Quoted 7 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:
1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 6 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

Someone who works on the same project under both their personal and corporate identities. For instance, me working on the Git project.

Some open source projects also require a CLA and you have to use a particular address to match the one that's listed on the CLA. For example, Google requires an address with a Google account, so in the hypothetical state where I was going to contribute to one of their projects, I'd need to use a different committer identity with my Gmail address.

Quoted 5 lines

I've also kept business logs in Git when I had a small business and I might well need to log approving a profit distribution (with my corporate address) and log accepting a profit distribution (with my personal address). Those would need separate digital signatures from my two different email addresses.

Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone.

Quoted 9 lines

That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them).

I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option.

Sometimes it's because I forgot to configure the repository-level git user config and started development first. Only when I tried to correct the committer did I feel the pain.

Quoted 1 line

-Peff

#24 lore raw
In reply to ZheNing Hu
Quoted 24 lines · ZheNing Hu
Quoted 11 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 00:50写道:
Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 8 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

I frequently need to distinguish between different user.name and user.email configurations on our company's internal GitHub.

Quoted 7 lines

The current problems are:

When I misconfigure (which happens occasionally), git commit --author only fixes the author part, I still need to additionally set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables to fix the committer information These environment variables are painful to use, requiring manual setup every time

I'm afraid I don't quite follow. If you are amending existing commits to fix them up after you have corrected your configuration then they will have the correct committer automatically when you run "git commit --amend --author=..." to correct the author. If you are committing before you have realized that user.{name,email} are misconfigured then I don't see how "--committer" helps because you have not yet realized anything is wrong.

Thanks

Phillip

Quoted 267 lines

If a --committer option could be provided to align with --author, users wouldn't need to remember and use these additional environment variables. This would greatly simplify the workflow and reduce cognitive overhead.

Quoted 261 lines

Thanks

Phillip

Quoted 256 lines

The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1997

   Documentation/git-commit.adoc |  9 +++-
   builtin/commit.c              | 58 ++++++++++++++++++++++++-
   t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++
   3 files changed, 144 insertions(+), 3 deletions(-)
Documentation/git-commit.adoc +8 -1
index 54c207ad45..a015c8328e 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
          [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
          [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
          [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-        [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+        [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
          [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
          [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
          [--] [<pathspec>...]
@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.
   `--date=<date>`::
       Override the author date used in the commit.
 
+`--committer=<committer>`::
+     Override the committer for the commit. Specify an explicit committer using the
+     standard `A U Thor <[email protected]>` format. Otherwise _<committer>_
+     is assumed to be a pattern and is used to search for an existing
+     commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
+     the commit author is then copied from the first such commit found.
+
   `-m <msg>`::
   `--message=<msg>`::
       Use _<msg>_ as the commit message.
builtin/commit.c +56 -2
index 0243f17d53..88e77cbaab 100644
@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {
          "           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n"
          "           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n"
          "           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n"
-        "           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n"
+        "           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n"
          "           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
          "           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n"
          "           [--] [<pathspec>...]"),
@@ -112,6 +112,7 @@ static enum {
   } commit_style;
 
   static const char *force_author;
+static const char *force_committer;
   static char *logfile;
   static char *template_file;
   /*
@@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)
       free(date);
   }
 
+static void determine_committer_info(struct strbuf *committer_ident)
+{
+     char *name, *email, *date;
+     struct ident_split committer;
+
+     name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
+     email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
+     date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
+
+     if (force_committer) {
+             struct ident_split ident;
+
+             if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)
+                     die(_("malformed --committer parameter"));
+             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+
+             if (ident.date_begin) {
+                     struct strbuf date_buf = STRBUF_INIT;
+                     strbuf_addch(&date_buf, '@');
+                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+                     strbuf_addch(&date_buf, ' ');
+                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+                     set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+             }
+     }
+
+     if (force_date) {
+             struct strbuf date_buf = STRBUF_INIT;
+             if (parse_force_date(force_date, &date_buf))
+                     die(_("invalid date format: %s"), force_date);
+             set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+     }
+
+     strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,
+                             IDENT_STRICT));
+     assert_split_ident(&committer, committer_ident);
+     free(name);
+     free(email);
+     free(date);
+}
+
   static int author_date_is_interesting(void)
   {
       return author_message || force_date;
@@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
       if (force_author && renew_authorship)
               die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
 
+     if (force_committer && !strchr(force_committer, '>'))
+             force_committer = find_author_by_nickname(force_committer);
+
       if (logfile || have_option_m || use_message)
               use_editor = 0;
 
@@ -1709,6 +1755,7 @@ int cmd_commit(int argc,
               OPT_FILENAME('F', "file", &logfile, N_("read message from file")),
               OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")),
               OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")),
+             OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")),
               OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
               OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
               OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
@@ -1785,6 +1832,7 @@ int cmd_commit(int argc,
 
       struct strbuf sb = STRBUF_INIT;
       struct strbuf author_ident = STRBUF_INIT;
+     struct strbuf committer_ident = STRBUF_INIT;
       const char *index_file, *reflog_msg;
       struct object_id oid;
       struct commit_list *parents = NULL;
@@ -1930,8 +1978,13 @@ int cmd_commit(int argc,
               append_merge_tag_headers(parents, &tail);
       }
 
+     if (force_committer) {
+             determine_committer_info(&committer_ident);
+     }
+
       if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,
-                              parents, &oid, author_ident.buf, NULL,
+                              parents, &oid, author_ident.buf,
+                              force_committer ? committer_ident.buf : NULL,
                                sign_commit, extra)) {
               rollback_index_files();
               die(_("failed to write commit object"));
@@ -1980,6 +2033,7 @@ cleanup:
       free_commit_extra_headers(extra);
       free_commit_list(parents);
       strbuf_release(&author_ident);
+     strbuf_release(&committer_ident);
       strbuf_release(&err);
       strbuf_release(&sb);
       free(logfile);
t/t7509-commit-authorship.sh +80 -0
index 8e373b566b..45527f6a70 100755
@@ -12,6 +12,11 @@ author_header () {
       sed -n -e '/^$/q' -e '/^author /p'
   }
 
+committer_header () {
+     git cat-file commit "$1" |
+     sed -n -e '/^$/q' -e '/^committer /p'
+}
+
   message_body () {
       git cat-file commit "$1" |
       sed -e '1,/^$/d'
@@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
       test_cmp expect actual
   '
 
+test_expect_success '--committer option overrides committer' '
+     git checkout Initial &&
+     echo "Test --committer" >>foo &&
+     test_tick &&
+     git commit -a -m "test committer" --committer="Custom Committer <[email protected]>" &&
+     committer_header HEAD >actual &&
+     grep "Custom Committer <[email protected]>" actual
+'
+
+test_expect_success '--committer with pattern search' '
+     echo "Test committer pattern" >>foo &&
+     test_tick &&
+     git commit -a -m "test committer pattern" --committer="Frigate" &&
+     committer_header HEAD >actual &&
+     grep "Frigate <[email protected]>" actual
+'
+
+test_expect_success '--committer malformed parameter' '
+     echo "Test malformed" >>foo &&
+     test_tick &&
+     test_must_fail git commit -a -m "test malformed" --committer="malformed committer"
+'
+
+test_expect_success '--committer with --amend option' '
+     git checkout -f Initial &&
+     echo "Test committer with amend" >>foo &&
+     test_tick &&
+     git commit -a -m "initial commit for amend test" &&
+     echo "Modified for amend" >>foo &&
+     test_tick &&
+     git commit -a --amend --no-edit \
+             --author="Test Author <[email protected]>" \
+             --committer="Test Committer <[email protected]>" &&
+     author_header HEAD >actual_author &&
+     grep "Test Author <[email protected]>" actual_author &&
+     committer_header HEAD >actual_committer &&
+     grep "Test Committer <[email protected]>" actual_committer
+'
+
+test_expect_success 'GIT_COMMITTER_* environment variables' '
+     git checkout -f Initial &&
+     echo "Test env vars" >>foo &&
+     test_tick &&
+     GIT_COMMITTER_NAME="Env Committer" \
+     GIT_COMMITTER_EMAIL="[email protected]" \
+     git commit -a -m "test committer env vars" &&
+     committer_header HEAD >actual &&
+     grep "Env Committer <[email protected]>" actual
+'
+
+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '
+     echo "Test override" >>foo &&
+     test_tick &&
+     GIT_COMMITTER_NAME="Env Committer" \
+     GIT_COMMITTER_EMAIL="[email protected]" \
+     git commit -a -m "test override" \
+             --committer="Override Committer <[email protected]>" &&
+     committer_header HEAD >actual &&
+     grep "Override Committer <[email protected]>" actual
+'
+
+test_expect_success '--date with --committer changes both author and committer dates' '
+     git checkout -f Initial &&
+     echo "Test date override" >>foo &&
+     test_tick &&
+     git commit -a -m "test date" \
+             --author="Date Author <[email protected]>" \
+             --committer="Date Committer <[email protected]>" \
+             --date="2024-06-15 10:30:00 +0800" &&
+     git log -1 --format="%ai" >author_date &&
+     git log -1 --format="%ci" >committer_date &&
+     grep "2024-06-15 10:30:00 +0800" author_date &&
+     grep "2024-06-15 10:30:00 +0800" committer_date
+'
+
   test_done

base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84

#10 lore raw
In reply to Patrick Steinhardt
Quoted 40 lines · Patrick Steinhardt
Quoted 39 lines · Jeff King
Quoted 27 lines · brian m. carlson
Quoted 9 lines · Phillip Wood
Quoted 7 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:
1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 6 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

Someone who works on the same project under both their personal and corporate identities. For instance, me working on the Git project.

Some open source projects also require a CLA and you have to use a particular address to match the one that's listed on the CLA. For example, Google requires an address with a Google account, so in the hypothetical state where I was going to contribute to one of their projects, I'd need to use a different committer identity with my Gmail address.

Quoted 5 lines

I've also kept business logs in Git when I had a small business and I might well need to log approving a profit distribution (with my corporate address) and log accepting a profit distribution (with my personal address). Those would need separate digital signatures from my two different email addresses.

Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone.

Quoted 8 lines
Quoted 8 lines

That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them).

I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option.

Yes, it strikes me as very inconvenient to have to specify "--committer" each time. I'd have though you'd either want to (i) set up an alias in which case you can start your alias with "-c user.name=..." or "!GIT_COMMITTER_NAME=...", or (ii) set GIT_COMMITTER_NAME in your shell.

Quoted 6 lines

The reason why I find it useful is mostly scripted uses. Sure, you can already set environment variables there. But from my experience, environment variables tend to be a significantly worse API compared to command line options:

- They are harder to discover in the manual page.

They're documented in the COMMIT INFORMATION section of the "git commit" man page, admittedly that comes after the options and examples but overriding the committer is a fairly niche requirement.

Quoted 2 lines

- You don't have any "guarantees" that Git actually interprets them, as there won't be an error if you mistype the name.

Playing devil's advocate even if you use "--committer" you still need to check the result to make sure there were no typo's in the committer info just as you would if you were setting GIT_COMMITTER_NAME.

Quoted 6 lines

- Cause and effect may be detached with environment variables, but with command line options that's never the case.

So I myself would prefer using "--committer" over its accompanying environment variable any point in time when I have a scripted use case for it.

I'm wary of cluttering the UI of one of our core porcelain command with options for use with scripting.

Thanks

Phillip

#38 lore raw
In reply to ZheNing Hu
Quoted 20 lines · ZheNing Hu
Quoted 9 lines · Junio C Hamano <[email protected]> 于2025年11月11日周二 03:22写道:
Quoted 2 lines · ZheNing Hu via GitGitGadget

What is this second author and how would its presence in the author list interact with your DCO obligation?

Quoted 9 lines

How did you make sure that whatever is in this patch were not copied by the "agent" from somewhere that we cannot copy the code from before deciding to send this patch? The "cannot copy from" may come in different shapes, from "their code is proprietary" to "their licensing terms are not compatible with GPLv2" to "they welcome us borrowing but we must give credit to them", any of which we should be careful to avoid.

Quoted 3 lines

This was automatically added by some code assistance tools. I indeed forgot to consider its impact on the open source license. I'll remove it right away.

Please don't silently remove it without answering the question you were asked. "The tool adds it but I disabled the agentic features of the tool and everything readers see in the submitted patch was what I typed, with no agent input" would be a perfect answer. "I did not consider the ramifications of use of the agentic tool, and I do not know where the code the tool added for me came from, so I cannot be sure I can contribute this patch to the project" would be a sad but may be an honest answer. "I cannot tell the origin but I can remove the line and claim I wrote everything myself" is not an answer that we want to see. I am sure that your answer would fall within the spectrum, and am hoping it would be the earlier, perfect one, or one close to it, but we need to hear it.

Thanks.

#13 lore raw
In reply to ZheNing Hu
Quoted 9 lines · ZheNing Hu
Quoted 9 lines

Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone.

That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them).

Quoted 7 lines
Quoted 3 lines

I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option.

Sometimes it's because I forgot to configure the repository-level git user config and started development first. Only when I tried to correct the committer did I feel the pain.

OK, this workflow does make sense to me. Fixing up an earlier mistake is inherently a one-off thing, and a command-line option is more ergonomic than using the environment variables.

Two small thoughts:

- I suspect what you'd usually want there is for the committer and the author to match. We have --committer-date-is-author-date for rebase, and conceptually I think something like --committer-is-author would do what you want here. But obviously it's less flexible, and I don't know if it's that much easier to use.

- Because it's easy to make such mistakes, when you override the author (so that it doesn't match the committer), git-commit prints an extra "Author:" line in the output to make that more obvious. Should we do the same with committer when you've overridden it? We already do print "Committer:" when the ident was guessed from system info, but I wonder if it would make sense to print when it was forced. I dunno. I guess the time you most need the hint is when you meant to use --committer and --author together, but only used --author. But I don't know how Git would infer that case (versus the normal case of you applying someone else's work and crediting them with --author).

I'm not sure if either is a useful direction, but they seemed sufficiently not-dumb for me to at least type them out. ;)

-Peff

#14 lore raw
Quoted 7 lines · Jeff King
Quoted 3 lines

Sometimes it's because I forgot to configure the repository-level git user config and started development first. Only when I tried to correct the committer did I feel the pain.

OK, this workflow does make sense to me. Fixing up an earlier mistake is inherently a one-off thing, and a command-line option is more ergonomic than using the environment variables.

Not very much, at least to me. Fixing up an earlier mistake may be one-shot thing but it is to correct multiple commits in one go, which would be error prone if you do so with "git commit --option". Either "fast-export | fast-import" pipe, or "git rebase" (which this patch does not give --committer option, but it already knows how to honor existing environment variables) would be used for that, no?

Quoted 7 lines

Two small thoughts:

- I suspect what you'd usually want there is for the committer and the author to match. We have --committer-date-is-author-date for rebase, and conceptually I think something like --committer-is-author would do what you want here. But obviously it's less flexible, and I don't know if it's that much easier to use.

I am not sure how the user experience of this would look like.

Quoted 11 lines

- Because it's easy to make such mistakes, when you override the author (so that it doesn't match the committer), git-commit prints an extra "Author:" line in the output to make that more obvious. Should we do the same with committer when you've overridden it? We already do print "Committer:" when the ident was guessed from system info, but I wonder if it would make sense to print when it was forced. I dunno. I guess the time you most need the hint is when you meant to use --committer and --author together, but only used --author. But I don't know how Git would infer that case (versus the normal case of you applying someone else's work and crediting them with --author).

Yup, guessing based on what you did _not_ give is always a hard task for any tool ;-).

Quoted 4 lines

I'm not sure if either is a useful direction, but they seemed sufficiently not-dumb for me to at least type them out. ;)

-Peff

#15 lore raw
Quoted 10 lines · Junio C Hamano
Quoted 7 lines · Jeff King
Quoted 3 lines

Sometimes it's because I forgot to configure the repository-level git user config and started development first. Only when I tried to correct the committer did I feel the pain.

OK, this workflow does make sense to me. Fixing up an earlier mistake is inherently a one-off thing, and a command-line option is more ergonomic than using the environment variables.

Quoted 6 lines

Not very much, at least to me. Fixing up an earlier mistake may be one-shot thing but it is to correct multiple commits in one go, which would be error prone if you do so with "git commit --option". Either "fast-export | fast-import" pipe, or "git rebase" (which this patch does not give --committer option, but it already knows how to honor existing environment variables) would be used for that, no?

I usually lean on "commit --amend" for this, coupled with rebase if there are multiple commits. So I've used:

  git rebase -x "git commit --no-edit --amend --author=..."

or similar when fixing up incorrect application of somebody else's patches (e.g., if I ended up using "git apply" and tweaking the commit message myself, rather than using "git am").

Quoted 9 lines
Quoted 7 lines

Two small thoughts:

- I suspect what you'd usually want there is for the committer and the author to match. We have --committer-date-is-author-date for rebase, and conceptually I think something like --committer-is-author would do what you want here. But obviously it's less flexible, and I don't know if it's that much easier to use.

I am not sure how the user experience of this would look like.

I just mean being able to do:

  git commit --amend --author='Foo Bar <[email protected]>' --committer-is-author

instead of:

  git commit --amend --author='Foo Bar <[email protected]>' --committer='Foo Bar <[email protected]>'

-Peff

#17 lore raw
Quoted 8 lines · Junio C Hamano
Quoted 6 lines · Jeff King

I just mean being able to do:

  git commit --amend --author='Foo Bar <[email protected]>' --committer-is-author

instead of:

Quoted 7 lines
Quoted 1 line
  git commit --amend --author='Foo Bar <[email protected]>' --committer='Foo Bar <[email protected]>'

Ah, I see. Like

    git -c user.name='Foo Bar' -c [email protected] commit --amend

Makes me wonder if we want user.ident that covers them both ;-)

Hmm, I hadn't thought to use "-c" config for this. That makes me question the utility of --committer a little bit. ;) I guess it is slightly more convenient than "-c" in that it will trigger the find_author_by_nickname() magic.

-Peff

#25 lore raw
In reply to Phillip Wood
Quoted 34 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 22:38写道:
Quoted 24 lines · ZheNing Hu
Quoted 11 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 00:50写道:
Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 8 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

I frequently need to distinguish between different user.name and user.email configurations on our company's internal GitHub.

Quoted 7 lines

The current problems are:

When I misconfigure (which happens occasionally), git commit --author only fixes the author part, I still need to additionally set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables to fix the committer information These environment variables are painful to use, requiring manual setup every time

Quoted 8 lines

I'm afraid I don't quite follow. If you are amending existing commits to fix them up after you have corrected your configuration then they will have the correct committer automatically when you run "git commit --amend --author=..." to correct the author. If you are committing before you have realized that user.{name,email} are misconfigured then I don't see how "--committer" helps because you have not yet realized anything is wrong.

You're right that after realizing the misconfiguration and correcting the repository's user.name and user.email, running git commit --amend will
fix the committer information, but the author remains unchanged. Users
then need an additional git commit --amend --author=... to fix the author, which does work but requires an extra step.

I see your point that this becomes more cumbersome when dealing with multiple commits. In such cases, users currently need to use something like:

```
GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \
GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \
git rebase -f <target>
```

This is indeed tedious and error-prone, especially when you want to quickly fix and push commits to the platform.

git commit --amend --author --committer or a new git rebase --author --committer
would provide a more user-friendly workflow for correcting identity information after misconfiguration, eliminating the need to manually set multiple
environment variables or run multiple commands.

Quoted 4 lines

Thanks

Phillip

Thanks

ZheNing Hu

Quoted 268 lines
Quoted 267 lines

If a --committer option could be provided to align with --author, users wouldn't need to remember and use these additional environment variables. This would greatly simplify the workflow and reduce cognitive overhead.

Quoted 261 lines

Thanks

Phillip

Quoted 256 lines

The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1997

   Documentation/git-commit.adoc |  9 +++-
   builtin/commit.c              | 58 ++++++++++++++++++++++++-
   t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++
   3 files changed, 144 insertions(+), 3 deletions(-)
Documentation/git-commit.adoc +8 -1
index 54c207ad45..a015c8328e 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
          [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
          [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
          [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-        [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+        [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
          [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
          [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
          [--] [<pathspec>...]
@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.
   `--date=<date>`::
       Override the author date used in the commit.
 
+`--committer=<committer>`::
+     Override the committer for the commit. Specify an explicit committer using the
+     standard `A U Thor <[email protected]>` format. Otherwise _<committer>_
+     is assumed to be a pattern and is used to search for an existing
+     commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
+     the commit author is then copied from the first such commit found.
+
   `-m <msg>`::
   `--message=<msg>`::
       Use _<msg>_ as the commit message.
builtin/commit.c +56 -2
index 0243f17d53..88e77cbaab 100644
@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {
          "           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n"
          "           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n"
          "           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n"
-        "           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n"
+        "           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n"
          "           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
          "           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n"
          "           [--] [<pathspec>...]"),
@@ -112,6 +112,7 @@ static enum {
   } commit_style;
 
   static const char *force_author;
+static const char *force_committer;
   static char *logfile;
   static char *template_file;
   /*
@@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)
       free(date);
   }
 
+static void determine_committer_info(struct strbuf *committer_ident)
+{
+     char *name, *email, *date;
+     struct ident_split committer;
+
+     name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
+     email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
+     date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
+
+     if (force_committer) {
+             struct ident_split ident;
+
+             if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)
+                     die(_("malformed --committer parameter"));
+             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+
+             if (ident.date_begin) {
+                     struct strbuf date_buf = STRBUF_INIT;
+                     strbuf_addch(&date_buf, '@');
+                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+                     strbuf_addch(&date_buf, ' ');
+                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+                     set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+             }
+     }
+
+     if (force_date) {
+             struct strbuf date_buf = STRBUF_INIT;
+             if (parse_force_date(force_date, &date_buf))
+                     die(_("invalid date format: %s"), force_date);
+             set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+     }
+
+     strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,
+                             IDENT_STRICT));
+     assert_split_ident(&committer, committer_ident);
+     free(name);
+     free(email);
+     free(date);
+}
+
   static int author_date_is_interesting(void)
   {
       return author_message || force_date;
@@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
       if (force_author && renew_authorship)
               die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
 
+     if (force_committer && !strchr(force_committer, '>'))
+             force_committer = find_author_by_nickname(force_committer);
+
       if (logfile || have_option_m || use_message)
               use_editor = 0;
 
@@ -1709,6 +1755,7 @@ int cmd_commit(int argc,
               OPT_FILENAME('F', "file", &logfile, N_("read message from file")),
               OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")),
               OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")),
+             OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")),
               OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
               OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
               OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
@@ -1785,6 +1832,7 @@ int cmd_commit(int argc,
 
       struct strbuf sb = STRBUF_INIT;
       struct strbuf author_ident = STRBUF_INIT;
+     struct strbuf committer_ident = STRBUF_INIT;
       const char *index_file, *reflog_msg;
       struct object_id oid;
       struct commit_list *parents = NULL;
@@ -1930,8 +1978,13 @@ int cmd_commit(int argc,
               append_merge_tag_headers(parents, &tail);
       }
 
+     if (force_committer) {
+             determine_committer_info(&committer_ident);
+     }
+
       if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,
-                              parents, &oid, author_ident.buf, NULL,
+                              parents, &oid, author_ident.buf,
+                              force_committer ? committer_ident.buf : NULL,
                                sign_commit, extra)) {
               rollback_index_files();
               die(_("failed to write commit object"));
@@ -1980,6 +2033,7 @@ cleanup:
       free_commit_extra_headers(extra);
       free_commit_list(parents);
       strbuf_release(&author_ident);
+     strbuf_release(&committer_ident);
       strbuf_release(&err);
       strbuf_release(&sb);
       free(logfile);
t/t7509-commit-authorship.sh +80 -0
index 8e373b566b..45527f6a70 100755
@@ -12,6 +12,11 @@ author_header () {
       sed -n -e '/^$/q' -e '/^author /p'
   }
 
+committer_header () {
+     git cat-file commit "$1" |
+     sed -n -e '/^$/q' -e '/^committer /p'
+}
+
   message_body () {
       git cat-file commit "$1" |
       sed -e '1,/^$/d'
@@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
       test_cmp expect actual
   '
 
+test_expect_success '--committer option overrides committer' '
+     git checkout Initial &&
+     echo "Test --committer" >>foo &&
+     test_tick &&
+     git commit -a -m "test committer" --committer="Custom Committer <[email protected]>" &&
+     committer_header HEAD >actual &&
+     grep "Custom Committer <[email protected]>" actual
+'
+
+test_expect_success '--committer with pattern search' '
+     echo "Test committer pattern" >>foo &&
+     test_tick &&
+     git commit -a -m "test committer pattern" --committer="Frigate" &&
+     committer_header HEAD >actual &&
+     grep "Frigate <[email protected]>" actual
+'
+
+test_expect_success '--committer malformed parameter' '
+     echo "Test malformed" >>foo &&
+     test_tick &&
+     test_must_fail git commit -a -m "test malformed" --committer="malformed committer"
+'
+
+test_expect_success '--committer with --amend option' '
+     git checkout -f Initial &&
+     echo "Test committer with amend" >>foo &&
+     test_tick &&
+     git commit -a -m "initial commit for amend test" &&
+     echo "Modified for amend" >>foo &&
+     test_tick &&
+     git commit -a --amend --no-edit \
+             --author="Test Author <[email protected]>" \
+             --committer="Test Committer <[email protected]>" &&
+     author_header HEAD >actual_author &&
+     grep "Test Author <[email protected]>" actual_author &&
+     committer_header HEAD >actual_committer &&
+     grep "Test Committer <[email protected]>" actual_committer
+'
+
+test_expect_success 'GIT_COMMITTER_* environment variables' '
+     git checkout -f Initial &&
+     echo "Test env vars" >>foo &&
+     test_tick &&
+     GIT_COMMITTER_NAME="Env Committer" \
+     GIT_COMMITTER_EMAIL="[email protected]" \
+     git commit -a -m "test committer env vars" &&
+     committer_header HEAD >actual &&
+     grep "Env Committer <[email protected]>" actual
+'
+
+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '
+     echo "Test override" >>foo &&
+     test_tick &&
+     GIT_COMMITTER_NAME="Env Committer" \
+     GIT_COMMITTER_EMAIL="[email protected]" \
+     git commit -a -m "test override" \
+             --committer="Override Committer <[email protected]>" &&
+     committer_header HEAD >actual &&
+     grep "Override Committer <[email protected]>" actual
+'
+
+test_expect_success '--date with --committer changes both author and committer dates' '
+     git checkout -f Initial &&
+     echo "Test date override" >>foo &&
+     test_tick &&
+     git commit -a -m "test date" \
+             --author="Date Author <[email protected]>" \
+             --committer="Date Committer <[email protected]>" \
+             --date="2024-06-15 10:30:00 +0800" &&
+     git log -1 --format="%ai" >author_date &&
+     git log -1 --format="%ci" >committer_date &&
+     grep "2024-06-15 10:30:00 +0800" author_date &&
+     grep "2024-06-15 10:30:00 +0800" committer_date
+'
+
   test_done

base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84

#11 lore raw
In reply to Phillip Wood
Quoted 47 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 22:53写道:
Quoted 45 lines · Patrick Steinhardt
Quoted 44 lines · Jeff King
Quoted 27 lines · brian m. carlson
Quoted 9 lines · Phillip Wood
Quoted 7 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:
1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 6 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

Someone who works on the same project under both their personal and corporate identities. For instance, me working on the Git project.

Some open source projects also require a CLA and you have to use a particular address to match the one that's listed on the CLA. For example, Google requires an address with a Google account, so in the hypothetical state where I was going to contribute to one of their projects, I'd need to use a different committer identity with my Gmail address.

Quoted 5 lines

I've also kept business logs in Git when I had a small business and I might well need to log approving a profit distribution (with my corporate address) and log accepting a profit distribution (with my personal address). Those would need separate digital signatures from my two different email addresses.

Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone.

That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them).

Quoted 8 lines
Quoted 3 lines
Quoted 3 lines

I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option.

Yes, it strikes me as very inconvenient to have to specify "--committer" each time. I'd have though you'd either want to (i) set up an alias in which case you can start your alias with "-c user.name=..." or "!GIT_COMMITTER_NAME=...", or (ii) set GIT_COMMITTER_NAME in your shell.

Since modifying the committer is a low-frequency operation, there's no real need to specifically configure an alias for it.

Quoted 11 lines
Quoted 6 lines

The reason why I find it useful is mostly scripted uses. Sure, you can already set environment variables there. But from my experience, environment variables tend to be a significantly worse API compared to command line options:

- They are harder to discover in the manual page.

They're documented in the COMMIT INFORMATION section of the "git commit" man page, admittedly that comes after the options and examples but overriding the committer is a fairly niche requirement.

Although it's niche or infrequent, when configuration errors do occur, the current fix process is quite painful for users. Previously, countless users have gone through great trouble seeking solutions because they didn't know how to resolve incorrect user.name and email configurations, which prevented them from pushing code to the company's servers.

This may also suggest that Git lacks simpler ways to fix their identity. In fact, many users end up needing to use more complex tools like git filter-repo to attempt fixing their commits, whereas git commit --committer and --author could become a relatively simple and memorable fix method.

Quoted 7 lines
Quoted 2 lines

- You don't have any "guarantees" that Git actually interprets them, as there won't be an error if you mistype the name.

Playing devil's advocate even if you use "--committer" you still need to check the result to make sure there were no typo's in the committer info just as you would if you were setting GIT_COMMITTER_NAME.

GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL are split into two parts. Sometimes I even forget whether GIT_COMMITTER_EMAIL should be "<email.example.com>" or "email.example.com", whereas --author or --committer provides a more compact and memorable "user <email.example.com>" pair.

Quoted 7 lines
Quoted 6 lines

- Cause and effect may be detached with environment variables, but with command line options that's never the case.

So I myself would prefer using "--committer" over its accompanying environment variable any point in time when I have a scripted use case for it.

Quoted 6 lines

I'm wary of cluttering the UI of one of our core porcelain command with options for use with scripting.

Thanks

Phillip

Thanks

ZheNing Hu

#39 lore raw
In reply to Junio C Hamano
Quoted 27 lines · Junio C Hamano <[email protected]> 于2025年11月11日周二 23:40写道:
Quoted 20 lines · ZheNing Hu
Quoted 9 lines · Junio C Hamano <[email protected]> 于2025年11月11日周二 03:22写道:
Quoted 2 lines · ZheNing Hu via GitGitGadget

What is this second author and how would its presence in the author list interact with your DCO obligation?

Quoted 9 lines

How did you make sure that whatever is in this patch were not copied by the "agent" from somewhere that we cannot copy the code from before deciding to send this patch? The "cannot copy from" may come in different shapes, from "their code is proprietary" to "their licensing terms are not compatible with GPLv2" to "they welcome us borrowing but we must give credit to them", any of which we should be careful to avoid.

Quoted 3 lines

This was automatically added by some code assistance tools. I indeed forgot to consider its impact on the open source license. I'll remove it right away.

Quoted 13 lines

Please don't silently remove it without answering the question you were asked. "The tool adds it but I disabled the agentic features of the tool and everything readers see in the submitted patch was what I typed, with no agent input" would be a perfect answer. "I did not consider the ramifications of use of the agentic tool, and I do not know where the code the tool added for me came from, so I cannot be sure I can contribute this patch to the project" would be a sad but may be an honest answer. "I cannot tell the origin but I can remove the line and claim I wrote everything myself" is not an answer that we want to see. I am sure that your answer would fall within the spectrum, and am hoping it would be the earlier, perfect one, or one close to it, but we need to hear it.

Ha, I think my situation is closer to the second case, but I'm certain that identical code cannot be found on GitHub or any other platform. Moreover, this code is entirely modeled after Git's own code (e.g., determine_identity is based on determine_author_info). There's no possibility of copying code from other projects with licenses incompatible with Git. However, your reminder is valid, especially in this era where AI Agents are heavily infiltrating development. Any similar open-source contributions in the future will need to pay attention to this point.

Quoted 1 line

Thanks.

#22 lore raw
In reply to Jeff King
Quoted 24 lines · Jeff King <[email protected]> 于2025年11月12日周三 03:15写道:
Quoted 9 lines · ZheNing Hu
Quoted 9 lines

Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone.

That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them).

Quoted 7 lines
Quoted 3 lines

I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option.

Sometimes it's because I forgot to configure the repository-level git user config and started development first. Only when I tried to correct the committer did I feel the pain.

OK, this workflow does make sense to me. Fixing up an earlier mistake is inherently a one-off thing, and a command-line option is more ergonomic than using the environment variables.

Quoted 8 lines

Two small thoughts:

- I suspect what you'd usually want there is for the committer and the author to match. We have --committer-date-is-author-date for rebase, and conceptually I think something like --committer-is-author would do what you want here. But obviously it's less flexible, and I don't know if it's that much easier to use.

Well, to be honest, I don't really like this kind of -A --BaseA option style. On the contrary, -A -B is actually simple and easy enough for me (or more symmetrical). However, it's true that git rebase probably doesn't have --author and --committer options. Perhaps this is also a pain point, or maybe something to look into for potential future contributions.

Quoted 12 lines

- Because it's easy to make such mistakes, when you override the author (so that it doesn't match the committer), git-commit prints an extra "Author:" line in the output to make that more obvious. Should we do the same with committer when you've overridden it? We already do print "Committer:" when the ident was guessed from system info, but I wonder if it would make sense to print when it was forced. I dunno. I guess the time you most need the hint is when you meant to use --committer and --author together, but only used --author. But I don't know how Git would infer that case (versus the normal case of you applying someone else's work and crediting them with --author).

Ah, you have a point. Perhaps the Commit should also be output, though it might be trivial (if you hadn't reminded me, I wouldn't have even noticed that git commit outputs Author when using --author).

Quoted 4 lines

I'm not sure if either is a useful direction, but they seemed sufficiently not-dumb for me to at least type them out. ;)

-Peff

#21 lore raw
In reply to Junio C Hamano
Quoted 11 lines · Junio C Hamano <[email protected]> 于2025年11月12日周三 04:16写道:
Quoted 7 lines · Jeff King
Quoted 3 lines

Sometimes it's because I forgot to configure the repository-level git user config and started development first. Only when I tried to correct the committer did I feel the pain.

OK, this workflow does make sense to me. Fixing up an earlier mistake is inherently a one-off thing, and a command-line option is more ergonomic than using the environment variables.

Quoted 7 lines

Not very much, at least to me. Fixing up an earlier mistake may be one-shot thing but it is to correct multiple commits in one go, which would be error prone if you do so with "git commit --option". Either "fast-export | fast-import" pipe, or "git rebase" (which this patch does not give --committer option, but it already knows how to honor existing environment variables) would be used for that, no?

Ha, perhaps this should be left to git rebase --committer --author in the future. GIT_AUTHOR_* and GIT_COMMITTER_* are indeed a bit cumbersome to use.

Quoted 29 lines
Quoted 7 lines

Two small thoughts:

- I suspect what you'd usually want there is for the committer and the author to match. We have --committer-date-is-author-date for rebase, and conceptually I think something like --committer-is-author would do what you want here. But obviously it's less flexible, and I don't know if it's that much easier to use.

I am not sure how the user experience of this would look like.

Quoted 11 lines

- Because it's easy to make such mistakes, when you override the author (so that it doesn't match the committer), git-commit prints an extra "Author:" line in the output to make that more obvious. Should we do the same with committer when you've overridden it? We already do print "Committer:" when the ident was guessed from system info, but I wonder if it would make sense to print when it was forced. I dunno. I guess the time you most need the hint is when you meant to use --committer and --author together, but only used --author. But I don't know how Git would infer that case (versus the normal case of you applying someone else's work and crediting them with --author).

Yup, guessing based on what you did _not_ give is always a hard task for any tool ;-).

Quoted 4 lines

I'm not sure if either is a useful direction, but they seemed sufficiently not-dumb for me to at least type them out. ;)

-Peff

#20 lore raw
In reply to Jeff King
Quoted 39 lines · Jeff King <[email protected]> 于2025年11月12日周三 05:33写道:
Quoted 10 lines · Junio C Hamano
Quoted 7 lines · Jeff King
Quoted 3 lines

Sometimes it's because I forgot to configure the repository-level git user config and started development first. Only when I tried to correct the committer did I feel the pain.

OK, this workflow does make sense to me. Fixing up an earlier mistake is inherently a one-off thing, and a command-line option is more ergonomic than using the environment variables.

Quoted 6 lines

Not very much, at least to me. Fixing up an earlier mistake may be one-shot thing but it is to correct multiple commits in one go, which would be error prone if you do so with "git commit --option". Either "fast-export | fast-import" pipe, or "git rebase" (which this patch does not give --committer option, but it already knows how to honor existing environment variables) would be used for that, no?

I usually lean on "commit --amend" for this, coupled with rebase if there are multiple commits. So I've used:

  git rebase -x "git commit --no-edit --amend --author=..."

or similar when fixing up incorrect application of somebody else's patches (e.g., if I ended up using "git apply" and tweaking the commit message myself, rather than using "git am").

Quoted 9 lines
Quoted 7 lines

Two small thoughts:

- I suspect what you'd usually want there is for the committer and the author to match. We have --committer-date-is-author-date for rebase, and conceptually I think something like --committer-is-author would do what you want here. But obviously it's less flexible, and I don't know if it's that much easier to use.

I am not sure how the user experience of this would look like.

Quoted 8 lines

I just mean being able to do:

  git commit --amend --author='Foo Bar <[email protected]>' --committer-is-author

instead of:

  git commit --amend --author='Foo Bar <[email protected]>' --committer='Foo Bar <[email protected]>'

If I wanted an elegant parameter myself, it would definitely be git commit --amend --user='Foo Bar <[email protected]>', instead of -A -B-is-A

Quoted 1 line

-Peff

#19 lore raw
In reply to Junio C Hamano
Quoted 7 lines · Junio C Hamano <[email protected]> 于2025年11月12日周三 05:58写道:
Quoted 4 lines · Jeff King

I just mean being able to do:

  git commit --amend --author='Foo Bar <[email protected]>' --committer-is-author
Quoted 8 lines
Quoted 3 lines

instead of:

  git commit --amend --author='Foo Bar <[email protected]>' --committer='Foo Bar <[email protected]>'

Ah, I see. Like

    git -c user.name='Foo Bar' -c [email protected] commit --amend

You still need:

git -c user.name ='Foo Bar' -c [email protected] commit --amend --author

It's hard to remember and not symmetric enough.

Quoted 1 line

Makes me wonder if we want user.ident that covers them both ;-)

#18 lore raw
In reply to Jeff King
Quoted 15 lines · Jeff King <[email protected]> 于2025年11月12日周三 06:23写道:
Quoted 12 lines · Junio C Hamano
Quoted 7 lines · Jeff King

I just mean being able to do:

  git commit --amend --author='Foo Bar <[email protected]>' --committer-is-author

instead of:

  git commit --amend --author='Foo Bar <[email protected]>' --committer='Foo Bar <[email protected]>'

Ah, I see. Like

Quoted 9 lines
Quoted 3 lines
    git -c user.name='Foo Bar' -c [email protected] commit --amend

Makes me wonder if we want user.ident that covers them both ;-)

Hmm, I hadn't thought to use "-c" config for this. That makes me question the utility of --committer a little bit. ;) I guess it is slightly more convenient than "-c" in that it will trigger the find_author_by_nickname() magic.

The advantages of --committer over -c user.name -c user.email:

1. It's symmetric with --author
2. A single option is sufficient
3. As you mentioned, it can search for the committer in commit history (although that wasn't my original intention)

Quoted 1 line

-Peff

#40 lore raw

PATCH v3 1/1 commit: add --committer option

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables.

Like --author, the --committer option supports two formats:
- Explicit identity: --committer="Name <[email protected]>"
- Pattern search: --committer="pattern" searches commit history for a matching committer and reuses that identity

To share code with the existing --author option, this patch refactors:

1. find_author_by_nickname() into find_identity_by_nickname() which handles both author and committer searches through an is_author parameter.

2. determine_author_info() into determine_identity() which handles identity parsing and setting for both author and committer through an is_author parameter.

---
commit: add --committer option

Currently, when users need to override the committer identity in git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables, which can be cumbersome in scripting scenarios or when frequently switching committer identities.

While git-commit already provides the --author option to conveniently override the author identity, there's no equivalent --committer option for the committer identity. This asymmetry creates an inconsistent user experience.

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1997

Range-diff vs v2:

1: 58e9e5c9d7 ! 1: acf724fad5 commit: add --committer option
@@ Commit message
an is_author parameter.

- Co-authored-by: Aone-Agent <[email protected]>

## Documentation/git-commit.adoc ##
@@ Documentation/git-commit.adoc: git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]

 Documentation/git-commit.adoc |   9 ++-
 builtin/commit.c              | 121 +++++++++++++++++++++++++---------
 t/t7509-commit-authorship.sh  |  84 ++++++++++++++++++++++-
 3 files changed, 180 insertions(+), 34 deletions(-)
Documentation/git-commit.adoc +8 -1
index 54c207ad45..ed4c54ae81 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
 	   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+	   [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
 	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
 	   [--] [<pathspec>...]
@@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details.
 	commit by that author (i.e. `git rev-list --all -i --author=<author>`);
 	the commit author is then copied from the first such commit found.
 
+`--committer=<committer>`::
+	Override the committer for the commit. Specify an explicit committer using the
+	standard `C O Mitter <[email protected]>` format. Otherwise _<committer>_
+	is assumed to be a pattern and is used to search for an existing
+	commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);
+	the commit committer is then copied from the first such commit found.
+
 `--date=<date>`::
 	Override the author date used in the commit.
 
builtin/commit.c +89 -32

224 lines not on this page · raw message

t/t7509-commit-authorship.sh +83 -1

100 lines not on this page · raw message

base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84

Signature · 1 line
gitgitgadget
#26 lore raw
In reply to ZheNing Hu
Quoted 9 lines · ZheNing Hu
Quoted 8 lines

I'm afraid I don't quite follow. If you are amending existing commits to fix them up after you have corrected your configuration then they will have the correct committer automatically when you run "git commit --amend --author=..." to correct the author. If you are committing before you have realized that user.{name,email} are misconfigured then I don't see how "--committer" helps because you have not yet realized anything is wrong.

Quoted 5 lines

You're right that after realizing the misconfiguration and correcting the repository's user.name and user.email, running git commit --amend will
fix the committer information, but the author remains unchanged. Users
then need an additional git commit --amend --author=... to fix the author, which does work but requires an extra step.

Isn't it more like "You need to run with --author to correct the authorship by amending the commit *anyway*, but while doing so, the committer information will automatically be corrected"? As I said earlier in a separate message, the author and the committer are not symmetric, so having "--author" does make sense in the above picture, while "--committer", as Phillip points out, much less.

#41 lore raw
In reply to ZheNing Hu via GitGitGadget

Documentation/git-commit.adoc See linkgit:git-rebase[1] for details. · line 182

Quoted 2 lines · ZheNing Hu via GitGitGadget

+--committer=<committer>::

Documentation/git-commit.adoc +1 -0
+	Override the committer for the commit. Specify an explicit committer using the

Isn't "set" or "use" more appropirate verb to use here?

We already take the committer identity from multiple plases, like user.{name,email}, or GIT_COMMITTER_{NAME,EMAIL} configuration, and with the patch we also take it from a command line option, with the usual precedence order (i.e., command line trumps environment which trumps configuration).

builtin/commit.c static void set_ident_var(char **buf, char *val) · line 634

Quoted 2 lines
builtin/commit.c +1 -1
-static void determine_author_info(struct strbuf *author_ident)
+static void determine_identity(struct strbuf *ident_str, int is_author)

"is_author" does not sound grammatical for this case; if you are giving an ident of an unknown kind to this function and supplying another parameter to let it know which kind, "is_author" may make sense, but not here.

As you will convert it into WANT_{AUTHOR,COMMITTER}_IDENT before using anyway, why not let the caller use the "enum want_ident" to tell this function what to do?

Quoted 41 lines

{

builtin/commit.c +18 -12
 	char *name, *email, *date;
-	struct ident_split author;
-
-	name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME"));
-	email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL"));
-	date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE"));
-
-	if (author_message) {
-		struct ident_split ident;
+	struct ident_split ident;
+	const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME";
+	const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL";
+	const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE";
+	const char *force_ident = is_author ? force_author : force_committer;
+	const char *param_name = is_author ? "--author" : "--committer";
+	int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;
+
+	name = xstrdup_or_null(getenv(env_name));
+	email = xstrdup_or_null(getenv(env_email));
+	date = xstrdup_or_null(getenv(env_date));
+
+	if (is_author && author_message) {
+		struct ident_split msg_ident;
 		size_t len;
 		const char *a;
 
 		a = find_commit_header(author_message_buffer, "author", &len);
 		if (!a)
 			die(_("commit '%s' lacks author header"), author_message);
-		if (split_ident_line(&ident, a, len) < 0)
+		if (split_ident_line(&msg_ident, a, len) < 0)
 			die(_("commit '%s' has malformed author line"), author_message);
 
-		set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
-		set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+		set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));
+		set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));
 
-		if (ident.date_begin) {
+		if (msg_ident.date_begin) {

builtin/commit.c static void set_ident_var(char **buf, char *val) · line 671

Quoted 10 lines
builtin/commit.c +2 -2
 			struct strbuf date_buf = STRBUF_INIT;
 			strbuf_addch(&date_buf, '@');
-			strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+			strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);
 			strbuf_addch(&date_buf, ' ');
-			strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+			strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);
 			set_ident_var(&date, strbuf_detach(&date_buf, NULL));
 		}
 	}

The helper tries to be generic between both kinds of ident, but we still need conditional that says "this part of the function is only when we are looking for author", which is rather unsatisfactory.

Also why do we need this much patch noise, only because you renamed one variable? I wonder if it would make it cleaner to move the body of this if() {} statement into a separate helper function, leaving only

	if (whose_ident == WANT_AUTHOR_IDENT)
		set_author_from_message(&name, &email, &date);

or something simple here?

#27 lore raw
In reply to Junio C Hamano
Quoted 18 lines · Junio C Hamano <[email protected]> 于2025年11月13日周四 01:24写道:
Quoted 9 lines · ZheNing Hu
Quoted 8 lines

I'm afraid I don't quite follow. If you are amending existing commits to fix them up after you have corrected your configuration then they will have the correct committer automatically when you run "git commit --amend --author=..." to correct the author. If you are committing before you have realized that user.{name,email} are misconfigured then I don't see how "--committer" helps because you have not yet realized anything is wrong.

Quoted 5 lines

You're right that after realizing the misconfiguration and correcting the repository's user.name and user.email, running git commit --amend will
fix the committer information, but the author remains unchanged. Users
then need an additional git commit --amend --author=... to fix the author, which does work but requires an extra step.

Quoted 7 lines

Isn't it more like "You need to run with --author to correct the authorship by amending the commit *anyway*, but while doing so, the committer information will automatically be corrected"? As I said earlier in a separate message, the author and the committer are not symmetric, so having "--author" does make sense in the above picture, while "--committer", as Phillip points out, much less.

Well, I admit that perhaps the design philosophy of author/committer is inconsistent (which has caused too much trouble), but for users, a consistent parameter interface is easier to understand and use.

#42 lore raw
In reply to Junio C Hamano
Quoted 6 lines · Junio C Hamano <[email protected]> 于2025年11月13日周四 02:56写道:
Quoted 2 lines · ZheNing Hu via GitGitGadget

+--committer=<committer>::
+ Override the committer for the commit. Specify an explicit committer using the

Quoted 8 lines

Isn't "set" or "use" more appropirate verb to use here?

We already take the committer identity from multiple plases, like user.{name,email}, or GIT_COMMITTER_{NAME,EMAIL} configuration, and with the patch we also take it from a command line option, with the usual precedence order (i.e., command line trumps environment which trumps configuration).

Since both --author=<author> and --date=<date> use the "Override the *" phrasing, --committer uses "Override the" just to maintain consistency, though changing it to "Set the" would also be acceptable.

Quoted 8 lines

builtin/commit.c static void set_ident_var(char **buf, char *val) · line 634

Quoted 2 lines
builtin/commit.c +1 -1
-static void determine_author_info(struct strbuf *author_ident)
+static void determine_identity(struct strbuf *ident_str, int is_author)

"is_author" does not sound grammatical for this case; if you are giving an ident of an unknown kind to this function and supplying another parameter to let it know which kind, "is_author" may make sense, but not here.

Quoted 4 lines

As you will convert it into WANT_{AUTHOR,COMMITTER}_IDENT before using anyway, why not let the caller use the "enum want_ident" to tell this function what to do?

Ok, will change.

Quoted 39 lines
Quoted 39 lines
 {
      char *name, *email, *date;
builtin/commit.c +17 -11
-     struct ident_split author;
-
-     name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME"));
-     email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL"));
-     date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE"));
-
-     if (author_message) {
-             struct ident_split ident;
+     struct ident_split ident;
+     const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME";
+     const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL";
+     const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE";
+     const char *force_ident = is_author ? force_author : force_committer;
+     const char *param_name = is_author ? "--author" : "--committer";
+     int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;
+
+     name = xstrdup_or_null(getenv(env_name));
+     email = xstrdup_or_null(getenv(env_email));
+     date = xstrdup_or_null(getenv(env_date));
+
+     if (is_author && author_message) {
+             struct ident_split msg_ident;
              size_t len;
              const char *a;
 
              a = find_commit_header(author_message_buffer, "author", &len);
              if (!a)
                      die(_("commit '%s' lacks author header"), author_message);
-             if (split_ident_line(&ident, a, len) < 0)
+             if (split_ident_line(&msg_ident, a, len) < 0)
                      die(_("commit '%s' has malformed author line"), author_message);
 
-             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
-             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+             set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));
+             set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));
Quoted 17 lines

builtin/commit.c static void set_ident_var(char **buf, char *val) · line 671

Quoted 12 lines
builtin/commit.c +3 -3
-             if (ident.date_begin) {
+             if (msg_ident.date_begin) {
                      struct strbuf date_buf = STRBUF_INIT;
                      strbuf_addch(&date_buf, '@');
-                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+                     strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);
                      strbuf_addch(&date_buf, ' ');
-                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+                     strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);
                      set_ident_var(&date, strbuf_detach(&date_buf, NULL));
              }
      }

The helper tries to be generic between both kinds of ident, but we still need conditional that says "this part of the function is only when we are looking for author", which is rather unsatisfactory.

Indeed, some specific logic should be moved to determine_author_info()/determine_committer_info().

Quoted 8 lines

Also why do we need this much patch noise, only because you renamed one variable? I wonder if it would make it cleaner to move the body of this if() {} statement into a separate helper function, leaving only

        if (whose_ident == WANT_AUTHOR_IDENT)
                set_author_from_message(&name, &email, &date);

Ok.

Quoted 1 line

or something simple here?

#43 lore raw

PATCH v4 1/1 commit: add --committer option

In reply to ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables.

Like --author, the --committer option supports two formats:
- Explicit identity: --committer="Name <[email protected]>"
- Pattern search: --committer="pattern" searches commit history for a matching committer and reuses that identity

To share code with the existing --author option, this patch refactors:

1. find_author_by_nickname() into find_identity_by_nickname() which handles both author and committer searches through an is_author parameter.

2. determine_author_info() into determine_identity() which handles identity parsing and setting for both author and committer through an is_author parameter.

---
commit: add --committer option

Currently, when users need to override the committer identity in git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables, which can be cumbersome in scripting scenarios or when frequently switching committer identities.

While git-commit already provides the --author option to conveniently override the author identity, there's no equivalent --committer option for the committer identity. This asymmetry creates an inconsistent user experience.

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/1997

Range-diff vs v3:

1: acf724fad5 ! 1: bb17f810ef commit: add --committer option

     @@ Documentation/git-commit.adoc: See linkgit:git-rebase[1] for details.
       	the commit author is then copied from the first such commit found.
       
      +`--committer=<committer>`::
     -+	Override the committer for the commit. Specify an explicit committer using the
     ++	Set the committer for the commit. Specify an explicit committer using the
      +	standard `C O Mitter <[email protected]>` format. Otherwise _<committer>_
      +	is assumed to be a pattern and is used to search for an existing
      +	commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);
     @@ builtin/commit.c: static void set_ident_var(char **buf, char *val)
       }
       
      -static void determine_author_info(struct strbuf *author_ident)
     -+static void determine_identity(struct strbuf *ident_str, int is_author)
     - {
     - 	char *name, *email, *date;
     +-{
     +-	char *name, *email, *date;
      -	struct ident_split author;
     --
     ++static void set_author_from_message(char **name, char **email, char **date) {
     ++	struct ident_split ident;
     ++	size_t len;
     ++	const char *a;
     + 
      -	name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME"));
      -	email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL"));
      -	date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE"));
     --
     ++	if (!author_message)
     ++		return;
     + 
      -	if (author_message) {
      -		struct ident_split ident;
     -+	struct ident_split ident;
     -+	const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME";
     -+	const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL";
     -+	const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE";
     -+	const char *force_ident = is_author ? force_author : force_committer;
     -+	const char *param_name = is_author ? "--author" : "--committer";
     -+	int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;
     -+
     -+	name = xstrdup_or_null(getenv(env_name));
     -+	email = xstrdup_or_null(getenv(env_email));
     -+	date = xstrdup_or_null(getenv(env_date));
     -+
     -+	if (is_author && author_message) {
     -+		struct ident_split msg_ident;
     - 		size_t len;
     - 		const char *a;
     - 
     - 		a = find_commit_header(author_message_buffer, "author", &len);
     - 		if (!a)
     - 			die(_("commit '%s' lacks author header"), author_message);
     +-		size_t len;
     +-		const char *a;
     +-
     +-		a = find_commit_header(author_message_buffer, "author", &len);
     +-		if (!a)
     +-			die(_("commit '%s' lacks author header"), author_message);
      -		if (split_ident_line(&ident, a, len) < 0)
     -+		if (split_ident_line(&msg_ident, a, len) < 0)
     - 			die(_("commit '%s' has malformed author line"), author_message);
     - 
     +-			die(_("commit '%s' has malformed author line"), author_message);
     +-
      -		set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
      -		set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
     -+		set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));
     -+		set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));
     - 
     +-
      -		if (ident.date_begin) {
     -+		if (msg_ident.date_begin) {
     - 			struct strbuf date_buf = STRBUF_INIT;
     - 			strbuf_addch(&date_buf, '@');
     +-			struct strbuf date_buf = STRBUF_INIT;
     +-			strbuf_addch(&date_buf, '@');
      -			strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
     -+			strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);
     - 			strbuf_addch(&date_buf, ' ');
     +-			strbuf_addch(&date_buf, ' ');
      -			strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
     -+			strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);
     - 			set_ident_var(&date, strbuf_detach(&date_buf, NULL));
     - 		}
     +-			set_ident_var(&date, strbuf_detach(&date_buf, NULL));
     +-		}
     ++	a = find_commit_header(author_message_buffer, "author", &len);
     ++	if (!a)
     ++		die(_("commit '%s' lacks author header"), author_message);
     ++	if (split_ident_line(&ident, a, len) < 0)
     ++		die(_("commit '%s' has malformed author line"), author_message);
     ++
     ++	set_ident_var(name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
     ++	set_ident_var(email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
     ++
     ++	if (ident.date_begin) {
     ++		struct strbuf date_buf = STRBUF_INIT;
     ++		strbuf_addch(&date_buf, '@');
     ++		strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
     ++		strbuf_addch(&date_buf, ' ');
     ++		strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
     ++		set_ident_var(date, strbuf_detach(&date_buf, NULL));
       	}
     ++}
       
      -	if (force_author) {
      -		struct ident_split ident;
     ++static void determine_identity(struct strbuf *ident_str, enum want_ident whose_ident,
     ++			       const char *env_name, const char *env_email, const char *env_date,
     ++			       const char *force_ident, const char *param_name,
     ++			       char *name, char *email, char *date)
     ++{
     ++	struct ident_split ident;
     ++
     ++
      +	if (force_ident) {
      +		struct ident_split force_ident_split;
     -+
     -+		if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)
     -+			die(_("malformed %s parameter"), param_name);
     -+		set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));
     -+		set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));
       
      -		if (split_ident_line(&ident, force_author, strlen(force_author)) < 0)
      -			die(_("malformed --author parameter"));
      -		set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
      -		set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
     -+		if (!is_author && force_ident_split.date_begin) {
     -+			struct strbuf date_buf = STRBUF_INIT;
     -+			strbuf_addch(&date_buf, '@');
     -+			strbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin);
     -+			strbuf_addch(&date_buf, ' ');
     -+			strbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin);
     -+			set_ident_var(&date, strbuf_detach(&date_buf, NULL));
     -+		}
     ++		if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)
     ++			die(_("malformed %s parameter"), param_name);
     ++		set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));
     ++		set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));
       	}
       
       	if (force_date) {
     @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)
       	}
       
      -	strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,
     -+	strbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date,
     ++	strbuf_addstr(ident_str, fmt_ident(name, email, whose_ident, date,
       				IDENT_STRICT));
      -	assert_split_ident(&author, author_ident);
      -	export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
     @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)
      -	export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
      +	assert_split_ident(&ident, ident_str);
      +
     -+	if (is_author) {
     -+		export_one("GIT_AUTHOR_NAME", ident.name_begin, ident.name_end, 0);
     -+		export_one("GIT_AUTHOR_EMAIL", ident.mail_begin, ident.mail_end, 0);
     -+		export_one("GIT_AUTHOR_DATE", ident.date_begin, ident.tz_end, '@');
     -+	} else {
     -+		export_one("GIT_COMMITTER_NAME", ident.name_begin, ident.name_end, 0);
     -+		export_one("GIT_COMMITTER_EMAIL", ident.mail_begin, ident.mail_end, 0);
     -+		export_one("GIT_COMMITTER_DATE", ident.date_begin, ident.tz_end, '@');
     -+	}
     ++	export_one(env_name, ident.name_begin, ident.name_end, 0);
     ++	export_one(env_email, ident.mail_begin, ident.mail_end, 0);
     ++	export_one(env_date, ident.date_begin, ident.tz_end, '@');
      +
       	free(name);
       	free(email);
     @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)
       
      +static void determine_author_info(struct strbuf *author_ident)
      +{
     -+	determine_identity(author_ident, 1);
     ++	char *name, *email, *date;
     ++
     ++	name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME"));
     ++	email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL"));
     ++	date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE"));
     ++
     ++	set_author_from_message(&name, &email, &date);
     ++
     ++	determine_identity(author_ident, WANT_AUTHOR_IDENT,
     ++			   "GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_AUTHOR_DATE",
     ++			   force_author, "--author",
     ++			   name, email, date);
      +}
      +
      +static void determine_committer_info(struct strbuf *committer_ident)
      +{
     -+	determine_identity(committer_ident, 0);
     ++	char *name, *email, *date;
     ++
     ++	name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
     ++	email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
     ++	date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
     ++
     ++	determine_identity(committer_ident, WANT_COMMITTER_IDENT,
     ++			   "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", "GIT_COMMITTER_DATE",
     ++			   force_committer, "--committer",
     ++			   name, email, date);
      +}
      +
       static int author_date_is_interesting(void)
     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha
       }
       
      -static const char *find_author_by_nickname(const char *name)
     -+static const char *find_identity_by_nickname(const char *name, int is_author)
     ++static const char *find_identity_by_nickname(const char *name, enum want_ident whose_ident)
       {
       	struct rev_info revs;
       	struct commit *commit;
       	struct strbuf buf = STRBUF_INIT;
       	const char *av[20];
       	int ac = 0;
     -+	const char *field = is_author ? "author" : "committer";
     -+	const char *format = is_author ? "%aN <%aE>" : "%cN <%cE>";
     ++	const char *field, *format;
     ++
     ++	if (whose_ident != WANT_AUTHOR_IDENT && whose_ident != WANT_COMMITTER_IDENT)
     ++		BUG("find_identity_by_nickname requires WANT_AUTHOR_IDENT or WANT_COMMITTER_IDENT");
     ++
     ++	field = whose_ident == WANT_AUTHOR_IDENT ? "author" : "committer";
     ++	format = whose_ident == WANT_AUTHOR_IDENT ? "%aN <%aE>" : "%cN <%cE>";
       
       	repo_init_revisions(the_repository, &revs, NULL);
      -	strbuf_addf(&buf, "--author=%s", name);
     @@ builtin/commit.c: static const char *find_author_by_nickname(const char *name)
      +
      +static const char *find_author_by_nickname(const char *name)
      +{
     -+	return find_identity_by_nickname(name, 1);
     ++	return find_identity_by_nickname(name, WANT_AUTHOR_IDENT);
      +}
      +
      +static const char *find_committer_by_nickname(const char *name)
      +{
     -+	return find_identity_by_nickname(name, 0);
     ++	return find_identity_by_nickname(name, WANT_COMMITTER_IDENT);
       }
       
       static void handle_ignored_arg(struct wt_status *s)
 Documentation/git-commit.adoc |   9 +-
 builtin/commit.c              | 155 ++++++++++++++++++++++++----------
 t/t7509-commit-authorship.sh  |  84 +++++++++++++++++-
 3 files changed, 200 insertions(+), 48 deletions(-)
Documentation/git-commit.adoc +8 -1
index 54c207ad45..a0c8a586de 100644
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
 	   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
 	   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
 	   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
-	   [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+	   [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]
 	   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
 	   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
 	   [--] [<pathspec>...]
@@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details.
 	commit by that author (i.e. `git rev-list --all -i --author=<author>`);
 	the commit author is then copied from the first such commit found.
 
+`--committer=<committer>`::
+	Set the committer for the commit. Specify an explicit committer using the
+	standard `C O Mitter <[email protected]>` format. Otherwise _<committer>_
+	is assumed to be a pattern and is used to search for an existing
+	commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);
+	the commit committer is then copied from the first such commit found.
+
 `--date=<date>`::
 	Override the author date used in the commit.
 
builtin/commit.c +109 -46

244 lines not on this page · raw message

t/t7509-commit-authorship.sh +83 -1

100 lines not on this page · raw message

base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84

Signature · 1 line
gitgitgadget
#28 lore raw
In reply to ZheNing Hu
Quoted 11 lines · ZheNing Hu
Quoted 7 lines

Isn't it more like "You need to run with --author to correct the authorship by amending the commit *anyway*, but while doing so, the committer information will automatically be corrected"? As I said earlier in a separate message, the author and the committer are not symmetric, so having "--author" does make sense in the above picture, while "--committer", as Phillip points out, much less.

Well, I admit that perhaps the design philosophy of author/committer is inconsistent (which has caused too much trouble), but for users, a consistent parameter interface is easier to understand and use.

I do not think it is about design philosophy at all, though.

The distinction comes from the difference between what "author" and "committer" fields record. The committer records the identity of the person who was at the keyboard when the commit object was created. The author records the identity of the person who wrote the change that the committer is turning into a commit. There is no symmetry between them, hence there is no inconsistency here.

And it also comes from the actual human user behaviour.

Many authors can pass their patches to a smaller number of committers who make them into part of the official project history, so when a commit is made, there is much stronger need to tweak who the author is for the commit than to tweak who the committer is. On the other hand, it is rare (if ever done) for multiple committers to share a single shell terminal session and take turns to make commit, where you would need to be able to say "this invocations of 'git commit' command is done by person X, who is different from the one who made the previous commit in this same shell session".

#30 lore raw
In reply to ZheNing Hu
Quoted 35 lines · ZheNing Hu
Quoted 34 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 22:38写道:
Quoted 31 lines · ZheNing Hu
Quoted 11 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 00:50写道:
Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 8 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

I frequently need to distinguish between different user.name and user.email configurations on our company's internal GitHub.

The current problems are:

When I misconfigure (which happens occasionally), git commit --author only fixes the author part, I still need to additionally set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables to fix the committer information These environment variables are painful to use, requiring manual setup every time

Quoted 9 lines
Quoted 8 lines

I'm afraid I don't quite follow. If you are amending existing commits to fix them up after you have corrected your configuration then they will have the correct committer automatically when you run "git commit --amend --author=..." to correct the author. If you are committing before you have realized that user.{name,email} are misconfigured then I don't see how "--committer" helps because you have not yet realized anything is wrong.

Hi

I use includeIf pattern in a config to separate identities

~/.gitconfig:

```
 [includeIf "gitdir:~/.local/src/personal/"]
     path ~/.gitconfig-personal
 [includeIf "gitdir:~/.local/src/companyA/"]
     path ~/.gitconfig-companyA
 [includeIf "gitdir:~/.local/src/companyB/"]
     path ~/.gitconfig-companyB
```

then each
~/.gitconfig-IDENTITY:

```
 [user]
     name = ...
     email = ...
     signingkey = ...
```
Quoted 5 lines

You're right that after realizing the misconfiguration and correcting the repository's user.name and user.email, running git commit --amend will fix the committer information, but the author remains unchanged. Users then need an additional git commit --amend --author=... to fix the author, which does work but requires an extra step.

For just one commit, after you fix identity (update .git/config or move project so includeIf uses correct config) then git commit --amend --reset-author should get right identity for both commiter and author.

Quoted 9 lines

I see your point that this becomes more cumbersome when dealing with multiple commits. In such cases, users currently need to use something like:

```
GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \
GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \
git rebase -f <target>
```

In my test ^ (using 2.51.2) did not set specified AUTHOR identity, but using:

  git rebase <target> -fx "git commit --amend --no-edit --reset-author"

is close to rewriting commits with new identity, but this will change both dates (committer, author).

If --reset-author is not used but either GIT_AUTHOR_* are exported or --author '...' is used in a -x arg, then author date is kept untouched.

Quoted 11 lines

This is indeed tedious and error-prone, especially when you want to quickly fix and push commits to the platform.

git commit --amend --author --committer or a new git rebase --author --committer
would provide a more user-friendly workflow for correcting identity information after misconfiguration, eliminating the need to manually set multiple
environment variables or run multiple commands.

Quoted 9 lines
Quoted 4 lines

Thanks

Phillip

Thanks

ZheNing Hu

--
Thanks,
Matej

#31 lore raw
Quoted 15 lines · Matej Dujava
Quoted 1 line · ZheNing Hu

Phillip Wood <[email protected]> 于2025年11月11日周二 22:38写道:

I use includeIf pattern in a config to separate identities

~/.gitconfig:

```
  [includeIf "gitdir:~/.local/src/personal/"]
      path ~/.gitconfig-personal
  [includeIf "gitdir:~/.local/src/companyA/"]
      path ~/.gitconfig-companyA
  [includeIf "gitdir:~/.local/src/companyB/"]
      path ~/.gitconfig-companyB
```
Quoted 8 lines

then each
~/.gitconfig-IDENTITY:

```
  [user]
      name = ...
      email = ...
      signingkey = ...
```

I think that's a common pattern, so long as one can arrange the directory structure so that the repositories for each identity are under a different sub-directory it works well.

Quoted 9 lines
Quoted 5 lines

You're right that after realizing the misconfiguration and correcting the repository's user.name and user.email, running git commit --amend will fix the committer information, but the author remains unchanged. Users then need an additional git commit --amend --author=... to fix the author, which does work but requires an extra step.

For just one commit, after you fix identity (update .git/config or move project so includeIf uses correct config) then git commit --amend --reset-author should get right identity for both commiter and author.

As you note below it also resets the author date which might to be desirable.

Quoted 10 lines
Quoted 8 lines

I see your point that this becomes more cumbersome when dealing with multiple commits. In such cases, users currently need to use something like:

```
GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \
GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \
git rebase -f <target>
```

In my test ^ (using 2.51.2) did not set specified AUTHOR identity

Indeed, rebase sets GIT_AUTHOR_{NAME,EMAIL,DATE} when running "git commit" to preserve the authorship of the commit being picked so any value that is set in the environment when running "git rebase" is ignored.

Quoted 8 lines

but using:

  git rebase <target> -fx "git commit --amend --no-edit --reset-author"

is close to rewriting commits with new identity, but this will change both dates (committer, author).
If --reset-author is not used but either GIT_AUTHOR_* are exported or --author '...' is used in a -x arg, then author date is kept untouched.

Yes "git rebase -x 'git commit --author=... --amend --no-edit'" is probably the easiest way to reset the author and committer.

Quoted 10 lines
Quoted 10 lines

This is indeed tedious and error-prone, especially when you want to quickly fix and push commits to the platform.

git commit --amend --author --committer or a new git rebase --author --committer
would provide a more user-friendly workflow for correcting identity information after misconfiguration, eliminating the need to manually set multiple
environment variables or run multiple commands.

I'm still not sure why we need a "--committer" option when the committer identity is taken from the corrected config anyway.

Thanks

Phillip

#29 lore raw
In reply to Junio C Hamano
Quoted 15 lines · Junio C Hamano <[email protected]> 于2025年11月16日周日 09:06写道:
Quoted 11 lines · ZheNing Hu
Quoted 7 lines

Isn't it more like "You need to run with --author to correct the authorship by amending the commit *anyway*, but while doing so, the committer information will automatically be corrected"? As I said earlier in a separate message, the author and the committer are not symmetric, so having "--author" does make sense in the above picture, while "--committer", as Phillip points out, much less.

Well, I admit that perhaps the design philosophy of author/committer is inconsistent (which has caused too much trouble), but for users, a consistent parameter interface is easier to understand and use.

Quoted 9 lines

I do not think it is about design philosophy at all, though.

The distinction comes from the difference between what "author" and "committer" fields record. The committer records the identity of the person who was at the keyboard when the commit object was created. The author records the identity of the person who wrote the change that the committer is turning into a commit. There is no symmetry between them, hence there is no inconsistency here.

I understand the difference between committer and author, but some regular Git users don't really pay much attention to the distinction between the two. For these users, being able to quickly and easily correct user information is sufficient — it's just that some previous solutions were a bit complicated.

Quoted 12 lines

And it also comes from the actual human user behaviour.

Many authors can pass their patches to a smaller number of committers who make them into part of the official project history, so when a commit is made, there is much stronger need to tweak who the author is for the commit than to tweak who the committer is. On the other hand, it is rare (if ever done) for multiple committers to share a single shell terminal session and take turns to make commit, where you would need to be able to say "this invocations of 'git commit' command is done by person X, who is different from the one who made the previous commit in this same shell session".

Yes, I believe users in this case are very rare. As I mentioned before, I just want to help those users who want to fix things after misconfiguration, making the fix a bit simpler.

#33 lore raw
In reply to Matej Dujava
Quoted 61 lines · Matej Dujava <[email protected]> 于2025年11月17日周一 06:12写道:
Quoted 35 lines · ZheNing Hu
Quoted 34 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 22:38写道:
Quoted 31 lines · ZheNing Hu
Quoted 11 lines · Phillip Wood <[email protected]> 于2025年11月11日周二 00:50写道:
Quoted 9 lines · ZheNing Hu via GitGitGadget

From: ZheNing Hu <[email protected]>

This patch introduces the --committer option to git-commit, providing:

1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities

Quoted 8 lines

What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity.

I frequently need to distinguish between different user.name and user.email configurations on our company's internal GitHub.

The current problems are:

When I misconfigure (which happens occasionally), git commit --author only fixes the author part, I still need to additionally set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables to fix the committer information These environment variables are painful to use, requiring manual setup every time

Quoted 9 lines
Quoted 8 lines

I'm afraid I don't quite follow. If you are amending existing commits to fix them up after you have corrected your configuration then they will have the correct committer automatically when you run "git commit --amend --author=..." to correct the author. If you are committing before you have realized that user.{name,email} are misconfigured then I don't see how "--committer" helps because you have not yet realized anything is wrong.

Hi

I use includeIf pattern in a config to separate identities

~/.gitconfig:

```
  [includeIf "gitdir:~/.local/src/personal/"]
      path ~/.gitconfig-personal
  [includeIf "gitdir:~/.local/src/companyA/"]
      path ~/.gitconfig-companyA
  [includeIf "gitdir:~/.local/src/companyB/"]
      path ~/.gitconfig-companyB
```
Quoted 9 lines

then each
~/.gitconfig-IDENTITY:

```
  [user]
      name = ...
      email = ...
      signingkey = ...
```

This does indeed appear to be a very good standard, but unfortunately many users in the past and possibly even in the future may not be aware of such a good practice.

Quoted 10 lines
Quoted 5 lines

You're right that after realizing the misconfiguration and correcting the repository's user.name and user.email, running git commit --amend will fix the committer information, but the author remains unchanged. Users then need an additional git commit --amend --author=... to fix the author, which does work but requires an extra step.

For just one commit, after you fix identity (update .git/config or move project so includeIf uses correct config) then git commit --amend --reset-author should get right identity for both commiter and author.

Ok...

Quoted 13 lines
Quoted 9 lines

I see your point that this becomes more cumbersome when dealing with multiple commits. In such cases, users currently need to use something like:

```
GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \
GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \
git rebase -f <target>
```

In my test ^ (using 2.51.2) did not set specified AUTHOR identity, but using:

Quoted 8 lines
   git rebase <target> -fx "git commit --amend --no-edit --reset-author"

is close to rewriting commits with new identity, but this will change both dates (committer, author).

If --reset-author is not used but either GIT_AUTHOR_* are exported or --author '...' is used in a -x arg, then author date is kept untouched.

This does look like a good approach indeed, and it should pretty much meet the user's requirements.

Quoted 24 lines
Quoted 11 lines

This is indeed tedious and error-prone, especially when you want to quickly fix and push commits to the platform.

git commit --amend --author --committer or a new git rebase --author --committer
would provide a more user-friendly workflow for correcting identity information after misconfiguration, eliminating the need to manually set multiple
environment variables or run multiple commands.

Quoted 9 lines
Quoted 4 lines

Thanks

Phillip

Thanks

ZheNing Hu

--
Thanks,
Matej

#32 lore raw
In reply to Phillip Wood
Quoted 81 lines · Phillip Wood <[email protected]> 于2025年11月17日周一 22:27写道:
Quoted 15 lines · Matej Dujava
Quoted 1 line · ZheNing Hu

Phillip Wood <[email protected]> 于2025年11月11日周二 22:38写道:

I use includeIf pattern in a config to separate identities

~/.gitconfig:

```
  [includeIf "gitdir:~/.local/src/personal/"]
      path ~/.gitconfig-personal
  [includeIf "gitdir:~/.local/src/companyA/"]
      path ~/.gitconfig-companyA
  [includeIf "gitdir:~/.local/src/companyB/"]
      path ~/.gitconfig-companyB
```
Quoted 8 lines

then each
~/.gitconfig-IDENTITY:

```
  [user]
      name = ...
      email = ...
      signingkey = ...
```

I think that's a common pattern, so long as one can arrange the directory structure so that the repositories for each identity are under a different sub-directory it works well.

Quoted 10 lines
Quoted 6 lines

You're right that after realizing the misconfiguration and correcting the repository's user.name and user.email, running git commit --amend will fix the committer information, but the author remains unchanged. Users then need an additional git commit --amend --author=... to fix the author,
which does work but requires an extra step.

For just one commit, after you fix identity (update .git/config or move project so includeIf uses correct config) then git commit --amend --reset-author should get right identity for both commiter and author.

As you note below it also resets the author date which might to be desirable.

Quoted 11 lines
Quoted 9 lines

I see your point that this becomes more cumbersome when dealing with multiple commits. In such cases, users currently need to use something like:

```
GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \
GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \
git rebase -f <target>
```

In my test ^ (using 2.51.2) did not set specified AUTHOR identity

Indeed, rebase sets GIT_AUTHOR_{NAME,EMAIL,DATE} when running "git commit" to preserve the authorship of the commit being picked so any value that is set in the environment when running "git rebase" is ignored.

Quoted 8 lines

but using:

   git rebase <target> -fx "git commit --amend --no-edit --reset-author"

is close to rewriting commits with new identity, but this will change both dates (committer, author).
If --reset-author is not used but either GIT_AUTHOR_* are exported or --author '...' is used in a -x arg, then author date is kept untouched.

Yes "git rebase -x 'git commit --author=... --amend --no-edit'" is probably the easiest way to reset the author and committer.

Quoted 10 lines
Quoted 10 lines

This is indeed tedious and error-prone, especially when you want to quickly fix and push commits to the platform.

git commit --amend --author --committer or a new git rebase --author --committer
would provide a more user-friendly workflow for correcting identity information after misconfiguration, eliminating the need to manually set multiple
environment variables or run multiple commands.

Quoted 3 lines

I'm still not sure why we need a "--committer" option when the committer identity is taken from the corrected config anyway.

Well, if the git rebase --fx approach is simple enough, I can also give up on introducing --committer.

Quoted 4 lines

Thanks

Phillip