{"thread":{"id":"64460","subject":"[PATCH] commit: add --committer option","startedAt":"2025-11-09T10:22:57Z","lastAt":"2025-11-17T15:18:29Z","messageCount":44,"participants":["ZheNing Hu via GitGitGadget","Patrick Steinhardt","ZheNing Hu","Phillip Wood","Junio C Hamano","brian m. carlson","Jeff King","Matej Dujava"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"530422","messageId":"pull.1997.git.1762683774166.gitgitgadget@gmail.com","threadId":"64460","inReplyTo":null,"subject":"[PATCH] commit: add --committer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-11-09T10:22:54Z","receivedAt":"2025-11-09T10:22:57Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nAdd --committer option to git-commit, allowing users to override the\ncommitter identity similar to how --author works. This provides a more\nconvenient alternative to setting GIT_COMMITTER_* environment variables.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n\nCo-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n---\n    commit: add --committer option\n    \n    Currently, when users need to override the committer identity in\n    git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL\n    environment variables, which can be cumbersome in scripting scenarios or\n    when frequently switching committer identities.\n    \n    While git-commit already provides the --author option to conveniently\n    override the author identity, there's no equivalent --committer option\n    for the committer identity. This asymmetry creates an inconsistent user\n    experience.\n    \n    This patch introduces the --committer option to git-commit, providing:\n    \n     1. Consistency with the existing --author option\n     2. A more convenient alternative to environment variables\n     3. Better support for automated workflows and scripts\n     4. Improved user experience when managing multiple identities\n    \n    The implementation follows the same pattern as the --author option,\n    accepting the format \"Name \" and properly validating the input.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1997\n\n Documentation/git-commit.adoc |  9 +++-\n builtin/commit.c              | 58 ++++++++++++++++++++++++-\n t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++\n 3 files changed, 144 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 54c207ad45..a015c8328e 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n \t   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n \t   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n-\t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n+\t   [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n \t   [--] [<pathspec>...]\n@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.\n `--date=<date>`::\n \tOverride the author date used in the commit.\n \n+`--committer=<committer>`::\n+\tOverride the committer for the commit. Specify an explicit committer using the\n+\tstandard `A U Thor <committer@example.com>` format. Otherwise _<committer>_\n+\tis assumed to be a pattern and is used to search for an existing\n+\tcommit by that author (i.e. `git rev-list --all -i --author=<committer>`);\n+\tthe commit author is then copied from the first such commit found.\n+\n `-m <msg>`::\n `--message=<msg>`::\n \tUse _<msg>_ as the commit message.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 0243f17d53..88e77cbaab 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {\n \t   \"           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\\n\"\n \t   \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n \t   \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n-\t   \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n+\t   \"           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\\n\"\n \t   \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n \t   \"           [--] [<pathspec>...]\"),\n@@ -112,6 +112,7 @@ static enum {\n } commit_style;\n \n static const char *force_author;\n+static const char *force_committer;\n static char *logfile;\n static char *template_file;\n /*\n@@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)\n \tfree(date);\n }\n \n+static void determine_committer_info(struct strbuf *committer_ident)\n+{\n+\tchar *name, *email, *date;\n+\tstruct ident_split committer;\n+\n+\tname = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n+\temail = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n+\tdate = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n+\n+\tif (force_committer) {\n+\t\tstruct ident_split ident;\n+\n+\t\tif (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)\n+\t\t\tdie(_(\"malformed --committer parameter\"));\n+\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n+\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n+\n+\t\tif (ident.date_begin) {\n+\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n+\t\t\tstrbuf_addch(&date_buf, '@');\n+\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n+\t\t\tstrbuf_addch(&date_buf, ' ');\n+\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n+\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n+\t\t}\n+\t}\n+\n+\tif (force_date) {\n+\t\tstruct strbuf date_buf = STRBUF_INIT;\n+\t\tif (parse_force_date(force_date, &date_buf))\n+\t\t\tdie(_(\"invalid date format: %s\"), force_date);\n+\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n+\t}\n+\n+\tstrbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,\n+\t\t\t\tIDENT_STRICT));\n+\tassert_split_ident(&committer, committer_ident);\n+\tfree(name);\n+\tfree(email);\n+\tfree(date);\n+}\n+\n static int author_date_is_interesting(void)\n {\n \treturn author_message || force_date;\n@@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \tif (force_author && renew_authorship)\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n \n+\tif (force_committer && !strchr(force_committer, '>'))\n+\t\tforce_committer = find_author_by_nickname(force_committer);\n+\n \tif (logfile || have_option_m || use_message)\n \t\tuse_editor = 0;\n \n@@ -1709,6 +1755,7 @@ int cmd_commit(int argc,\n \t\tOPT_FILENAME('F', \"file\", &logfile, N_(\"read message from file\")),\n \t\tOPT_STRING(0, \"author\", &force_author, N_(\"author\"), N_(\"override author for commit\")),\n \t\tOPT_STRING(0, \"date\", &force_date, N_(\"date\"), N_(\"override date for commit\")),\n+\t\tOPT_STRING(0, \"committer\", &force_committer, N_(\"committer\"), N_(\"override committer for commit\")),\n \t\tOPT_CALLBACK('m', \"message\", &message, N_(\"message\"), N_(\"commit message\"), opt_parse_m),\n \t\tOPT_STRING('c', \"reedit-message\", &edit_message, N_(\"commit\"), N_(\"reuse and edit message from specified commit\")),\n \t\tOPT_STRING('C', \"reuse-message\", &use_message, N_(\"commit\"), N_(\"reuse message from specified commit\")),\n@@ -1785,6 +1832,7 @@ int cmd_commit(int argc,\n \n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf author_ident = STRBUF_INIT;\n+\tstruct strbuf committer_ident = STRBUF_INIT;\n \tconst char *index_file, *reflog_msg;\n \tstruct object_id oid;\n \tstruct commit_list *parents = NULL;\n@@ -1930,8 +1978,13 @@ int cmd_commit(int argc,\n \t\tappend_merge_tag_headers(parents, &tail);\n \t}\n \n+\tif (force_committer) {\n+\t\tdetermine_committer_info(&committer_ident);\n+\t}\n+\n \tif (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n-\t\t\t\t parents, &oid, author_ident.buf, NULL,\n+\t\t\t\t parents, &oid, author_ident.buf,\n+\t\t\t\t force_committer ? committer_ident.buf : NULL,\n \t\t\t\t sign_commit, extra)) {\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n@@ -1980,6 +2033,7 @@ cleanup:\n \tfree_commit_extra_headers(extra);\n \tfree_commit_list(parents);\n \tstrbuf_release(&author_ident);\n+\tstrbuf_release(&committer_ident);\n \tstrbuf_release(&err);\n \tstrbuf_release(&sb);\n \tfree(logfile);\ndiff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh\nindex 8e373b566b..45527f6a70 100755\n--- a/t/t7509-commit-authorship.sh\n+++ b/t/t7509-commit-authorship.sh\n@@ -12,6 +12,11 @@ author_header () {\n \tsed -n -e '/^$/q' -e '/^author /p'\n }\n \n+committer_header () {\n+\tgit cat-file commit \"$1\" |\n+\tsed -n -e '/^$/q' -e '/^committer /p'\n+}\n+\n message_body () {\n \tgit cat-file commit \"$1\" |\n \tsed -e '1,/^$/d'\n@@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--committer option overrides committer' '\n+\tgit checkout Initial &&\n+\techo \"Test --committer\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test committer\" --committer=\"Custom Committer <custom@committer.example>\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Custom Committer <custom@committer.example>\" actual\n+'\n+\n+test_expect_success '--committer with pattern search' '\n+\techo \"Test committer pattern\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test committer pattern\" --committer=\"Frigate\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Frigate <flying@over.world>\" actual\n+'\n+\n+test_expect_success '--committer malformed parameter' '\n+\techo \"Test malformed\" >>foo &&\n+\ttest_tick &&\n+\ttest_must_fail git commit -a -m \"test malformed\" --committer=\"malformed committer\"\n+'\n+\n+test_expect_success '--committer with --amend option' '\n+\tgit checkout -f Initial &&\n+\techo \"Test committer with amend\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"initial commit for amend test\" &&\n+\techo \"Modified for amend\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a --amend --no-edit \\\n+\t\t--author=\"Test Author <test@author.example>\" \\\n+\t\t--committer=\"Test Committer <test@committer.example>\" &&\n+\tauthor_header HEAD >actual_author &&\n+\tgrep \"Test Author <test@author.example>\" actual_author &&\n+\tcommitter_header HEAD >actual_committer &&\n+\tgrep \"Test Committer <test@committer.example>\" actual_committer\n+'\n+\n+test_expect_success 'GIT_COMMITTER_* environment variables' '\n+\tgit checkout -f Initial &&\n+\techo \"Test env vars\" >>foo &&\n+\ttest_tick &&\n+\tGIT_COMMITTER_NAME=\"Env Committer\" \\\n+\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n+\tgit commit -a -m \"test committer env vars\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Env Committer <env@test.example>\" actual\n+'\n+\n+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n+\techo \"Test override\" >>foo &&\n+\ttest_tick &&\n+\tGIT_COMMITTER_NAME=\"Env Committer\" \\\n+\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n+\tgit commit -a -m \"test override\" \\\n+\t\t--committer=\"Override Committer <override@test.example>\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Override Committer <override@test.example>\" actual\n+'\n+\n+test_expect_success '--date with --committer changes both author and committer dates' '\n+\tgit checkout -f Initial &&\n+\techo \"Test date override\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test date\" \\\n+\t\t--author=\"Date Author <date@author.example>\" \\\n+\t\t--committer=\"Date Committer <date@committer.example>\" \\\n+\t\t--date=\"2024-06-15 10:30:00 +0800\" &&\n+\tgit log -1 --format=\"%ai\" >author_date &&\n+\tgit log -1 --format=\"%ci\" >committer_date &&\n+\tgrep \"2024-06-15 10:30:00 +0800\" author_date &&\n+\tgrep \"2024-06-15 10:30:00 +0800\" committer_date\n+'\n+\n test_done\n\nbase-commit: 4badef0c3503dc29059d678abba7fac0f042bc84\n-- \ngitgitgadget\n"},{"id":"530434","messageId":"aRGvVwRcsJA9CD9c@pks.im","threadId":"64460","inReplyTo":"pull.1997.git.1762683774166.gitgitgadget@gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-10T09:24:39Z","receivedAt":"2025-11-10T09:24:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Nov 09, 2025 at 10:22:54AM +0000, ZheNing Hu via GitGitGadget wrote:\n> From: ZheNing Hu <adlternative@gmail.com>\n> \n> Add --committer option to git-commit, allowing users to override the\n> committer identity similar to how --author works. This provides a more\n> convenient alternative to setting GIT_COMMITTER_* environment variables.\n\nYeah, I can see how that's useful.\n\n> diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\n> index 54c207ad45..a015c8328e 100644\n> --- a/Documentation/git-commit.adoc\n> +++ b/Documentation/git-commit.adoc\n> @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n>  \t   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n>  \t   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n>  \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n> -\t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n> +\t   [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n>  \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n>  \t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n>  \t   [--] [<pathspec>...]\n\nNit: I'd move `--committer` before `--date` so that it comes directly\nafter `--author`.\n\n> @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.\n>  `--date=<date>`::\n>  \tOverride the author date used in the commit.\n>  \n> +`--committer=<committer>`::\n> +\tOverride the committer for the commit. Specify an explicit committer using the\n> +\tstandard `A U Thor <committer@example.com>` format. Otherwise _<committer>_\n> +\tis assumed to be a pattern and is used to search for an existing\n> +\tcommit by that author (i.e. `git rev-list --all -i --author=<committer>`);\n> +\tthe commit author is then copied from the first such commit found.\n\nThis matches the description of `--author`.\n\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 0243f17d53..88e77cbaab 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)\n>  \tfree(date);\n>  }\n>  \n> +static void determine_committer_info(struct strbuf *committer_ident)\n> +{\n> +\tchar *name, *email, *date;\n> +\tstruct ident_split committer;\n> +\n> +\tname = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n> +\temail = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n> +\tdate = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n> +\n> +\tif (force_committer) {\n> +\t\tstruct ident_split ident;\n> +\n> +\t\tif (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)\n> +\t\t\tdie(_(\"malformed --committer parameter\"));\n> +\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n> +\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n> +\n> +\t\tif (ident.date_begin) {\n> +\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n> +\t\t\tstrbuf_addch(&date_buf, '@');\n> +\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n> +\t\t\tstrbuf_addch(&date_buf, ' ');\n> +\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n> +\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> +\t\t}\n> +\t}\n> +\n> +\tif (force_date) {\n> +\t\tstruct strbuf date_buf = STRBUF_INIT;\n> +\t\tif (parse_force_date(force_date, &date_buf))\n> +\t\t\tdie(_(\"invalid date format: %s\"), force_date);\n> +\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> +\t}\n> +\n> +\tstrbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,\n> +\t\t\t\tIDENT_STRICT));\n> +\tassert_split_ident(&committer, committer_ident);\n> +\tfree(name);\n> +\tfree(email);\n> +\tfree(date);\n> +}\n> +\n>  static int author_date_is_interesting(void)\n>  {\n>  \treturn author_message || force_date;\n\nA lot of the infra in this new function is shared with\n`determine_author_info()`. It would be great if we could refactor it so\nthat the common parts are shared given that this all is quite\nnon-trivial.\n\nMaybe we could have something like `determine_identity()` that contains\nthe common bits between both functions? It might ultimately not really\nbe worth it, but at least the functionality in the `force_committer`\ncondition feels like it should be pulled out.\n\n> @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n>  \tif (force_author && renew_authorship)\n>  \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n>  \n> +\tif (force_committer && !strchr(force_committer, '>'))\n> +\t\tforce_committer = find_author_by_nickname(force_committer);\n> +\n>  \tif (logfile || have_option_m || use_message)\n>  \t\tuse_editor = 0;\n>  \n\nIs it the right thing to search by author here? Shouldn't we rather be\nsearching by committer?\n\n> @@ -1930,8 +1978,13 @@ int cmd_commit(int argc,\n>  \t\tappend_merge_tag_headers(parents, &tail);\n>  \t}\n>  \n> +\tif (force_committer) {\n> +\t\tdetermine_committer_info(&committer_ident);\n> +\t}\n> +\n\nNit: we tend to not use braces around single-line bodies.\n\nThanks!\n\nPatrick\n"},{"id":"530442","messageId":"CAOLTT8SC55mEGUDHis+DO5OPijox3xRN-76VkwJ8ryVN59hMtA@mail.gmail.com","threadId":"64460","inReplyTo":"aRGvVwRcsJA9CD9c@pks.im","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-10T14:17:07Z","receivedAt":"2025-11-10T14:17:22Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Patrick Steinhardt <ps@pks.im> 于2025年11月10日周一 17:24写道：\n>\n> On Sun, Nov 09, 2025 at 10:22:54AM +0000, ZheNing Hu via GitGitGadget wrote:\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> > Add --committer option to git-commit, allowing users to override the\n> > committer identity similar to how --author works. This provides a more\n> > convenient alternative to setting GIT_COMMITTER_* environment variables.\n>\n> Yeah, I can see how that's useful.\n>\n\nI'm glad we're aligned on this.\n\n> > diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\n> > index 54c207ad45..a015c8328e 100644\n> > --- a/Documentation/git-commit.adoc\n> > +++ b/Documentation/git-commit.adoc\n> > @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n> >          [--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>...]\n>\n> Nit: I'd move `--committer` before `--date` so that it comes directly\n> after `--author`.\n>\n\nAgreed, will fix.\n\n> > @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.\n> >  `--date=<date>`::\n> >       Override the author date used in the commit.\n> >\n> > +`--committer=<committer>`::\n> > +     Override the committer for the commit. Specify an explicit committer using the\n> > +     standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_\n> > +     is assumed to be a pattern and is used to search for an existing\n> > +     commit by that author (i.e. `git rev-list --all -i --author=<committer>`);\n> > +     the commit author is then copied from the first such commit found.\n>\n> This matches the description of `--author`.\n>\n\nAgreed, I will fix it to use committer description.\n\n> > diff --git a/builtin/commit.c b/builtin/commit.c\n> > index 0243f17d53..88e77cbaab 100644\n> > --- a/builtin/commit.c\n> > +++ b/builtin/commit.c\n> > @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)\n> >       free(date);\n> >  }\n> >\n> > +static void determine_committer_info(struct strbuf *committer_ident)\n> > +{\n> > +     char *name, *email, *date;\n> > +     struct ident_split committer;\n> > +\n> > +     name = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n> > +     email = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n> > +     date = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n> > +\n> > +     if (force_committer) {\n> > +             struct ident_split ident;\n> > +\n> > +             if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)\n> > +                     die(_(\"malformed --committer parameter\"));\n> > +             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n> > +             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n> > +\n> > +             if (ident.date_begin) {\n> > +                     struct strbuf date_buf = STRBUF_INIT;\n> > +                     strbuf_addch(&date_buf, '@');\n> > +                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n> > +                     strbuf_addch(&date_buf, ' ');\n> > +                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n> > +                     set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> > +             }\n> > +     }\n> > +\n> > +     if (force_date) {\n> > +             struct strbuf date_buf = STRBUF_INIT;\n> > +             if (parse_force_date(force_date, &date_buf))\n> > +                     die(_(\"invalid date format: %s\"), force_date);\n> > +             set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> > +     }\n> > +\n> > +     strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,\n> > +                             IDENT_STRICT));\n> > +     assert_split_ident(&committer, committer_ident);\n> > +     free(name);\n> > +     free(email);\n> > +     free(date);\n> > +}\n> > +\n> >  static int author_date_is_interesting(void)\n> >  {\n> >       return author_message || force_date;\n>\n> A lot of the infra in this new function is shared with\n> `determine_author_info()`. It would be great if we could refactor it so\n> that the common parts are shared given that this all is quite\n> non-trivial.\n>\n> Maybe we could have something like `determine_identity()` that contains\n> the common bits between both functions? It might ultimately not really\n> be worth it, but at least the functionality in the `force_committer`\n> condition feels like it should be pulled out.\n>\n\nGood suggestion, I will refactor them to use `determine_identity()`,\nwhich should be more generic, and it even caught that I missed\nupdating `GIT_COMMITTER_*`.\n\n> > @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n> >       if (force_author && renew_authorship)\n> >               die(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n> >\n> > +     if (force_committer && !strchr(force_committer, '>'))\n> > +             force_committer = find_author_by_nickname(force_committer);\n> > +\n> >       if (logfile || have_option_m || use_message)\n> >               use_editor = 0;\n> >\n>\n> Is it the right thing to search by author here? Shouldn't we rather be\n> searching by committer?\n>\n\nYes, there should also be a `find_identity_by_nickname()` or\n`find_committer_by_nickname()` here.\n\n> > @@ -1930,8 +1978,13 @@ int cmd_commit(int argc,\n> >               append_merge_tag_headers(parents, &tail);\n> >       }\n> >\n> > +     if (force_committer) {\n> > +             determine_committer_info(&committer_ident);\n> > +     }\n> > +\n>\n> Nit: we tend to not use braces around single-line bodies.\n>\n\nAgree.\n\n> Thanks!\n>\n> Patrick\n"},{"id":"530451","messageId":"6be20c41-15a0-4732-bd12-4927a59a9f59@gmail.com","threadId":"64460","inReplyTo":"pull.1997.git.1762683774166.gitgitgadget@gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-11-10T16:50:04Z","receivedAt":"2025-11-10T16:50:07Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi ZheNing\n\nOn 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> From: ZheNing Hu <adlternative@gmail.com>\n> \n>      \n>      This patch introduces the --committer option to git-commit, providing:\n>      \n>       1. Consistency with the existing --author option\n>       2. A more convenient alternative to environment variables\n>       3. Better support for automated workflows and scripts\n>       4. Improved user experience when managing multiple identities\n\nWhat's the use case for the same person committing under different \nidentities? We already have a config mechanism to set different \nidentities for different repositories but I'm struggling to see why \nsomeone would want to create commits under multiple identities in a \nsingle repository. For scripts it easy enough to set the relevant \nenvironment variables if a tool wants to create commits under its own \nidentity.\n\nThanks\n\nPhillip\n\n>      The implementation follows the same pattern as the --author option,\n>      accepting the format \"Name \" and properly validating the input.\n> \n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1997\n> \n>   Documentation/git-commit.adoc |  9 +++-\n>   builtin/commit.c              | 58 ++++++++++++++++++++++++-\n>   t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++\n>   3 files changed, 144 insertions(+), 3 deletions(-)\n> \n> diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\n> index 54c207ad45..a015c8328e 100644\n> --- a/Documentation/git-commit.adoc\n> +++ b/Documentation/git-commit.adoc\n> @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n>   \t   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n>   \t   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n>   \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n> -\t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n> +\t   [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n>   \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n>   \t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n>   \t   [--] [<pathspec>...]\n> @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.\n>   `--date=<date>`::\n>   \tOverride the author date used in the commit.\n>   \n> +`--committer=<committer>`::\n> +\tOverride the committer for the commit. Specify an explicit committer using the\n> +\tstandard `A U Thor <committer@example.com>` format. Otherwise _<committer>_\n> +\tis assumed to be a pattern and is used to search for an existing\n> +\tcommit by that author (i.e. `git rev-list --all -i --author=<committer>`);\n> +\tthe commit author is then copied from the first such commit found.\n> +\n>   `-m <msg>`::\n>   `--message=<msg>`::\n>   \tUse _<msg>_ as the commit message.\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 0243f17d53..88e77cbaab 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {\n>   \t   \"           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\\n\"\n>   \t   \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n>   \t   \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n> -\t   \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n> +\t   \"           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\\n\"\n>   \t   \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n>   \t   \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n>   \t   \"           [--] [<pathspec>...]\"),\n> @@ -112,6 +112,7 @@ static enum {\n>   } commit_style;\n>   \n>   static const char *force_author;\n> +static const char *force_committer;\n>   static char *logfile;\n>   static char *template_file;\n>   /*\n> @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)\n>   \tfree(date);\n>   }\n>   \n> +static void determine_committer_info(struct strbuf *committer_ident)\n> +{\n> +\tchar *name, *email, *date;\n> +\tstruct ident_split committer;\n> +\n> +\tname = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n> +\temail = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n> +\tdate = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n> +\n> +\tif (force_committer) {\n> +\t\tstruct ident_split ident;\n> +\n> +\t\tif (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)\n> +\t\t\tdie(_(\"malformed --committer parameter\"));\n> +\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n> +\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n> +\n> +\t\tif (ident.date_begin) {\n> +\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n> +\t\t\tstrbuf_addch(&date_buf, '@');\n> +\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n> +\t\t\tstrbuf_addch(&date_buf, ' ');\n> +\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n> +\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> +\t\t}\n> +\t}\n> +\n> +\tif (force_date) {\n> +\t\tstruct strbuf date_buf = STRBUF_INIT;\n> +\t\tif (parse_force_date(force_date, &date_buf))\n> +\t\t\tdie(_(\"invalid date format: %s\"), force_date);\n> +\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> +\t}\n> +\n> +\tstrbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,\n> +\t\t\t\tIDENT_STRICT));\n> +\tassert_split_ident(&committer, committer_ident);\n> +\tfree(name);\n> +\tfree(email);\n> +\tfree(date);\n> +}\n> +\n>   static int author_date_is_interesting(void)\n>   {\n>   \treturn author_message || force_date;\n> @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n>   \tif (force_author && renew_authorship)\n>   \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n>   \n> +\tif (force_committer && !strchr(force_committer, '>'))\n> +\t\tforce_committer = find_author_by_nickname(force_committer);\n> +\n>   \tif (logfile || have_option_m || use_message)\n>   \t\tuse_editor = 0;\n>   \n> @@ -1709,6 +1755,7 @@ int cmd_commit(int argc,\n>   \t\tOPT_FILENAME('F', \"file\", &logfile, N_(\"read message from file\")),\n>   \t\tOPT_STRING(0, \"author\", &force_author, N_(\"author\"), N_(\"override author for commit\")),\n>   \t\tOPT_STRING(0, \"date\", &force_date, N_(\"date\"), N_(\"override date for commit\")),\n> +\t\tOPT_STRING(0, \"committer\", &force_committer, N_(\"committer\"), N_(\"override committer for commit\")),\n>   \t\tOPT_CALLBACK('m', \"message\", &message, N_(\"message\"), N_(\"commit message\"), opt_parse_m),\n>   \t\tOPT_STRING('c', \"reedit-message\", &edit_message, N_(\"commit\"), N_(\"reuse and edit message from specified commit\")),\n>   \t\tOPT_STRING('C', \"reuse-message\", &use_message, N_(\"commit\"), N_(\"reuse message from specified commit\")),\n> @@ -1785,6 +1832,7 @@ int cmd_commit(int argc,\n>   \n>   \tstruct strbuf sb = STRBUF_INIT;\n>   \tstruct strbuf author_ident = STRBUF_INIT;\n> +\tstruct strbuf committer_ident = STRBUF_INIT;\n>   \tconst char *index_file, *reflog_msg;\n>   \tstruct object_id oid;\n>   \tstruct commit_list *parents = NULL;\n> @@ -1930,8 +1978,13 @@ int cmd_commit(int argc,\n>   \t\tappend_merge_tag_headers(parents, &tail);\n>   \t}\n>   \n> +\tif (force_committer) {\n> +\t\tdetermine_committer_info(&committer_ident);\n> +\t}\n> +\n>   \tif (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n> -\t\t\t\t parents, &oid, author_ident.buf, NULL,\n> +\t\t\t\t parents, &oid, author_ident.buf,\n> +\t\t\t\t force_committer ? committer_ident.buf : NULL,\n>   \t\t\t\t sign_commit, extra)) {\n>   \t\trollback_index_files();\n>   \t\tdie(_(\"failed to write commit object\"));\n> @@ -1980,6 +2033,7 @@ cleanup:\n>   \tfree_commit_extra_headers(extra);\n>   \tfree_commit_list(parents);\n>   \tstrbuf_release(&author_ident);\n> +\tstrbuf_release(&committer_ident);\n>   \tstrbuf_release(&err);\n>   \tstrbuf_release(&sb);\n>   \tfree(logfile);\n> diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh\n> index 8e373b566b..45527f6a70 100755\n> --- a/t/t7509-commit-authorship.sh\n> +++ b/t/t7509-commit-authorship.sh\n> @@ -12,6 +12,11 @@ author_header () {\n>   \tsed -n -e '/^$/q' -e '/^author /p'\n>   }\n>   \n> +committer_header () {\n> +\tgit cat-file commit \"$1\" |\n> +\tsed -n -e '/^$/q' -e '/^committer /p'\n> +}\n> +\n>   message_body () {\n>   \tgit cat-file commit \"$1\" |\n>   \tsed -e '1,/^$/d'\n> @@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n>   \ttest_cmp expect actual\n>   '\n>   \n> +test_expect_success '--committer option overrides committer' '\n> +\tgit checkout Initial &&\n> +\techo \"Test --committer\" >>foo &&\n> +\ttest_tick &&\n> +\tgit commit -a -m \"test committer\" --committer=\"Custom Committer <custom@committer.example>\" &&\n> +\tcommitter_header HEAD >actual &&\n> +\tgrep \"Custom Committer <custom@committer.example>\" actual\n> +'\n> +\n> +test_expect_success '--committer with pattern search' '\n> +\techo \"Test committer pattern\" >>foo &&\n> +\ttest_tick &&\n> +\tgit commit -a -m \"test committer pattern\" --committer=\"Frigate\" &&\n> +\tcommitter_header HEAD >actual &&\n> +\tgrep \"Frigate <flying@over.world>\" actual\n> +'\n> +\n> +test_expect_success '--committer malformed parameter' '\n> +\techo \"Test malformed\" >>foo &&\n> +\ttest_tick &&\n> +\ttest_must_fail git commit -a -m \"test malformed\" --committer=\"malformed committer\"\n> +'\n> +\n> +test_expect_success '--committer with --amend option' '\n> +\tgit checkout -f Initial &&\n> +\techo \"Test committer with amend\" >>foo &&\n> +\ttest_tick &&\n> +\tgit commit -a -m \"initial commit for amend test\" &&\n> +\techo \"Modified for amend\" >>foo &&\n> +\ttest_tick &&\n> +\tgit commit -a --amend --no-edit \\\n> +\t\t--author=\"Test Author <test@author.example>\" \\\n> +\t\t--committer=\"Test Committer <test@committer.example>\" &&\n> +\tauthor_header HEAD >actual_author &&\n> +\tgrep \"Test Author <test@author.example>\" actual_author &&\n> +\tcommitter_header HEAD >actual_committer &&\n> +\tgrep \"Test Committer <test@committer.example>\" actual_committer\n> +'\n> +\n> +test_expect_success 'GIT_COMMITTER_* environment variables' '\n> +\tgit checkout -f Initial &&\n> +\techo \"Test env vars\" >>foo &&\n> +\ttest_tick &&\n> +\tGIT_COMMITTER_NAME=\"Env Committer\" \\\n> +\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n> +\tgit commit -a -m \"test committer env vars\" &&\n> +\tcommitter_header HEAD >actual &&\n> +\tgrep \"Env Committer <env@test.example>\" actual\n> +'\n> +\n> +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n> +\techo \"Test override\" >>foo &&\n> +\ttest_tick &&\n> +\tGIT_COMMITTER_NAME=\"Env Committer\" \\\n> +\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n> +\tgit commit -a -m \"test override\" \\\n> +\t\t--committer=\"Override Committer <override@test.example>\" &&\n> +\tcommitter_header HEAD >actual &&\n> +\tgrep \"Override Committer <override@test.example>\" actual\n> +'\n> +\n> +test_expect_success '--date with --committer changes both author and committer dates' '\n> +\tgit checkout -f Initial &&\n> +\techo \"Test date override\" >>foo &&\n> +\ttest_tick &&\n> +\tgit commit -a -m \"test date\" \\\n> +\t\t--author=\"Date Author <date@author.example>\" \\\n> +\t\t--committer=\"Date Committer <date@committer.example>\" \\\n> +\t\t--date=\"2024-06-15 10:30:00 +0800\" &&\n> +\tgit log -1 --format=\"%ai\" >author_date &&\n> +\tgit log -1 --format=\"%ci\" >committer_date &&\n> +\tgrep \"2024-06-15 10:30:00 +0800\" author_date &&\n> +\tgrep \"2024-06-15 10:30:00 +0800\" committer_date\n> +'\n> +\n>   test_done\n> \n> base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84\n\n"},{"id":"530452","messageId":"pull.1997.v2.git.1762793782815.gitgitgadget@gmail.com","threadId":"64460","inReplyTo":"pull.1997.git.1762683774166.gitgitgadget@gmail.com","subject":"[PATCH v2] commit: add --committer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-11-10T16:56:22Z","receivedAt":"2025-11-10T16:56:25Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nAdd --committer option to git-commit, allowing users to override the\ncommitter identity similar to how --author works. This provides a more\nconvenient alternative to setting GIT_COMMITTER_* environment variables.\n\nLike --author, the --committer option supports two formats:\n- Explicit identity: --committer=\"Name <email@example.com>\"\n- Pattern search: --committer=\"pattern\" searches commit history for a\n  matching committer and reuses that identity\n\nTo share code with the existing --author option, this patch refactors:\n\n1. find_author_by_nickname() into find_identity_by_nickname() which\n   handles both author and committer searches through an is_author\n   parameter.\n\n2. determine_author_info() into determine_identity() which handles\n   identity parsing and setting for both author and committer through\n   an is_author parameter.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\nCo-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n---\n    commit: add --committer option\n    \n    Currently, when users need to override the committer identity in\n    git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL\n    environment variables, which can be cumbersome in scripting scenarios or\n    when frequently switching committer identities.\n    \n    While git-commit already provides the --author option to conveniently\n    override the author identity, there's no equivalent --committer option\n    for the committer identity. This asymmetry creates an inconsistent user\n    experience.\n    \n    This patch introduces the --committer option to git-commit, providing:\n    \n     1. Consistency with the existing --author option\n     2. A more convenient alternative to environment variables\n     3. Better support for automated workflows and scripts\n     4. Improved user experience when managing multiple identities\n    \n    The implementation follows the same pattern as the --author option,\n    accepting the format \"Name \" and properly validating the input.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1997\n\nRange-diff vs v1:\n\n 1:  05e97b439f ! 1:  58e9e5c9d7 commit: add --committer option\n     @@ Commit message\n          committer identity similar to how --author works. This provides a more\n          convenient alternative to setting GIT_COMMITTER_* environment variables.\n      \n     -    Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n     +    Like --author, the --committer option supports two formats:\n     +    - Explicit identity: --committer=\"Name <email@example.com>\"\n     +    - Pattern search: --committer=\"pattern\" searches commit history for a\n     +      matching committer and reuses that identity\n     +\n     +    To share code with the existing --author option, this patch refactors:\n     +\n     +    1. find_author_by_nickname() into find_identity_by_nickname() which\n     +       handles both author and committer searches through an is_author\n     +       parameter.\n     +\n     +    2. determine_author_info() into determine_identity() which handles\n     +       identity parsing and setting for both author and committer through\n     +       an is_author parameter.\n      \n     +    Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n          Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n      \n       ## Documentation/git-commit.adoc ##\n     @@ Documentation/git-commit.adoc: git commit [-a | --interactive | --patch] [-s] [-\n       \t   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n       \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n      -\t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n     -+\t   [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n     ++\t   [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n       \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n       \t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n       \t   [--] [<pathspec>...]\n      @@ Documentation/git-commit.adoc: See linkgit:git-rebase[1] for details.\n     - `--date=<date>`::\n     - \tOverride the author date used in the commit.\n     + \tcommit by that author (i.e. `git rev-list --all -i --author=<author>`);\n     + \tthe commit author is then copied from the first such commit found.\n       \n      +`--committer=<committer>`::\n      +\tOverride the committer for the commit. Specify an explicit committer using the\n     -+\tstandard `A U Thor <committer@example.com>` format. Otherwise _<committer>_\n     ++\tstandard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_\n      +\tis assumed to be a pattern and is used to search for an existing\n     -+\tcommit by that author (i.e. `git rev-list --all -i --author=<committer>`);\n     -+\tthe commit author is then copied from the first such commit found.\n     ++\tcommit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);\n     ++\tthe commit committer is then copied from the first such commit found.\n      +\n     - `-m <msg>`::\n     - `--message=<msg>`::\n     - \tUse _<msg>_ as the commit message.\n     + `--date=<date>`::\n     + \tOverride the author date used in the commit.\n     + \n      \n       ## builtin/commit.c ##\n      @@ builtin/commit.c: static const char * const builtin_commit_usage[] = {\n     @@ builtin/commit.c: static const char * const builtin_commit_usage[] = {\n       \t   \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n       \t   \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n      -\t   \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n     -+\t   \"           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\\n\"\n     ++\t   \"           [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n       \t   \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n       \t   \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n       \t   \"           [--] [<pathspec>...]\"),\n     @@ builtin/commit.c: static enum {\n       static char *logfile;\n       static char *template_file;\n       /*\n     -@@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)\n     - \tfree(date);\n     +@@ builtin/commit.c: static void set_ident_var(char **buf, char *val)\n     + \t*buf = val;\n       }\n       \n     -+static void determine_committer_info(struct strbuf *committer_ident)\n     -+{\n     -+\tchar *name, *email, *date;\n     -+\tstruct ident_split committer;\n     -+\n     -+\tname = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n     -+\temail = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n     -+\tdate = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n     +-static void determine_author_info(struct strbuf *author_ident)\n     ++static void determine_identity(struct strbuf *ident_str, int is_author)\n     + {\n     + \tchar *name, *email, *date;\n     +-\tstruct ident_split author;\n     +-\n     +-\tname = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n     +-\temail = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n     +-\tdate = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n     +-\n     +-\tif (author_message) {\n     +-\t\tstruct ident_split ident;\n     ++\tstruct ident_split ident;\n     ++\tconst char *env_name = is_author ? \"GIT_AUTHOR_NAME\" : \"GIT_COMMITTER_NAME\";\n     ++\tconst char *env_email = is_author ? \"GIT_AUTHOR_EMAIL\" : \"GIT_COMMITTER_EMAIL\";\n     ++\tconst char *env_date = is_author ? \"GIT_AUTHOR_DATE\" : \"GIT_COMMITTER_DATE\";\n     ++\tconst char *force_ident = is_author ? force_author : force_committer;\n     ++\tconst char *param_name = is_author ? \"--author\" : \"--committer\";\n     ++\tint ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;\n      +\n     -+\tif (force_committer) {\n     -+\t\tstruct ident_split ident;\n     ++\tname = xstrdup_or_null(getenv(env_name));\n     ++\temail = xstrdup_or_null(getenv(env_email));\n     ++\tdate = xstrdup_or_null(getenv(env_date));\n      +\n     -+\t\tif (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)\n     -+\t\t\tdie(_(\"malformed --committer parameter\"));\n     -+\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n     -+\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n     ++\tif (is_author && author_message) {\n     ++\t\tstruct ident_split msg_ident;\n     + \t\tsize_t len;\n     + \t\tconst char *a;\n     + \n     + \t\ta = find_commit_header(author_message_buffer, \"author\", &len);\n     + \t\tif (!a)\n     + \t\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n     +-\t\tif (split_ident_line(&ident, a, len) < 0)\n     ++\t\tif (split_ident_line(&msg_ident, a, len) < 0)\n     + \t\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n     + \n     +-\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n     +-\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n     ++\t\tset_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));\n     ++\t\tset_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));\n     + \n     +-\t\tif (ident.date_begin) {\n     ++\t\tif (msg_ident.date_begin) {\n     + \t\t\tstruct strbuf date_buf = STRBUF_INIT;\n     + \t\t\tstrbuf_addch(&date_buf, '@');\n     +-\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n     ++\t\t\tstrbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);\n     + \t\t\tstrbuf_addch(&date_buf, ' ');\n     +-\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n     ++\t\t\tstrbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);\n     + \t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n     + \t\t}\n     + \t}\n     + \n     +-\tif (force_author) {\n     +-\t\tstruct ident_split ident;\n     ++\tif (force_ident) {\n     ++\t\tstruct ident_split force_ident_split;\n      +\n     -+\t\tif (ident.date_begin) {\n     ++\t\tif (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)\n     ++\t\t\tdie(_(\"malformed %s parameter\"), param_name);\n     ++\t\tset_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));\n     ++\t\tset_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));\n     + \n     +-\t\tif (split_ident_line(&ident, force_author, strlen(force_author)) < 0)\n     +-\t\t\tdie(_(\"malformed --author parameter\"));\n     +-\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n     +-\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n     ++\t\tif (!is_author && force_ident_split.date_begin) {\n      +\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n      +\t\t\tstrbuf_addch(&date_buf, '@');\n     -+\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n     ++\t\t\tstrbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin);\n      +\t\t\tstrbuf_addch(&date_buf, ' ');\n     -+\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n     ++\t\t\tstrbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin);\n      +\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n      +\t\t}\n     -+\t}\n     + \t}\n     + \n     + \tif (force_date) {\n     +@@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)\n     + \t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n     + \t}\n     + \n     +-\tstrbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,\n     ++\tstrbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date,\n     + \t\t\t\tIDENT_STRICT));\n     +-\tassert_split_ident(&author, author_ident);\n     +-\texport_one(\"GIT_AUTHOR_NAME\", author.name_begin, author.name_end, 0);\n     +-\texport_one(\"GIT_AUTHOR_EMAIL\", author.mail_begin, author.mail_end, 0);\n     +-\texport_one(\"GIT_AUTHOR_DATE\", author.date_begin, author.tz_end, '@');\n     ++\tassert_split_ident(&ident, ident_str);\n      +\n     -+\tif (force_date) {\n     -+\t\tstruct strbuf date_buf = STRBUF_INIT;\n     -+\t\tif (parse_force_date(force_date, &date_buf))\n     -+\t\t\tdie(_(\"invalid date format: %s\"), force_date);\n     -+\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n     ++\tif (is_author) {\n     ++\t\texport_one(\"GIT_AUTHOR_NAME\", ident.name_begin, ident.name_end, 0);\n     ++\t\texport_one(\"GIT_AUTHOR_EMAIL\", ident.mail_begin, ident.mail_end, 0);\n     ++\t\texport_one(\"GIT_AUTHOR_DATE\", ident.date_begin, ident.tz_end, '@');\n     ++\t} else {\n     ++\t\texport_one(\"GIT_COMMITTER_NAME\", ident.name_begin, ident.name_end, 0);\n     ++\t\texport_one(\"GIT_COMMITTER_EMAIL\", ident.mail_begin, ident.mail_end, 0);\n     ++\t\texport_one(\"GIT_COMMITTER_DATE\", ident.date_begin, ident.tz_end, '@');\n      +\t}\n      +\n     -+\tstrbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,\n     -+\t\t\t\tIDENT_STRICT));\n     -+\tassert_split_ident(&committer, committer_ident);\n     -+\tfree(name);\n     -+\tfree(email);\n     -+\tfree(date);\n     + \tfree(name);\n     + \tfree(email);\n     + \tfree(date);\n     + }\n     + \n     ++static void determine_author_info(struct strbuf *author_ident)\n     ++{\n     ++\tdetermine_identity(author_ident, 1);\n     ++}\n     ++\n     ++static void determine_committer_info(struct strbuf *committer_ident)\n     ++{\n     ++\tdetermine_identity(committer_ident, 0);\n      +}\n      +\n       static int author_date_is_interesting(void)\n       {\n       \treturn author_message || force_date;\n     +@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,\n     + \treturn 1;\n     + }\n     + \n     +-static const char *find_author_by_nickname(const char *name)\n     ++static const char *find_identity_by_nickname(const char *name, int is_author)\n     + {\n     + \tstruct rev_info revs;\n     + \tstruct commit *commit;\n     + \tstruct strbuf buf = STRBUF_INIT;\n     + \tconst char *av[20];\n     + \tint ac = 0;\n     ++\tconst char *field = is_author ? \"author\" : \"committer\";\n     ++\tconst char *format = is_author ? \"%aN <%aE>\" : \"%cN <%cE>\";\n     + \n     + \trepo_init_revisions(the_repository, &revs, NULL);\n     +-\tstrbuf_addf(&buf, \"--author=%s\", name);\n     ++\tstrbuf_addf(&buf, \"--%s=%s\", field, name);\n     + \tav[++ac] = \"--all\";\n     + \tav[++ac] = \"-i\";\n     + \tav[++ac] = buf.buf;\n     +@@ builtin/commit.c: static const char *find_author_by_nickname(const char *name)\n     + \t\tctx.date_mode.type = DATE_NORMAL;\n     + \t\tstrbuf_release(&buf);\n     + \t\trepo_format_commit_message(the_repository, commit,\n     +-\t\t\t\t\t   \"%aN <%aE>\", &buf, &ctx);\n     ++\t\t\t\t\t   format, &buf, &ctx);\n     + \t\trelease_revisions(&revs);\n     + \t\treturn strbuf_detach(&buf, NULL);\n     + \t}\n     +-\tdie(_(\"--author '%s' is not 'Name <email>' and matches no existing author\"), name);\n     ++\tdie(_(\"--%s '%s' is not 'Name <email>' and matches no existing %s\"),\n     ++\t    field, name, field);\n     ++}\n     ++\n     ++static const char *find_author_by_nickname(const char *name)\n     ++{\n     ++\treturn find_identity_by_nickname(name, 1);\n     ++}\n     ++\n     ++static const char *find_committer_by_nickname(const char *name)\n     ++{\n     ++\treturn find_identity_by_nickname(name, 0);\n     + }\n     + \n     + static void handle_ignored_arg(struct wt_status *s)\n      @@ builtin/commit.c: static int parse_and_validate_options(int argc, const char *argv[],\n       \tif (force_author && renew_authorship)\n       \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n       \n      +\tif (force_committer && !strchr(force_committer, '>'))\n     -+\t\tforce_committer = find_author_by_nickname(force_committer);\n     ++\t\tforce_committer = find_committer_by_nickname(force_committer);\n      +\n       \tif (logfile || have_option_m || use_message)\n       \t\tuse_editor = 0;\n     @@ builtin/commit.c: int cmd_commit(int argc,\n       \t\tappend_merge_tag_headers(parents, &tail);\n       \t}\n       \n     -+\tif (force_committer) {\n     ++\tif (force_committer)\n      +\t\tdetermine_committer_info(&committer_ident);\n     -+\t}\n      +\n       \tif (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n      -\t\t\t\t parents, &oid, author_ident.buf, NULL,\n     @@ t/t7509-commit-authorship.sh: author_header () {\n       message_body () {\n       \tgit cat-file commit \"$1\" |\n       \tsed -e '1,/^$/d'\n     + }\n     + \n     + test_expect_success '-C option copies authorship and message' '\n     +-\ttest_commit --author Frigate\\ \\<flying@over.world\\> \\\n     ++\ttest_env GIT_COMMITTER_NAME=\"Frigate\" \\\n     ++\t\tGIT_COMMITTER_EMAIL=\"flying@over.world\" \\\n     ++\t\ttest_commit --author Frigate\\ \\<flying@over.world\\> \\\n     + \t\t\"Initial Commit\" foo Initial Initial &&\n     + \techo \"Test 1\" >>foo &&\n     + \ttest_tick &&\n      @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n       \ttest_cmp expect actual\n       '\n     @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PI\n      +\tgit checkout -f Initial &&\n      +\techo \"Test env vars\" >>foo &&\n      +\ttest_tick &&\n     -+\tGIT_COMMITTER_NAME=\"Env Committer\" \\\n     -+\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n     -+\tgit commit -a -m \"test committer env vars\" &&\n     ++\ttest_env GIT_COMMITTER_NAME=\"Env Committer\" \\\n     ++\t\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n     ++\t\tgit commit -a -m \"test committer env vars\" &&\n      +\tcommitter_header HEAD >actual &&\n      +\tgrep \"Env Committer <env@test.example>\" actual\n      +'\n     @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PI\n      +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n      +\techo \"Test override\" >>foo &&\n      +\ttest_tick &&\n     -+\tGIT_COMMITTER_NAME=\"Env Committer\" \\\n     -+\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n     -+\tgit commit -a -m \"test override\" \\\n     ++\ttest_env GIT_COMMITTER_NAME=\"Env Committer\" \\\n     ++\t\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n     ++\t\tgit commit -a -m \"test override\" \\\n      +\t\t--committer=\"Override Committer <override@test.example>\" &&\n      +\tcommitter_header HEAD >actual &&\n      +\tgrep \"Override Committer <override@test.example>\" actual\n\n\n Documentation/git-commit.adoc |   9 ++-\n builtin/commit.c              | 121 +++++++++++++++++++++++++---------\n t/t7509-commit-authorship.sh  |  84 ++++++++++++++++++++++-\n 3 files changed, 180 insertions(+), 34 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 54c207ad45..ed4c54ae81 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n \t   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n \t   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n-\t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n+\t   [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n \t   [--] [<pathspec>...]\n@@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details.\n \tcommit by that author (i.e. `git rev-list --all -i --author=<author>`);\n \tthe commit author is then copied from the first such commit found.\n \n+`--committer=<committer>`::\n+\tOverride the committer for the commit. Specify an explicit committer using the\n+\tstandard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_\n+\tis assumed to be a pattern and is used to search for an existing\n+\tcommit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);\n+\tthe commit committer is then copied from the first such commit found.\n+\n `--date=<date>`::\n \tOverride the author date used in the commit.\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 0243f17d53..3b249dd878 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {\n \t   \"           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\\n\"\n \t   \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n \t   \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n-\t   \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n+\t   \"           [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n \t   \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n \t   \"           [--] [<pathspec>...]\"),\n@@ -112,6 +112,7 @@ static enum {\n } commit_style;\n \n static const char *force_author;\n+static const char *force_committer;\n static char *logfile;\n static char *template_file;\n /*\n@@ -630,46 +631,61 @@ static void set_ident_var(char **buf, char *val)\n \t*buf = val;\n }\n \n-static void determine_author_info(struct strbuf *author_ident)\n+static void determine_identity(struct strbuf *ident_str, int is_author)\n {\n \tchar *name, *email, *date;\n-\tstruct ident_split author;\n-\n-\tname = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n-\temail = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n-\tdate = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n-\n-\tif (author_message) {\n-\t\tstruct ident_split ident;\n+\tstruct ident_split ident;\n+\tconst char *env_name = is_author ? \"GIT_AUTHOR_NAME\" : \"GIT_COMMITTER_NAME\";\n+\tconst char *env_email = is_author ? \"GIT_AUTHOR_EMAIL\" : \"GIT_COMMITTER_EMAIL\";\n+\tconst char *env_date = is_author ? \"GIT_AUTHOR_DATE\" : \"GIT_COMMITTER_DATE\";\n+\tconst char *force_ident = is_author ? force_author : force_committer;\n+\tconst char *param_name = is_author ? \"--author\" : \"--committer\";\n+\tint ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;\n+\n+\tname = xstrdup_or_null(getenv(env_name));\n+\temail = xstrdup_or_null(getenv(env_email));\n+\tdate = xstrdup_or_null(getenv(env_date));\n+\n+\tif (is_author && author_message) {\n+\t\tstruct ident_split msg_ident;\n \t\tsize_t len;\n \t\tconst char *a;\n \n \t\ta = find_commit_header(author_message_buffer, \"author\", &len);\n \t\tif (!a)\n \t\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n-\t\tif (split_ident_line(&ident, a, len) < 0)\n+\t\tif (split_ident_line(&msg_ident, a, len) < 0)\n \t\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n \n-\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n-\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n+\t\tset_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));\n+\t\tset_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));\n \n-\t\tif (ident.date_begin) {\n+\t\tif (msg_ident.date_begin) {\n \t\t\tstruct strbuf date_buf = STRBUF_INIT;\n \t\t\tstrbuf_addch(&date_buf, '@');\n-\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n+\t\t\tstrbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);\n \t\t\tstrbuf_addch(&date_buf, ' ');\n-\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n+\t\t\tstrbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);\n \t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n \t\t}\n \t}\n \n-\tif (force_author) {\n-\t\tstruct ident_split ident;\n+\tif (force_ident) {\n+\t\tstruct ident_split force_ident_split;\n+\n+\t\tif (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)\n+\t\t\tdie(_(\"malformed %s parameter\"), param_name);\n+\t\tset_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));\n+\t\tset_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));\n \n-\t\tif (split_ident_line(&ident, force_author, strlen(force_author)) < 0)\n-\t\t\tdie(_(\"malformed --author parameter\"));\n-\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n-\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n+\t\tif (!is_author && force_ident_split.date_begin) {\n+\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n+\t\t\tstrbuf_addch(&date_buf, '@');\n+\t\t\tstrbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin);\n+\t\t\tstrbuf_addch(&date_buf, ' ');\n+\t\t\tstrbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin);\n+\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n+\t\t}\n \t}\n \n \tif (force_date) {\n@@ -679,17 +695,35 @@ static void determine_author_info(struct strbuf *author_ident)\n \t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n \t}\n \n-\tstrbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,\n+\tstrbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date,\n \t\t\t\tIDENT_STRICT));\n-\tassert_split_ident(&author, author_ident);\n-\texport_one(\"GIT_AUTHOR_NAME\", author.name_begin, author.name_end, 0);\n-\texport_one(\"GIT_AUTHOR_EMAIL\", author.mail_begin, author.mail_end, 0);\n-\texport_one(\"GIT_AUTHOR_DATE\", author.date_begin, author.tz_end, '@');\n+\tassert_split_ident(&ident, ident_str);\n+\n+\tif (is_author) {\n+\t\texport_one(\"GIT_AUTHOR_NAME\", ident.name_begin, ident.name_end, 0);\n+\t\texport_one(\"GIT_AUTHOR_EMAIL\", ident.mail_begin, ident.mail_end, 0);\n+\t\texport_one(\"GIT_AUTHOR_DATE\", ident.date_begin, ident.tz_end, '@');\n+\t} else {\n+\t\texport_one(\"GIT_COMMITTER_NAME\", ident.name_begin, ident.name_end, 0);\n+\t\texport_one(\"GIT_COMMITTER_EMAIL\", ident.mail_begin, ident.mail_end, 0);\n+\t\texport_one(\"GIT_COMMITTER_DATE\", ident.date_begin, ident.tz_end, '@');\n+\t}\n+\n \tfree(name);\n \tfree(email);\n \tfree(date);\n }\n \n+static void determine_author_info(struct strbuf *author_ident)\n+{\n+\tdetermine_identity(author_ident, 1);\n+}\n+\n+static void determine_committer_info(struct strbuf *committer_ident)\n+{\n+\tdetermine_identity(committer_ident, 0);\n+}\n+\n static int author_date_is_interesting(void)\n {\n \treturn author_message || force_date;\n@@ -1137,16 +1171,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \treturn 1;\n }\n \n-static const char *find_author_by_nickname(const char *name)\n+static const char *find_identity_by_nickname(const char *name, int is_author)\n {\n \tstruct rev_info revs;\n \tstruct commit *commit;\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *av[20];\n \tint ac = 0;\n+\tconst char *field = is_author ? \"author\" : \"committer\";\n+\tconst char *format = is_author ? \"%aN <%aE>\" : \"%cN <%cE>\";\n \n \trepo_init_revisions(the_repository, &revs, NULL);\n-\tstrbuf_addf(&buf, \"--author=%s\", name);\n+\tstrbuf_addf(&buf, \"--%s=%s\", field, name);\n \tav[++ac] = \"--all\";\n \tav[++ac] = \"-i\";\n \tav[++ac] = buf.buf;\n@@ -1164,11 +1200,22 @@ static const char *find_author_by_nickname(const char *name)\n \t\tctx.date_mode.type = DATE_NORMAL;\n \t\tstrbuf_release(&buf);\n \t\trepo_format_commit_message(the_repository, commit,\n-\t\t\t\t\t   \"%aN <%aE>\", &buf, &ctx);\n+\t\t\t\t\t   format, &buf, &ctx);\n \t\trelease_revisions(&revs);\n \t\treturn strbuf_detach(&buf, NULL);\n \t}\n-\tdie(_(\"--author '%s' is not 'Name <email>' and matches no existing author\"), name);\n+\tdie(_(\"--%s '%s' is not 'Name <email>' and matches no existing %s\"),\n+\t    field, name, field);\n+}\n+\n+static const char *find_author_by_nickname(const char *name)\n+{\n+\treturn find_identity_by_nickname(name, 1);\n+}\n+\n+static const char *find_committer_by_nickname(const char *name)\n+{\n+\treturn find_identity_by_nickname(name, 0);\n }\n \n static void handle_ignored_arg(struct wt_status *s)\n@@ -1321,6 +1368,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \tif (force_author && renew_authorship)\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n \n+\tif (force_committer && !strchr(force_committer, '>'))\n+\t\tforce_committer = find_committer_by_nickname(force_committer);\n+\n \tif (logfile || have_option_m || use_message)\n \t\tuse_editor = 0;\n \n@@ -1709,6 +1759,7 @@ int cmd_commit(int argc,\n \t\tOPT_FILENAME('F', \"file\", &logfile, N_(\"read message from file\")),\n \t\tOPT_STRING(0, \"author\", &force_author, N_(\"author\"), N_(\"override author for commit\")),\n \t\tOPT_STRING(0, \"date\", &force_date, N_(\"date\"), N_(\"override date for commit\")),\n+\t\tOPT_STRING(0, \"committer\", &force_committer, N_(\"committer\"), N_(\"override committer for commit\")),\n \t\tOPT_CALLBACK('m', \"message\", &message, N_(\"message\"), N_(\"commit message\"), opt_parse_m),\n \t\tOPT_STRING('c', \"reedit-message\", &edit_message, N_(\"commit\"), N_(\"reuse and edit message from specified commit\")),\n \t\tOPT_STRING('C', \"reuse-message\", &use_message, N_(\"commit\"), N_(\"reuse message from specified commit\")),\n@@ -1785,6 +1836,7 @@ int cmd_commit(int argc,\n \n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf author_ident = STRBUF_INIT;\n+\tstruct strbuf committer_ident = STRBUF_INIT;\n \tconst char *index_file, *reflog_msg;\n \tstruct object_id oid;\n \tstruct commit_list *parents = NULL;\n@@ -1930,8 +1982,12 @@ int cmd_commit(int argc,\n \t\tappend_merge_tag_headers(parents, &tail);\n \t}\n \n+\tif (force_committer)\n+\t\tdetermine_committer_info(&committer_ident);\n+\n \tif (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n-\t\t\t\t parents, &oid, author_ident.buf, NULL,\n+\t\t\t\t parents, &oid, author_ident.buf,\n+\t\t\t\t force_committer ? committer_ident.buf : NULL,\n \t\t\t\t sign_commit, extra)) {\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n@@ -1980,6 +2036,7 @@ cleanup:\n \tfree_commit_extra_headers(extra);\n \tfree_commit_list(parents);\n \tstrbuf_release(&author_ident);\n+\tstrbuf_release(&committer_ident);\n \tstrbuf_release(&err);\n \tstrbuf_release(&sb);\n \tfree(logfile);\ndiff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh\nindex 8e373b566b..7e163e02d1 100755\n--- a/t/t7509-commit-authorship.sh\n+++ b/t/t7509-commit-authorship.sh\n@@ -12,13 +12,20 @@ author_header () {\n \tsed -n -e '/^$/q' -e '/^author /p'\n }\n \n+committer_header () {\n+\tgit cat-file commit \"$1\" |\n+\tsed -n -e '/^$/q' -e '/^committer /p'\n+}\n+\n message_body () {\n \tgit cat-file commit \"$1\" |\n \tsed -e '1,/^$/d'\n }\n \n test_expect_success '-C option copies authorship and message' '\n-\ttest_commit --author Frigate\\ \\<flying@over.world\\> \\\n+\ttest_env GIT_COMMITTER_NAME=\"Frigate\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"flying@over.world\" \\\n+\t\ttest_commit --author Frigate\\ \\<flying@over.world\\> \\\n \t\t\"Initial Commit\" foo Initial Initial &&\n \techo \"Test 1\" >>foo &&\n \ttest_tick &&\n@@ -171,4 +178,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--committer option overrides committer' '\n+\tgit checkout Initial &&\n+\techo \"Test --committer\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test committer\" --committer=\"Custom Committer <custom@committer.example>\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Custom Committer <custom@committer.example>\" actual\n+'\n+\n+test_expect_success '--committer with pattern search' '\n+\techo \"Test committer pattern\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test committer pattern\" --committer=\"Frigate\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Frigate <flying@over.world>\" actual\n+'\n+\n+test_expect_success '--committer malformed parameter' '\n+\techo \"Test malformed\" >>foo &&\n+\ttest_tick &&\n+\ttest_must_fail git commit -a -m \"test malformed\" --committer=\"malformed committer\"\n+'\n+\n+test_expect_success '--committer with --amend option' '\n+\tgit checkout -f Initial &&\n+\techo \"Test committer with amend\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"initial commit for amend test\" &&\n+\techo \"Modified for amend\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a --amend --no-edit \\\n+\t\t--author=\"Test Author <test@author.example>\" \\\n+\t\t--committer=\"Test Committer <test@committer.example>\" &&\n+\tauthor_header HEAD >actual_author &&\n+\tgrep \"Test Author <test@author.example>\" actual_author &&\n+\tcommitter_header HEAD >actual_committer &&\n+\tgrep \"Test Committer <test@committer.example>\" actual_committer\n+'\n+\n+test_expect_success 'GIT_COMMITTER_* environment variables' '\n+\tgit checkout -f Initial &&\n+\techo \"Test env vars\" >>foo &&\n+\ttest_tick &&\n+\ttest_env GIT_COMMITTER_NAME=\"Env Committer\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n+\t\tgit commit -a -m \"test committer env vars\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Env Committer <env@test.example>\" actual\n+'\n+\n+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n+\techo \"Test override\" >>foo &&\n+\ttest_tick &&\n+\ttest_env GIT_COMMITTER_NAME=\"Env Committer\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n+\t\tgit commit -a -m \"test override\" \\\n+\t\t--committer=\"Override Committer <override@test.example>\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Override Committer <override@test.example>\" actual\n+'\n+\n+test_expect_success '--date with --committer changes both author and committer dates' '\n+\tgit checkout -f Initial &&\n+\techo \"Test date override\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test date\" \\\n+\t\t--author=\"Date Author <date@author.example>\" \\\n+\t\t--committer=\"Date Committer <date@committer.example>\" \\\n+\t\t--date=\"2024-06-15 10:30:00 +0800\" &&\n+\tgit log -1 --format=\"%ai\" >author_date &&\n+\tgit log -1 --format=\"%ci\" >committer_date &&\n+\tgrep \"2024-06-15 10:30:00 +0800\" author_date &&\n+\tgrep \"2024-06-15 10:30:00 +0800\" committer_date\n+'\n+\n test_done\n\nbase-commit: 4badef0c3503dc29059d678abba7fac0f042bc84\n-- \ngitgitgadget\n"},{"id":"530455","messageId":"xmqq7bvx3i0u.fsf@gitster.g","threadId":"64460","inReplyTo":"aRGvVwRcsJA9CD9c@pks.im","subject":"Re: [PATCH] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-10T17:38:09Z","receivedAt":"2025-11-10T17:38:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Sun, Nov 09, 2025 at 10:22:54AM +0000, ZheNing Hu via GitGitGadget wrote:\n>> From: ZheNing Hu <adlternative@gmail.com>\n>> \n>> Add --committer option to git-commit, allowing users to override the\n>> committer identity similar to how --author works. This provides a more\n>> convenient alternative to setting GIT_COMMITTER_* environment variables.\n>\n> Yeah, I can see how that's useful.\n\nWell, I don't.  Naming somebody other than yourself as the author\nmay be something that is needed from time to time by human users,\nbut lying about the committer who made commits?  Our tradition is to\ngive long rope to let users hang themselves, but we already have the\nenvironment variable override specifically designed for scripted uses,\nwhere there may be very legit uses of recording arbitrary committer\nidentity that has nothing to do with the identity the current user\nwho is running the Git processes usually uses.  I do not think it is\na \"useful\" change to make it more it ergonomic to perform certain\noperations that we may not want to encourage.\n\nSo I dunno.\n\n\n"},{"id":"530458","messageId":"aRIoleD6nP-kA4Xn@fruit.crustytoothpaste.net","threadId":"64460","inReplyTo":"6be20c41-15a0-4732-bd12-4927a59a9f59@gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2025-11-10T18:01:57Z","receivedAt":"2025-11-10T18:02:04Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2025-11-10 at 16:50:04, Phillip Wood wrote:\n> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> > From: ZheNing Hu <adlternative@gmail.com>\n> > \n> >      This patch introduces the --committer option to git-commit, providing:\n> >       1. Consistency with the existing --author option\n> >       2. A more convenient alternative to environment variables\n> >       3. Better support for automated workflows and scripts\n> >       4. Improved user experience when managing multiple identities\n> \n> What's the use case for the same person committing under different\n> identities? We already have a config mechanism to set different identities\n> for different repositories but I'm struggling to see why someone would want\n> to create commits under multiple identities in a single repository. For\n> scripts it easy enough to set the relevant environment variables if a tool\n> wants to create commits under its own identity.\n\nSomeone who works on the same project under both their personal and\ncorporate identities.  For instance, me working on the Git project.\n\nSome open source projects also require a CLA and you have to use a\nparticular address to match the one that's listed on the CLA.  For\nexample, Google requires an address with a Google account, so in the\nhypothetical state where I was going to contribute to one of their\nprojects, I'd need to use a different committer identity with my Gmail\naddress.\n\nI've also kept business logs in Git when I had a small business and I\nmight well need to log approving a profit distribution (with my\ncorporate address) and log accepting a profit distribution (with my\npersonal address).  Those would need separate digital signatures from my\ntwo different email addresses.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"530470","messageId":"xmqqo6p9zo8f.fsf@gitster.g","threadId":"64460","inReplyTo":"pull.1997.v2.git.1762793782815.gitgitgadget@gmail.com","subject":"Re: [PATCH v2] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-10T19:22:56Z","receivedAt":"2025-11-10T19:22:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n\nWhat is this second author and how would its presence in the author\nlist interact with your DCO obligation?\n\nHow did you make sure that whatever is in this patch were not copied\nby the \"agent\" from somewhere that we cannot copy the code from\nbefore deciding to send this patch?  The \"cannot copy from\" may come\nin different shapes, from \"their code is proprietary\" to \"their\nlicensing terms are not compatible with GPLv2\" to \"they welcome us\nborrowing but we must give credit to them\", any of which we should\nbe careful to avoid.\n\n\n"},{"id":"530472","messageId":"xmqqjyzxznxm.fsf@gitster.g","threadId":"64460","inReplyTo":"xmqqo6p9zo8f.fsf@gitster.g","subject":"Re: [PATCH v2] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-10T19:29:25Z","receivedAt":"2025-11-10T19:29:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n>> Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n>\n> What is this second author and how would its presence in the author\n> list interact with your DCO obligation?\n>\n> How did you make sure that whatever is in this patch were not copied\n> by the \"agent\" from somewhere that we cannot copy the code from\n> before deciding to send this patch?  The \"cannot copy from\" may come\n> in different shapes, from \"their code is proprietary\" to \"their\n> licensing terms are not compatible with GPLv2\" to \"they welcome us\n> borrowing but we must give credit to them\", any of which we should\n> be careful to avoid.\n\nWell, the last one is not something we should *avoid*.  If their\nlicensing terms are compatible with ours but they want to be\ncredited, then we comply that request and credit them.\n\nBut I think readers got the idea.\n\nThanks.\n"},{"id":"530474","messageId":"20251110201136.GB127132@coredump.intra.peff.net","threadId":"64460","inReplyTo":"aRIoleD6nP-kA4Xn@fruit.crustytoothpaste.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-11-10T20:11:36Z","receivedAt":"2025-11-10T20:11:38Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote:\n\n> On 2025-11-10 at 16:50:04, Phillip Wood wrote:\n> > On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> > > From: ZheNing Hu <adlternative@gmail.com>\n> > > \n> > >      This patch introduces the --committer option to git-commit, providing:\n> > >       1. Consistency with the existing --author option\n> > >       2. A more convenient alternative to environment variables\n> > >       3. Better support for automated workflows and scripts\n> > >       4. Improved user experience when managing multiple identities\n> > \n> > What's the use case for the same person committing under different\n> > identities? We already have a config mechanism to set different identities\n> > for different repositories but I'm struggling to see why someone would want\n> > to create commits under multiple identities in a single repository. For\n> > scripts it easy enough to set the relevant environment variables if a tool\n> > wants to create commits under its own identity.\n> \n> Someone who works on the same project under both their personal and\n> corporate identities.  For instance, me working on the Git project.\n> \n> Some open source projects also require a CLA and you have to use a\n> particular address to match the one that's listed on the CLA.  For\n> example, Google requires an address with a Google account, so in the\n> hypothetical state where I was going to contribute to one of their\n> projects, I'd need to use a different committer identity with my Gmail\n> address.\n> \n> I've also kept business logs in Git when I had a small business and I\n> might well need to log approving a profit distribution (with my\n> corporate address) and log accepting a profit distribution (with my\n> personal address).  Those would need separate digital signatures from my\n> two different email addresses.\n\nIs a \"--committer\" option the best solution there, though? I'd think\nyou'd want to set user.* in the repo-level .git/config (or using a\ndir-specific include) would be less error-prone.\n\nThat doesn't help for using two identities for the same repo, but in my\nexperience it is easier to use two separate repositories for that to\nmatch the organization of the work (even if you may sometimes fetch\nbetween them).\n\nI'm not totally opposed to the new flag, and in general I'd defer to\npeople who say they find a new feature useful. I'm just having a hard\ntime imagining a scenario where it's the best option.\n\n-Peff\n"},{"id":"530475","messageId":"xmqq8qgdzgng.fsf@gitster.g","threadId":"64460","inReplyTo":"20251110201136.GB127132@coredump.intra.peff.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-10T22:06:43Z","receivedAt":"2025-11-10T22:06:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Is a \"--committer\" option the best solution there, though? I'd think\n> you'd want to set user.* in the repo-level .git/config (or using a\n> dir-specific include) would be less error-prone.\n>\n> That doesn't help for using two identities for the same repo, but in my\n> experience it is easier to use two separate repositories for that to\n> match the organization of the work (even if you may sometimes fetch\n> between them).\n\nThis happens to match my experience, but my use case may be rather\nskewed.  The two sets of contents managed by my two clones that push\ninto the same repository (of course to two different branches) are\nrather disjoint and they never merge into each other (in fact they\ndo not even share the root commit).\n\nIf you are working under two identities on the same codebase, I\nwould imagine that two repo arrangement may be more cumbersome than\nworking in a single repository and switching between the identities,\nand may be preferrable as long as you are confident that you won't\ncommit a change under the \"other\" (wrong) identity.  Of course, your\nreflogs and notes would also follow the \"then-current\" committer\nidentity, so if I were to flip between two identities while working\non the same codebase in a single repository, I am very likely to\nexport GIT_{AUTHOR,COMMITTER}_* environment variables, dedicate that\nshell/window to the work done under that identity, and switch the\nenvironment variables if/when I want to switch (or have another\nshell/window with the other identities exported---perhaps I'd do\nthat in a secondary worktree).  I cannot imagine myself keep giving\n--author and --committer between my two identities without mistakes.\n\n> I'm not totally opposed to the new flag, and in general I'd defer to\n> people who say they find a new feature useful. I'm just having a hard\n> time imagining a scenario where it's the best option.\n\nSame here.  The \"give them long enough rope\" principle tells me that\nthis may be worth having if even only to have symmetry with existing\n\"--author\" option, but these two are not inherently symmetric to\nbegin with, and I am not sure if there is a scenario in which this\nnew option is the best thing to use.\n"},{"id":"530499","messageId":"aRLdsu-MUgPPdIgX@pks.im","threadId":"64460","inReplyTo":"20251110201136.GB127132@coredump.intra.peff.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-11-11T06:54:42Z","receivedAt":"2025-11-11T06:54:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Nov 10, 2025 at 03:11:36PM -0500, Jeff King wrote:\n> On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote:\n> \n> > On 2025-11-10 at 16:50:04, Phillip Wood wrote:\n> > > On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> > > > From: ZheNing Hu <adlternative@gmail.com>\n> > > > \n> > > >      This patch introduces the --committer option to git-commit, providing:\n> > > >       1. Consistency with the existing --author option\n> > > >       2. A more convenient alternative to environment variables\n> > > >       3. Better support for automated workflows and scripts\n> > > >       4. Improved user experience when managing multiple identities\n> > > \n> > > What's the use case for the same person committing under different\n> > > identities? We already have a config mechanism to set different identities\n> > > for different repositories but I'm struggling to see why someone would want\n> > > to create commits under multiple identities in a single repository. For\n> > > scripts it easy enough to set the relevant environment variables if a tool\n> > > wants to create commits under its own identity.\n> > \n> > Someone who works on the same project under both their personal and\n> > corporate identities.  For instance, me working on the Git project.\n> > \n> > Some open source projects also require a CLA and you have to use a\n> > particular address to match the one that's listed on the CLA.  For\n> > example, Google requires an address with a Google account, so in the\n> > hypothetical state where I was going to contribute to one of their\n> > projects, I'd need to use a different committer identity with my Gmail\n> > address.\n> > \n> > I've also kept business logs in Git when I had a small business and I\n> > might well need to log approving a profit distribution (with my\n> > corporate address) and log accepting a profit distribution (with my\n> > personal address).  Those would need separate digital signatures from my\n> > two different email addresses.\n> \n> Is a \"--committer\" option the best solution there, though? I'd think\n> you'd want to set user.* in the repo-level .git/config (or using a\n> dir-specific include) would be less error-prone.\n> \n> That doesn't help for using two identities for the same repo, but in my\n> experience it is easier to use two separate repositories for that to\n> match the organization of the work (even if you may sometimes fetch\n> between them).\n> \n> I'm not totally opposed to the new flag, and in general I'd defer to\n> people who say they find a new feature useful. I'm just having a hard\n> time imagining a scenario where it's the best option.\n\nThe reason why I find it useful is mostly scripted uses. Sure, you can\nalready set environment variables there. But from my experience,\nenvironment variables tend to be a significantly worse API compared to\ncommand line options:\n\n  - They are harder to discover in the manual page.\n\n  - You don't have any \"guarantees\" that Git actually interprets them,\n    as there won't be an error if you mistype the name.\n\n  - Cause and effect may be detached with environment variables, but\n    with command line options that's never the case.\n\nSo I myself would prefer using \"--committer\" over its accompanying\nenvironment variable any point in time when I have a scripted use case\nfor it.\n\nPatrick\n"},{"id":"530504","messageId":"CAOLTT8TPrNTCjHwJfdeei+t8+7AxGC-dvbq-4oHJ=qNn_c+-jQ@mail.gmail.com","threadId":"64460","inReplyTo":"6be20c41-15a0-4732-bd12-4927a59a9f59@gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-11T13:01:22Z","receivedAt":"2025-11-11T13:01:35Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道：\n>\n> Hi ZheNing\n>\n> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> > From: ZheNing Hu <adlternative@gmail.com>\n> >\n> >\n> >      This patch introduces the --committer option to git-commit, providing:\n> >\n> >       1. Consistency with the existing --author option\n> >       2. A more convenient alternative to environment variables\n> >       3. Better support for automated workflows and scripts\n> >       4. Improved user experience when managing multiple identities\n>\n> What's the use case for the same person committing under different\n> identities? We already have a config mechanism to set different\n> identities for different repositories but I'm struggling to see why\n> someone would want to create commits under multiple identities in a\n> single repository. For scripts it easy enough to set the relevant\n> environment variables if a tool wants to create commits under its own\n> identity.\n>\n\nI frequently need to distinguish between different user.name and user.email\nconfigurations on our company's internal GitHub.\n\nThe current problems are:\n\nWhen I misconfigure (which happens occasionally), git commit --author only fixes\nthe author part, I still need to additionally set GIT_COMMITTER_NAME and\nGIT_COMMITTER_EMAIL environment variables to fix the committer information\nThese environment variables are painful to use, requiring manual setup\nevery time\n\nIf a --committer option could be provided to align with --author, users wouldn't\nneed to remember and use these additional environment variables.\nThis would greatly simplify the workflow and reduce cognitive overhead.\n\n\n> Thanks\n>\n> Phillip\n>\n> >      The implementation follows the same pattern as the --author option,\n> >      accepting the format \"Name \" and properly validating the input.\n> >\n> > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1\n> > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1\n> > Pull-Request: https://github.com/gitgitgadget/git/pull/1997\n> >\n> >   Documentation/git-commit.adoc |  9 +++-\n> >   builtin/commit.c              | 58 ++++++++++++++++++++++++-\n> >   t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++\n> >   3 files changed, 144 insertions(+), 3 deletions(-)\n> >\n> > diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\n> > index 54c207ad45..a015c8328e 100644\n> > --- a/Documentation/git-commit.adoc\n> > +++ b/Documentation/git-commit.adoc\n> > @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n> >          [--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>...]\n> > @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.\n> >   `--date=<date>`::\n> >       Override the author date used in the commit.\n> >\n> > +`--committer=<committer>`::\n> > +     Override the committer for the commit. Specify an explicit committer using the\n> > +     standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_\n> > +     is assumed to be a pattern and is used to search for an existing\n> > +     commit by that author (i.e. `git rev-list --all -i --author=<committer>`);\n> > +     the commit author is then copied from the first such commit found.\n> > +\n> >   `-m <msg>`::\n> >   `--message=<msg>`::\n> >       Use _<msg>_ as the commit message.\n> > diff --git a/builtin/commit.c b/builtin/commit.c\n> > index 0243f17d53..88e77cbaab 100644\n> > --- a/builtin/commit.c\n> > +++ b/builtin/commit.c\n> > @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {\n> >          \"           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\\n\"\n> >          \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n> >          \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n> > -        \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n> > +        \"           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\\n\"\n> >          \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n> >          \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n> >          \"           [--] [<pathspec>...]\"),\n> > @@ -112,6 +112,7 @@ static enum {\n> >   } commit_style;\n> >\n> >   static const char *force_author;\n> > +static const char *force_committer;\n> >   static char *logfile;\n> >   static char *template_file;\n> >   /*\n> > @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)\n> >       free(date);\n> >   }\n> >\n> > +static void determine_committer_info(struct strbuf *committer_ident)\n> > +{\n> > +     char *name, *email, *date;\n> > +     struct ident_split committer;\n> > +\n> > +     name = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n> > +     email = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n> > +     date = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n> > +\n> > +     if (force_committer) {\n> > +             struct ident_split ident;\n> > +\n> > +             if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)\n> > +                     die(_(\"malformed --committer parameter\"));\n> > +             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n> > +             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n> > +\n> > +             if (ident.date_begin) {\n> > +                     struct strbuf date_buf = STRBUF_INIT;\n> > +                     strbuf_addch(&date_buf, '@');\n> > +                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n> > +                     strbuf_addch(&date_buf, ' ');\n> > +                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n> > +                     set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> > +             }\n> > +     }\n> > +\n> > +     if (force_date) {\n> > +             struct strbuf date_buf = STRBUF_INIT;\n> > +             if (parse_force_date(force_date, &date_buf))\n> > +                     die(_(\"invalid date format: %s\"), force_date);\n> > +             set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> > +     }\n> > +\n> > +     strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,\n> > +                             IDENT_STRICT));\n> > +     assert_split_ident(&committer, committer_ident);\n> > +     free(name);\n> > +     free(email);\n> > +     free(date);\n> > +}\n> > +\n> >   static int author_date_is_interesting(void)\n> >   {\n> >       return author_message || force_date;\n> > @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n> >       if (force_author && renew_authorship)\n> >               die(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n> >\n> > +     if (force_committer && !strchr(force_committer, '>'))\n> > +             force_committer = find_author_by_nickname(force_committer);\n> > +\n> >       if (logfile || have_option_m || use_message)\n> >               use_editor = 0;\n> >\n> > @@ -1709,6 +1755,7 @@ int cmd_commit(int argc,\n> >               OPT_FILENAME('F', \"file\", &logfile, N_(\"read message from file\")),\n> >               OPT_STRING(0, \"author\", &force_author, N_(\"author\"), N_(\"override author for commit\")),\n> >               OPT_STRING(0, \"date\", &force_date, N_(\"date\"), N_(\"override date for commit\")),\n> > +             OPT_STRING(0, \"committer\", &force_committer, N_(\"committer\"), N_(\"override committer for commit\")),\n> >               OPT_CALLBACK('m', \"message\", &message, N_(\"message\"), N_(\"commit message\"), opt_parse_m),\n> >               OPT_STRING('c', \"reedit-message\", &edit_message, N_(\"commit\"), N_(\"reuse and edit message from specified commit\")),\n> >               OPT_STRING('C', \"reuse-message\", &use_message, N_(\"commit\"), N_(\"reuse message from specified commit\")),\n> > @@ -1785,6 +1832,7 @@ int cmd_commit(int argc,\n> >\n> >       struct strbuf sb = STRBUF_INIT;\n> >       struct strbuf author_ident = STRBUF_INIT;\n> > +     struct strbuf committer_ident = STRBUF_INIT;\n> >       const char *index_file, *reflog_msg;\n> >       struct object_id oid;\n> >       struct commit_list *parents = NULL;\n> > @@ -1930,8 +1978,13 @@ int cmd_commit(int argc,\n> >               append_merge_tag_headers(parents, &tail);\n> >       }\n> >\n> > +     if (force_committer) {\n> > +             determine_committer_info(&committer_ident);\n> > +     }\n> > +\n> >       if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n> > -                              parents, &oid, author_ident.buf, NULL,\n> > +                              parents, &oid, author_ident.buf,\n> > +                              force_committer ? committer_ident.buf : NULL,\n> >                                sign_commit, extra)) {\n> >               rollback_index_files();\n> >               die(_(\"failed to write commit object\"));\n> > @@ -1980,6 +2033,7 @@ cleanup:\n> >       free_commit_extra_headers(extra);\n> >       free_commit_list(parents);\n> >       strbuf_release(&author_ident);\n> > +     strbuf_release(&committer_ident);\n> >       strbuf_release(&err);\n> >       strbuf_release(&sb);\n> >       free(logfile);\n> > diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh\n> > index 8e373b566b..45527f6a70 100755\n> > --- a/t/t7509-commit-authorship.sh\n> > +++ b/t/t7509-commit-authorship.sh\n> > @@ -12,6 +12,11 @@ author_header () {\n> >       sed -n -e '/^$/q' -e '/^author /p'\n> >   }\n> >\n> > +committer_header () {\n> > +     git cat-file commit \"$1\" |\n> > +     sed -n -e '/^$/q' -e '/^committer /p'\n> > +}\n> > +\n> >   message_body () {\n> >       git cat-file commit \"$1\" |\n> >       sed -e '1,/^$/d'\n> > @@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n> >       test_cmp expect actual\n> >   '\n> >\n> > +test_expect_success '--committer option overrides committer' '\n> > +     git checkout Initial &&\n> > +     echo \"Test --committer\" >>foo &&\n> > +     test_tick &&\n> > +     git commit -a -m \"test committer\" --committer=\"Custom Committer <custom@committer.example>\" &&\n> > +     committer_header HEAD >actual &&\n> > +     grep \"Custom Committer <custom@committer.example>\" actual\n> > +'\n> > +\n> > +test_expect_success '--committer with pattern search' '\n> > +     echo \"Test committer pattern\" >>foo &&\n> > +     test_tick &&\n> > +     git commit -a -m \"test committer pattern\" --committer=\"Frigate\" &&\n> > +     committer_header HEAD >actual &&\n> > +     grep \"Frigate <flying@over.world>\" actual\n> > +'\n> > +\n> > +test_expect_success '--committer malformed parameter' '\n> > +     echo \"Test malformed\" >>foo &&\n> > +     test_tick &&\n> > +     test_must_fail git commit -a -m \"test malformed\" --committer=\"malformed committer\"\n> > +'\n> > +\n> > +test_expect_success '--committer with --amend option' '\n> > +     git checkout -f Initial &&\n> > +     echo \"Test committer with amend\" >>foo &&\n> > +     test_tick &&\n> > +     git commit -a -m \"initial commit for amend test\" &&\n> > +     echo \"Modified for amend\" >>foo &&\n> > +     test_tick &&\n> > +     git commit -a --amend --no-edit \\\n> > +             --author=\"Test Author <test@author.example>\" \\\n> > +             --committer=\"Test Committer <test@committer.example>\" &&\n> > +     author_header HEAD >actual_author &&\n> > +     grep \"Test Author <test@author.example>\" actual_author &&\n> > +     committer_header HEAD >actual_committer &&\n> > +     grep \"Test Committer <test@committer.example>\" actual_committer\n> > +'\n> > +\n> > +test_expect_success 'GIT_COMMITTER_* environment variables' '\n> > +     git checkout -f Initial &&\n> > +     echo \"Test env vars\" >>foo &&\n> > +     test_tick &&\n> > +     GIT_COMMITTER_NAME=\"Env Committer\" \\\n> > +     GIT_COMMITTER_EMAIL=\"env@test.example\" \\\n> > +     git commit -a -m \"test committer env vars\" &&\n> > +     committer_header HEAD >actual &&\n> > +     grep \"Env Committer <env@test.example>\" actual\n> > +'\n> > +\n> > +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n> > +     echo \"Test override\" >>foo &&\n> > +     test_tick &&\n> > +     GIT_COMMITTER_NAME=\"Env Committer\" \\\n> > +     GIT_COMMITTER_EMAIL=\"env@test.example\" \\\n> > +     git commit -a -m \"test override\" \\\n> > +             --committer=\"Override Committer <override@test.example>\" &&\n> > +     committer_header HEAD >actual &&\n> > +     grep \"Override Committer <override@test.example>\" actual\n> > +'\n> > +\n> > +test_expect_success '--date with --committer changes both author and committer dates' '\n> > +     git checkout -f Initial &&\n> > +     echo \"Test date override\" >>foo &&\n> > +     test_tick &&\n> > +     git commit -a -m \"test date\" \\\n> > +             --author=\"Date Author <date@author.example>\" \\\n> > +             --committer=\"Date Committer <date@committer.example>\" \\\n> > +             --date=\"2024-06-15 10:30:00 +0800\" &&\n> > +     git log -1 --format=\"%ai\" >author_date &&\n> > +     git log -1 --format=\"%ci\" >committer_date &&\n> > +     grep \"2024-06-15 10:30:00 +0800\" author_date &&\n> > +     grep \"2024-06-15 10:30:00 +0800\" committer_date\n> > +'\n> > +\n> >   test_done\n> >\n> > base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84\n>\n"},{"id":"530506","messageId":"CAOLTT8RSvOoQyXeB6bxf68iX8_ptnsWALHhT8ztCYiR7ifue-Q@mail.gmail.com","threadId":"64460","inReplyTo":"xmqq7bvx3i0u.fsf@gitster.g","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-11T13:19:44Z","receivedAt":"2025-11-11T13:19:57Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 01:38写道：\n>\n> Patrick Steinhardt <ps@pks.im> writes:\n>\n> > On Sun, Nov 09, 2025 at 10:22:54AM +0000, ZheNing Hu via GitGitGadget wrote:\n> >> From: ZheNing Hu <adlternative@gmail.com>\n> >>\n> >> Add --committer option to git-commit, allowing users to override the\n> >> committer identity similar to how --author works. This provides a more\n> >> convenient alternative to setting GIT_COMMITTER_* environment variables.\n> >\n> > Yeah, I can see how that's useful.\n>\n> Well, I don't.  Naming somebody other than yourself as the author\n> may be something that is needed from time to time by human users,\n> but lying about the committer who made commits?  Our tradition is to\n> give long rope to let users hang themselves, but we already have the\n> environment variable override specifically designed for scripted uses,\n> where there may be very legit uses of recording arbitrary committer\n> identity that has nothing to do with the identity the current user\n> who is running the Git processes usually uses.  I do not think it is\n> a \"useful\" change to make it more it ergonomic to perform certain\n> operations that we may not want to encourage.\n>\n> So I dunno.\n>\n\nI understand your concern about not wanting to encourage users\nto misrepresent their committer identity. However, I'd like to point out\nthat if someone truly wants to \"lie\" about the committer identity, there's\nnothing stopping them today. They can already do this through multiple\nmethods.\n\nMy primary motivation for this patch is not to make it easier to\nfalsify identity,\nbut rather to help legitimate users who work across different repositories\nwith different identities. These users occasionally need to fix accidentally\nmisconfigured committer information, and the current methods\n(setting environment variables or editing config files) can be cumbersome\nfor this use case.\n\nYou can see from [1] how many people have struggled with modifying\ncommits committer.\n\n>\n\n[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\n"},{"id":"530510","messageId":"CAOLTT8SDpVfk8XqOJDL1=ASJwLf8EYtCo05NS3M2zZUx9JV6fA@mail.gmail.com","threadId":"64460","inReplyTo":"xmqqo6p9zo8f.fsf@gitster.g","subject":"Re: [PATCH v2] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-11T13:36:31Z","receivedAt":"2025-11-11T13:36:44Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 03:22写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> > Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n>\n> What is this second author and how would its presence in the author\n> list interact with your DCO obligation?\n>\n> How did you make sure that whatever is in this patch were not copied\n> by the \"agent\" from somewhere that we cannot copy the code from\n> before deciding to send this patch?  The \"cannot copy from\" may come\n> in different shapes, from \"their code is proprietary\" to \"their\n> licensing terms are not compatible with GPLv2\" to \"they welcome us\n> borrowing but we must give credit to them\", any of which we should\n> be careful to avoid.\n>\n>\n\nThis was automatically added by some code assistance tools.\nI indeed forgot to consider its impact on the open source license.\nI'll remove it right away.\n"},{"id":"530511","messageId":"CAOLTT8RweGOmxNK=vKDv8w-8AJM7QUfLBw4WOKeY1EpSVeB6iQ@mail.gmail.com","threadId":"64460","inReplyTo":"20251110201136.GB127132@coredump.intra.peff.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-11T13:42:38Z","receivedAt":"2025-11-11T13:42:52Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Jeff King <peff@peff.net> 于2025年11月11日周二 04:11写道：\n>\n> On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote:\n>\n> > On 2025-11-10 at 16:50:04, Phillip Wood wrote:\n> > > On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> > > > From: ZheNing Hu <adlternative@gmail.com>\n> > > >\n> > > >      This patch introduces the --committer option to git-commit, providing:\n> > > >       1. Consistency with the existing --author option\n> > > >       2. A more convenient alternative to environment variables\n> > > >       3. Better support for automated workflows and scripts\n> > > >       4. Improved user experience when managing multiple identities\n> > >\n> > > What's the use case for the same person committing under different\n> > > identities? We already have a config mechanism to set different identities\n> > > for different repositories but I'm struggling to see why someone would want\n> > > to create commits under multiple identities in a single repository. For\n> > > scripts it easy enough to set the relevant environment variables if a tool\n> > > wants to create commits under its own identity.\n> >\n> > Someone who works on the same project under both their personal and\n> > corporate identities.  For instance, me working on the Git project.\n> >\n> > Some open source projects also require a CLA and you have to use a\n> > particular address to match the one that's listed on the CLA.  For\n> > example, Google requires an address with a Google account, so in the\n> > hypothetical state where I was going to contribute to one of their\n> > projects, I'd need to use a different committer identity with my Gmail\n> > address.\n> >\n> > I've also kept business logs in Git when I had a small business and I\n> > might well need to log approving a profit distribution (with my\n> > corporate address) and log accepting a profit distribution (with my\n> > personal address).  Those would need separate digital signatures from my\n> > two different email addresses.\n>\n> Is a \"--committer\" option the best solution there, though? I'd think\n> you'd want to set user.* in the repo-level .git/config (or using a\n> dir-specific include) would be less error-prone.\n>\n> That doesn't help for using two identities for the same repo, but in my\n> experience it is easier to use two separate repositories for that to\n> match the organization of the work (even if you may sometimes fetch\n> between them).\n>\n> I'm not totally opposed to the new flag, and in general I'd defer to\n> people who say they find a new feature useful. I'm just having a hard\n> time imagining a scenario where it's the best option.\n>\n\nSometimes it's because I forgot to configure the repository-level git user\nconfig and started development first. Only when I tried to correct the\ncommitter did I feel the pain.\n\n> -Peff\n"},{"id":"530516","messageId":"7d99f00c-3602-4b28-8efd-4780cad41ca8@gmail.com","threadId":"64460","inReplyTo":"CAOLTT8TPrNTCjHwJfdeei+t8+7AxGC-dvbq-4oHJ=qNn_c+-jQ@mail.gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-11-11T14:38:40Z","receivedAt":"2025-11-11T14:38:43Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 11/11/2025 13:01, ZheNing Hu wrote:\n> Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道：\n>> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n>>> From: ZheNing Hu <adlternative@gmail.com>\n>>>\n>>>\n>>>       This patch introduces the --committer option to git-commit, providing:\n>>>\n>>>        1. Consistency with the existing --author option\n>>>        2. A more convenient alternative to environment variables\n>>>        3. Better support for automated workflows and scripts\n>>>        4. Improved user experience when managing multiple identities\n>>\n>> What's the use case for the same person committing under different\n>> identities? We already have a config mechanism to set different\n>> identities for different repositories but I'm struggling to see why\n>> someone would want to create commits under multiple identities in a\n>> single repository. For scripts it easy enough to set the relevant\n>> environment variables if a tool wants to create commits under its own\n>> identity.\n>>\n> \n> I frequently need to distinguish between different user.name and user.email\n> configurations on our company's internal GitHub.\n> \n> The current problems are:\n> \n> When I misconfigure (which happens occasionally), git commit --author only fixes\n> the author part, I still need to additionally set GIT_COMMITTER_NAME and\n> GIT_COMMITTER_EMAIL environment variables to fix the committer information\n> These environment variables are painful to use, requiring manual setup\n> every time\n\nI'm afraid I don't quite follow. If you are amending existing commits to \nfix them up after you have corrected your configuration then they will \nhave the correct committer automatically when you run \"git commit \n--amend --author=...\" to correct the author. If you are committing \nbefore you have realized that user.{name,email} are misconfigured then I \ndon't see how \"--committer\" helps because you have not yet realized \nanything is wrong.\n\nThanks\n\nPhillip\n\n> If a --committer option could be provided to align with --author, users wouldn't\n> need to remember and use these additional environment variables.\n> This would greatly simplify the workflow and reduce cognitive overhead.\n> \n> \n>> Thanks\n>>\n>> Phillip\n>>\n>>>       The implementation follows the same pattern as the --author option,\n>>>       accepting the format \"Name \" and properly validating the input.\n>>>\n>>> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1\n>>> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1\n>>> Pull-Request: https://github.com/gitgitgadget/git/pull/1997\n>>>\n>>>    Documentation/git-commit.adoc |  9 +++-\n>>>    builtin/commit.c              | 58 ++++++++++++++++++++++++-\n>>>    t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++\n>>>    3 files changed, 144 insertions(+), 3 deletions(-)\n>>>\n>>> diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\n>>> index 54c207ad45..a015c8328e 100644\n>>> --- a/Documentation/git-commit.adoc\n>>> +++ b/Documentation/git-commit.adoc\n>>> @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n>>>           [--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>...]\n>>> @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.\n>>>    `--date=<date>`::\n>>>        Override the author date used in the commit.\n>>>\n>>> +`--committer=<committer>`::\n>>> +     Override the committer for the commit. Specify an explicit committer using the\n>>> +     standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_\n>>> +     is assumed to be a pattern and is used to search for an existing\n>>> +     commit by that author (i.e. `git rev-list --all -i --author=<committer>`);\n>>> +     the commit author is then copied from the first such commit found.\n>>> +\n>>>    `-m <msg>`::\n>>>    `--message=<msg>`::\n>>>        Use _<msg>_ as the commit message.\n>>> diff --git a/builtin/commit.c b/builtin/commit.c\n>>> index 0243f17d53..88e77cbaab 100644\n>>> --- a/builtin/commit.c\n>>> +++ b/builtin/commit.c\n>>> @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {\n>>>           \"           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\\n\"\n>>>           \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n>>>           \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n>>> -        \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n>>> +        \"           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\\n\"\n>>>           \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n>>>           \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n>>>           \"           [--] [<pathspec>...]\"),\n>>> @@ -112,6 +112,7 @@ static enum {\n>>>    } commit_style;\n>>>\n>>>    static const char *force_author;\n>>> +static const char *force_committer;\n>>>    static char *logfile;\n>>>    static char *template_file;\n>>>    /*\n>>> @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)\n>>>        free(date);\n>>>    }\n>>>\n>>> +static void determine_committer_info(struct strbuf *committer_ident)\n>>> +{\n>>> +     char *name, *email, *date;\n>>> +     struct ident_split committer;\n>>> +\n>>> +     name = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n>>> +     email = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n>>> +     date = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n>>> +\n>>> +     if (force_committer) {\n>>> +             struct ident_split ident;\n>>> +\n>>> +             if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)\n>>> +                     die(_(\"malformed --committer parameter\"));\n>>> +             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n>>> +             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n>>> +\n>>> +             if (ident.date_begin) {\n>>> +                     struct strbuf date_buf = STRBUF_INIT;\n>>> +                     strbuf_addch(&date_buf, '@');\n>>> +                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n>>> +                     strbuf_addch(&date_buf, ' ');\n>>> +                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n>>> +                     set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n>>> +             }\n>>> +     }\n>>> +\n>>> +     if (force_date) {\n>>> +             struct strbuf date_buf = STRBUF_INIT;\n>>> +             if (parse_force_date(force_date, &date_buf))\n>>> +                     die(_(\"invalid date format: %s\"), force_date);\n>>> +             set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n>>> +     }\n>>> +\n>>> +     strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,\n>>> +                             IDENT_STRICT));\n>>> +     assert_split_ident(&committer, committer_ident);\n>>> +     free(name);\n>>> +     free(email);\n>>> +     free(date);\n>>> +}\n>>> +\n>>>    static int author_date_is_interesting(void)\n>>>    {\n>>>        return author_message || force_date;\n>>> @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n>>>        if (force_author && renew_authorship)\n>>>                die(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n>>>\n>>> +     if (force_committer && !strchr(force_committer, '>'))\n>>> +             force_committer = find_author_by_nickname(force_committer);\n>>> +\n>>>        if (logfile || have_option_m || use_message)\n>>>                use_editor = 0;\n>>>\n>>> @@ -1709,6 +1755,7 @@ int cmd_commit(int argc,\n>>>                OPT_FILENAME('F', \"file\", &logfile, N_(\"read message from file\")),\n>>>                OPT_STRING(0, \"author\", &force_author, N_(\"author\"), N_(\"override author for commit\")),\n>>>                OPT_STRING(0, \"date\", &force_date, N_(\"date\"), N_(\"override date for commit\")),\n>>> +             OPT_STRING(0, \"committer\", &force_committer, N_(\"committer\"), N_(\"override committer for commit\")),\n>>>                OPT_CALLBACK('m', \"message\", &message, N_(\"message\"), N_(\"commit message\"), opt_parse_m),\n>>>                OPT_STRING('c', \"reedit-message\", &edit_message, N_(\"commit\"), N_(\"reuse and edit message from specified commit\")),\n>>>                OPT_STRING('C', \"reuse-message\", &use_message, N_(\"commit\"), N_(\"reuse message from specified commit\")),\n>>> @@ -1785,6 +1832,7 @@ int cmd_commit(int argc,\n>>>\n>>>        struct strbuf sb = STRBUF_INIT;\n>>>        struct strbuf author_ident = STRBUF_INIT;\n>>> +     struct strbuf committer_ident = STRBUF_INIT;\n>>>        const char *index_file, *reflog_msg;\n>>>        struct object_id oid;\n>>>        struct commit_list *parents = NULL;\n>>> @@ -1930,8 +1978,13 @@ int cmd_commit(int argc,\n>>>                append_merge_tag_headers(parents, &tail);\n>>>        }\n>>>\n>>> +     if (force_committer) {\n>>> +             determine_committer_info(&committer_ident);\n>>> +     }\n>>> +\n>>>        if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n>>> -                              parents, &oid, author_ident.buf, NULL,\n>>> +                              parents, &oid, author_ident.buf,\n>>> +                              force_committer ? committer_ident.buf : NULL,\n>>>                                 sign_commit, extra)) {\n>>>                rollback_index_files();\n>>>                die(_(\"failed to write commit object\"));\n>>> @@ -1980,6 +2033,7 @@ cleanup:\n>>>        free_commit_extra_headers(extra);\n>>>        free_commit_list(parents);\n>>>        strbuf_release(&author_ident);\n>>> +     strbuf_release(&committer_ident);\n>>>        strbuf_release(&err);\n>>>        strbuf_release(&sb);\n>>>        free(logfile);\n>>> diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh\n>>> index 8e373b566b..45527f6a70 100755\n>>> --- a/t/t7509-commit-authorship.sh\n>>> +++ b/t/t7509-commit-authorship.sh\n>>> @@ -12,6 +12,11 @@ author_header () {\n>>>        sed -n -e '/^$/q' -e '/^author /p'\n>>>    }\n>>>\n>>> +committer_header () {\n>>> +     git cat-file commit \"$1\" |\n>>> +     sed -n -e '/^$/q' -e '/^committer /p'\n>>> +}\n>>> +\n>>>    message_body () {\n>>>        git cat-file commit \"$1\" |\n>>>        sed -e '1,/^$/d'\n>>> @@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n>>>        test_cmp expect actual\n>>>    '\n>>>\n>>> +test_expect_success '--committer option overrides committer' '\n>>> +     git checkout Initial &&\n>>> +     echo \"Test --committer\" >>foo &&\n>>> +     test_tick &&\n>>> +     git commit -a -m \"test committer\" --committer=\"Custom Committer <custom@committer.example>\" &&\n>>> +     committer_header HEAD >actual &&\n>>> +     grep \"Custom Committer <custom@committer.example>\" actual\n>>> +'\n>>> +\n>>> +test_expect_success '--committer with pattern search' '\n>>> +     echo \"Test committer pattern\" >>foo &&\n>>> +     test_tick &&\n>>> +     git commit -a -m \"test committer pattern\" --committer=\"Frigate\" &&\n>>> +     committer_header HEAD >actual &&\n>>> +     grep \"Frigate <flying@over.world>\" actual\n>>> +'\n>>> +\n>>> +test_expect_success '--committer malformed parameter' '\n>>> +     echo \"Test malformed\" >>foo &&\n>>> +     test_tick &&\n>>> +     test_must_fail git commit -a -m \"test malformed\" --committer=\"malformed committer\"\n>>> +'\n>>> +\n>>> +test_expect_success '--committer with --amend option' '\n>>> +     git checkout -f Initial &&\n>>> +     echo \"Test committer with amend\" >>foo &&\n>>> +     test_tick &&\n>>> +     git commit -a -m \"initial commit for amend test\" &&\n>>> +     echo \"Modified for amend\" >>foo &&\n>>> +     test_tick &&\n>>> +     git commit -a --amend --no-edit \\\n>>> +             --author=\"Test Author <test@author.example>\" \\\n>>> +             --committer=\"Test Committer <test@committer.example>\" &&\n>>> +     author_header HEAD >actual_author &&\n>>> +     grep \"Test Author <test@author.example>\" actual_author &&\n>>> +     committer_header HEAD >actual_committer &&\n>>> +     grep \"Test Committer <test@committer.example>\" actual_committer\n>>> +'\n>>> +\n>>> +test_expect_success 'GIT_COMMITTER_* environment variables' '\n>>> +     git checkout -f Initial &&\n>>> +     echo \"Test env vars\" >>foo &&\n>>> +     test_tick &&\n>>> +     GIT_COMMITTER_NAME=\"Env Committer\" \\\n>>> +     GIT_COMMITTER_EMAIL=\"env@test.example\" \\\n>>> +     git commit -a -m \"test committer env vars\" &&\n>>> +     committer_header HEAD >actual &&\n>>> +     grep \"Env Committer <env@test.example>\" actual\n>>> +'\n>>> +\n>>> +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n>>> +     echo \"Test override\" >>foo &&\n>>> +     test_tick &&\n>>> +     GIT_COMMITTER_NAME=\"Env Committer\" \\\n>>> +     GIT_COMMITTER_EMAIL=\"env@test.example\" \\\n>>> +     git commit -a -m \"test override\" \\\n>>> +             --committer=\"Override Committer <override@test.example>\" &&\n>>> +     committer_header HEAD >actual &&\n>>> +     grep \"Override Committer <override@test.example>\" actual\n>>> +'\n>>> +\n>>> +test_expect_success '--date with --committer changes both author and committer dates' '\n>>> +     git checkout -f Initial &&\n>>> +     echo \"Test date override\" >>foo &&\n>>> +     test_tick &&\n>>> +     git commit -a -m \"test date\" \\\n>>> +             --author=\"Date Author <date@author.example>\" \\\n>>> +             --committer=\"Date Committer <date@committer.example>\" \\\n>>> +             --date=\"2024-06-15 10:30:00 +0800\" &&\n>>> +     git log -1 --format=\"%ai\" >author_date &&\n>>> +     git log -1 --format=\"%ci\" >committer_date &&\n>>> +     grep \"2024-06-15 10:30:00 +0800\" author_date &&\n>>> +     grep \"2024-06-15 10:30:00 +0800\" committer_date\n>>> +'\n>>> +\n>>>    test_done\n>>>\n>>> base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84\n>>\n> \n\n"},{"id":"530517","messageId":"f7a9bf6d-d723-437f-badd-517fbf47d945@gmail.com","threadId":"64460","inReplyTo":"aRLdsu-MUgPPdIgX@pks.im","subject":"Re: [PATCH] commit: add --committer option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-11-11T14:53:03Z","receivedAt":"2025-11-11T14:53:07Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 11/11/2025 06:54, Patrick Steinhardt wrote:\n> On Mon, Nov 10, 2025 at 03:11:36PM -0500, Jeff King wrote:\n>> On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote:\n>>\n>>> On 2025-11-10 at 16:50:04, Phillip Wood wrote:\n>>>> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n>>>>> From: ZheNing Hu <adlternative@gmail.com>\n>>>>>\n>>>>>       This patch introduces the --committer option to git-commit, providing:\n>>>>>        1. Consistency with the existing --author option\n>>>>>        2. A more convenient alternative to environment variables\n>>>>>        3. Better support for automated workflows and scripts\n>>>>>        4. Improved user experience when managing multiple identities\n>>>>\n>>>> What's the use case for the same person committing under different\n>>>> identities? We already have a config mechanism to set different identities\n>>>> for different repositories but I'm struggling to see why someone would want\n>>>> to create commits under multiple identities in a single repository. For\n>>>> scripts it easy enough to set the relevant environment variables if a tool\n>>>> wants to create commits under its own identity.\n>>>\n>>> Someone who works on the same project under both their personal and\n>>> corporate identities.  For instance, me working on the Git project.\n>>>\n>>> Some open source projects also require a CLA and you have to use a\n>>> particular address to match the one that's listed on the CLA.  For\n>>> example, Google requires an address with a Google account, so in the\n>>> hypothetical state where I was going to contribute to one of their\n>>> projects, I'd need to use a different committer identity with my Gmail\n>>> address.\n>>>\n>>> I've also kept business logs in Git when I had a small business and I\n>>> might well need to log approving a profit distribution (with my\n>>> corporate address) and log accepting a profit distribution (with my\n>>> personal address).  Those would need separate digital signatures from my\n>>> two different email addresses.\n>>\n>> Is a \"--committer\" option the best solution there, though? I'd think\n>> you'd want to set user.* in the repo-level .git/config (or using a\n>> dir-specific include) would be less error-prone.\n>>\n>> That doesn't help for using two identities for the same repo, but in my\n>> experience it is easier to use two separate repositories for that to\n>> match the organization of the work (even if you may sometimes fetch\n>> between them).\n>>\n>> I'm not totally opposed to the new flag, and in general I'd defer to\n>> people who say they find a new feature useful. I'm just having a hard\n>> time imagining a scenario where it's the best option.\n\nYes, it strikes me as very inconvenient to have to specify \"--committer\" \neach time. I'd have though you'd either want to (i) set up an alias in \nwhich case you can start your alias with \"-c user.name=...\" or \n\"!GIT_COMMITTER_NAME=...\", or (ii) set GIT_COMMITTER_NAME in your shell.\n> The reason why I find it useful is mostly scripted uses. Sure, you can\n> already set environment variables there. But from my experience,\n> environment variables tend to be a significantly worse API compared to\n> command line options:\n> \n>    - They are harder to discover in the manual page.\n\nThey're documented in the COMMIT INFORMATION section of the \"git commit\" \nman page, admittedly that comes after the options and examples but \noverriding the committer is a fairly niche requirement.\n\n>    - You don't have any \"guarantees\" that Git actually interprets them,\n>      as there won't be an error if you mistype the name.\n\nPlaying devil's advocate even if you use \"--committer\" you still need to \ncheck the result to make sure there were no typo's in the committer info \njust as you would if you were setting GIT_COMMITTER_NAME.\n\n>    - Cause and effect may be detached with environment variables, but\n>      with command line options that's never the case.\n> \n> So I myself would prefer using \"--committer\" over its accompanying\n> environment variable any point in time when I have a scripted use case\n> for it.\n\nI'm wary of cluttering the UI of one of our core porcelain command with \noptions for use with scripting.\n\nThanks\n\nPhillip\n"},{"id":"530519","messageId":"xmqq7bvwy3vi.fsf@gitster.g","threadId":"64460","inReplyTo":"CAOLTT8SDpVfk8XqOJDL1=ASJwLf8EYtCo05NS3M2zZUx9JV6fA@mail.gmail.com","subject":"Re: [PATCH v2] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-11T15:40:17Z","receivedAt":"2025-11-11T15:40:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n> Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 03:22写道：\n>>\n>> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n>> > Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n>>\n>> What is this second author and how would its presence in the author\n>> list interact with your DCO obligation?\n>>\n>> How did you make sure that whatever is in this patch were not copied\n>> by the \"agent\" from somewhere that we cannot copy the code from\n>> before deciding to send this patch?  The \"cannot copy from\" may come\n>> in different shapes, from \"their code is proprietary\" to \"their\n>> licensing terms are not compatible with GPLv2\" to \"they welcome us\n>> borrowing but we must give credit to them\", any of which we should\n>> be careful to avoid.\n>>\n>>\n>\n> This was automatically added by some code assistance tools.\n> I indeed forgot to consider its impact on the open source license.\n> I'll remove it right away.\n\nPlease don't silently remove it without answering the question you\nwere asked.  \"The tool adds it but I disabled the agentic features\nof the tool and everything readers see in the submitted patch was\nwhat I typed, with no agent input\" would be a perfect answer.  \"I\ndid not consider the ramifications of use of the agentic tool, and I\ndo not know where the code the tool added for me came from, so I\ncannot be sure I can contribute this patch to the project\" would be\na sad but may be an honest answer.  \"I cannot tell the origin but I\ncan remove the line and claim I wrote everything myself\" is not an\nanswer that we want to see.  I am sure that your answer would fall\nwithin the spectrum, and am hoping it would be the earlier, perfect\none, or one close to it, but we need to hear it.\n\nThanks.\n"},{"id":"530525","messageId":"20251111191508.GA1907007@coredump.intra.peff.net","threadId":"64460","inReplyTo":"CAOLTT8RweGOmxNK=vKDv8w-8AJM7QUfLBw4WOKeY1EpSVeB6iQ@mail.gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-11-11T19:15:08Z","receivedAt":"2025-11-11T19:15:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Nov 11, 2025 at 09:42:38PM +0800, ZheNing Hu wrote:\n\n> > Is a \"--committer\" option the best solution there, though? I'd think\n> > you'd want to set user.* in the repo-level .git/config (or using a\n> > dir-specific include) would be less error-prone.\n> >\n> > That doesn't help for using two identities for the same repo, but in my\n> > experience it is easier to use two separate repositories for that to\n> > match the organization of the work (even if you may sometimes fetch\n> > between them).\n> >\n> > I'm not totally opposed to the new flag, and in general I'd defer to\n> > people who say they find a new feature useful. I'm just having a hard\n> > time imagining a scenario where it's the best option.\n> \n> Sometimes it's because I forgot to configure the repository-level git user\n> config and started development first. Only when I tried to correct the\n> committer did I feel the pain.\n\nOK, this workflow does make sense to me. Fixing up an earlier mistake is\ninherently a one-off thing, and a command-line option is more ergonomic\nthan using the environment variables.\n\nTwo small thoughts:\n\n  - I suspect what you'd usually want there is for the committer and the\n    author to match. We have --committer-date-is-author-date for rebase,\n    and conceptually I think something like --committer-is-author would\n    do what you want here. But obviously it's less flexible, and I don't\n    know if it's that much easier to use.\n\n  - Because it's easy to make such mistakes, when you override the\n    author (so that it doesn't match the committer), git-commit prints\n    an extra \"Author:\" line in the output to make that more obvious.\n    Should we do the same with committer when you've overridden it?  We\n    already do print \"Committer:\" when the ident was guessed from system\n    info, but I wonder if it would make sense to print when it was\n    forced. I dunno. I guess the time you most need the hint is when you\n    meant to use --committer and --author together, but only used\n    --author. But I don't know how Git would infer that case (versus the\n    normal case of you applying someone else's work and crediting them\n    with --author).\n\nI'm not sure if either is a useful direction, but they seemed\nsufficiently not-dumb for me to at least type them out. ;)\n\n-Peff\n"},{"id":"530541","messageId":"xmqqh5v0wcif.fsf@gitster.g","threadId":"64460","inReplyTo":"20251111191508.GA1907007@coredump.intra.peff.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-11T20:16:40Z","receivedAt":"2025-11-11T20:16:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n>> Sometimes it's because I forgot to configure the repository-level git user\n>> config and started development first. Only when I tried to correct the\n>> committer did I feel the pain.\n>\n> OK, this workflow does make sense to me. Fixing up an earlier mistake is\n> inherently a one-off thing, and a command-line option is more ergonomic\n> than using the environment variables.\n\nNot very much, at least to me.  Fixing up an earlier mistake may be\none-shot thing but it is to correct multiple commits in one go,\nwhich would be error prone if you do so with \"git commit --option\".\nEither \"fast-export | fast-import\" pipe, or \"git rebase\" (which this\npatch does not give --committer option, but it already knows how to\nhonor existing environment variables) would be used for that, no?\n\n> Two small thoughts:\n>\n>   - I suspect what you'd usually want there is for the committer and the\n>     author to match. We have --committer-date-is-author-date for rebase,\n>     and conceptually I think something like --committer-is-author would\n>     do what you want here. But obviously it's less flexible, and I don't\n>     know if it's that much easier to use.\n\nI am not sure how the user experience of this would look like.\n\n>   - Because it's easy to make such mistakes, when you override the\n>     author (so that it doesn't match the committer), git-commit prints\n>     an extra \"Author:\" line in the output to make that more obvious.\n>     Should we do the same with committer when you've overridden it?  We\n>     already do print \"Committer:\" when the ident was guessed from system\n>     info, but I wonder if it would make sense to print when it was\n>     forced. I dunno. I guess the time you most need the hint is when you\n>     meant to use --committer and --author together, but only used\n>     --author. But I don't know how Git would infer that case (versus the\n>     normal case of you applying someone else's work and crediting them\n>     with --author).\n\nYup, guessing based on what you did _not_ give is always a hard task\nfor any tool ;-).\n\n> I'm not sure if either is a useful direction, but they seemed\n> sufficiently not-dumb for me to at least type them out. ;)\n>\n> -Peff\n"},{"id":"530545","messageId":"20251111213339.GA4053071@coredump.intra.peff.net","threadId":"64460","inReplyTo":"xmqqh5v0wcif.fsf@gitster.g","subject":"Re: [PATCH] commit: add --committer option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-11-11T21:33:39Z","receivedAt":"2025-11-11T21:33:41Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Nov 11, 2025 at 12:16:40PM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> >> Sometimes it's because I forgot to configure the repository-level git user\n> >> config and started development first. Only when I tried to correct the\n> >> committer did I feel the pain.\n> >\n> > OK, this workflow does make sense to me. Fixing up an earlier mistake is\n> > inherently a one-off thing, and a command-line option is more ergonomic\n> > than using the environment variables.\n> \n> Not very much, at least to me.  Fixing up an earlier mistake may be\n> one-shot thing but it is to correct multiple commits in one go,\n> which would be error prone if you do so with \"git commit --option\".\n> Either \"fast-export | fast-import\" pipe, or \"git rebase\" (which this\n> patch does not give --committer option, but it already knows how to\n> honor existing environment variables) would be used for that, no?\n\nI usually lean on \"commit --amend\" for this, coupled with rebase if\nthere are multiple commits. So I've used:\n\n  git rebase -x \"git commit --no-edit --amend --author=...\"\n \nor similar when fixing up incorrect application of somebody else's\npatches (e.g., if I ended up using \"git apply\" and tweaking the commit\nmessage myself, rather than using \"git am\").\n\n> > Two small thoughts:\n> >\n> >   - I suspect what you'd usually want there is for the committer and the\n> >     author to match. We have --committer-date-is-author-date for rebase,\n> >     and conceptually I think something like --committer-is-author would\n> >     do what you want here. But obviously it's less flexible, and I don't\n> >     know if it's that much easier to use.\n> \n> I am not sure how the user experience of this would look like.\n\nI just mean being able to do:\n\n  git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author\n\ninstead of:\n\n  git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>'\n\n-Peff\n"},{"id":"530547","messageId":"xmqqwm3wut8i.fsf@gitster.g","threadId":"64460","inReplyTo":"20251111213339.GA4053071@coredump.intra.peff.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-11T21:58:21Z","receivedAt":"2025-11-11T21:58:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I just mean being able to do:\n>\n>   git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author\n>\n> instead of:\n>\n>   git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>'\n\nAh, I see.  Like\n\n    git -c user.name='Foo Bar' -c user.email=foo@example.com commit --amend\n\nMakes me wonder if we want user.ident that covers them both ;-)\n"},{"id":"530550","messageId":"20251111222335.GA4054883@coredump.intra.peff.net","threadId":"64460","inReplyTo":"xmqqwm3wut8i.fsf@gitster.g","subject":"Re: [PATCH] commit: add --committer option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2025-11-11T22:23:35Z","receivedAt":"2025-11-11T22:23:37Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Nov 11, 2025 at 01:58:21PM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > I just mean being able to do:\n> >\n> >   git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author\n> >\n> > instead of:\n> >\n> >   git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>'\n> \n> Ah, I see.  Like\n> \n>     git -c user.name='Foo Bar' -c user.email=foo@example.com commit --amend\n> \n> Makes me wonder if we want user.ident that covers them both ;-)\n\nHmm, I hadn't thought to use \"-c\" config for this. That makes me\nquestion the utility of --committer a little bit. ;) I guess it is\nslightly more convenient than \"-c\" in that it will trigger the\nfind_author_by_nickname() magic.\n\n-Peff\n"},{"id":"530590","messageId":"CAOLTT8TLEsSAcfUomoActrUSzje5mQNE7EweXh8WtY3sOfzfkg@mail.gmail.com","threadId":"64460","inReplyTo":"7d99f00c-3602-4b28-8efd-4780cad41ca8@gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-12T15:58:02Z","receivedAt":"2025-11-12T15:58:15Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道：\n>\n> On 11/11/2025 13:01, ZheNing Hu wrote:\n> > Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道：\n> >> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> >>> From: ZheNing Hu <adlternative@gmail.com>\n> >>>\n> >>>\n> >>>       This patch introduces the --committer option to git-commit, providing:\n> >>>\n> >>>        1. Consistency with the existing --author option\n> >>>        2. A more convenient alternative to environment variables\n> >>>        3. Better support for automated workflows and scripts\n> >>>        4. Improved user experience when managing multiple identities\n> >>\n> >> What's the use case for the same person committing under different\n> >> identities? We already have a config mechanism to set different\n> >> identities for different repositories but I'm struggling to see why\n> >> someone would want to create commits under multiple identities in a\n> >> single repository. For scripts it easy enough to set the relevant\n> >> environment variables if a tool wants to create commits under its own\n> >> identity.\n> >>\n> >\n> > I frequently need to distinguish between different user.name and user.email\n> > configurations on our company's internal GitHub.\n> >\n> > The current problems are:\n> >\n> > When I misconfigure (which happens occasionally), git commit --author only fixes\n> > the author part, I still need to additionally set GIT_COMMITTER_NAME and\n> > GIT_COMMITTER_EMAIL environment variables to fix the committer information\n> > These environment variables are painful to use, requiring manual setup\n> > every time\n>\n> I'm afraid I don't quite follow. If you are amending existing commits to\n> fix them up after you have corrected your configuration then they will\n> have the correct committer automatically when you run \"git commit\n> --amend --author=...\" to correct the author. If you are committing\n> before you have realized that user.{name,email} are misconfigured then I\n> don't see how \"--committer\" helps because you have not yet realized\n> anything is wrong.\n>\n\nYou're right that after realizing the misconfiguration and correcting the\nrepository's user.name and user.email, running `git commit --amend` will\n fix the committer information, but the author remains unchanged. Users\nthen need an additional `git commit --amend --author=...` to fix the author,\nwhich does work but requires an extra step.\n\nI see your point that this becomes more cumbersome when dealing with\nmultiple commits. In such cases, users currently need to use something like:\n\n```\nGIT_AUTHOR_NAME=\"...\" GIT_AUTHOR_EMAIL=\"...\" \\\nGIT_COMMITTER_NAME=\"...\" GIT_COMMITTER_EMAIL=\"...\" \\\ngit rebase -f <target>\n```\n\nThis is indeed tedious and error-prone, especially when you want\nto quickly fix and push commits to the platform.\n\n`git commit --amend --author --committer` or a new `git rebase\n--author --committer`\nwould provide a more user-friendly workflow for correcting identity\ninformation after misconfiguration, eliminating the need to manually\nset multiple\nenvironment variables or run multiple commands.\n\n> Thanks\n>\n> Phillip\n>\n\nThanks\n\nZheNing Hu\n\n> > If a --committer option could be provided to align with --author, users wouldn't\n> > need to remember and use these additional environment variables.\n> > This would greatly simplify the workflow and reduce cognitive overhead.\n> >\n> >\n> >> Thanks\n> >>\n> >> Phillip\n> >>\n> >>>       The implementation follows the same pattern as the --author option,\n> >>>       accepting the format \"Name \" and properly validating the input.\n> >>>\n> >>> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1\n> >>> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1\n> >>> Pull-Request: https://github.com/gitgitgadget/git/pull/1997\n> >>>\n> >>>    Documentation/git-commit.adoc |  9 +++-\n> >>>    builtin/commit.c              | 58 ++++++++++++++++++++++++-\n> >>>    t/t7509-commit-authorship.sh  | 80 +++++++++++++++++++++++++++++++++++\n> >>>    3 files changed, 144 insertions(+), 3 deletions(-)\n> >>>\n> >>> diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\n> >>> index 54c207ad45..a015c8328e 100644\n> >>> --- a/Documentation/git-commit.adoc\n> >>> +++ b/Documentation/git-commit.adoc\n> >>> @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n> >>>           [--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>...]\n> >>> @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.\n> >>>    `--date=<date>`::\n> >>>        Override the author date used in the commit.\n> >>>\n> >>> +`--committer=<committer>`::\n> >>> +     Override the committer for the commit. Specify an explicit committer using the\n> >>> +     standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_\n> >>> +     is assumed to be a pattern and is used to search for an existing\n> >>> +     commit by that author (i.e. `git rev-list --all -i --author=<committer>`);\n> >>> +     the commit author is then copied from the first such commit found.\n> >>> +\n> >>>    `-m <msg>`::\n> >>>    `--message=<msg>`::\n> >>>        Use _<msg>_ as the commit message.\n> >>> diff --git a/builtin/commit.c b/builtin/commit.c\n> >>> index 0243f17d53..88e77cbaab 100644\n> >>> --- a/builtin/commit.c\n> >>> +++ b/builtin/commit.c\n> >>> @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {\n> >>>           \"           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\\n\"\n> >>>           \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n> >>>           \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n> >>> -        \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n> >>> +        \"           [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\\n\"\n> >>>           \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n> >>>           \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n> >>>           \"           [--] [<pathspec>...]\"),\n> >>> @@ -112,6 +112,7 @@ static enum {\n> >>>    } commit_style;\n> >>>\n> >>>    static const char *force_author;\n> >>> +static const char *force_committer;\n> >>>    static char *logfile;\n> >>>    static char *template_file;\n> >>>    /*\n> >>> @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)\n> >>>        free(date);\n> >>>    }\n> >>>\n> >>> +static void determine_committer_info(struct strbuf *committer_ident)\n> >>> +{\n> >>> +     char *name, *email, *date;\n> >>> +     struct ident_split committer;\n> >>> +\n> >>> +     name = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n> >>> +     email = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n> >>> +     date = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n> >>> +\n> >>> +     if (force_committer) {\n> >>> +             struct ident_split ident;\n> >>> +\n> >>> +             if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)\n> >>> +                     die(_(\"malformed --committer parameter\"));\n> >>> +             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n> >>> +             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n> >>> +\n> >>> +             if (ident.date_begin) {\n> >>> +                     struct strbuf date_buf = STRBUF_INIT;\n> >>> +                     strbuf_addch(&date_buf, '@');\n> >>> +                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n> >>> +                     strbuf_addch(&date_buf, ' ');\n> >>> +                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n> >>> +                     set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> >>> +             }\n> >>> +     }\n> >>> +\n> >>> +     if (force_date) {\n> >>> +             struct strbuf date_buf = STRBUF_INIT;\n> >>> +             if (parse_force_date(force_date, &date_buf))\n> >>> +                     die(_(\"invalid date format: %s\"), force_date);\n> >>> +             set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> >>> +     }\n> >>> +\n> >>> +     strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,\n> >>> +                             IDENT_STRICT));\n> >>> +     assert_split_ident(&committer, committer_ident);\n> >>> +     free(name);\n> >>> +     free(email);\n> >>> +     free(date);\n> >>> +}\n> >>> +\n> >>>    static int author_date_is_interesting(void)\n> >>>    {\n> >>>        return author_message || force_date;\n> >>> @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n> >>>        if (force_author && renew_authorship)\n> >>>                die(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n> >>>\n> >>> +     if (force_committer && !strchr(force_committer, '>'))\n> >>> +             force_committer = find_author_by_nickname(force_committer);\n> >>> +\n> >>>        if (logfile || have_option_m || use_message)\n> >>>                use_editor = 0;\n> >>>\n> >>> @@ -1709,6 +1755,7 @@ int cmd_commit(int argc,\n> >>>                OPT_FILENAME('F', \"file\", &logfile, N_(\"read message from file\")),\n> >>>                OPT_STRING(0, \"author\", &force_author, N_(\"author\"), N_(\"override author for commit\")),\n> >>>                OPT_STRING(0, \"date\", &force_date, N_(\"date\"), N_(\"override date for commit\")),\n> >>> +             OPT_STRING(0, \"committer\", &force_committer, N_(\"committer\"), N_(\"override committer for commit\")),\n> >>>                OPT_CALLBACK('m', \"message\", &message, N_(\"message\"), N_(\"commit message\"), opt_parse_m),\n> >>>                OPT_STRING('c', \"reedit-message\", &edit_message, N_(\"commit\"), N_(\"reuse and edit message from specified commit\")),\n> >>>                OPT_STRING('C', \"reuse-message\", &use_message, N_(\"commit\"), N_(\"reuse message from specified commit\")),\n> >>> @@ -1785,6 +1832,7 @@ int cmd_commit(int argc,\n> >>>\n> >>>        struct strbuf sb = STRBUF_INIT;\n> >>>        struct strbuf author_ident = STRBUF_INIT;\n> >>> +     struct strbuf committer_ident = STRBUF_INIT;\n> >>>        const char *index_file, *reflog_msg;\n> >>>        struct object_id oid;\n> >>>        struct commit_list *parents = NULL;\n> >>> @@ -1930,8 +1978,13 @@ int cmd_commit(int argc,\n> >>>                append_merge_tag_headers(parents, &tail);\n> >>>        }\n> >>>\n> >>> +     if (force_committer) {\n> >>> +             determine_committer_info(&committer_ident);\n> >>> +     }\n> >>> +\n> >>>        if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n> >>> -                              parents, &oid, author_ident.buf, NULL,\n> >>> +                              parents, &oid, author_ident.buf,\n> >>> +                              force_committer ? committer_ident.buf : NULL,\n> >>>                                 sign_commit, extra)) {\n> >>>                rollback_index_files();\n> >>>                die(_(\"failed to write commit object\"));\n> >>> @@ -1980,6 +2033,7 @@ cleanup:\n> >>>        free_commit_extra_headers(extra);\n> >>>        free_commit_list(parents);\n> >>>        strbuf_release(&author_ident);\n> >>> +     strbuf_release(&committer_ident);\n> >>>        strbuf_release(&err);\n> >>>        strbuf_release(&sb);\n> >>>        free(logfile);\n> >>> diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh\n> >>> index 8e373b566b..45527f6a70 100755\n> >>> --- a/t/t7509-commit-authorship.sh\n> >>> +++ b/t/t7509-commit-authorship.sh\n> >>> @@ -12,6 +12,11 @@ author_header () {\n> >>>        sed -n -e '/^$/q' -e '/^author /p'\n> >>>    }\n> >>>\n> >>> +committer_header () {\n> >>> +     git cat-file commit \"$1\" |\n> >>> +     sed -n -e '/^$/q' -e '/^committer /p'\n> >>> +}\n> >>> +\n> >>>    message_body () {\n> >>>        git cat-file commit \"$1\" |\n> >>>        sed -e '1,/^$/d'\n> >>> @@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n> >>>        test_cmp expect actual\n> >>>    '\n> >>>\n> >>> +test_expect_success '--committer option overrides committer' '\n> >>> +     git checkout Initial &&\n> >>> +     echo \"Test --committer\" >>foo &&\n> >>> +     test_tick &&\n> >>> +     git commit -a -m \"test committer\" --committer=\"Custom Committer <custom@committer.example>\" &&\n> >>> +     committer_header HEAD >actual &&\n> >>> +     grep \"Custom Committer <custom@committer.example>\" actual\n> >>> +'\n> >>> +\n> >>> +test_expect_success '--committer with pattern search' '\n> >>> +     echo \"Test committer pattern\" >>foo &&\n> >>> +     test_tick &&\n> >>> +     git commit -a -m \"test committer pattern\" --committer=\"Frigate\" &&\n> >>> +     committer_header HEAD >actual &&\n> >>> +     grep \"Frigate <flying@over.world>\" actual\n> >>> +'\n> >>> +\n> >>> +test_expect_success '--committer malformed parameter' '\n> >>> +     echo \"Test malformed\" >>foo &&\n> >>> +     test_tick &&\n> >>> +     test_must_fail git commit -a -m \"test malformed\" --committer=\"malformed committer\"\n> >>> +'\n> >>> +\n> >>> +test_expect_success '--committer with --amend option' '\n> >>> +     git checkout -f Initial &&\n> >>> +     echo \"Test committer with amend\" >>foo &&\n> >>> +     test_tick &&\n> >>> +     git commit -a -m \"initial commit for amend test\" &&\n> >>> +     echo \"Modified for amend\" >>foo &&\n> >>> +     test_tick &&\n> >>> +     git commit -a --amend --no-edit \\\n> >>> +             --author=\"Test Author <test@author.example>\" \\\n> >>> +             --committer=\"Test Committer <test@committer.example>\" &&\n> >>> +     author_header HEAD >actual_author &&\n> >>> +     grep \"Test Author <test@author.example>\" actual_author &&\n> >>> +     committer_header HEAD >actual_committer &&\n> >>> +     grep \"Test Committer <test@committer.example>\" actual_committer\n> >>> +'\n> >>> +\n> >>> +test_expect_success 'GIT_COMMITTER_* environment variables' '\n> >>> +     git checkout -f Initial &&\n> >>> +     echo \"Test env vars\" >>foo &&\n> >>> +     test_tick &&\n> >>> +     GIT_COMMITTER_NAME=\"Env Committer\" \\\n> >>> +     GIT_COMMITTER_EMAIL=\"env@test.example\" \\\n> >>> +     git commit -a -m \"test committer env vars\" &&\n> >>> +     committer_header HEAD >actual &&\n> >>> +     grep \"Env Committer <env@test.example>\" actual\n> >>> +'\n> >>> +\n> >>> +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n> >>> +     echo \"Test override\" >>foo &&\n> >>> +     test_tick &&\n> >>> +     GIT_COMMITTER_NAME=\"Env Committer\" \\\n> >>> +     GIT_COMMITTER_EMAIL=\"env@test.example\" \\\n> >>> +     git commit -a -m \"test override\" \\\n> >>> +             --committer=\"Override Committer <override@test.example>\" &&\n> >>> +     committer_header HEAD >actual &&\n> >>> +     grep \"Override Committer <override@test.example>\" actual\n> >>> +'\n> >>> +\n> >>> +test_expect_success '--date with --committer changes both author and committer dates' '\n> >>> +     git checkout -f Initial &&\n> >>> +     echo \"Test date override\" >>foo &&\n> >>> +     test_tick &&\n> >>> +     git commit -a -m \"test date\" \\\n> >>> +             --author=\"Date Author <date@author.example>\" \\\n> >>> +             --committer=\"Date Committer <date@committer.example>\" \\\n> >>> +             --date=\"2024-06-15 10:30:00 +0800\" &&\n> >>> +     git log -1 --format=\"%ai\" >author_date &&\n> >>> +     git log -1 --format=\"%ci\" >committer_date &&\n> >>> +     grep \"2024-06-15 10:30:00 +0800\" author_date &&\n> >>> +     grep \"2024-06-15 10:30:00 +0800\" committer_date\n> >>> +'\n> >>> +\n> >>>    test_done\n> >>>\n> >>> base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84\n> >>\n> >\n>\n"},{"id":"530591","messageId":"CAOLTT8Q_ajnCdnbLSJM_VQLpsHQPNBr_qh6L-X4KFyRxaXcJdg@mail.gmail.com","threadId":"64460","inReplyTo":"f7a9bf6d-d723-437f-badd-517fbf47d945@gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-12T16:11:33Z","receivedAt":"2025-11-12T16:11:46Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:53写道：\n>\n> On 11/11/2025 06:54, Patrick Steinhardt wrote:\n> > On Mon, Nov 10, 2025 at 03:11:36PM -0500, Jeff King wrote:\n> >> On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote:\n> >>\n> >>> On 2025-11-10 at 16:50:04, Phillip Wood wrote:\n> >>>> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> >>>>> From: ZheNing Hu <adlternative@gmail.com>\n> >>>>>\n> >>>>>       This patch introduces the --committer option to git-commit, providing:\n> >>>>>        1. Consistency with the existing --author option\n> >>>>>        2. A more convenient alternative to environment variables\n> >>>>>        3. Better support for automated workflows and scripts\n> >>>>>        4. Improved user experience when managing multiple identities\n> >>>>\n> >>>> What's the use case for the same person committing under different\n> >>>> identities? We already have a config mechanism to set different identities\n> >>>> for different repositories but I'm struggling to see why someone would want\n> >>>> to create commits under multiple identities in a single repository. For\n> >>>> scripts it easy enough to set the relevant environment variables if a tool\n> >>>> wants to create commits under its own identity.\n> >>>\n> >>> Someone who works on the same project under both their personal and\n> >>> corporate identities.  For instance, me working on the Git project.\n> >>>\n> >>> Some open source projects also require a CLA and you have to use a\n> >>> particular address to match the one that's listed on the CLA.  For\n> >>> example, Google requires an address with a Google account, so in the\n> >>> hypothetical state where I was going to contribute to one of their\n> >>> projects, I'd need to use a different committer identity with my Gmail\n> >>> address.\n> >>>\n> >>> I've also kept business logs in Git when I had a small business and I\n> >>> might well need to log approving a profit distribution (with my\n> >>> corporate address) and log accepting a profit distribution (with my\n> >>> personal address).  Those would need separate digital signatures from my\n> >>> two different email addresses.\n> >>\n> >> Is a \"--committer\" option the best solution there, though? I'd think\n> >> you'd want to set user.* in the repo-level .git/config (or using a\n> >> dir-specific include) would be less error-prone.\n> >>\n> >> That doesn't help for using two identities for the same repo, but in my\n> >> experience it is easier to use two separate repositories for that to\n> >> match the organization of the work (even if you may sometimes fetch\n> >> between them).\n> >>\n> >> I'm not totally opposed to the new flag, and in general I'd defer to\n> >> people who say they find a new feature useful. I'm just having a hard\n> >> time imagining a scenario where it's the best option.\n>\n> Yes, it strikes me as very inconvenient to have to specify \"--committer\"\n> each time. I'd have though you'd either want to (i) set up an alias in\n> which case you can start your alias with \"-c user.name=...\" or\n> \"!GIT_COMMITTER_NAME=...\", or (ii) set GIT_COMMITTER_NAME in your shell.\n\nSince modifying the committer is a low-frequency operation, there's\nno real need to specifically configure an alias for it.\n\n> > The reason why I find it useful is mostly scripted uses. Sure, you can\n> > already set environment variables there. But from my experience,\n> > environment variables tend to be a significantly worse API compared to\n> > command line options:\n> >\n> >    - They are harder to discover in the manual page.\n>\n> They're documented in the COMMIT INFORMATION section of the \"git commit\"\n> man page, admittedly that comes after the options and examples but\n> overriding the committer is a fairly niche requirement.\n>\n\nAlthough it's niche or infrequent, when configuration errors do occur,\nthe current fix process is quite painful for users. Previously, countless\nusers have gone through great trouble seeking solutions because they\ndidn't know how to resolve incorrect user.name and email configurations,\nwhich prevented them from pushing code to the company's servers.\n\nThis may also suggest that Git lacks simpler ways to fix their identity.\nIn fact, many users end up needing to use more complex tools like git\nfilter-repo to attempt fixing their commits, whereas git commit --committer\nand --author could become a relatively simple and memorable fix method.\n\n> >    - You don't have any \"guarantees\" that Git actually interprets them,\n> >      as there won't be an error if you mistype the name.\n>\n> Playing devil's advocate even if you use \"--committer\" you still need to\n> check the result to make sure there were no typo's in the committer info\n> just as you would if you were setting GIT_COMMITTER_NAME.\n>\n\nGIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL are split\ninto two parts. Sometimes I even forget whether GIT_COMMITTER_EMAIL\nshould be \"<email.example.com>\" or \"email.example.com\", whereas\n--author or --committer provides a more compact and memorable\n\"user <email.example.com>\" pair.\n\n> >    - Cause and effect may be detached with environment variables, but\n> >      with command line options that's never the case.\n> >\n> > So I myself would prefer using \"--committer\" over its accompanying\n> > environment variable any point in time when I have a scripted use case\n> > for it.\n>\n> I'm wary of cluttering the UI of one of our core porcelain command with\n> options for use with scripting.\n>\n> Thanks\n>\n> Phillip\n\nThanks\n\nZheNing Hu\n"},{"id":"530593","messageId":"CAOLTT8SN1xHvCCCo7b_RqMqQ9TGZR5=0SJnoe0uQ-Zv4-4V=1g@mail.gmail.com","threadId":"64460","inReplyTo":"xmqq7bvwy3vi.fsf@gitster.g","subject":"Re: [PATCH v2] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-12T16:23:47Z","receivedAt":"2025-11-12T16:24:00Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 23:40写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> > Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 03:22写道：\n> >>\n> >> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> >>\n> >> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n> >> > Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n> >>\n> >> What is this second author and how would its presence in the author\n> >> list interact with your DCO obligation?\n> >>\n> >> How did you make sure that whatever is in this patch were not copied\n> >> by the \"agent\" from somewhere that we cannot copy the code from\n> >> before deciding to send this patch?  The \"cannot copy from\" may come\n> >> in different shapes, from \"their code is proprietary\" to \"their\n> >> licensing terms are not compatible with GPLv2\" to \"they welcome us\n> >> borrowing but we must give credit to them\", any of which we should\n> >> be careful to avoid.\n> >>\n> >>\n> >\n> > This was automatically added by some code assistance tools.\n> > I indeed forgot to consider its impact on the open source license.\n> > I'll remove it right away.\n>\n> Please don't silently remove it without answering the question you\n> were asked.  \"The tool adds it but I disabled the agentic features\n> of the tool and everything readers see in the submitted patch was\n> what I typed, with no agent input\" would be a perfect answer.  \"I\n> did not consider the ramifications of use of the agentic tool, and I\n> do not know where the code the tool added for me came from, so I\n> cannot be sure I can contribute this patch to the project\" would be\n> a sad but may be an honest answer.  \"I cannot tell the origin but I\n> can remove the line and claim I wrote everything myself\" is not an\n> answer that we want to see.  I am sure that your answer would fall\n> within the spectrum, and am hoping it would be the earlier, perfect\n> one, or one close to it, but we need to hear it.\n>\n\nHa, I think my situation is closer to the second case, but I'm certain\nthat identical code cannot be found on GitHub or any other platform.\nMoreover, this code is entirely modeled after Git's own code\n(e.g., determine_identity is based on determine_author_info). There's\nno possibility of copying code from other projects with licenses\nincompatible with Git. However, your reminder is valid, especially\nin this era where AI Agents are heavily infiltrating development.\nAny similar open-source contributions in the future will need to\npay attention to this point.\n\n> Thanks.\n"},{"id":"530596","messageId":"CAOLTT8S4D5M8uzzx75p_kAr5wsW3JyhVEQg=FW4ww+aYfTnk0g@mail.gmail.com","threadId":"64460","inReplyTo":"20251111191508.GA1907007@coredump.intra.peff.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-12T16:37:37Z","receivedAt":"2025-11-12T16:37:51Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Jeff King <peff@peff.net> 于2025年11月12日周三 03:15写道：\n>\n> On Tue, Nov 11, 2025 at 09:42:38PM +0800, ZheNing Hu wrote:\n>\n> > > Is a \"--committer\" option the best solution there, though? I'd think\n> > > you'd want to set user.* in the repo-level .git/config (or using a\n> > > dir-specific include) would be less error-prone.\n> > >\n> > > That doesn't help for using two identities for the same repo, but in my\n> > > experience it is easier to use two separate repositories for that to\n> > > match the organization of the work (even if you may sometimes fetch\n> > > between them).\n> > >\n> > > I'm not totally opposed to the new flag, and in general I'd defer to\n> > > people who say they find a new feature useful. I'm just having a hard\n> > > time imagining a scenario where it's the best option.\n> >\n> > Sometimes it's because I forgot to configure the repository-level git user\n> > config and started development first. Only when I tried to correct the\n> > committer did I feel the pain.\n>\n> OK, this workflow does make sense to me. Fixing up an earlier mistake is\n> inherently a one-off thing, and a command-line option is more ergonomic\n> than using the environment variables.\n>\n> Two small thoughts:\n>\n>   - I suspect what you'd usually want there is for the committer and the\n>     author to match. We have --committer-date-is-author-date for rebase,\n>     and conceptually I think something like --committer-is-author would\n>     do what you want here. But obviously it's less flexible, and I don't\n>     know if it's that much easier to use.\n>\n\nWell, to be honest, I don't really like this kind of -A --BaseA option style.\nOn the contrary, -A -B is actually simple and easy enough for me\n(or more symmetrical). However, it's true that git rebase probably\ndoesn't have --author and --committer options. Perhaps this is also\na pain point, or maybe something to look into for potential future\ncontributions.\n\n>   - Because it's easy to make such mistakes, when you override the\n>     author (so that it doesn't match the committer), git-commit prints\n>     an extra \"Author:\" line in the output to make that more obvious.\n>     Should we do the same with committer when you've overridden it?  We\n>     already do print \"Committer:\" when the ident was guessed from system\n>     info, but I wonder if it would make sense to print when it was\n>     forced. I dunno. I guess the time you most need the hint is when you\n>     meant to use --committer and --author together, but only used\n>     --author. But I don't know how Git would infer that case (versus the\n>     normal case of you applying someone else's work and crediting them\n>     with --author).\n>\n\nAh, you have a point. Perhaps the Commit should also be output,\nthough it might be trivial (if you hadn't reminded me, I wouldn't have even\nnoticed that git commit outputs Author when using --author).\n\n> I'm not sure if either is a useful direction, but they seemed\n> sufficiently not-dumb for me to at least type them out. ;)\n>\n> -Peff\n"},{"id":"530597","messageId":"CAOLTT8TYuiLRADQyZ3k0Mi_uGESiGRdNB0qH=EFJPYknJJKTag@mail.gmail.com","threadId":"64460","inReplyTo":"xmqqh5v0wcif.fsf@gitster.g","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-12T16:41:37Z","receivedAt":"2025-11-12T16:41:52Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2025年11月12日周三 04:16写道：\n>\n> Jeff King <peff@peff.net> writes:\n>\n> >> Sometimes it's because I forgot to configure the repository-level git user\n> >> config and started development first. Only when I tried to correct the\n> >> committer did I feel the pain.\n> >\n> > OK, this workflow does make sense to me. Fixing up an earlier mistake is\n> > inherently a one-off thing, and a command-line option is more ergonomic\n> > than using the environment variables.\n>\n> Not very much, at least to me.  Fixing up an earlier mistake may be\n> one-shot thing but it is to correct multiple commits in one go,\n> which would be error prone if you do so with \"git commit --option\".\n> Either \"fast-export | fast-import\" pipe, or \"git rebase\" (which this\n> patch does not give --committer option, but it already knows how to\n> honor existing environment variables) would be used for that, no?\n>\n\nHa, perhaps this should be left to git rebase --committer --author\nin the future. GIT_AUTHOR_* and GIT_COMMITTER_* are indeed\na bit cumbersome to use.\n\n> > Two small thoughts:\n> >\n> >   - I suspect what you'd usually want there is for the committer and the\n> >     author to match. We have --committer-date-is-author-date for rebase,\n> >     and conceptually I think something like --committer-is-author would\n> >     do what you want here. But obviously it's less flexible, and I don't\n> >     know if it's that much easier to use.\n>\n> I am not sure how the user experience of this would look like.\n>\n> >   - Because it's easy to make such mistakes, when you override the\n> >     author (so that it doesn't match the committer), git-commit prints\n> >     an extra \"Author:\" line in the output to make that more obvious.\n> >     Should we do the same with committer when you've overridden it?  We\n> >     already do print \"Committer:\" when the ident was guessed from system\n> >     info, but I wonder if it would make sense to print when it was\n> >     forced. I dunno. I guess the time you most need the hint is when you\n> >     meant to use --committer and --author together, but only used\n> >     --author. But I don't know how Git would infer that case (versus the\n> >     normal case of you applying someone else's work and crediting them\n> >     with --author).\n>\n> Yup, guessing based on what you did _not_ give is always a hard task\n> for any tool ;-).\n>\n> > I'm not sure if either is a useful direction, but they seemed\n> > sufficiently not-dumb for me to at least type them out. ;)\n> >\n> > -Peff\n"},{"id":"530598","messageId":"CAOLTT8SZXooypwP27BFXaQdETTPFqOVxGtNtO6j=p7WgaBeLgQ@mail.gmail.com","threadId":"64460","inReplyTo":"20251111213339.GA4053071@coredump.intra.peff.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-12T16:46:10Z","receivedAt":"2025-11-12T16:46:24Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Jeff King <peff@peff.net> 于2025年11月12日周三 05:33写道：\n>\n> On Tue, Nov 11, 2025 at 12:16:40PM -0800, Junio C Hamano wrote:\n>\n> > Jeff King <peff@peff.net> writes:\n> >\n> > >> Sometimes it's because I forgot to configure the repository-level git user\n> > >> config and started development first. Only when I tried to correct the\n> > >> committer did I feel the pain.\n> > >\n> > > OK, this workflow does make sense to me. Fixing up an earlier mistake is\n> > > inherently a one-off thing, and a command-line option is more ergonomic\n> > > than using the environment variables.\n> >\n> > Not very much, at least to me.  Fixing up an earlier mistake may be\n> > one-shot thing but it is to correct multiple commits in one go,\n> > which would be error prone if you do so with \"git commit --option\".\n> > Either \"fast-export | fast-import\" pipe, or \"git rebase\" (which this\n> > patch does not give --committer option, but it already knows how to\n> > honor existing environment variables) would be used for that, no?\n>\n> I usually lean on \"commit --amend\" for this, coupled with rebase if\n> there are multiple commits. So I've used:\n>\n>   git rebase -x \"git commit --no-edit --amend --author=...\"\n>\n> or similar when fixing up incorrect application of somebody else's\n> patches (e.g., if I ended up using \"git apply\" and tweaking the commit\n> message myself, rather than using \"git am\").\n>\n> > > Two small thoughts:\n> > >\n> > >   - I suspect what you'd usually want there is for the committer and the\n> > >     author to match. We have --committer-date-is-author-date for rebase,\n> > >     and conceptually I think something like --committer-is-author would\n> > >     do what you want here. But obviously it's less flexible, and I don't\n> > >     know if it's that much easier to use.\n> >\n> > I am not sure how the user experience of this would look like.\n>\n> I just mean being able to do:\n>\n>   git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author\n>\n> instead of:\n>\n>   git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>'\n>\n\nIf I wanted an elegant parameter myself, it would definitely be\n`git commit --amend --user='Foo Bar <foo@example.com>'`, instead of `-A -B-is-A`\n\n> -Peff\n"},{"id":"530600","messageId":"CAOLTT8QH_+4R4cqQf91giPxOE9H67bWZfcjA7sTeMbn5p1PnSg@mail.gmail.com","threadId":"64460","inReplyTo":"xmqqwm3wut8i.fsf@gitster.g","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-12T16:48:32Z","receivedAt":"2025-11-12T16:48:46Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2025年11月12日周三 05:58写道：\n>\n> Jeff King <peff@peff.net> writes:\n>\n> > I just mean being able to do:\n> >\n> >   git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author\n> >\n> > instead of:\n> >\n> >   git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>'\n>\n> Ah, I see.  Like\n>\n>     git -c user.name='Foo Bar' -c user.email=foo@example.com commit --amend\n>\n\nYou still need:\n\ngit -c user.name ='Foo Bar' -c user.email=foo@example.com commit\n--amend --author\n\nIt's hard to remember and not symmetric enough.\n\n> Makes me wonder if we want user.ident that covers them both ;-)\n"},{"id":"530603","messageId":"CAOLTT8RyH7nJzzeva6Kjn+mvCjGF3mCSLTLcUH-yeEH0+3LL0Q@mail.gmail.com","threadId":"64460","inReplyTo":"20251111222335.GA4054883@coredump.intra.peff.net","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-12T16:51:58Z","receivedAt":"2025-11-12T16:52:12Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Jeff King <peff@peff.net> 于2025年11月12日周三 06:23写道：\n>\n> On Tue, Nov 11, 2025 at 01:58:21PM -0800, Junio C Hamano wrote:\n>\n> > Jeff King <peff@peff.net> writes:\n> >\n> > > I just mean being able to do:\n> > >\n> > >   git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author\n> > >\n> > > instead of:\n> > >\n> > >   git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>'\n> >\n> > Ah, I see.  Like\n> >\n> >     git -c user.name='Foo Bar' -c user.email=foo@example.com commit --amend\n> >\n> > Makes me wonder if we want user.ident that covers them both ;-)\n>\n> Hmm, I hadn't thought to use \"-c\" config for this. That makes me\n> question the utility of --committer a little bit. ;) I guess it is\n> slightly more convenient than \"-c\" in that it will trigger the\n> find_author_by_nickname() magic.\n>\n\nThe advantages of --committer over -c user.name -c user.email:\n\n1. It's symmetric with --author\n2. A single option is sufficient\n3. As you mentioned, it can search for the committer in commit\nhistory (although that wasn't my original intention)\n\n> -Peff\n"},{"id":"530604","messageId":"pull.1997.v3.git.1762966535495.gitgitgadget@gmail.com","threadId":"64460","inReplyTo":"pull.1997.v2.git.1762793782815.gitgitgadget@gmail.com","subject":"[PATCH v3] commit: add --committer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-11-12T16:55:35Z","receivedAt":"2025-11-12T16:55:38Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nAdd --committer option to git-commit, allowing users to override the\ncommitter identity similar to how --author works. This provides a more\nconvenient alternative to setting GIT_COMMITTER_* environment variables.\n\nLike --author, the --committer option supports two formats:\n- Explicit identity: --committer=\"Name <email@example.com>\"\n- Pattern search: --committer=\"pattern\" searches commit history for a\n  matching committer and reuses that identity\n\nTo share code with the existing --author option, this patch refactors:\n\n1. find_author_by_nickname() into find_identity_by_nickname() which\n   handles both author and committer searches through an is_author\n   parameter.\n\n2. determine_author_info() into determine_identity() which handles\n   identity parsing and setting for both author and committer through\n   an is_author parameter.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    commit: add --committer option\n    \n    Currently, when users need to override the committer identity in\n    git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL\n    environment variables, which can be cumbersome in scripting scenarios or\n    when frequently switching committer identities.\n    \n    While git-commit already provides the --author option to conveniently\n    override the author identity, there's no equivalent --committer option\n    for the committer identity. This asymmetry creates an inconsistent user\n    experience.\n    \n    This patch introduces the --committer option to git-commit, providing:\n    \n     1. Consistency with the existing --author option\n     2. A more convenient alternative to environment variables\n     3. Better support for automated workflows and scripts\n     4. Improved user experience when managing multiple identities\n    \n    The implementation follows the same pattern as the --author option,\n    accepting the format \"Name \" and properly validating the input.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1997\n\nRange-diff vs v2:\n\n 1:  58e9e5c9d7 ! 1:  acf724fad5 commit: add --committer option\n     @@ Commit message\n             an is_author parameter.\n      \n          Signed-off-by: ZheNing Hu <adlternative@gmail.com>\n     -    Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>\n      \n       ## Documentation/git-commit.adoc ##\n      @@ Documentation/git-commit.adoc: git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n\n\n Documentation/git-commit.adoc |   9 ++-\n builtin/commit.c              | 121 +++++++++++++++++++++++++---------\n t/t7509-commit-authorship.sh  |  84 ++++++++++++++++++++++-\n 3 files changed, 180 insertions(+), 34 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 54c207ad45..ed4c54ae81 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n \t   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n \t   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n-\t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n+\t   [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n \t   [--] [<pathspec>...]\n@@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details.\n \tcommit by that author (i.e. `git rev-list --all -i --author=<author>`);\n \tthe commit author is then copied from the first such commit found.\n \n+`--committer=<committer>`::\n+\tOverride the committer for the commit. Specify an explicit committer using the\n+\tstandard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_\n+\tis assumed to be a pattern and is used to search for an existing\n+\tcommit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);\n+\tthe commit committer is then copied from the first such commit found.\n+\n `--date=<date>`::\n \tOverride the author date used in the commit.\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 0243f17d53..3b249dd878 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {\n \t   \"           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\\n\"\n \t   \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n \t   \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n-\t   \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n+\t   \"           [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n \t   \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n \t   \"           [--] [<pathspec>...]\"),\n@@ -112,6 +112,7 @@ static enum {\n } commit_style;\n \n static const char *force_author;\n+static const char *force_committer;\n static char *logfile;\n static char *template_file;\n /*\n@@ -630,46 +631,61 @@ static void set_ident_var(char **buf, char *val)\n \t*buf = val;\n }\n \n-static void determine_author_info(struct strbuf *author_ident)\n+static void determine_identity(struct strbuf *ident_str, int is_author)\n {\n \tchar *name, *email, *date;\n-\tstruct ident_split author;\n-\n-\tname = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n-\temail = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n-\tdate = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n-\n-\tif (author_message) {\n-\t\tstruct ident_split ident;\n+\tstruct ident_split ident;\n+\tconst char *env_name = is_author ? \"GIT_AUTHOR_NAME\" : \"GIT_COMMITTER_NAME\";\n+\tconst char *env_email = is_author ? \"GIT_AUTHOR_EMAIL\" : \"GIT_COMMITTER_EMAIL\";\n+\tconst char *env_date = is_author ? \"GIT_AUTHOR_DATE\" : \"GIT_COMMITTER_DATE\";\n+\tconst char *force_ident = is_author ? force_author : force_committer;\n+\tconst char *param_name = is_author ? \"--author\" : \"--committer\";\n+\tint ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;\n+\n+\tname = xstrdup_or_null(getenv(env_name));\n+\temail = xstrdup_or_null(getenv(env_email));\n+\tdate = xstrdup_or_null(getenv(env_date));\n+\n+\tif (is_author && author_message) {\n+\t\tstruct ident_split msg_ident;\n \t\tsize_t len;\n \t\tconst char *a;\n \n \t\ta = find_commit_header(author_message_buffer, \"author\", &len);\n \t\tif (!a)\n \t\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n-\t\tif (split_ident_line(&ident, a, len) < 0)\n+\t\tif (split_ident_line(&msg_ident, a, len) < 0)\n \t\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n \n-\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n-\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n+\t\tset_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));\n+\t\tset_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));\n \n-\t\tif (ident.date_begin) {\n+\t\tif (msg_ident.date_begin) {\n \t\t\tstruct strbuf date_buf = STRBUF_INIT;\n \t\t\tstrbuf_addch(&date_buf, '@');\n-\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n+\t\t\tstrbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);\n \t\t\tstrbuf_addch(&date_buf, ' ');\n-\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n+\t\t\tstrbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);\n \t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n \t\t}\n \t}\n \n-\tif (force_author) {\n-\t\tstruct ident_split ident;\n+\tif (force_ident) {\n+\t\tstruct ident_split force_ident_split;\n+\n+\t\tif (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)\n+\t\t\tdie(_(\"malformed %s parameter\"), param_name);\n+\t\tset_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));\n+\t\tset_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));\n \n-\t\tif (split_ident_line(&ident, force_author, strlen(force_author)) < 0)\n-\t\t\tdie(_(\"malformed --author parameter\"));\n-\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n-\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n+\t\tif (!is_author && force_ident_split.date_begin) {\n+\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n+\t\t\tstrbuf_addch(&date_buf, '@');\n+\t\t\tstrbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin);\n+\t\t\tstrbuf_addch(&date_buf, ' ');\n+\t\t\tstrbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin);\n+\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n+\t\t}\n \t}\n \n \tif (force_date) {\n@@ -679,17 +695,35 @@ static void determine_author_info(struct strbuf *author_ident)\n \t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n \t}\n \n-\tstrbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,\n+\tstrbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date,\n \t\t\t\tIDENT_STRICT));\n-\tassert_split_ident(&author, author_ident);\n-\texport_one(\"GIT_AUTHOR_NAME\", author.name_begin, author.name_end, 0);\n-\texport_one(\"GIT_AUTHOR_EMAIL\", author.mail_begin, author.mail_end, 0);\n-\texport_one(\"GIT_AUTHOR_DATE\", author.date_begin, author.tz_end, '@');\n+\tassert_split_ident(&ident, ident_str);\n+\n+\tif (is_author) {\n+\t\texport_one(\"GIT_AUTHOR_NAME\", ident.name_begin, ident.name_end, 0);\n+\t\texport_one(\"GIT_AUTHOR_EMAIL\", ident.mail_begin, ident.mail_end, 0);\n+\t\texport_one(\"GIT_AUTHOR_DATE\", ident.date_begin, ident.tz_end, '@');\n+\t} else {\n+\t\texport_one(\"GIT_COMMITTER_NAME\", ident.name_begin, ident.name_end, 0);\n+\t\texport_one(\"GIT_COMMITTER_EMAIL\", ident.mail_begin, ident.mail_end, 0);\n+\t\texport_one(\"GIT_COMMITTER_DATE\", ident.date_begin, ident.tz_end, '@');\n+\t}\n+\n \tfree(name);\n \tfree(email);\n \tfree(date);\n }\n \n+static void determine_author_info(struct strbuf *author_ident)\n+{\n+\tdetermine_identity(author_ident, 1);\n+}\n+\n+static void determine_committer_info(struct strbuf *committer_ident)\n+{\n+\tdetermine_identity(committer_ident, 0);\n+}\n+\n static int author_date_is_interesting(void)\n {\n \treturn author_message || force_date;\n@@ -1137,16 +1171,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \treturn 1;\n }\n \n-static const char *find_author_by_nickname(const char *name)\n+static const char *find_identity_by_nickname(const char *name, int is_author)\n {\n \tstruct rev_info revs;\n \tstruct commit *commit;\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *av[20];\n \tint ac = 0;\n+\tconst char *field = is_author ? \"author\" : \"committer\";\n+\tconst char *format = is_author ? \"%aN <%aE>\" : \"%cN <%cE>\";\n \n \trepo_init_revisions(the_repository, &revs, NULL);\n-\tstrbuf_addf(&buf, \"--author=%s\", name);\n+\tstrbuf_addf(&buf, \"--%s=%s\", field, name);\n \tav[++ac] = \"--all\";\n \tav[++ac] = \"-i\";\n \tav[++ac] = buf.buf;\n@@ -1164,11 +1200,22 @@ static const char *find_author_by_nickname(const char *name)\n \t\tctx.date_mode.type = DATE_NORMAL;\n \t\tstrbuf_release(&buf);\n \t\trepo_format_commit_message(the_repository, commit,\n-\t\t\t\t\t   \"%aN <%aE>\", &buf, &ctx);\n+\t\t\t\t\t   format, &buf, &ctx);\n \t\trelease_revisions(&revs);\n \t\treturn strbuf_detach(&buf, NULL);\n \t}\n-\tdie(_(\"--author '%s' is not 'Name <email>' and matches no existing author\"), name);\n+\tdie(_(\"--%s '%s' is not 'Name <email>' and matches no existing %s\"),\n+\t    field, name, field);\n+}\n+\n+static const char *find_author_by_nickname(const char *name)\n+{\n+\treturn find_identity_by_nickname(name, 1);\n+}\n+\n+static const char *find_committer_by_nickname(const char *name)\n+{\n+\treturn find_identity_by_nickname(name, 0);\n }\n \n static void handle_ignored_arg(struct wt_status *s)\n@@ -1321,6 +1368,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \tif (force_author && renew_authorship)\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n \n+\tif (force_committer && !strchr(force_committer, '>'))\n+\t\tforce_committer = find_committer_by_nickname(force_committer);\n+\n \tif (logfile || have_option_m || use_message)\n \t\tuse_editor = 0;\n \n@@ -1709,6 +1759,7 @@ int cmd_commit(int argc,\n \t\tOPT_FILENAME('F', \"file\", &logfile, N_(\"read message from file\")),\n \t\tOPT_STRING(0, \"author\", &force_author, N_(\"author\"), N_(\"override author for commit\")),\n \t\tOPT_STRING(0, \"date\", &force_date, N_(\"date\"), N_(\"override date for commit\")),\n+\t\tOPT_STRING(0, \"committer\", &force_committer, N_(\"committer\"), N_(\"override committer for commit\")),\n \t\tOPT_CALLBACK('m', \"message\", &message, N_(\"message\"), N_(\"commit message\"), opt_parse_m),\n \t\tOPT_STRING('c', \"reedit-message\", &edit_message, N_(\"commit\"), N_(\"reuse and edit message from specified commit\")),\n \t\tOPT_STRING('C', \"reuse-message\", &use_message, N_(\"commit\"), N_(\"reuse message from specified commit\")),\n@@ -1785,6 +1836,7 @@ int cmd_commit(int argc,\n \n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf author_ident = STRBUF_INIT;\n+\tstruct strbuf committer_ident = STRBUF_INIT;\n \tconst char *index_file, *reflog_msg;\n \tstruct object_id oid;\n \tstruct commit_list *parents = NULL;\n@@ -1930,8 +1982,12 @@ int cmd_commit(int argc,\n \t\tappend_merge_tag_headers(parents, &tail);\n \t}\n \n+\tif (force_committer)\n+\t\tdetermine_committer_info(&committer_ident);\n+\n \tif (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n-\t\t\t\t parents, &oid, author_ident.buf, NULL,\n+\t\t\t\t parents, &oid, author_ident.buf,\n+\t\t\t\t force_committer ? committer_ident.buf : NULL,\n \t\t\t\t sign_commit, extra)) {\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n@@ -1980,6 +2036,7 @@ cleanup:\n \tfree_commit_extra_headers(extra);\n \tfree_commit_list(parents);\n \tstrbuf_release(&author_ident);\n+\tstrbuf_release(&committer_ident);\n \tstrbuf_release(&err);\n \tstrbuf_release(&sb);\n \tfree(logfile);\ndiff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh\nindex 8e373b566b..7e163e02d1 100755\n--- a/t/t7509-commit-authorship.sh\n+++ b/t/t7509-commit-authorship.sh\n@@ -12,13 +12,20 @@ author_header () {\n \tsed -n -e '/^$/q' -e '/^author /p'\n }\n \n+committer_header () {\n+\tgit cat-file commit \"$1\" |\n+\tsed -n -e '/^$/q' -e '/^committer /p'\n+}\n+\n message_body () {\n \tgit cat-file commit \"$1\" |\n \tsed -e '1,/^$/d'\n }\n \n test_expect_success '-C option copies authorship and message' '\n-\ttest_commit --author Frigate\\ \\<flying@over.world\\> \\\n+\ttest_env GIT_COMMITTER_NAME=\"Frigate\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"flying@over.world\" \\\n+\t\ttest_commit --author Frigate\\ \\<flying@over.world\\> \\\n \t\t\"Initial Commit\" foo Initial Initial &&\n \techo \"Test 1\" >>foo &&\n \ttest_tick &&\n@@ -171,4 +178,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--committer option overrides committer' '\n+\tgit checkout Initial &&\n+\techo \"Test --committer\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test committer\" --committer=\"Custom Committer <custom@committer.example>\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Custom Committer <custom@committer.example>\" actual\n+'\n+\n+test_expect_success '--committer with pattern search' '\n+\techo \"Test committer pattern\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test committer pattern\" --committer=\"Frigate\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Frigate <flying@over.world>\" actual\n+'\n+\n+test_expect_success '--committer malformed parameter' '\n+\techo \"Test malformed\" >>foo &&\n+\ttest_tick &&\n+\ttest_must_fail git commit -a -m \"test malformed\" --committer=\"malformed committer\"\n+'\n+\n+test_expect_success '--committer with --amend option' '\n+\tgit checkout -f Initial &&\n+\techo \"Test committer with amend\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"initial commit for amend test\" &&\n+\techo \"Modified for amend\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a --amend --no-edit \\\n+\t\t--author=\"Test Author <test@author.example>\" \\\n+\t\t--committer=\"Test Committer <test@committer.example>\" &&\n+\tauthor_header HEAD >actual_author &&\n+\tgrep \"Test Author <test@author.example>\" actual_author &&\n+\tcommitter_header HEAD >actual_committer &&\n+\tgrep \"Test Committer <test@committer.example>\" actual_committer\n+'\n+\n+test_expect_success 'GIT_COMMITTER_* environment variables' '\n+\tgit checkout -f Initial &&\n+\techo \"Test env vars\" >>foo &&\n+\ttest_tick &&\n+\ttest_env GIT_COMMITTER_NAME=\"Env Committer\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n+\t\tgit commit -a -m \"test committer env vars\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Env Committer <env@test.example>\" actual\n+'\n+\n+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n+\techo \"Test override\" >>foo &&\n+\ttest_tick &&\n+\ttest_env GIT_COMMITTER_NAME=\"Env Committer\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n+\t\tgit commit -a -m \"test override\" \\\n+\t\t--committer=\"Override Committer <override@test.example>\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Override Committer <override@test.example>\" actual\n+'\n+\n+test_expect_success '--date with --committer changes both author and committer dates' '\n+\tgit checkout -f Initial &&\n+\techo \"Test date override\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test date\" \\\n+\t\t--author=\"Date Author <date@author.example>\" \\\n+\t\t--committer=\"Date Committer <date@committer.example>\" \\\n+\t\t--date=\"2024-06-15 10:30:00 +0800\" &&\n+\tgit log -1 --format=\"%ai\" >author_date &&\n+\tgit log -1 --format=\"%ci\" >committer_date &&\n+\tgrep \"2024-06-15 10:30:00 +0800\" author_date &&\n+\tgrep \"2024-06-15 10:30:00 +0800\" committer_date\n+'\n+\n test_done\n\nbase-commit: 4badef0c3503dc29059d678abba7fac0f042bc84\n-- \ngitgitgadget\n"},{"id":"530608","messageId":"xmqqqzu3qi43.fsf@gitster.g","threadId":"64460","inReplyTo":"CAOLTT8TLEsSAcfUomoActrUSzje5mQNE7EweXh8WtY3sOfzfkg@mail.gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-12T17:24:28Z","receivedAt":"2025-11-12T17:24:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>> I'm afraid I don't quite follow. If you are amending existing commits to\n>> fix them up after you have corrected your configuration then they will\n>> have the correct committer automatically when you run \"git commit\n>> --amend --author=...\" to correct the author. If you are committing\n>> before you have realized that user.{name,email} are misconfigured then I\n>> don't see how \"--committer\" helps because you have not yet realized\n>> anything is wrong.\n>>\n>\n> You're right that after realizing the misconfiguration and correcting the\n> repository's user.name and user.email, running `git commit --amend` will\n>  fix the committer information, but the author remains unchanged. Users\n> then need an additional `git commit --amend --author=...` to fix the author,\n> which does work but requires an extra step.\n\nIsn't it more like \"You need to run with --author to correct the\nauthorship by amending the commit *anyway*, but while doing so, the\ncommitter information will automatically be corrected\"?  As I said\nearlier in a separate message, the author and the committer are not\nsymmetric, so having \"--author\" does make sense in the above picture,\nwhile \"--committer\", as Phillip points out, much less.\n\n"},{"id":"530610","messageId":"xmqqfrajqdv0.fsf@gitster.g","threadId":"64460","inReplyTo":"pull.1997.v3.git.1762966535495.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-12T18:56:19Z","receivedAt":"2025-11-12T18:56:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +`--committer=<committer>`::\n> +\tOverride the committer for the commit. Specify an explicit committer using the\n\nIsn't \"set\" or \"use\" more appropirate verb to use here?\n\nWe already take the committer identity from multiple plases, like\nuser.{name,email}, or GIT_COMMITTER_{NAME,EMAIL} configuration, and\nwith the patch we also take it from a command line option, with the\nusual precedence order (i.e., command line trumps environment which\ntrumps configuration).\n\n> -static void determine_author_info(struct strbuf *author_ident)\n> +static void determine_identity(struct strbuf *ident_str, int is_author)\n\n\"is_author\" does not sound grammatical for this case; if you are\ngiving an ident of an unknown kind to this function and supplying\nanother parameter to let it know which kind, \"is_author\" may make\nsense, but not here.\n\nAs you will convert it into WANT_{AUTHOR,COMMITTER}_IDENT before\nusing anyway, why not let the caller use the \"enum want_ident\" to\ntell this function what to do?\n\n>  {\n>  \tchar *name, *email, *date;\n> -\tstruct ident_split author;\n> -\n> -\tname = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n> -\temail = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n> -\tdate = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n> -\n> -\tif (author_message) {\n> -\t\tstruct ident_split ident;\n> +\tstruct ident_split ident;\n> +\tconst char *env_name = is_author ? \"GIT_AUTHOR_NAME\" : \"GIT_COMMITTER_NAME\";\n> +\tconst char *env_email = is_author ? \"GIT_AUTHOR_EMAIL\" : \"GIT_COMMITTER_EMAIL\";\n> +\tconst char *env_date = is_author ? \"GIT_AUTHOR_DATE\" : \"GIT_COMMITTER_DATE\";\n> +\tconst char *force_ident = is_author ? force_author : force_committer;\n> +\tconst char *param_name = is_author ? \"--author\" : \"--committer\";\n> +\tint ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;\n> +\n> +\tname = xstrdup_or_null(getenv(env_name));\n> +\temail = xstrdup_or_null(getenv(env_email));\n> +\tdate = xstrdup_or_null(getenv(env_date));\n> +\n> +\tif (is_author && author_message) {\n> +\t\tstruct ident_split msg_ident;\n>  \t\tsize_t len;\n>  \t\tconst char *a;\n>  \n>  \t\ta = find_commit_header(author_message_buffer, \"author\", &len);\n>  \t\tif (!a)\n>  \t\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n> -\t\tif (split_ident_line(&ident, a, len) < 0)\n> +\t\tif (split_ident_line(&msg_ident, a, len) < 0)\n>  \t\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n>  \n> -\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n> -\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n> +\t\tset_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));\n> +\t\tset_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));\n>  \n> -\t\tif (ident.date_begin) {\n> +\t\tif (msg_ident.date_begin) {\n>  \t\t\tstruct strbuf date_buf = STRBUF_INIT;\n>  \t\t\tstrbuf_addch(&date_buf, '@');\n> -\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n> +\t\t\tstrbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);\n>  \t\t\tstrbuf_addch(&date_buf, ' ');\n> -\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n> +\t\t\tstrbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);\n>  \t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n>  \t\t}\n>  \t}\n\nThe helper tries to be generic between both kinds of ident, but we\nstill need conditional that says \"this part of the function is only\nwhen we are looking for author\", which is rather unsatisfactory.\n\nAlso why do we need this much patch noise, only because you renamed\none variable?  I wonder if it would make it cleaner to move the body\nof this if() {} statement into a separate helper function, leaving\nonly\n\n\tif (whose_ident == WANT_AUTHOR_IDENT)\n\t\tset_author_from_message(&name, &email, &date);\n\nor something simple here?\n"},{"id":"530729","messageId":"CAOLTT8RSN-72gVXDS-zrr=7wW4HU=CL6acyDx_UXnvo4c5MaRA@mail.gmail.com","threadId":"64460","inReplyTo":"xmqqqzu3qi43.fsf@gitster.g","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-15T05:29:18Z","receivedAt":"2025-11-15T05:29:31Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2025年11月13日周四 01:24写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> >> I'm afraid I don't quite follow. If you are amending existing commits to\n> >> fix them up after you have corrected your configuration then they will\n> >> have the correct committer automatically when you run \"git commit\n> >> --amend --author=...\" to correct the author. If you are committing\n> >> before you have realized that user.{name,email} are misconfigured then I\n> >> don't see how \"--committer\" helps because you have not yet realized\n> >> anything is wrong.\n> >>\n> >\n> > You're right that after realizing the misconfiguration and correcting the\n> > repository's user.name and user.email, running `git commit --amend` will\n> >  fix the committer information, but the author remains unchanged. Users\n> > then need an additional `git commit --amend --author=...` to fix the author,\n> > which does work but requires an extra step.\n>\n> Isn't it more like \"You need to run with --author to correct the\n> authorship by amending the commit *anyway*, but while doing so, the\n> committer information will automatically be corrected\"?  As I said\n> earlier in a separate message, the author and the committer are not\n> symmetric, so having \"--author\" does make sense in the above picture,\n> while \"--committer\", as Phillip points out, much less.\n>\n\nWell, I admit that perhaps the design philosophy of author/committer is\ninconsistent (which has caused too much trouble), but for users, a consistent\nparameter interface is easier to understand and use.\n"},{"id":"530730","messageId":"CAOLTT8SfLGnov2ZT5s7fz+DiN0fW-VFjFDVzv0J5GuwevMX2Kw@mail.gmail.com","threadId":"64460","inReplyTo":"xmqqfrajqdv0.fsf@gitster.g","subject":"Re: [PATCH v3] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-15T06:33:28Z","receivedAt":"2025-11-15T06:33:41Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2025年11月13日周四 02:56写道：\n>\n> \"ZheNing Hu via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > +`--committer=<committer>`::\n> > +     Override the committer for the commit. Specify an explicit committer using the\n>\n> Isn't \"set\" or \"use\" more appropirate verb to use here?\n>\n> We already take the committer identity from multiple plases, like\n> user.{name,email}, or GIT_COMMITTER_{NAME,EMAIL} configuration, and\n> with the patch we also take it from a command line option, with the\n> usual precedence order (i.e., command line trumps environment which\n> trumps configuration).\n>\n\nSince both `--author=<author>` and `--date=<date>` use the\n\"Override the *\" phrasing,  `--committer` uses \"Override the\" just\nto maintain consistency, though changing it to \"Set the\" would\nalso be acceptable.\n\n> > -static void determine_author_info(struct strbuf *author_ident)\n> > +static void determine_identity(struct strbuf *ident_str, int is_author)\n>\n> \"is_author\" does not sound grammatical for this case; if you are\n> giving an ident of an unknown kind to this function and supplying\n> another parameter to let it know which kind, \"is_author\" may make\n> sense, but not here.\n>\n> As you will convert it into WANT_{AUTHOR,COMMITTER}_IDENT before\n> using anyway, why not let the caller use the \"enum want_ident\" to\n> tell this function what to do?\n>\n\nOk, will change.\n\n> >  {\n> >       char *name, *email, *date;\n> > -     struct ident_split author;\n> > -\n> > -     name = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n> > -     email = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n> > -     date = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n> > -\n> > -     if (author_message) {\n> > -             struct ident_split ident;\n> > +     struct ident_split ident;\n> > +     const char *env_name = is_author ? \"GIT_AUTHOR_NAME\" : \"GIT_COMMITTER_NAME\";\n> > +     const char *env_email = is_author ? \"GIT_AUTHOR_EMAIL\" : \"GIT_COMMITTER_EMAIL\";\n> > +     const char *env_date = is_author ? \"GIT_AUTHOR_DATE\" : \"GIT_COMMITTER_DATE\";\n> > +     const char *force_ident = is_author ? force_author : force_committer;\n> > +     const char *param_name = is_author ? \"--author\" : \"--committer\";\n> > +     int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;\n> > +\n> > +     name = xstrdup_or_null(getenv(env_name));\n> > +     email = xstrdup_or_null(getenv(env_email));\n> > +     date = xstrdup_or_null(getenv(env_date));\n> > +\n> > +     if (is_author && author_message) {\n> > +             struct ident_split msg_ident;\n> >               size_t len;\n> >               const char *a;\n> >\n> >               a = find_commit_header(author_message_buffer, \"author\", &len);\n> >               if (!a)\n> >                       die(_(\"commit '%s' lacks author header\"), author_message);\n> > -             if (split_ident_line(&ident, a, len) < 0)\n> > +             if (split_ident_line(&msg_ident, a, len) < 0)\n> >                       die(_(\"commit '%s' has malformed author line\"), author_message);\n> >\n> > -             set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n> > -             set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n> > +             set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));\n> > +             set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));\n> >\n> > -             if (ident.date_begin) {\n> > +             if (msg_ident.date_begin) {\n> >                       struct strbuf date_buf = STRBUF_INIT;\n> >                       strbuf_addch(&date_buf, '@');\n> > -                     strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n> > +                     strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);\n> >                       strbuf_addch(&date_buf, ' ');\n> > -                     strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n> > +                     strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);\n> >                       set_ident_var(&date, strbuf_detach(&date_buf, NULL));\n> >               }\n> >       }\n>\n> The helper tries to be generic between both kinds of ident, but we\n> still need conditional that says \"this part of the function is only\n> when we are looking for author\", which is rather unsatisfactory.\n>\n\nIndeed, some specific logic should be moved to\ndetermine_author_info()/determine_committer_info().\n\n> Also why do we need this much patch noise, only because you renamed\n> one variable?  I wonder if it would make it cleaner to move the body\n> of this if() {} statement into a separate helper function, leaving\n> only\n>\n>         if (whose_ident == WANT_AUTHOR_IDENT)\n>                 set_author_from_message(&name, &email, &date);\n>\n\nOk.\n\n> or something simple here?\n"},{"id":"530744","messageId":"pull.1997.v4.git.1763221413640.gitgitgadget@gmail.com","threadId":"64460","inReplyTo":"pull.1997.v3.git.1762966535495.gitgitgadget@gmail.com","subject":"[PATCH v4] commit: add --committer option","fromName":"ZheNing Hu via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-11-15T15:43:33Z","receivedAt":"2025-11-15T15:43:36Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"From: ZheNing Hu <adlternative@gmail.com>\n\nAdd --committer option to git-commit, allowing users to override the\ncommitter identity similar to how --author works. This provides a more\nconvenient alternative to setting GIT_COMMITTER_* environment variables.\n\nLike --author, the --committer option supports two formats:\n- Explicit identity: --committer=\"Name <email@example.com>\"\n- Pattern search: --committer=\"pattern\" searches commit history for a\n  matching committer and reuses that identity\n\nTo share code with the existing --author option, this patch refactors:\n\n1. find_author_by_nickname() into find_identity_by_nickname() which\n   handles both author and committer searches through an is_author\n   parameter.\n\n2. determine_author_info() into determine_identity() which handles\n   identity parsing and setting for both author and committer through\n   an is_author parameter.\n\nSigned-off-by: ZheNing Hu <adlternative@gmail.com>\n---\n    commit: add --committer option\n    \n    Currently, when users need to override the committer identity in\n    git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL\n    environment variables, which can be cumbersome in scripting scenarios or\n    when frequently switching committer identities.\n    \n    While git-commit already provides the --author option to conveniently\n    override the author identity, there's no equivalent --committer option\n    for the committer identity. This asymmetry creates an inconsistent user\n    experience.\n    \n    This patch introduces the --committer option to git-commit, providing:\n    \n     1. Consistency with the existing --author option\n     2. A more convenient alternative to environment variables\n     3. Better support for automated workflows and scripts\n     4. Improved user experience when managing multiple identities\n    \n    The implementation follows the same pattern as the --author option,\n    accepting the format \"Name \" and properly validating the input.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1997\n\nRange-diff vs v3:\n\n 1:  acf724fad5 ! 1:  bb17f810ef commit: add --committer option\n     @@ Documentation/git-commit.adoc: See linkgit:git-rebase[1] for details.\n       \tthe commit author is then copied from the first such commit found.\n       \n      +`--committer=<committer>`::\n     -+\tOverride the committer for the commit. Specify an explicit committer using the\n     ++\tSet the committer for the commit. Specify an explicit committer using the\n      +\tstandard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_\n      +\tis assumed to be a pattern and is used to search for an existing\n      +\tcommit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);\n     @@ builtin/commit.c: static void set_ident_var(char **buf, char *val)\n       }\n       \n      -static void determine_author_info(struct strbuf *author_ident)\n     -+static void determine_identity(struct strbuf *ident_str, int is_author)\n     - {\n     - \tchar *name, *email, *date;\n     +-{\n     +-\tchar *name, *email, *date;\n      -\tstruct ident_split author;\n     --\n     ++static void set_author_from_message(char **name, char **email, char **date) {\n     ++\tstruct ident_split ident;\n     ++\tsize_t len;\n     ++\tconst char *a;\n     + \n      -\tname = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n      -\temail = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n      -\tdate = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n     --\n     ++\tif (!author_message)\n     ++\t\treturn;\n     + \n      -\tif (author_message) {\n      -\t\tstruct ident_split ident;\n     -+\tstruct ident_split ident;\n     -+\tconst char *env_name = is_author ? \"GIT_AUTHOR_NAME\" : \"GIT_COMMITTER_NAME\";\n     -+\tconst char *env_email = is_author ? \"GIT_AUTHOR_EMAIL\" : \"GIT_COMMITTER_EMAIL\";\n     -+\tconst char *env_date = is_author ? \"GIT_AUTHOR_DATE\" : \"GIT_COMMITTER_DATE\";\n     -+\tconst char *force_ident = is_author ? force_author : force_committer;\n     -+\tconst char *param_name = is_author ? \"--author\" : \"--committer\";\n     -+\tint ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT;\n     -+\n     -+\tname = xstrdup_or_null(getenv(env_name));\n     -+\temail = xstrdup_or_null(getenv(env_email));\n     -+\tdate = xstrdup_or_null(getenv(env_date));\n     -+\n     -+\tif (is_author && author_message) {\n     -+\t\tstruct ident_split msg_ident;\n     - \t\tsize_t len;\n     - \t\tconst char *a;\n     - \n     - \t\ta = find_commit_header(author_message_buffer, \"author\", &len);\n     - \t\tif (!a)\n     - \t\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n     +-\t\tsize_t len;\n     +-\t\tconst char *a;\n     +-\n     +-\t\ta = find_commit_header(author_message_buffer, \"author\", &len);\n     +-\t\tif (!a)\n     +-\t\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n      -\t\tif (split_ident_line(&ident, a, len) < 0)\n     -+\t\tif (split_ident_line(&msg_ident, a, len) < 0)\n     - \t\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n     - \n     +-\t\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n     +-\n      -\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n      -\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n     -+\t\tset_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin));\n     -+\t\tset_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin));\n     - \n     +-\n      -\t\tif (ident.date_begin) {\n     -+\t\tif (msg_ident.date_begin) {\n     - \t\t\tstruct strbuf date_buf = STRBUF_INIT;\n     - \t\t\tstrbuf_addch(&date_buf, '@');\n     +-\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n     +-\t\t\tstrbuf_addch(&date_buf, '@');\n      -\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n     -+\t\t\tstrbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin);\n     - \t\t\tstrbuf_addch(&date_buf, ' ');\n     +-\t\t\tstrbuf_addch(&date_buf, ' ');\n      -\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n     -+\t\t\tstrbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin);\n     - \t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n     - \t\t}\n     +-\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n     +-\t\t}\n     ++\ta = find_commit_header(author_message_buffer, \"author\", &len);\n     ++\tif (!a)\n     ++\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n     ++\tif (split_ident_line(&ident, a, len) < 0)\n     ++\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n     ++\n     ++\tset_ident_var(name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n     ++\tset_ident_var(email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n     ++\n     ++\tif (ident.date_begin) {\n     ++\t\tstruct strbuf date_buf = STRBUF_INIT;\n     ++\t\tstrbuf_addch(&date_buf, '@');\n     ++\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n     ++\t\tstrbuf_addch(&date_buf, ' ');\n     ++\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n     ++\t\tset_ident_var(date, strbuf_detach(&date_buf, NULL));\n       \t}\n     ++}\n       \n      -\tif (force_author) {\n      -\t\tstruct ident_split ident;\n     ++static void determine_identity(struct strbuf *ident_str, enum want_ident whose_ident,\n     ++\t\t\t       const char *env_name, const char *env_email, const char *env_date,\n     ++\t\t\t       const char *force_ident, const char *param_name,\n     ++\t\t\t       char *name, char *email, char *date)\n     ++{\n     ++\tstruct ident_split ident;\n     ++\n     ++\n      +\tif (force_ident) {\n      +\t\tstruct ident_split force_ident_split;\n     -+\n     -+\t\tif (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)\n     -+\t\t\tdie(_(\"malformed %s parameter\"), param_name);\n     -+\t\tset_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));\n     -+\t\tset_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));\n       \n      -\t\tif (split_ident_line(&ident, force_author, strlen(force_author)) < 0)\n      -\t\t\tdie(_(\"malformed --author parameter\"));\n      -\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n      -\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n     -+\t\tif (!is_author && force_ident_split.date_begin) {\n     -+\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n     -+\t\t\tstrbuf_addch(&date_buf, '@');\n     -+\t\t\tstrbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin);\n     -+\t\t\tstrbuf_addch(&date_buf, ' ');\n     -+\t\t\tstrbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin);\n     -+\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n     -+\t\t}\n     ++\t\tif (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)\n     ++\t\t\tdie(_(\"malformed %s parameter\"), param_name);\n     ++\t\tset_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));\n     ++\t\tset_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));\n       \t}\n       \n       \tif (force_date) {\n     @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)\n       \t}\n       \n      -\tstrbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,\n     -+\tstrbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date,\n     ++\tstrbuf_addstr(ident_str, fmt_ident(name, email, whose_ident, date,\n       \t\t\t\tIDENT_STRICT));\n      -\tassert_split_ident(&author, author_ident);\n      -\texport_one(\"GIT_AUTHOR_NAME\", author.name_begin, author.name_end, 0);\n     @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)\n      -\texport_one(\"GIT_AUTHOR_DATE\", author.date_begin, author.tz_end, '@');\n      +\tassert_split_ident(&ident, ident_str);\n      +\n     -+\tif (is_author) {\n     -+\t\texport_one(\"GIT_AUTHOR_NAME\", ident.name_begin, ident.name_end, 0);\n     -+\t\texport_one(\"GIT_AUTHOR_EMAIL\", ident.mail_begin, ident.mail_end, 0);\n     -+\t\texport_one(\"GIT_AUTHOR_DATE\", ident.date_begin, ident.tz_end, '@');\n     -+\t} else {\n     -+\t\texport_one(\"GIT_COMMITTER_NAME\", ident.name_begin, ident.name_end, 0);\n     -+\t\texport_one(\"GIT_COMMITTER_EMAIL\", ident.mail_begin, ident.mail_end, 0);\n     -+\t\texport_one(\"GIT_COMMITTER_DATE\", ident.date_begin, ident.tz_end, '@');\n     -+\t}\n     ++\texport_one(env_name, ident.name_begin, ident.name_end, 0);\n     ++\texport_one(env_email, ident.mail_begin, ident.mail_end, 0);\n     ++\texport_one(env_date, ident.date_begin, ident.tz_end, '@');\n      +\n       \tfree(name);\n       \tfree(email);\n     @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident)\n       \n      +static void determine_author_info(struct strbuf *author_ident)\n      +{\n     -+\tdetermine_identity(author_ident, 1);\n     ++\tchar *name, *email, *date;\n     ++\n     ++\tname = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n     ++\temail = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n     ++\tdate = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n     ++\n     ++\tset_author_from_message(&name, &email, &date);\n     ++\n     ++\tdetermine_identity(author_ident, WANT_AUTHOR_IDENT,\n     ++\t\t\t   \"GIT_AUTHOR_NAME\", \"GIT_AUTHOR_EMAIL\", \"GIT_AUTHOR_DATE\",\n     ++\t\t\t   force_author, \"--author\",\n     ++\t\t\t   name, email, date);\n      +}\n      +\n      +static void determine_committer_info(struct strbuf *committer_ident)\n      +{\n     -+\tdetermine_identity(committer_ident, 0);\n     ++\tchar *name, *email, *date;\n     ++\n     ++\tname = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n     ++\temail = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n     ++\tdate = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n     ++\n     ++\tdetermine_identity(committer_ident, WANT_COMMITTER_IDENT,\n     ++\t\t\t   \"GIT_COMMITTER_NAME\", \"GIT_COMMITTER_EMAIL\", \"GIT_COMMITTER_DATE\",\n     ++\t\t\t   force_committer, \"--committer\",\n     ++\t\t\t   name, email, date);\n      +}\n      +\n       static int author_date_is_interesting(void)\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n       }\n       \n      -static const char *find_author_by_nickname(const char *name)\n     -+static const char *find_identity_by_nickname(const char *name, int is_author)\n     ++static const char *find_identity_by_nickname(const char *name, enum want_ident whose_ident)\n       {\n       \tstruct rev_info revs;\n       \tstruct commit *commit;\n       \tstruct strbuf buf = STRBUF_INIT;\n       \tconst char *av[20];\n       \tint ac = 0;\n     -+\tconst char *field = is_author ? \"author\" : \"committer\";\n     -+\tconst char *format = is_author ? \"%aN <%aE>\" : \"%cN <%cE>\";\n     ++\tconst char *field, *format;\n     ++\n     ++\tif (whose_ident != WANT_AUTHOR_IDENT && whose_ident != WANT_COMMITTER_IDENT)\n     ++\t\tBUG(\"find_identity_by_nickname requires WANT_AUTHOR_IDENT or WANT_COMMITTER_IDENT\");\n     ++\n     ++\tfield = whose_ident == WANT_AUTHOR_IDENT ? \"author\" : \"committer\";\n     ++\tformat = whose_ident == WANT_AUTHOR_IDENT ? \"%aN <%aE>\" : \"%cN <%cE>\";\n       \n       \trepo_init_revisions(the_repository, &revs, NULL);\n      -\tstrbuf_addf(&buf, \"--author=%s\", name);\n     @@ builtin/commit.c: static const char *find_author_by_nickname(const char *name)\n      +\n      +static const char *find_author_by_nickname(const char *name)\n      +{\n     -+\treturn find_identity_by_nickname(name, 1);\n     ++\treturn find_identity_by_nickname(name, WANT_AUTHOR_IDENT);\n      +}\n      +\n      +static const char *find_committer_by_nickname(const char *name)\n      +{\n     -+\treturn find_identity_by_nickname(name, 0);\n     ++\treturn find_identity_by_nickname(name, WANT_COMMITTER_IDENT);\n       }\n       \n       static void handle_ignored_arg(struct wt_status *s)\n\n\n Documentation/git-commit.adoc |   9 +-\n builtin/commit.c              | 155 ++++++++++++++++++++++++----------\n t/t7509-commit-authorship.sh  |  84 +++++++++++++++++-\n 3 files changed, 200 insertions(+), 48 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 54c207ad45..a0c8a586de 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]\n \t   [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n \t   [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n \t   [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n-\t   [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n+\t   [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n \t   [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n \t   [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n \t   [--] [<pathspec>...]\n@@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details.\n \tcommit by that author (i.e. `git rev-list --all -i --author=<author>`);\n \tthe commit author is then copied from the first such commit found.\n \n+`--committer=<committer>`::\n+\tSet the committer for the commit. Specify an explicit committer using the\n+\tstandard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_\n+\tis assumed to be a pattern and is used to search for an existing\n+\tcommit by that committer (i.e. `git rev-list --all -i --committer=<committer>`);\n+\tthe commit committer is then copied from the first such commit found.\n+\n `--date=<date>`::\n \tOverride the author date used in the commit.\n \ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 0243f17d53..13138a3868 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {\n \t   \"           [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\\n\"\n \t   \"           [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\\n\"\n \t   \"           [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\\n\"\n-\t   \"           [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n+\t   \"           [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\\n\"\n \t   \"           [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\\n\"\n \t   \"           [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\\n\"\n \t   \"           [--] [<pathspec>...]\"),\n@@ -112,6 +112,7 @@ static enum {\n } commit_style;\n \n static const char *force_author;\n+static const char *force_committer;\n static char *logfile;\n static char *template_file;\n /*\n@@ -630,46 +631,48 @@ static void set_ident_var(char **buf, char *val)\n \t*buf = val;\n }\n \n-static void determine_author_info(struct strbuf *author_ident)\n-{\n-\tchar *name, *email, *date;\n-\tstruct ident_split author;\n+static void set_author_from_message(char **name, char **email, char **date) {\n+\tstruct ident_split ident;\n+\tsize_t len;\n+\tconst char *a;\n \n-\tname = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n-\temail = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n-\tdate = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n+\tif (!author_message)\n+\t\treturn;\n \n-\tif (author_message) {\n-\t\tstruct ident_split ident;\n-\t\tsize_t len;\n-\t\tconst char *a;\n-\n-\t\ta = find_commit_header(author_message_buffer, \"author\", &len);\n-\t\tif (!a)\n-\t\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n-\t\tif (split_ident_line(&ident, a, len) < 0)\n-\t\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n-\n-\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n-\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n-\n-\t\tif (ident.date_begin) {\n-\t\t\tstruct strbuf date_buf = STRBUF_INIT;\n-\t\t\tstrbuf_addch(&date_buf, '@');\n-\t\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n-\t\t\tstrbuf_addch(&date_buf, ' ');\n-\t\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n-\t\t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n-\t\t}\n+\ta = find_commit_header(author_message_buffer, \"author\", &len);\n+\tif (!a)\n+\t\tdie(_(\"commit '%s' lacks author header\"), author_message);\n+\tif (split_ident_line(&ident, a, len) < 0)\n+\t\tdie(_(\"commit '%s' has malformed author line\"), author_message);\n+\n+\tset_ident_var(name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n+\tset_ident_var(email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n+\n+\tif (ident.date_begin) {\n+\t\tstruct strbuf date_buf = STRBUF_INIT;\n+\t\tstrbuf_addch(&date_buf, '@');\n+\t\tstrbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);\n+\t\tstrbuf_addch(&date_buf, ' ');\n+\t\tstrbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);\n+\t\tset_ident_var(date, strbuf_detach(&date_buf, NULL));\n \t}\n+}\n \n-\tif (force_author) {\n-\t\tstruct ident_split ident;\n+static void determine_identity(struct strbuf *ident_str, enum want_ident whose_ident,\n+\t\t\t       const char *env_name, const char *env_email, const char *env_date,\n+\t\t\t       const char *force_ident, const char *param_name,\n+\t\t\t       char *name, char *email, char *date)\n+{\n+\tstruct ident_split ident;\n+\n+\n+\tif (force_ident) {\n+\t\tstruct ident_split force_ident_split;\n \n-\t\tif (split_ident_line(&ident, force_author, strlen(force_author)) < 0)\n-\t\t\tdie(_(\"malformed --author parameter\"));\n-\t\tset_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));\n-\t\tset_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));\n+\t\tif (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0)\n+\t\t\tdie(_(\"malformed %s parameter\"), param_name);\n+\t\tset_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin));\n+\t\tset_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin));\n \t}\n \n \tif (force_date) {\n@@ -679,17 +682,49 @@ static void determine_author_info(struct strbuf *author_ident)\n \t\tset_ident_var(&date, strbuf_detach(&date_buf, NULL));\n \t}\n \n-\tstrbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date,\n+\tstrbuf_addstr(ident_str, fmt_ident(name, email, whose_ident, date,\n \t\t\t\tIDENT_STRICT));\n-\tassert_split_ident(&author, author_ident);\n-\texport_one(\"GIT_AUTHOR_NAME\", author.name_begin, author.name_end, 0);\n-\texport_one(\"GIT_AUTHOR_EMAIL\", author.mail_begin, author.mail_end, 0);\n-\texport_one(\"GIT_AUTHOR_DATE\", author.date_begin, author.tz_end, '@');\n+\tassert_split_ident(&ident, ident_str);\n+\n+\texport_one(env_name, ident.name_begin, ident.name_end, 0);\n+\texport_one(env_email, ident.mail_begin, ident.mail_end, 0);\n+\texport_one(env_date, ident.date_begin, ident.tz_end, '@');\n+\n \tfree(name);\n \tfree(email);\n \tfree(date);\n }\n \n+static void determine_author_info(struct strbuf *author_ident)\n+{\n+\tchar *name, *email, *date;\n+\n+\tname = xstrdup_or_null(getenv(\"GIT_AUTHOR_NAME\"));\n+\temail = xstrdup_or_null(getenv(\"GIT_AUTHOR_EMAIL\"));\n+\tdate = xstrdup_or_null(getenv(\"GIT_AUTHOR_DATE\"));\n+\n+\tset_author_from_message(&name, &email, &date);\n+\n+\tdetermine_identity(author_ident, WANT_AUTHOR_IDENT,\n+\t\t\t   \"GIT_AUTHOR_NAME\", \"GIT_AUTHOR_EMAIL\", \"GIT_AUTHOR_DATE\",\n+\t\t\t   force_author, \"--author\",\n+\t\t\t   name, email, date);\n+}\n+\n+static void determine_committer_info(struct strbuf *committer_ident)\n+{\n+\tchar *name, *email, *date;\n+\n+\tname = xstrdup_or_null(getenv(\"GIT_COMMITTER_NAME\"));\n+\temail = xstrdup_or_null(getenv(\"GIT_COMMITTER_EMAIL\"));\n+\tdate = xstrdup_or_null(getenv(\"GIT_COMMITTER_DATE\"));\n+\n+\tdetermine_identity(committer_ident, WANT_COMMITTER_IDENT,\n+\t\t\t   \"GIT_COMMITTER_NAME\", \"GIT_COMMITTER_EMAIL\", \"GIT_COMMITTER_DATE\",\n+\t\t\t   force_committer, \"--committer\",\n+\t\t\t   name, email, date);\n+}\n+\n static int author_date_is_interesting(void)\n {\n \treturn author_message || force_date;\n@@ -1137,16 +1172,23 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \treturn 1;\n }\n \n-static const char *find_author_by_nickname(const char *name)\n+static const char *find_identity_by_nickname(const char *name, enum want_ident whose_ident)\n {\n \tstruct rev_info revs;\n \tstruct commit *commit;\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *av[20];\n \tint ac = 0;\n+\tconst char *field, *format;\n+\n+\tif (whose_ident != WANT_AUTHOR_IDENT && whose_ident != WANT_COMMITTER_IDENT)\n+\t\tBUG(\"find_identity_by_nickname requires WANT_AUTHOR_IDENT or WANT_COMMITTER_IDENT\");\n+\n+\tfield = whose_ident == WANT_AUTHOR_IDENT ? \"author\" : \"committer\";\n+\tformat = whose_ident == WANT_AUTHOR_IDENT ? \"%aN <%aE>\" : \"%cN <%cE>\";\n \n \trepo_init_revisions(the_repository, &revs, NULL);\n-\tstrbuf_addf(&buf, \"--author=%s\", name);\n+\tstrbuf_addf(&buf, \"--%s=%s\", field, name);\n \tav[++ac] = \"--all\";\n \tav[++ac] = \"-i\";\n \tav[++ac] = buf.buf;\n@@ -1164,11 +1206,22 @@ static const char *find_author_by_nickname(const char *name)\n \t\tctx.date_mode.type = DATE_NORMAL;\n \t\tstrbuf_release(&buf);\n \t\trepo_format_commit_message(the_repository, commit,\n-\t\t\t\t\t   \"%aN <%aE>\", &buf, &ctx);\n+\t\t\t\t\t   format, &buf, &ctx);\n \t\trelease_revisions(&revs);\n \t\treturn strbuf_detach(&buf, NULL);\n \t}\n-\tdie(_(\"--author '%s' is not 'Name <email>' and matches no existing author\"), name);\n+\tdie(_(\"--%s '%s' is not 'Name <email>' and matches no existing %s\"),\n+\t    field, name, field);\n+}\n+\n+static const char *find_author_by_nickname(const char *name)\n+{\n+\treturn find_identity_by_nickname(name, WANT_AUTHOR_IDENT);\n+}\n+\n+static const char *find_committer_by_nickname(const char *name)\n+{\n+\treturn find_identity_by_nickname(name, WANT_COMMITTER_IDENT);\n }\n \n static void handle_ignored_arg(struct wt_status *s)\n@@ -1321,6 +1374,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \tif (force_author && renew_authorship)\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--reset-author\", \"--author\");\n \n+\tif (force_committer && !strchr(force_committer, '>'))\n+\t\tforce_committer = find_committer_by_nickname(force_committer);\n+\n \tif (logfile || have_option_m || use_message)\n \t\tuse_editor = 0;\n \n@@ -1709,6 +1765,7 @@ int cmd_commit(int argc,\n \t\tOPT_FILENAME('F', \"file\", &logfile, N_(\"read message from file\")),\n \t\tOPT_STRING(0, \"author\", &force_author, N_(\"author\"), N_(\"override author for commit\")),\n \t\tOPT_STRING(0, \"date\", &force_date, N_(\"date\"), N_(\"override date for commit\")),\n+\t\tOPT_STRING(0, \"committer\", &force_committer, N_(\"committer\"), N_(\"override committer for commit\")),\n \t\tOPT_CALLBACK('m', \"message\", &message, N_(\"message\"), N_(\"commit message\"), opt_parse_m),\n \t\tOPT_STRING('c', \"reedit-message\", &edit_message, N_(\"commit\"), N_(\"reuse and edit message from specified commit\")),\n \t\tOPT_STRING('C', \"reuse-message\", &use_message, N_(\"commit\"), N_(\"reuse message from specified commit\")),\n@@ -1785,6 +1842,7 @@ int cmd_commit(int argc,\n \n \tstruct strbuf sb = STRBUF_INIT;\n \tstruct strbuf author_ident = STRBUF_INIT;\n+\tstruct strbuf committer_ident = STRBUF_INIT;\n \tconst char *index_file, *reflog_msg;\n \tstruct object_id oid;\n \tstruct commit_list *parents = NULL;\n@@ -1930,8 +1988,12 @@ int cmd_commit(int argc,\n \t\tappend_merge_tag_headers(parents, &tail);\n \t}\n \n+\tif (force_committer)\n+\t\tdetermine_committer_info(&committer_ident);\n+\n \tif (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,\n-\t\t\t\t parents, &oid, author_ident.buf, NULL,\n+\t\t\t\t parents, &oid, author_ident.buf,\n+\t\t\t\t force_committer ? committer_ident.buf : NULL,\n \t\t\t\t sign_commit, extra)) {\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n@@ -1980,6 +2042,7 @@ cleanup:\n \tfree_commit_extra_headers(extra);\n \tfree_commit_list(parents);\n \tstrbuf_release(&author_ident);\n+\tstrbuf_release(&committer_ident);\n \tstrbuf_release(&err);\n \tstrbuf_release(&sb);\n \tfree(logfile);\ndiff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh\nindex 8e373b566b..7e163e02d1 100755\n--- a/t/t7509-commit-authorship.sh\n+++ b/t/t7509-commit-authorship.sh\n@@ -12,13 +12,20 @@ author_header () {\n \tsed -n -e '/^$/q' -e '/^author /p'\n }\n \n+committer_header () {\n+\tgit cat-file commit \"$1\" |\n+\tsed -n -e '/^$/q' -e '/^committer /p'\n+}\n+\n message_body () {\n \tgit cat-file commit \"$1\" |\n \tsed -e '1,/^$/d'\n }\n \n test_expect_success '-C option copies authorship and message' '\n-\ttest_commit --author Frigate\\ \\<flying@over.world\\> \\\n+\ttest_env GIT_COMMITTER_NAME=\"Frigate\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"flying@over.world\" \\\n+\t\ttest_commit --author Frigate\\ \\<flying@over.world\\> \\\n \t\t\"Initial Commit\" foo Initial Initial &&\n \techo \"Test 1\" >>foo &&\n \ttest_tick &&\n@@ -171,4 +178,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--committer option overrides committer' '\n+\tgit checkout Initial &&\n+\techo \"Test --committer\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test committer\" --committer=\"Custom Committer <custom@committer.example>\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Custom Committer <custom@committer.example>\" actual\n+'\n+\n+test_expect_success '--committer with pattern search' '\n+\techo \"Test committer pattern\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test committer pattern\" --committer=\"Frigate\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Frigate <flying@over.world>\" actual\n+'\n+\n+test_expect_success '--committer malformed parameter' '\n+\techo \"Test malformed\" >>foo &&\n+\ttest_tick &&\n+\ttest_must_fail git commit -a -m \"test malformed\" --committer=\"malformed committer\"\n+'\n+\n+test_expect_success '--committer with --amend option' '\n+\tgit checkout -f Initial &&\n+\techo \"Test committer with amend\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"initial commit for amend test\" &&\n+\techo \"Modified for amend\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a --amend --no-edit \\\n+\t\t--author=\"Test Author <test@author.example>\" \\\n+\t\t--committer=\"Test Committer <test@committer.example>\" &&\n+\tauthor_header HEAD >actual_author &&\n+\tgrep \"Test Author <test@author.example>\" actual_author &&\n+\tcommitter_header HEAD >actual_committer &&\n+\tgrep \"Test Committer <test@committer.example>\" actual_committer\n+'\n+\n+test_expect_success 'GIT_COMMITTER_* environment variables' '\n+\tgit checkout -f Initial &&\n+\techo \"Test env vars\" >>foo &&\n+\ttest_tick &&\n+\ttest_env GIT_COMMITTER_NAME=\"Env Committer\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n+\t\tgit commit -a -m \"test committer env vars\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Env Committer <env@test.example>\" actual\n+'\n+\n+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '\n+\techo \"Test override\" >>foo &&\n+\ttest_tick &&\n+\ttest_env GIT_COMMITTER_NAME=\"Env Committer\" \\\n+\t\tGIT_COMMITTER_EMAIL=\"env@test.example\" \\\n+\t\tgit commit -a -m \"test override\" \\\n+\t\t--committer=\"Override Committer <override@test.example>\" &&\n+\tcommitter_header HEAD >actual &&\n+\tgrep \"Override Committer <override@test.example>\" actual\n+'\n+\n+test_expect_success '--date with --committer changes both author and committer dates' '\n+\tgit checkout -f Initial &&\n+\techo \"Test date override\" >>foo &&\n+\ttest_tick &&\n+\tgit commit -a -m \"test date\" \\\n+\t\t--author=\"Date Author <date@author.example>\" \\\n+\t\t--committer=\"Date Committer <date@committer.example>\" \\\n+\t\t--date=\"2024-06-15 10:30:00 +0800\" &&\n+\tgit log -1 --format=\"%ai\" >author_date &&\n+\tgit log -1 --format=\"%ci\" >committer_date &&\n+\tgrep \"2024-06-15 10:30:00 +0800\" author_date &&\n+\tgrep \"2024-06-15 10:30:00 +0800\" committer_date\n+'\n+\n test_done\n\nbase-commit: 4badef0c3503dc29059d678abba7fac0f042bc84\n-- \ngitgitgadget\n"},{"id":"530766","messageId":"xmqqfraeeqgw.fsf@gitster.g","threadId":"64460","inReplyTo":"CAOLTT8RSN-72gVXDS-zrr=7wW4HU=CL6acyDx_UXnvo4c5MaRA@mail.gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-11-16T01:06:07Z","receivedAt":"2025-11-16T01:06:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"ZheNing Hu <adlternative@gmail.com> writes:\n\n>> Isn't it more like \"You need to run with --author to correct the\n>> authorship by amending the commit *anyway*, but while doing so, the\n>> committer information will automatically be corrected\"?  As I said\n>> earlier in a separate message, the author and the committer are not\n>> symmetric, so having \"--author\" does make sense in the above picture,\n>> while \"--committer\", as Phillip points out, much less.\n>>\n>\n> Well, I admit that perhaps the design philosophy of author/committer is\n> inconsistent (which has caused too much trouble), but for users, a consistent\n> parameter interface is easier to understand and use.\n\nI do not think it is about design philosophy at all, though.\n\nThe distinction comes from the difference between what \"author\" and\n\"committer\" fields record.  The committer records the identity of\nthe person who was at the keyboard when the commit object was\ncreated.  The author records the identity of the person who wrote\nthe change that the committer is turning into a commit.  There is no\nsymmetry between them, hence there is no inconsistency here.\n\nAnd it also comes from the actual human user behaviour.\n\nMany authors can pass their patches to a smaller number of\ncommitters who make them into part of the official project history,\nso when a commit is made, there is much stronger need to tweak who\nthe author is for the commit than to tweak who the committer is.  On\nthe other hand, it is rare (if ever done) for multiple committers to\nshare a single shell terminal session and take turns to make commit,\nwhere you would need to be able to say \"this invocations of 'git\ncommit' command is done by person X, who is different from the one\nwho made the previous commit in this same shell session\".\n\n"},{"id":"530776","messageId":"aRpMW-g_rt4jStnu@kocurkovo.cz","threadId":"64460","inReplyTo":"CAOLTT8TLEsSAcfUomoActrUSzje5mQNE7EweXh8WtY3sOfzfkg@mail.gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"Matej Dujava","fromEmail":"mdujava@kocurkovo.cz","sentAt":"2025-11-16T22:12:43Z","receivedAt":"2025-11-16T22:21:02Z","isPatch":true,"sender":{"key":"mdujava@kocurkovo.cz","avatar":null},"body":"On Wed, Nov 12, 2025 at 11:58:02PM +0800, ZheNing Hu wrote:\n>Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道：\n>>\n>> On 11/11/2025 13:01, ZheNing Hu wrote:\n>> > Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道：\n>> >> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n>> >>> From: ZheNing Hu <adlternative@gmail.com>\n>> >>>\n>> >>>\n>> >>>       This patch introduces the --committer option to git-commit, providing:\n>> >>>\n>> >>>        1. Consistency with the existing --author option\n>> >>>        2. A more convenient alternative to environment variables\n>> >>>        3. Better support for automated workflows and scripts\n>> >>>        4. Improved user experience when managing multiple identities\n>> >>\n>> >> What's the use case for the same person committing under different\n>> >> identities? We already have a config mechanism to set different\n>> >> identities for different repositories but I'm struggling to see why\n>> >> someone would want to create commits under multiple identities in a\n>> >> single repository. For scripts it easy enough to set the relevant\n>> >> environment variables if a tool wants to create commits under its own\n>> >> identity.\n>> >>\n>> >\n>> > I frequently need to distinguish between different user.name and user.email\n>> > configurations on our company's internal GitHub.\n>> >\n>> > The current problems are:\n>> >\n>> > When I misconfigure (which happens occasionally), git commit --author only fixes\n>> > the author part, I still need to additionally set GIT_COMMITTER_NAME and\n>> > GIT_COMMITTER_EMAIL environment variables to fix the committer information\n>> > These environment variables are painful to use, requiring manual setup\n>> > every time\n>>\n>> I'm afraid I don't quite follow. If you are amending existing commits to\n>> fix them up after you have corrected your configuration then they will\n>> have the correct committer automatically when you run \"git commit\n>> --amend --author=...\" to correct the author. If you are committing\n>> before you have realized that user.{name,email} are misconfigured then I\n>> don't see how \"--committer\" helps because you have not yet realized\n>> anything is wrong.\n>>\n>\n\nHi\n\nI use includeIf pattern in a config to separate identities\n\n~/.gitconfig:\n```\n  [includeIf \"gitdir:~/.local/src/personal/\"]\n      path ~/.gitconfig-personal\n  [includeIf \"gitdir:~/.local/src/companyA/\"]\n      path ~/.gitconfig-companyA\n  [includeIf \"gitdir:~/.local/src/companyB/\"]\n      path ~/.gitconfig-companyB\n```\n\nthen each\n~/.gitconfig-IDENTITY:\n```\n  [user]\n      name = ...\n      email = ...\n      signingkey = ...\n```\n\n>You're right that after realizing the misconfiguration and correcting the\n>repository's user.name and user.email, running `git commit --amend` will\n> fix the committer information, but the author remains unchanged. Users\n>then need an additional `git commit --amend --author=...` to fix the author,\n>which does work but requires an extra step.\n\nFor just one commit, after you fix identity (update .git/config or move\nproject so includeIf uses correct config) then `git commit --amend\n--reset-author` should get right identity for both commiter and author.\n\n>\n>I see your point that this becomes more cumbersome when dealing with\n>multiple commits. In such cases, users currently need to use something like:\n>\n>```\n>GIT_AUTHOR_NAME=\"...\" GIT_AUTHOR_EMAIL=\"...\" \\\n>GIT_COMMITTER_NAME=\"...\" GIT_COMMITTER_EMAIL=\"...\" \\\n>git rebase -f <target>\n>```\n\nIn my test ^ (using 2.51.2) did not set specified AUTHOR identity, but\nusing:\n\n   git rebase <target> -fx \"git commit --amend --no-edit --reset-author\"\n\nis close to rewriting commits with new identity, but this will change\nboth dates (committer, author). \n\nIf --reset-author is not used but either GIT_AUTHOR_* are exported or\n--author '...' is used in a -x arg, then author date is kept untouched.\n\n>\n>This is indeed tedious and error-prone, especially when you want\n>to quickly fix and push commits to the platform.\n>\n>`git commit --amend --author --committer` or a new `git rebase\n>--author --committer`\n>would provide a more user-friendly workflow for correcting identity\n>information after misconfiguration, eliminating the need to manually\n>set multiple\n>environment variables or run multiple commands.\n>\n>> Thanks\n>>\n>> Phillip\n>>\n>\n>Thanks\n>\n>ZheNing Hu\n>\n\n--\nThanks,\nMatej\n"},{"id":"530807","messageId":"10ad6161-8d9d-4e07-ab62-105adac2220d@gmail.com","threadId":"64460","inReplyTo":"aRpMW-g_rt4jStnu@kocurkovo.cz","subject":"Re: [PATCH] commit: add --committer option","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2025-11-17T14:27:13Z","receivedAt":"2025-11-17T14:27:17Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 16/11/2025 22:12, Matej Dujava wrote:\n> On Wed, Nov 12, 2025 at 11:58:02PM +0800, ZheNing Hu wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道：\n> \n> I use includeIf pattern in a config to separate identities\n> \n> ~/.gitconfig:\n> ```\n>   [includeIf \"gitdir:~/.local/src/personal/\"]\n>       path ~/.gitconfig-personal\n>   [includeIf \"gitdir:~/.local/src/companyA/\"]\n>       path ~/.gitconfig-companyA\n>   [includeIf \"gitdir:~/.local/src/companyB/\"]\n>       path ~/.gitconfig-companyB\n> ```\n> \n> then each\n> ~/.gitconfig-IDENTITY:\n> ```\n>   [user]\n>       name = ...\n>       email = ...\n>       signingkey = ...\n> ```\n\nI think that's a common pattern, so long as one can arrange the \ndirectory structure so that the repositories for each identity are under \na different sub-directory it works well.\n\n>> You're right that after realizing the misconfiguration and correcting the\n>> repository's user.name and user.email, running `git commit --amend` will\n>> fix the committer information, but the author remains unchanged. Users\n>> then need an additional `git commit --amend --author=...` to fix the \n>> author,\n>> which does work but requires an extra step.\n> \n> For just one commit, after you fix identity (update .git/config or move\n> project so includeIf uses correct config) then `git commit --amend\n> --reset-author` should get right identity for both commiter and author.\n\nAs you note below it also resets the author date which might to be \ndesirable.\n>> I see your point that this becomes more cumbersome when dealing with\n>> multiple commits. In such cases, users currently need to use something \n>> like:\n>>\n>> ```\n>> GIT_AUTHOR_NAME=\"...\" GIT_AUTHOR_EMAIL=\"...\" \\\n>> GIT_COMMITTER_NAME=\"...\" GIT_COMMITTER_EMAIL=\"...\" \\\n>> git rebase -f <target>\n>> ```\n> \n> In my test ^ (using 2.51.2) did not set specified AUTHOR identity\n\nIndeed, rebase sets GIT_AUTHOR_{NAME,EMAIL,DATE} when running \"git \ncommit\" to preserve the authorship of the commit being picked so any \nvalue that is set in the environment when running \"git rebase\" is ignored.\n\n> but using:\n> \n>    git rebase <target> -fx \"git commit --amend --no-edit --reset-author\"\n> \n> is close to rewriting commits with new identity, but this will change\n> both dates (committer, author).\n> If --reset-author is not used but either GIT_AUTHOR_* are exported or\n> --author '...' is used in a -x arg, then author date is kept untouched.\n\nYes \"git rebase -x 'git commit --author=... --amend --no-edit'\" is \nprobably the easiest way to reset the author and committer.\n>>\n>> This is indeed tedious and error-prone, especially when you want\n>> to quickly fix and push commits to the platform.\n>>\n>> `git commit --amend --author --committer` or a new `git rebase\n>> --author --committer`\n>> would provide a more user-friendly workflow for correcting identity\n>> information after misconfiguration, eliminating the need to manually\n>> set multiple\n>> environment variables or run multiple commands.\n\nI'm still not sure why we need a \"--committer\" option when the committer \nidentity is taken from the corrected config anyway.\n\nThanks\n\nPhillip\n\n"},{"id":"530810","messageId":"CAOLTT8SEwipDtthO2T0CkE5-rCJdq9oDjTKcBEH2YxzpjAM9jQ@mail.gmail.com","threadId":"64460","inReplyTo":"xmqqfraeeqgw.fsf@gitster.g","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-17T15:06:07Z","receivedAt":"2025-11-17T15:06:20Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Junio C Hamano <gitster@pobox.com> 于2025年11月16日周日 09:06写道：\n>\n> ZheNing Hu <adlternative@gmail.com> writes:\n>\n> >> Isn't it more like \"You need to run with --author to correct the\n> >> authorship by amending the commit *anyway*, but while doing so, the\n> >> committer information will automatically be corrected\"?  As I said\n> >> earlier in a separate message, the author and the committer are not\n> >> symmetric, so having \"--author\" does make sense in the above picture,\n> >> while \"--committer\", as Phillip points out, much less.\n> >>\n> >\n> > Well, I admit that perhaps the design philosophy of author/committer is\n> > inconsistent (which has caused too much trouble), but for users, a consistent\n> > parameter interface is easier to understand and use.\n>\n> I do not think it is about design philosophy at all, though.\n>\n> The distinction comes from the difference between what \"author\" and\n> \"committer\" fields record.  The committer records the identity of\n> the person who was at the keyboard when the commit object was\n> created.  The author records the identity of the person who wrote\n> the change that the committer is turning into a commit.  There is no\n> symmetry between them, hence there is no inconsistency here.\n>\n\nI understand the difference between committer and author,\nbut some regular Git users don't really pay much attention to the\ndistinction between the two. For these users, being able to quickly\nand easily correct user information is sufficient — it's just that some\nprevious solutions were a bit complicated.\n\n> And it also comes from the actual human user behaviour.\n>\n> Many authors can pass their patches to a smaller number of\n> committers who make them into part of the official project history,\n> so when a commit is made, there is much stronger need to tweak who\n> the author is for the commit than to tweak who the committer is.  On\n> the other hand, it is rare (if ever done) for multiple committers to\n> share a single shell terminal session and take turns to make commit,\n> where you would need to be able to say \"this invocations of 'git\n> commit' command is done by person X, who is different from the one\n> who made the previous commit in this same shell session\".\n>\n\nYes, I believe users in this case are very rare. As I mentioned before,\nI just want to help those users who want to fix things after misconfiguration,\nmaking the fix a bit simpler.\n"},{"id":"530811","messageId":"CAOLTT8SOSV48PA-4F3QuHTJQNB_O=Ne8G8JmRTg_uvBypjFeJw@mail.gmail.com","threadId":"64460","inReplyTo":"aRpMW-g_rt4jStnu@kocurkovo.cz","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-17T15:15:34Z","receivedAt":"2025-11-17T15:15:48Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Matej Dujava <mdujava@kocurkovo.cz> 于2025年11月17日周一 06:12写道：\n>\n> On Wed, Nov 12, 2025 at 11:58:02PM +0800, ZheNing Hu wrote:\n> >Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道：\n> >>\n> >> On 11/11/2025 13:01, ZheNing Hu wrote:\n> >> > Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道：\n> >> >> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote:\n> >> >>> From: ZheNing Hu <adlternative@gmail.com>\n> >> >>>\n> >> >>>\n> >> >>>       This patch introduces the --committer option to git-commit, providing:\n> >> >>>\n> >> >>>        1. Consistency with the existing --author option\n> >> >>>        2. A more convenient alternative to environment variables\n> >> >>>        3. Better support for automated workflows and scripts\n> >> >>>        4. Improved user experience when managing multiple identities\n> >> >>\n> >> >> What's the use case for the same person committing under different\n> >> >> identities? We already have a config mechanism to set different\n> >> >> identities for different repositories but I'm struggling to see why\n> >> >> someone would want to create commits under multiple identities in a\n> >> >> single repository. For scripts it easy enough to set the relevant\n> >> >> environment variables if a tool wants to create commits under its own\n> >> >> identity.\n> >> >>\n> >> >\n> >> > I frequently need to distinguish between different user.name and user.email\n> >> > configurations on our company's internal GitHub.\n> >> >\n> >> > The current problems are:\n> >> >\n> >> > When I misconfigure (which happens occasionally), git commit --author only fixes\n> >> > the author part, I still need to additionally set GIT_COMMITTER_NAME and\n> >> > GIT_COMMITTER_EMAIL environment variables to fix the committer information\n> >> > These environment variables are painful to use, requiring manual setup\n> >> > every time\n> >>\n> >> I'm afraid I don't quite follow. If you are amending existing commits to\n> >> fix them up after you have corrected your configuration then they will\n> >> have the correct committer automatically when you run \"git commit\n> >> --amend --author=...\" to correct the author. If you are committing\n> >> before you have realized that user.{name,email} are misconfigured then I\n> >> don't see how \"--committer\" helps because you have not yet realized\n> >> anything is wrong.\n> >>\n> >\n>\n> Hi\n>\n> I use includeIf pattern in a config to separate identities\n>\n> ~/.gitconfig:\n> ```\n>   [includeIf \"gitdir:~/.local/src/personal/\"]\n>       path ~/.gitconfig-personal\n>   [includeIf \"gitdir:~/.local/src/companyA/\"]\n>       path ~/.gitconfig-companyA\n>   [includeIf \"gitdir:~/.local/src/companyB/\"]\n>       path ~/.gitconfig-companyB\n> ```\n>\n> then each\n> ~/.gitconfig-IDENTITY:\n> ```\n>   [user]\n>       name = ...\n>       email = ...\n>       signingkey = ...\n> ```\n>\n\nThis does indeed appear to be a very good standard,\nbut unfortunately many users in the past and possibly\neven in the future may not be aware of such a good practice.\n\n> >You're right that after realizing the misconfiguration and correcting the\n> >repository's user.name and user.email, running `git commit --amend` will\n> > fix the committer information, but the author remains unchanged. Users\n> >then need an additional `git commit --amend --author=...` to fix the author,\n> >which does work but requires an extra step.\n>\n> For just one commit, after you fix identity (update .git/config or move\n> project so includeIf uses correct config) then `git commit --amend\n> --reset-author` should get right identity for both commiter and author.\n>\n\nOk...\n\n> >\n> >I see your point that this becomes more cumbersome when dealing with\n> >multiple commits. In such cases, users currently need to use something like:\n> >\n> >```\n> >GIT_AUTHOR_NAME=\"...\" GIT_AUTHOR_EMAIL=\"...\" \\\n> >GIT_COMMITTER_NAME=\"...\" GIT_COMMITTER_EMAIL=\"...\" \\\n> >git rebase -f <target>\n> >```\n>\n> In my test ^ (using 2.51.2) did not set specified AUTHOR identity, but\n> using:\n>\n>    git rebase <target> -fx \"git commit --amend --no-edit --reset-author\"\n>\n> is close to rewriting commits with new identity, but this will change\n> both dates (committer, author).\n>\n> If --reset-author is not used but either GIT_AUTHOR_* are exported or\n> --author '...' is used in a -x arg, then author date is kept untouched.\n>\n\nThis does look like a good approach indeed, and it should\npretty much meet the user's requirements.\n\n> >\n> >This is indeed tedious and error-prone, especially when you want\n> >to quickly fix and push commits to the platform.\n> >\n> >`git commit --amend --author --committer` or a new `git rebase\n> >--author --committer`\n> >would provide a more user-friendly workflow for correcting identity\n> >information after misconfiguration, eliminating the need to manually\n> >set multiple\n> >environment variables or run multiple commands.\n> >\n> >> Thanks\n> >>\n> >> Phillip\n> >>\n> >\n> >Thanks\n> >\n> >ZheNing Hu\n> >\n>\n> --\n> Thanks,\n> Matej\n"},{"id":"530812","messageId":"CAOLTT8QF9JhA-EXbgnZ30m=+T_DP+t+qbJPc1e5WmZaJhy9kvw@mail.gmail.com","threadId":"64460","inReplyTo":"10ad6161-8d9d-4e07-ab62-105adac2220d@gmail.com","subject":"Re: [PATCH] commit: add --committer option","fromName":"ZheNing Hu","fromEmail":"adlternative@gmail.com","sentAt":"2025-11-17T15:18:14Z","receivedAt":"2025-11-17T15:18:29Z","isPatch":true,"sender":{"key":"adlternative@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58138461?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> 于2025年11月17日周一 22:27写道：\n>\n> On 16/11/2025 22:12, Matej Dujava wrote:\n> > On Wed, Nov 12, 2025 at 11:58:02PM +0800, ZheNing Hu wrote:\n> >> Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道：\n> >\n> > I use includeIf pattern in a config to separate identities\n> >\n> > ~/.gitconfig:\n> > ```\n> >   [includeIf \"gitdir:~/.local/src/personal/\"]\n> >       path ~/.gitconfig-personal\n> >   [includeIf \"gitdir:~/.local/src/companyA/\"]\n> >       path ~/.gitconfig-companyA\n> >   [includeIf \"gitdir:~/.local/src/companyB/\"]\n> >       path ~/.gitconfig-companyB\n> > ```\n> >\n> > then each\n> > ~/.gitconfig-IDENTITY:\n> > ```\n> >   [user]\n> >       name = ...\n> >       email = ...\n> >       signingkey = ...\n> > ```\n>\n> I think that's a common pattern, so long as one can arrange the\n> directory structure so that the repositories for each identity are under\n> a different sub-directory it works well.\n>\n> >> You're right that after realizing the misconfiguration and correcting the\n> >> repository's user.name and user.email, running `git commit --amend` will\n> >> fix the committer information, but the author remains unchanged. Users\n> >> then need an additional `git commit --amend --author=...` to fix the\n> >> author,\n> >> which does work but requires an extra step.\n> >\n> > For just one commit, after you fix identity (update .git/config or move\n> > project so includeIf uses correct config) then `git commit --amend\n> > --reset-author` should get right identity for both commiter and author.\n>\n> As you note below it also resets the author date which might to be\n> desirable.\n> >> I see your point that this becomes more cumbersome when dealing with\n> >> multiple commits. In such cases, users currently need to use something\n> >> like:\n> >>\n> >> ```\n> >> GIT_AUTHOR_NAME=\"...\" GIT_AUTHOR_EMAIL=\"...\" \\\n> >> GIT_COMMITTER_NAME=\"...\" GIT_COMMITTER_EMAIL=\"...\" \\\n> >> git rebase -f <target>\n> >> ```\n> >\n> > In my test ^ (using 2.51.2) did not set specified AUTHOR identity\n>\n> Indeed, rebase sets GIT_AUTHOR_{NAME,EMAIL,DATE} when running \"git\n> commit\" to preserve the authorship of the commit being picked so any\n> value that is set in the environment when running \"git rebase\" is ignored.\n>\n> > but using:\n> >\n> >    git rebase <target> -fx \"git commit --amend --no-edit --reset-author\"\n> >\n> > is close to rewriting commits with new identity, but this will change\n> > both dates (committer, author).\n> > If --reset-author is not used but either GIT_AUTHOR_* are exported or\n> > --author '...' is used in a -x arg, then author date is kept untouched.\n>\n> Yes \"git rebase -x 'git commit --author=... --amend --no-edit'\" is\n> probably the easiest way to reset the author and committer.\n> >>\n> >> This is indeed tedious and error-prone, especially when you want\n> >> to quickly fix and push commits to the platform.\n> >>\n> >> `git commit --amend --author --committer` or a new `git rebase\n> >> --author --committer`\n> >> would provide a more user-friendly workflow for correcting identity\n> >> information after misconfiguration, eliminating the need to manually\n> >> set multiple\n> >> environment variables or run multiple commands.\n>\n> I'm still not sure why we need a \"--committer\" option when the committer\n> identity is taken from the corrected config anyway.\n>\n\nWell, if the `git rebase --fx` approach is simple enough, I can also\ngive up on introducing `--committer`.\n\n> Thanks\n>\n> Phillip\n>\n"}]}