* [PATCH] commit: add --committer option
@ 2025-11-09 10:22 ZheNing Hu via GitGitGadget
2025-11-10 9:24 ` Patrick Steinhardt
` (2 more replies)
0 siblings, 3 replies; 44+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2025-11-09 10:22 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Jeff King, ZheNing Hu, ZheNing Hu
From: ZheNing Hu <adlternative@gmail.com>
Add --committer option to git-commit, allowing users to override the
committer identity similar to how --author works. This provides a more
convenient alternative to setting GIT_COMMITTER_* environment variables.
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com>
---
commit: add --committer option
Currently, when users need to override the committer identity in
git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL
environment variables, which can be cumbersome in scripting scenarios or
when frequently switching committer identities.
While git-commit already provides the --author option to conveniently
override the author identity, there's no equivalent --committer option
for the committer identity. This asymmetry creates an inconsistent user
experience.
This patch introduces the --committer option to git-commit, providing:
1. Consistency with the existing --author option
2. A more convenient alternative to environment variables
3. Better support for automated workflows and scripts
4. Improved user experience when managing multiple identities
The implementation follows the same pattern as the --author option,
accepting the format "Name " and properly validating the input.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1997
Documentation/git-commit.adoc | 9 +++-
builtin/commit.c | 58 ++++++++++++++++++++++++-
t/t7509-commit-authorship.sh | 80 +++++++++++++++++++++++++++++++++++
3 files changed, 144 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc
index 54c207ad45..a015c8328e 100644
--- a/Documentation/git-commit.adoc
+++ b/Documentation/git-commit.adoc
@@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend]
[--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]
[-F <file> | -m <msg>] [--reset-author] [--allow-empty]
[--allow-empty-message] [--no-verify] [-e] [--author=<author>]
- [--date=<date>] [--cleanup=<mode>] [--[no-]status]
+ [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]
[-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
[(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]
[--] [<pathspec>...]
@@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details.
`--date=<date>`::
Override the author date used in the commit.
+`--committer=<committer>`::
+ Override the committer for the commit. Specify an explicit committer using the
+ standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_
+ is assumed to be a pattern and is used to search for an existing
+ commit by that author (i.e. `git rev-list --all -i --author=<committer>`);
+ the commit author is then copied from the first such commit found.
+
`-m <msg>`::
`--message=<msg>`::
Use _<msg>_ as the commit message.
diff --git a/builtin/commit.c b/builtin/commit.c
index 0243f17d53..88e77cbaab 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = {
" [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n"
" [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n"
" [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n"
- " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n"
+ " [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n"
" [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n"
" [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n"
" [--] [<pathspec>...]"),
@@ -112,6 +112,7 @@ static enum {
} commit_style;
static const char *force_author;
+static const char *force_committer;
static char *logfile;
static char *template_file;
/*
@@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident)
free(date);
}
+static void determine_committer_info(struct strbuf *committer_ident)
+{
+ char *name, *email, *date;
+ struct ident_split committer;
+
+ name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME"));
+ email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL"));
+ date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE"));
+
+ if (force_committer) {
+ struct ident_split ident;
+
+ if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0)
+ die(_("malformed --committer parameter"));
+ set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin));
+ set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin));
+
+ if (ident.date_begin) {
+ struct strbuf date_buf = STRBUF_INIT;
+ strbuf_addch(&date_buf, '@');
+ strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin);
+ strbuf_addch(&date_buf, ' ');
+ strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin);
+ set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+ }
+ }
+
+ if (force_date) {
+ struct strbuf date_buf = STRBUF_INIT;
+ if (parse_force_date(force_date, &date_buf))
+ die(_("invalid date format: %s"), force_date);
+ set_ident_var(&date, strbuf_detach(&date_buf, NULL));
+ }
+
+ strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date,
+ IDENT_STRICT));
+ assert_split_ident(&committer, committer_ident);
+ free(name);
+ free(email);
+ free(date);
+}
+
static int author_date_is_interesting(void)
{
return author_message || force_date;
@@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (force_author && renew_authorship)
die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author");
+ if (force_committer && !strchr(force_committer, '>'))
+ force_committer = find_author_by_nickname(force_committer);
+
if (logfile || have_option_m || use_message)
use_editor = 0;
@@ -1709,6 +1755,7 @@ int cmd_commit(int argc,
OPT_FILENAME('F', "file", &logfile, N_("read message from file")),
OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")),
OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")),
+ OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")),
OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m),
OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")),
OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
@@ -1785,6 +1832,7 @@ int cmd_commit(int argc,
struct strbuf sb = STRBUF_INIT;
struct strbuf author_ident = STRBUF_INIT;
+ struct strbuf committer_ident = STRBUF_INIT;
const char *index_file, *reflog_msg;
struct object_id oid;
struct commit_list *parents = NULL;
@@ -1930,8 +1978,13 @@ int cmd_commit(int argc,
append_merge_tag_headers(parents, &tail);
}
+ if (force_committer) {
+ determine_committer_info(&committer_ident);
+ }
+
if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid,
- parents, &oid, author_ident.buf, NULL,
+ parents, &oid, author_ident.buf,
+ force_committer ? committer_ident.buf : NULL,
sign_commit, extra)) {
rollback_index_files();
die(_("failed to write commit object"));
@@ -1980,6 +2033,7 @@ cleanup:
free_commit_extra_headers(extra);
free_commit_list(parents);
strbuf_release(&author_ident);
+ strbuf_release(&committer_ident);
strbuf_release(&err);
strbuf_release(&sb);
free(logfile);
diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh
index 8e373b566b..45527f6a70 100755
--- a/t/t7509-commit-authorship.sh
+++ b/t/t7509-commit-authorship.sh
@@ -12,6 +12,11 @@ author_header () {
sed -n -e '/^$/q' -e '/^author /p'
}
+committer_header () {
+ git cat-file commit "$1" |
+ sed -n -e '/^$/q' -e '/^committer /p'
+}
+
message_body () {
git cat-file commit "$1" |
sed -e '1,/^$/d'
@@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' '
test_cmp expect actual
'
+test_expect_success '--committer option overrides committer' '
+ git checkout Initial &&
+ echo "Test --committer" >>foo &&
+ test_tick &&
+ git commit -a -m "test committer" --committer="Custom Committer <custom@committer.example>" &&
+ committer_header HEAD >actual &&
+ grep "Custom Committer <custom@committer.example>" actual
+'
+
+test_expect_success '--committer with pattern search' '
+ echo "Test committer pattern" >>foo &&
+ test_tick &&
+ git commit -a -m "test committer pattern" --committer="Frigate" &&
+ committer_header HEAD >actual &&
+ grep "Frigate <flying@over.world>" actual
+'
+
+test_expect_success '--committer malformed parameter' '
+ echo "Test malformed" >>foo &&
+ test_tick &&
+ test_must_fail git commit -a -m "test malformed" --committer="malformed committer"
+'
+
+test_expect_success '--committer with --amend option' '
+ git checkout -f Initial &&
+ echo "Test committer with amend" >>foo &&
+ test_tick &&
+ git commit -a -m "initial commit for amend test" &&
+ echo "Modified for amend" >>foo &&
+ test_tick &&
+ git commit -a --amend --no-edit \
+ --author="Test Author <test@author.example>" \
+ --committer="Test Committer <test@committer.example>" &&
+ author_header HEAD >actual_author &&
+ grep "Test Author <test@author.example>" actual_author &&
+ committer_header HEAD >actual_committer &&
+ grep "Test Committer <test@committer.example>" actual_committer
+'
+
+test_expect_success 'GIT_COMMITTER_* environment variables' '
+ git checkout -f Initial &&
+ echo "Test env vars" >>foo &&
+ test_tick &&
+ GIT_COMMITTER_NAME="Env Committer" \
+ GIT_COMMITTER_EMAIL="env@test.example" \
+ git commit -a -m "test committer env vars" &&
+ committer_header HEAD >actual &&
+ grep "Env Committer <env@test.example>" actual
+'
+
+test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' '
+ echo "Test override" >>foo &&
+ test_tick &&
+ GIT_COMMITTER_NAME="Env Committer" \
+ GIT_COMMITTER_EMAIL="env@test.example" \
+ git commit -a -m "test override" \
+ --committer="Override Committer <override@test.example>" &&
+ committer_header HEAD >actual &&
+ grep "Override Committer <override@test.example>" actual
+'
+
+test_expect_success '--date with --committer changes both author and committer dates' '
+ git checkout -f Initial &&
+ echo "Test date override" >>foo &&
+ test_tick &&
+ git commit -a -m "test date" \
+ --author="Date Author <date@author.example>" \
+ --committer="Date Committer <date@committer.example>" \
+ --date="2024-06-15 10:30:00 +0800" &&
+ git log -1 --format="%ai" >author_date &&
+ git log -1 --format="%ci" >committer_date &&
+ grep "2024-06-15 10:30:00 +0800" author_date &&
+ grep "2024-06-15 10:30:00 +0800" committer_date
+'
+
test_done
base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84
--
gitgitgadget
^ permalink raw reply related [flat|nested] 44+ messages in thread* Re: [PATCH] commit: add --committer option 2025-11-09 10:22 [PATCH] commit: add --committer option ZheNing Hu via GitGitGadget @ 2025-11-10 9:24 ` Patrick Steinhardt 2025-11-10 14:17 ` ZheNing Hu 2025-11-10 17:38 ` Junio C Hamano 2025-11-10 16:50 ` Phillip Wood 2025-11-10 16:56 ` [PATCH v2] " ZheNing Hu via GitGitGadget 2 siblings, 2 replies; 44+ messages in thread From: Patrick Steinhardt @ 2025-11-10 9:24 UTC (permalink / raw) To: ZheNing Hu via GitGitGadget; +Cc: git, Junio C Hamano, Jeff King, ZheNing Hu On Sun, Nov 09, 2025 at 10:22:54AM +0000, ZheNing Hu via GitGitGadget wrote: > From: ZheNing Hu <adlternative@gmail.com> > > Add --committer option to git-commit, allowing users to override the > committer identity similar to how --author works. This provides a more > convenient alternative to setting GIT_COMMITTER_* environment variables. Yeah, I can see how that's useful. > diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc > index 54c207ad45..a015c8328e 100644 > --- a/Documentation/git-commit.adoc > +++ b/Documentation/git-commit.adoc > @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] > [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] > [-F <file> | -m <msg>] [--reset-author] [--allow-empty] > [--allow-empty-message] [--no-verify] [-e] [--author=<author>] > - [--date=<date>] [--cleanup=<mode>] [--[no-]status] > + [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status] > [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] > [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] > [--] [<pathspec>...] Nit: I'd move `--committer` before `--date` so that it comes directly after `--author`. > @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details. > `--date=<date>`:: > Override the author date used in the commit. > > +`--committer=<committer>`:: > + Override the committer for the commit. Specify an explicit committer using the > + standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_ > + is assumed to be a pattern and is used to search for an existing > + commit by that author (i.e. `git rev-list --all -i --author=<committer>`); > + the commit author is then copied from the first such commit found. This matches the description of `--author`. > diff --git a/builtin/commit.c b/builtin/commit.c > index 0243f17d53..88e77cbaab 100644 > --- a/builtin/commit.c > +++ b/builtin/commit.c > @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident) > free(date); > } > > +static void determine_committer_info(struct strbuf *committer_ident) > +{ > + char *name, *email, *date; > + struct ident_split committer; > + > + name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); > + email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); > + date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); > + > + if (force_committer) { > + struct ident_split ident; > + > + if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0) > + die(_("malformed --committer parameter")); > + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); > + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); > + > + if (ident.date_begin) { > + struct strbuf date_buf = STRBUF_INIT; > + strbuf_addch(&date_buf, '@'); > + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); > + strbuf_addch(&date_buf, ' '); > + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); > + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > + } > + } > + > + if (force_date) { > + struct strbuf date_buf = STRBUF_INIT; > + if (parse_force_date(force_date, &date_buf)) > + die(_("invalid date format: %s"), force_date); > + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > + } > + > + strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date, > + IDENT_STRICT)); > + assert_split_ident(&committer, committer_ident); > + free(name); > + free(email); > + free(date); > +} > + > static int author_date_is_interesting(void) > { > return author_message || force_date; A lot of the infra in this new function is shared with `determine_author_info()`. It would be great if we could refactor it so that the common parts are shared given that this all is quite non-trivial. Maybe we could have something like `determine_identity()` that contains the common bits between both functions? It might ultimately not really be worth it, but at least the functionality in the `force_committer` condition feels like it should be pulled out. > @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[], > if (force_author && renew_authorship) > die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); > > + if (force_committer && !strchr(force_committer, '>')) > + force_committer = find_author_by_nickname(force_committer); > + > if (logfile || have_option_m || use_message) > use_editor = 0; > Is it the right thing to search by author here? Shouldn't we rather be searching by committer? > @@ -1930,8 +1978,13 @@ int cmd_commit(int argc, > append_merge_tag_headers(parents, &tail); > } > > + if (force_committer) { > + determine_committer_info(&committer_ident); > + } > + Nit: we tend to not use braces around single-line bodies. Thanks! Patrick ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 9:24 ` Patrick Steinhardt @ 2025-11-10 14:17 ` ZheNing Hu 2025-11-10 17:38 ` Junio C Hamano 1 sibling, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-10 14:17 UTC (permalink / raw) To: Patrick Steinhardt Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano, Jeff King Patrick Steinhardt <ps@pks.im> 于2025年11月10日周一 17:24写道: > > On Sun, Nov 09, 2025 at 10:22:54AM +0000, ZheNing Hu via GitGitGadget wrote: > > From: ZheNing Hu <adlternative@gmail.com> > > > > Add --committer option to git-commit, allowing users to override the > > committer identity similar to how --author works. This provides a more > > convenient alternative to setting GIT_COMMITTER_* environment variables. > > Yeah, I can see how that's useful. > I'm glad we're aligned on this. > > diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc > > index 54c207ad45..a015c8328e 100644 > > --- a/Documentation/git-commit.adoc > > +++ b/Documentation/git-commit.adoc > > @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] > > [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] > > [-F <file> | -m <msg>] [--reset-author] [--allow-empty] > > [--allow-empty-message] [--no-verify] [-e] [--author=<author>] > > - [--date=<date>] [--cleanup=<mode>] [--[no-]status] > > + [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status] > > [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] > > [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] > > [--] [<pathspec>...] > > Nit: I'd move `--committer` before `--date` so that it comes directly > after `--author`. > Agreed, will fix. > > @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details. > > `--date=<date>`:: > > Override the author date used in the commit. > > > > +`--committer=<committer>`:: > > + Override the committer for the commit. Specify an explicit committer using the > > + standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_ > > + is assumed to be a pattern and is used to search for an existing > > + commit by that author (i.e. `git rev-list --all -i --author=<committer>`); > > + the commit author is then copied from the first such commit found. > > This matches the description of `--author`. > Agreed, I will fix it to use committer description. > > diff --git a/builtin/commit.c b/builtin/commit.c > > index 0243f17d53..88e77cbaab 100644 > > --- a/builtin/commit.c > > +++ b/builtin/commit.c > > @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident) > > free(date); > > } > > > > +static void determine_committer_info(struct strbuf *committer_ident) > > +{ > > + char *name, *email, *date; > > + struct ident_split committer; > > + > > + name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); > > + email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); > > + date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); > > + > > + if (force_committer) { > > + struct ident_split ident; > > + > > + if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0) > > + die(_("malformed --committer parameter")); > > + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); > > + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); > > + > > + if (ident.date_begin) { > > + struct strbuf date_buf = STRBUF_INIT; > > + strbuf_addch(&date_buf, '@'); > > + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); > > + strbuf_addch(&date_buf, ' '); > > + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); > > + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > > + } > > + } > > + > > + if (force_date) { > > + struct strbuf date_buf = STRBUF_INIT; > > + if (parse_force_date(force_date, &date_buf)) > > + die(_("invalid date format: %s"), force_date); > > + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > > + } > > + > > + strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date, > > + IDENT_STRICT)); > > + assert_split_ident(&committer, committer_ident); > > + free(name); > > + free(email); > > + free(date); > > +} > > + > > static int author_date_is_interesting(void) > > { > > return author_message || force_date; > > A lot of the infra in this new function is shared with > `determine_author_info()`. It would be great if we could refactor it so > that the common parts are shared given that this all is quite > non-trivial. > > Maybe we could have something like `determine_identity()` that contains > the common bits between both functions? It might ultimately not really > be worth it, but at least the functionality in the `force_committer` > condition feels like it should be pulled out. > Good suggestion, I will refactor them to use `determine_identity()`, which should be more generic, and it even caught that I missed updating `GIT_COMMITTER_*`. > > @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[], > > if (force_author && renew_authorship) > > die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); > > > > + if (force_committer && !strchr(force_committer, '>')) > > + force_committer = find_author_by_nickname(force_committer); > > + > > if (logfile || have_option_m || use_message) > > use_editor = 0; > > > > Is it the right thing to search by author here? Shouldn't we rather be > searching by committer? > Yes, there should also be a `find_identity_by_nickname()` or `find_committer_by_nickname()` here. > > @@ -1930,8 +1978,13 @@ int cmd_commit(int argc, > > append_merge_tag_headers(parents, &tail); > > } > > > > + if (force_committer) { > > + determine_committer_info(&committer_ident); > > + } > > + > > Nit: we tend to not use braces around single-line bodies. > Agree. > Thanks! > > Patrick ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 9:24 ` Patrick Steinhardt 2025-11-10 14:17 ` ZheNing Hu @ 2025-11-10 17:38 ` Junio C Hamano 2025-11-11 13:19 ` ZheNing Hu 1 sibling, 1 reply; 44+ messages in thread From: Junio C Hamano @ 2025-11-10 17:38 UTC (permalink / raw) To: Patrick Steinhardt Cc: ZheNing Hu via GitGitGadget, git, Jeff King, ZheNing Hu Patrick Steinhardt <ps@pks.im> writes: > On Sun, Nov 09, 2025 at 10:22:54AM +0000, ZheNing Hu via GitGitGadget wrote: >> From: ZheNing Hu <adlternative@gmail.com> >> >> Add --committer option to git-commit, allowing users to override the >> committer identity similar to how --author works. This provides a more >> convenient alternative to setting GIT_COMMITTER_* environment variables. > > Yeah, I can see how that's useful. Well, I don't. Naming somebody other than yourself as the author may be something that is needed from time to time by human users, but lying about the committer who made commits? Our tradition is to give long rope to let users hang themselves, but we already have the environment variable override specifically designed for scripted uses, where there may be very legit uses of recording arbitrary committer identity that has nothing to do with the identity the current user who is running the Git processes usually uses. I do not think it is a "useful" change to make it more it ergonomic to perform certain operations that we may not want to encourage. So I dunno. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 17:38 ` Junio C Hamano @ 2025-11-11 13:19 ` ZheNing Hu 0 siblings, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-11 13:19 UTC (permalink / raw) To: Junio C Hamano Cc: Patrick Steinhardt, ZheNing Hu via GitGitGadget, git, Jeff King Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 01:38写道: > > Patrick Steinhardt <ps@pks.im> writes: > > > On Sun, Nov 09, 2025 at 10:22:54AM +0000, ZheNing Hu via GitGitGadget wrote: > >> From: ZheNing Hu <adlternative@gmail.com> > >> > >> Add --committer option to git-commit, allowing users to override the > >> committer identity similar to how --author works. This provides a more > >> convenient alternative to setting GIT_COMMITTER_* environment variables. > > > > Yeah, I can see how that's useful. > > Well, I don't. Naming somebody other than yourself as the author > may be something that is needed from time to time by human users, > but lying about the committer who made commits? Our tradition is to > give long rope to let users hang themselves, but we already have the > environment variable override specifically designed for scripted uses, > where there may be very legit uses of recording arbitrary committer > identity that has nothing to do with the identity the current user > who is running the Git processes usually uses. I do not think it is > a "useful" change to make it more it ergonomic to perform certain > operations that we may not want to encourage. > > So I dunno. > I understand your concern about not wanting to encourage users to misrepresent their committer identity. However, I'd like to point out that if someone truly wants to "lie" about the committer identity, there's nothing stopping them today. They can already do this through multiple methods. My primary motivation for this patch is not to make it easier to falsify identity, but rather to help legitimate users who work across different repositories with different identities. These users occasionally need to fix accidentally misconfigured committer information, and the current methods (setting environment variables or editing config files) can be cumbersome for this use case. You can see from [1] how many people have struggled with modifying commits committer. > [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 ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-09 10:22 [PATCH] commit: add --committer option ZheNing Hu via GitGitGadget 2025-11-10 9:24 ` Patrick Steinhardt @ 2025-11-10 16:50 ` Phillip Wood 2025-11-10 18:01 ` brian m. carlson 2025-11-11 13:01 ` ZheNing Hu 2025-11-10 16:56 ` [PATCH v2] " ZheNing Hu via GitGitGadget 2 siblings, 2 replies; 44+ messages in thread From: Phillip Wood @ 2025-11-10 16:50 UTC (permalink / raw) To: ZheNing Hu via GitGitGadget, git; +Cc: Junio C Hamano, Jeff King, ZheNing Hu Hi ZheNing On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > From: ZheNing Hu <adlternative@gmail.com> > > > This patch introduces the --committer option to git-commit, providing: > > 1. Consistency with the existing --author option > 2. A more convenient alternative to environment variables > 3. Better support for automated workflows and scripts > 4. Improved user experience when managing multiple identities What's the use case for the same person committing under different identities? We already have a config mechanism to set different identities for different repositories but I'm struggling to see why someone would want to create commits under multiple identities in a single repository. For scripts it easy enough to set the relevant environment variables if a tool wants to create commits under its own identity. Thanks Phillip > The implementation follows the same pattern as the --author option, > accepting the format "Name " and properly validating the input. > > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1 > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1 > Pull-Request: https://github.com/gitgitgadget/git/pull/1997 > > Documentation/git-commit.adoc | 9 +++- > builtin/commit.c | 58 ++++++++++++++++++++++++- > t/t7509-commit-authorship.sh | 80 +++++++++++++++++++++++++++++++++++ > 3 files changed, 144 insertions(+), 3 deletions(-) > > diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc > index 54c207ad45..a015c8328e 100644 > --- a/Documentation/git-commit.adoc > +++ b/Documentation/git-commit.adoc > @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] > [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] > [-F <file> | -m <msg>] [--reset-author] [--allow-empty] > [--allow-empty-message] [--no-verify] [-e] [--author=<author>] > - [--date=<date>] [--cleanup=<mode>] [--[no-]status] > + [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status] > [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] > [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] > [--] [<pathspec>...] > @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details. > `--date=<date>`:: > Override the author date used in the commit. > > +`--committer=<committer>`:: > + Override the committer for the commit. Specify an explicit committer using the > + standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_ > + is assumed to be a pattern and is used to search for an existing > + commit by that author (i.e. `git rev-list --all -i --author=<committer>`); > + the commit author is then copied from the first such commit found. > + > `-m <msg>`:: > `--message=<msg>`:: > Use _<msg>_ as the commit message. > diff --git a/builtin/commit.c b/builtin/commit.c > index 0243f17d53..88e77cbaab 100644 > --- a/builtin/commit.c > +++ b/builtin/commit.c > @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = { > " [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n" > " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" > " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" > - " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" > + " [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n" > " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" > " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" > " [--] [<pathspec>...]"), > @@ -112,6 +112,7 @@ static enum { > } commit_style; > > static const char *force_author; > +static const char *force_committer; > static char *logfile; > static char *template_file; > /* > @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident) > free(date); > } > > +static void determine_committer_info(struct strbuf *committer_ident) > +{ > + char *name, *email, *date; > + struct ident_split committer; > + > + name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); > + email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); > + date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); > + > + if (force_committer) { > + struct ident_split ident; > + > + if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0) > + die(_("malformed --committer parameter")); > + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); > + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); > + > + if (ident.date_begin) { > + struct strbuf date_buf = STRBUF_INIT; > + strbuf_addch(&date_buf, '@'); > + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); > + strbuf_addch(&date_buf, ' '); > + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); > + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > + } > + } > + > + if (force_date) { > + struct strbuf date_buf = STRBUF_INIT; > + if (parse_force_date(force_date, &date_buf)) > + die(_("invalid date format: %s"), force_date); > + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > + } > + > + strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date, > + IDENT_STRICT)); > + assert_split_ident(&committer, committer_ident); > + free(name); > + free(email); > + free(date); > +} > + > static int author_date_is_interesting(void) > { > return author_message || force_date; > @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[], > if (force_author && renew_authorship) > die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); > > + if (force_committer && !strchr(force_committer, '>')) > + force_committer = find_author_by_nickname(force_committer); > + > if (logfile || have_option_m || use_message) > use_editor = 0; > > @@ -1709,6 +1755,7 @@ int cmd_commit(int argc, > OPT_FILENAME('F', "file", &logfile, N_("read message from file")), > OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), > OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), > + OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")), > OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), > OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), > OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), > @@ -1785,6 +1832,7 @@ int cmd_commit(int argc, > > struct strbuf sb = STRBUF_INIT; > struct strbuf author_ident = STRBUF_INIT; > + struct strbuf committer_ident = STRBUF_INIT; > const char *index_file, *reflog_msg; > struct object_id oid; > struct commit_list *parents = NULL; > @@ -1930,8 +1978,13 @@ int cmd_commit(int argc, > append_merge_tag_headers(parents, &tail); > } > > + if (force_committer) { > + determine_committer_info(&committer_ident); > + } > + > if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, > - parents, &oid, author_ident.buf, NULL, > + parents, &oid, author_ident.buf, > + force_committer ? committer_ident.buf : NULL, > sign_commit, extra)) { > rollback_index_files(); > die(_("failed to write commit object")); > @@ -1980,6 +2033,7 @@ cleanup: > free_commit_extra_headers(extra); > free_commit_list(parents); > strbuf_release(&author_ident); > + strbuf_release(&committer_ident); > strbuf_release(&err); > strbuf_release(&sb); > free(logfile); > diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh > index 8e373b566b..45527f6a70 100755 > --- a/t/t7509-commit-authorship.sh > +++ b/t/t7509-commit-authorship.sh > @@ -12,6 +12,11 @@ author_header () { > sed -n -e '/^$/q' -e '/^author /p' > } > > +committer_header () { > + git cat-file commit "$1" | > + sed -n -e '/^$/q' -e '/^committer /p' > +} > + > message_body () { > git cat-file commit "$1" | > sed -e '1,/^$/d' > @@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' > test_cmp expect actual > ' > > +test_expect_success '--committer option overrides committer' ' > + git checkout Initial && > + echo "Test --committer" >>foo && > + test_tick && > + git commit -a -m "test committer" --committer="Custom Committer <custom@committer.example>" && > + committer_header HEAD >actual && > + grep "Custom Committer <custom@committer.example>" actual > +' > + > +test_expect_success '--committer with pattern search' ' > + echo "Test committer pattern" >>foo && > + test_tick && > + git commit -a -m "test committer pattern" --committer="Frigate" && > + committer_header HEAD >actual && > + grep "Frigate <flying@over.world>" actual > +' > + > +test_expect_success '--committer malformed parameter' ' > + echo "Test malformed" >>foo && > + test_tick && > + test_must_fail git commit -a -m "test malformed" --committer="malformed committer" > +' > + > +test_expect_success '--committer with --amend option' ' > + git checkout -f Initial && > + echo "Test committer with amend" >>foo && > + test_tick && > + git commit -a -m "initial commit for amend test" && > + echo "Modified for amend" >>foo && > + test_tick && > + git commit -a --amend --no-edit \ > + --author="Test Author <test@author.example>" \ > + --committer="Test Committer <test@committer.example>" && > + author_header HEAD >actual_author && > + grep "Test Author <test@author.example>" actual_author && > + committer_header HEAD >actual_committer && > + grep "Test Committer <test@committer.example>" actual_committer > +' > + > +test_expect_success 'GIT_COMMITTER_* environment variables' ' > + git checkout -f Initial && > + echo "Test env vars" >>foo && > + test_tick && > + GIT_COMMITTER_NAME="Env Committer" \ > + GIT_COMMITTER_EMAIL="env@test.example" \ > + git commit -a -m "test committer env vars" && > + committer_header HEAD >actual && > + grep "Env Committer <env@test.example>" actual > +' > + > +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' ' > + echo "Test override" >>foo && > + test_tick && > + GIT_COMMITTER_NAME="Env Committer" \ > + GIT_COMMITTER_EMAIL="env@test.example" \ > + git commit -a -m "test override" \ > + --committer="Override Committer <override@test.example>" && > + committer_header HEAD >actual && > + grep "Override Committer <override@test.example>" actual > +' > + > +test_expect_success '--date with --committer changes both author and committer dates' ' > + git checkout -f Initial && > + echo "Test date override" >>foo && > + test_tick && > + git commit -a -m "test date" \ > + --author="Date Author <date@author.example>" \ > + --committer="Date Committer <date@committer.example>" \ > + --date="2024-06-15 10:30:00 +0800" && > + git log -1 --format="%ai" >author_date && > + git log -1 --format="%ci" >committer_date && > + grep "2024-06-15 10:30:00 +0800" author_date && > + grep "2024-06-15 10:30:00 +0800" committer_date > +' > + > test_done > > base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84 ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 16:50 ` Phillip Wood @ 2025-11-10 18:01 ` brian m. carlson 2025-11-10 20:11 ` Jeff King 2025-11-11 13:01 ` ZheNing Hu 1 sibling, 1 reply; 44+ messages in thread From: brian m. carlson @ 2025-11-10 18:01 UTC (permalink / raw) To: phillip.wood Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano, Jeff King, ZheNing Hu [-- Attachment #1: Type: text/plain, Size: 1811 bytes --] On 2025-11-10 at 16:50:04, Phillip Wood wrote: > On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > > From: ZheNing Hu <adlternative@gmail.com> > > > > This patch introduces the --committer option to git-commit, providing: > > 1. Consistency with the existing --author option > > 2. A more convenient alternative to environment variables > > 3. Better support for automated workflows and scripts > > 4. Improved user experience when managing multiple identities > > What's the use case for the same person committing under different > identities? We already have a config mechanism to set different identities > for different repositories but I'm struggling to see why someone would want > to create commits under multiple identities in a single repository. For > scripts it easy enough to set the relevant environment variables if a tool > wants to create commits under its own identity. Someone who works on the same project under both their personal and corporate identities. For instance, me working on the Git project. Some open source projects also require a CLA and you have to use a particular address to match the one that's listed on the CLA. For example, Google requires an address with a Google account, so in the hypothetical state where I was going to contribute to one of their projects, I'd need to use a different committer identity with my Gmail address. I've also kept business logs in Git when I had a small business and I might well need to log approving a profit distribution (with my corporate address) and log accepting a profit distribution (with my personal address). Those would need separate digital signatures from my two different email addresses. -- brian m. carlson (they/them) Toronto, Ontario, CA [-- Attachment #2: signature.asc --] [-- Type: application/pgp-signature, Size: 262 bytes --] ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 18:01 ` brian m. carlson @ 2025-11-10 20:11 ` Jeff King 2025-11-10 22:06 ` Junio C Hamano ` (2 more replies) 0 siblings, 3 replies; 44+ messages in thread From: Jeff King @ 2025-11-10 20:11 UTC (permalink / raw) To: brian m. carlson Cc: phillip.wood, ZheNing Hu via GitGitGadget, git, Junio C Hamano, ZheNing Hu On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote: > On 2025-11-10 at 16:50:04, Phillip Wood wrote: > > On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > > > From: ZheNing Hu <adlternative@gmail.com> > > > > > > This patch introduces the --committer option to git-commit, providing: > > > 1. Consistency with the existing --author option > > > 2. A more convenient alternative to environment variables > > > 3. Better support for automated workflows and scripts > > > 4. Improved user experience when managing multiple identities > > > > What's the use case for the same person committing under different > > identities? We already have a config mechanism to set different identities > > for different repositories but I'm struggling to see why someone would want > > to create commits under multiple identities in a single repository. For > > scripts it easy enough to set the relevant environment variables if a tool > > wants to create commits under its own identity. > > Someone who works on the same project under both their personal and > corporate identities. For instance, me working on the Git project. > > Some open source projects also require a CLA and you have to use a > particular address to match the one that's listed on the CLA. For > example, Google requires an address with a Google account, so in the > hypothetical state where I was going to contribute to one of their > projects, I'd need to use a different committer identity with my Gmail > address. > > I've also kept business logs in Git when I had a small business and I > might well need to log approving a profit distribution (with my > corporate address) and log accepting a profit distribution (with my > personal address). Those would need separate digital signatures from my > two different email addresses. Is a "--committer" option the best solution there, though? I'd think you'd want to set user.* in the repo-level .git/config (or using a dir-specific include) would be less error-prone. That doesn't help for using two identities for the same repo, but in my experience it is easier to use two separate repositories for that to match the organization of the work (even if you may sometimes fetch between them). I'm not totally opposed to the new flag, and in general I'd defer to people who say they find a new feature useful. I'm just having a hard time imagining a scenario where it's the best option. -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 20:11 ` Jeff King @ 2025-11-10 22:06 ` Junio C Hamano 2025-11-11 6:54 ` Patrick Steinhardt 2025-11-11 13:42 ` ZheNing Hu 2 siblings, 0 replies; 44+ messages in thread From: Junio C Hamano @ 2025-11-10 22:06 UTC (permalink / raw) To: Jeff King Cc: brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git, ZheNing Hu Jeff King <peff@peff.net> writes: > Is a "--committer" option the best solution there, though? I'd think > you'd want to set user.* in the repo-level .git/config (or using a > dir-specific include) would be less error-prone. > > That doesn't help for using two identities for the same repo, but in my > experience it is easier to use two separate repositories for that to > match the organization of the work (even if you may sometimes fetch > between them). This happens to match my experience, but my use case may be rather skewed. The two sets of contents managed by my two clones that push into the same repository (of course to two different branches) are rather disjoint and they never merge into each other (in fact they do not even share the root commit). If you are working under two identities on the same codebase, I would imagine that two repo arrangement may be more cumbersome than working in a single repository and switching between the identities, and may be preferrable as long as you are confident that you won't commit a change under the "other" (wrong) identity. Of course, your reflogs and notes would also follow the "then-current" committer identity, so if I were to flip between two identities while working on the same codebase in a single repository, I am very likely to export GIT_{AUTHOR,COMMITTER}_* environment variables, dedicate that shell/window to the work done under that identity, and switch the environment variables if/when I want to switch (or have another shell/window with the other identities exported---perhaps I'd do that in a secondary worktree). I cannot imagine myself keep giving --author and --committer between my two identities without mistakes. > I'm not totally opposed to the new flag, and in general I'd defer to > people who say they find a new feature useful. I'm just having a hard > time imagining a scenario where it's the best option. Same here. The "give them long enough rope" principle tells me that this may be worth having if even only to have symmetry with existing "--author" option, but these two are not inherently symmetric to begin with, and I am not sure if there is a scenario in which this new option is the best thing to use. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 20:11 ` Jeff King 2025-11-10 22:06 ` Junio C Hamano @ 2025-11-11 6:54 ` Patrick Steinhardt 2025-11-11 14:53 ` Phillip Wood 2025-11-11 13:42 ` ZheNing Hu 2 siblings, 1 reply; 44+ messages in thread From: Patrick Steinhardt @ 2025-11-11 6:54 UTC (permalink / raw) To: Jeff King Cc: brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git, Junio C Hamano, ZheNing Hu On Mon, Nov 10, 2025 at 03:11:36PM -0500, Jeff King wrote: > On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote: > > > On 2025-11-10 at 16:50:04, Phillip Wood wrote: > > > On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > > > > From: ZheNing Hu <adlternative@gmail.com> > > > > > > > > This patch introduces the --committer option to git-commit, providing: > > > > 1. Consistency with the existing --author option > > > > 2. A more convenient alternative to environment variables > > > > 3. Better support for automated workflows and scripts > > > > 4. Improved user experience when managing multiple identities > > > > > > What's the use case for the same person committing under different > > > identities? We already have a config mechanism to set different identities > > > for different repositories but I'm struggling to see why someone would want > > > to create commits under multiple identities in a single repository. For > > > scripts it easy enough to set the relevant environment variables if a tool > > > wants to create commits under its own identity. > > > > Someone who works on the same project under both their personal and > > corporate identities. For instance, me working on the Git project. > > > > Some open source projects also require a CLA and you have to use a > > particular address to match the one that's listed on the CLA. For > > example, Google requires an address with a Google account, so in the > > hypothetical state where I was going to contribute to one of their > > projects, I'd need to use a different committer identity with my Gmail > > address. > > > > I've also kept business logs in Git when I had a small business and I > > might well need to log approving a profit distribution (with my > > corporate address) and log accepting a profit distribution (with my > > personal address). Those would need separate digital signatures from my > > two different email addresses. > > Is a "--committer" option the best solution there, though? I'd think > you'd want to set user.* in the repo-level .git/config (or using a > dir-specific include) would be less error-prone. > > That doesn't help for using two identities for the same repo, but in my > experience it is easier to use two separate repositories for that to > match the organization of the work (even if you may sometimes fetch > between them). > > I'm not totally opposed to the new flag, and in general I'd defer to > people who say they find a new feature useful. I'm just having a hard > time imagining a scenario where it's the best option. The reason why I find it useful is mostly scripted uses. Sure, you can already set environment variables there. But from my experience, environment variables tend to be a significantly worse API compared to command line options: - They are harder to discover in the manual page. - You don't have any "guarantees" that Git actually interprets them, as there won't be an error if you mistype the name. - Cause and effect may be detached with environment variables, but with command line options that's never the case. So I myself would prefer using "--committer" over its accompanying environment variable any point in time when I have a scripted use case for it. Patrick ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 6:54 ` Patrick Steinhardt @ 2025-11-11 14:53 ` Phillip Wood 2025-11-12 16:11 ` ZheNing Hu 0 siblings, 1 reply; 44+ messages in thread From: Phillip Wood @ 2025-11-11 14:53 UTC (permalink / raw) To: Patrick Steinhardt, Jeff King Cc: brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git, Junio C Hamano, ZheNing Hu On 11/11/2025 06:54, Patrick Steinhardt wrote: > On Mon, Nov 10, 2025 at 03:11:36PM -0500, Jeff King wrote: >> On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote: >> >>> On 2025-11-10 at 16:50:04, Phillip Wood wrote: >>>> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: >>>>> From: ZheNing Hu <adlternative@gmail.com> >>>>> >>>>> This patch introduces the --committer option to git-commit, providing: >>>>> 1. Consistency with the existing --author option >>>>> 2. A more convenient alternative to environment variables >>>>> 3. Better support for automated workflows and scripts >>>>> 4. Improved user experience when managing multiple identities >>>> >>>> What's the use case for the same person committing under different >>>> identities? We already have a config mechanism to set different identities >>>> for different repositories but I'm struggling to see why someone would want >>>> to create commits under multiple identities in a single repository. For >>>> scripts it easy enough to set the relevant environment variables if a tool >>>> wants to create commits under its own identity. >>> >>> Someone who works on the same project under both their personal and >>> corporate identities. For instance, me working on the Git project. >>> >>> Some open source projects also require a CLA and you have to use a >>> particular address to match the one that's listed on the CLA. For >>> example, Google requires an address with a Google account, so in the >>> hypothetical state where I was going to contribute to one of their >>> projects, I'd need to use a different committer identity with my Gmail >>> address. >>> >>> I've also kept business logs in Git when I had a small business and I >>> might well need to log approving a profit distribution (with my >>> corporate address) and log accepting a profit distribution (with my >>> personal address). Those would need separate digital signatures from my >>> two different email addresses. >> >> Is a "--committer" option the best solution there, though? I'd think >> you'd want to set user.* in the repo-level .git/config (or using a >> dir-specific include) would be less error-prone. >> >> That doesn't help for using two identities for the same repo, but in my >> experience it is easier to use two separate repositories for that to >> match the organization of the work (even if you may sometimes fetch >> between them). >> >> I'm not totally opposed to the new flag, and in general I'd defer to >> people who say they find a new feature useful. I'm just having a hard >> time imagining a scenario where it's the best option. Yes, it strikes me as very inconvenient to have to specify "--committer" each time. I'd have though you'd either want to (i) set up an alias in which case you can start your alias with "-c user.name=..." or "!GIT_COMMITTER_NAME=...", or (ii) set GIT_COMMITTER_NAME in your shell. > The reason why I find it useful is mostly scripted uses. Sure, you can > already set environment variables there. But from my experience, > environment variables tend to be a significantly worse API compared to > command line options: > > - They are harder to discover in the manual page. They're documented in the COMMIT INFORMATION section of the "git commit" man page, admittedly that comes after the options and examples but overriding the committer is a fairly niche requirement. > - You don't have any "guarantees" that Git actually interprets them, > as there won't be an error if you mistype the name. Playing devil's advocate even if you use "--committer" you still need to check the result to make sure there were no typo's in the committer info just as you would if you were setting GIT_COMMITTER_NAME. > - Cause and effect may be detached with environment variables, but > with command line options that's never the case. > > So I myself would prefer using "--committer" over its accompanying > environment variable any point in time when I have a scripted use case > for it. I'm wary of cluttering the UI of one of our core porcelain command with options for use with scripting. Thanks Phillip ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 14:53 ` Phillip Wood @ 2025-11-12 16:11 ` ZheNing Hu 0 siblings, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-12 16:11 UTC (permalink / raw) To: phillip.wood Cc: Patrick Steinhardt, Jeff King, brian m. carlson, ZheNing Hu via GitGitGadget, git, Junio C Hamano Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:53写道: > > On 11/11/2025 06:54, Patrick Steinhardt wrote: > > On Mon, Nov 10, 2025 at 03:11:36PM -0500, Jeff King wrote: > >> On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote: > >> > >>> On 2025-11-10 at 16:50:04, Phillip Wood wrote: > >>>> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > >>>>> From: ZheNing Hu <adlternative@gmail.com> > >>>>> > >>>>> This patch introduces the --committer option to git-commit, providing: > >>>>> 1. Consistency with the existing --author option > >>>>> 2. A more convenient alternative to environment variables > >>>>> 3. Better support for automated workflows and scripts > >>>>> 4. Improved user experience when managing multiple identities > >>>> > >>>> What's the use case for the same person committing under different > >>>> identities? We already have a config mechanism to set different identities > >>>> for different repositories but I'm struggling to see why someone would want > >>>> to create commits under multiple identities in a single repository. For > >>>> scripts it easy enough to set the relevant environment variables if a tool > >>>> wants to create commits under its own identity. > >>> > >>> Someone who works on the same project under both their personal and > >>> corporate identities. For instance, me working on the Git project. > >>> > >>> Some open source projects also require a CLA and you have to use a > >>> particular address to match the one that's listed on the CLA. For > >>> example, Google requires an address with a Google account, so in the > >>> hypothetical state where I was going to contribute to one of their > >>> projects, I'd need to use a different committer identity with my Gmail > >>> address. > >>> > >>> I've also kept business logs in Git when I had a small business and I > >>> might well need to log approving a profit distribution (with my > >>> corporate address) and log accepting a profit distribution (with my > >>> personal address). Those would need separate digital signatures from my > >>> two different email addresses. > >> > >> Is a "--committer" option the best solution there, though? I'd think > >> you'd want to set user.* in the repo-level .git/config (or using a > >> dir-specific include) would be less error-prone. > >> > >> That doesn't help for using two identities for the same repo, but in my > >> experience it is easier to use two separate repositories for that to > >> match the organization of the work (even if you may sometimes fetch > >> between them). > >> > >> I'm not totally opposed to the new flag, and in general I'd defer to > >> people who say they find a new feature useful. I'm just having a hard > >> time imagining a scenario where it's the best option. > > Yes, it strikes me as very inconvenient to have to specify "--committer" > each time. I'd have though you'd either want to (i) set up an alias in > which case you can start your alias with "-c user.name=..." or > "!GIT_COMMITTER_NAME=...", or (ii) set GIT_COMMITTER_NAME in your shell. Since modifying the committer is a low-frequency operation, there's no real need to specifically configure an alias for it. > > The reason why I find it useful is mostly scripted uses. Sure, you can > > already set environment variables there. But from my experience, > > environment variables tend to be a significantly worse API compared to > > command line options: > > > > - They are harder to discover in the manual page. > > They're documented in the COMMIT INFORMATION section of the "git commit" > man page, admittedly that comes after the options and examples but > overriding the committer is a fairly niche requirement. > Although it's niche or infrequent, when configuration errors do occur, the current fix process is quite painful for users. Previously, countless users have gone through great trouble seeking solutions because they didn't know how to resolve incorrect user.name and email configurations, which prevented them from pushing code to the company's servers. This may also suggest that Git lacks simpler ways to fix their identity. In fact, many users end up needing to use more complex tools like git filter-repo to attempt fixing their commits, whereas git commit --committer and --author could become a relatively simple and memorable fix method. > > - You don't have any "guarantees" that Git actually interprets them, > > as there won't be an error if you mistype the name. > > Playing devil's advocate even if you use "--committer" you still need to > check the result to make sure there were no typo's in the committer info > just as you would if you were setting GIT_COMMITTER_NAME. > GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL are split into two parts. Sometimes I even forget whether GIT_COMMITTER_EMAIL should be "<email.example.com>" or "email.example.com", whereas --author or --committer provides a more compact and memorable "user <email.example.com>" pair. > > - Cause and effect may be detached with environment variables, but > > with command line options that's never the case. > > > > So I myself would prefer using "--committer" over its accompanying > > environment variable any point in time when I have a scripted use case > > for it. > > I'm wary of cluttering the UI of one of our core porcelain command with > options for use with scripting. > > Thanks > > Phillip Thanks ZheNing Hu ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 20:11 ` Jeff King 2025-11-10 22:06 ` Junio C Hamano 2025-11-11 6:54 ` Patrick Steinhardt @ 2025-11-11 13:42 ` ZheNing Hu 2025-11-11 19:15 ` Jeff King 2 siblings, 1 reply; 44+ messages in thread From: ZheNing Hu @ 2025-11-11 13:42 UTC (permalink / raw) To: Jeff King Cc: brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git, Junio C Hamano Jeff King <peff@peff.net> 于2025年11月11日周二 04:11写道: > > On Mon, Nov 10, 2025 at 06:01:57PM +0000, brian m. carlson wrote: > > > On 2025-11-10 at 16:50:04, Phillip Wood wrote: > > > On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > > > > From: ZheNing Hu <adlternative@gmail.com> > > > > > > > > This patch introduces the --committer option to git-commit, providing: > > > > 1. Consistency with the existing --author option > > > > 2. A more convenient alternative to environment variables > > > > 3. Better support for automated workflows and scripts > > > > 4. Improved user experience when managing multiple identities > > > > > > What's the use case for the same person committing under different > > > identities? We already have a config mechanism to set different identities > > > for different repositories but I'm struggling to see why someone would want > > > to create commits under multiple identities in a single repository. For > > > scripts it easy enough to set the relevant environment variables if a tool > > > wants to create commits under its own identity. > > > > Someone who works on the same project under both their personal and > > corporate identities. For instance, me working on the Git project. > > > > Some open source projects also require a CLA and you have to use a > > particular address to match the one that's listed on the CLA. For > > example, Google requires an address with a Google account, so in the > > hypothetical state where I was going to contribute to one of their > > projects, I'd need to use a different committer identity with my Gmail > > address. > > > > I've also kept business logs in Git when I had a small business and I > > might well need to log approving a profit distribution (with my > > corporate address) and log accepting a profit distribution (with my > > personal address). Those would need separate digital signatures from my > > two different email addresses. > > Is a "--committer" option the best solution there, though? I'd think > you'd want to set user.* in the repo-level .git/config (or using a > dir-specific include) would be less error-prone. > > That doesn't help for using two identities for the same repo, but in my > experience it is easier to use two separate repositories for that to > match the organization of the work (even if you may sometimes fetch > between them). > > I'm not totally opposed to the new flag, and in general I'd defer to > people who say they find a new feature useful. I'm just having a hard > time imagining a scenario where it's the best option. > Sometimes it's because I forgot to configure the repository-level git user config and started development first. Only when I tried to correct the committer did I feel the pain. > -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 13:42 ` ZheNing Hu @ 2025-11-11 19:15 ` Jeff King 2025-11-11 20:16 ` Junio C Hamano 2025-11-12 16:37 ` ZheNing Hu 0 siblings, 2 replies; 44+ messages in thread From: Jeff King @ 2025-11-11 19:15 UTC (permalink / raw) To: ZheNing Hu Cc: brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git, Junio C Hamano On Tue, Nov 11, 2025 at 09:42:38PM +0800, ZheNing Hu wrote: > > Is a "--committer" option the best solution there, though? I'd think > > you'd want to set user.* in the repo-level .git/config (or using a > > dir-specific include) would be less error-prone. > > > > That doesn't help for using two identities for the same repo, but in my > > experience it is easier to use two separate repositories for that to > > match the organization of the work (even if you may sometimes fetch > > between them). > > > > I'm not totally opposed to the new flag, and in general I'd defer to > > people who say they find a new feature useful. I'm just having a hard > > time imagining a scenario where it's the best option. > > Sometimes it's because I forgot to configure the repository-level git user > config and started development first. Only when I tried to correct the > committer did I feel the pain. OK, this workflow does make sense to me. Fixing up an earlier mistake is inherently a one-off thing, and a command-line option is more ergonomic than using the environment variables. Two small thoughts: - I suspect what you'd usually want there is for the committer and the author to match. We have --committer-date-is-author-date for rebase, and conceptually I think something like --committer-is-author would do what you want here. But obviously it's less flexible, and I don't know if it's that much easier to use. - Because it's easy to make such mistakes, when you override the author (so that it doesn't match the committer), git-commit prints an extra "Author:" line in the output to make that more obvious. Should we do the same with committer when you've overridden it? We already do print "Committer:" when the ident was guessed from system info, but I wonder if it would make sense to print when it was forced. I dunno. I guess the time you most need the hint is when you meant to use --committer and --author together, but only used --author. But I don't know how Git would infer that case (versus the normal case of you applying someone else's work and crediting them with --author). I'm not sure if either is a useful direction, but they seemed sufficiently not-dumb for me to at least type them out. ;) -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 19:15 ` Jeff King @ 2025-11-11 20:16 ` Junio C Hamano 2025-11-11 21:33 ` Jeff King 2025-11-12 16:41 ` ZheNing Hu 2025-11-12 16:37 ` ZheNing Hu 1 sibling, 2 replies; 44+ messages in thread From: Junio C Hamano @ 2025-11-11 20:16 UTC (permalink / raw) To: Jeff King Cc: ZheNing Hu, brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git Jeff King <peff@peff.net> writes: >> Sometimes it's because I forgot to configure the repository-level git user >> config and started development first. Only when I tried to correct the >> committer did I feel the pain. > > OK, this workflow does make sense to me. Fixing up an earlier mistake is > inherently a one-off thing, and a command-line option is more ergonomic > than using the environment variables. Not very much, at least to me. Fixing up an earlier mistake may be one-shot thing but it is to correct multiple commits in one go, which would be error prone if you do so with "git commit --option". Either "fast-export | fast-import" pipe, or "git rebase" (which this patch does not give --committer option, but it already knows how to honor existing environment variables) would be used for that, no? > Two small thoughts: > > - I suspect what you'd usually want there is for the committer and the > author to match. We have --committer-date-is-author-date for rebase, > and conceptually I think something like --committer-is-author would > do what you want here. But obviously it's less flexible, and I don't > know if it's that much easier to use. I am not sure how the user experience of this would look like. > - Because it's easy to make such mistakes, when you override the > author (so that it doesn't match the committer), git-commit prints > an extra "Author:" line in the output to make that more obvious. > Should we do the same with committer when you've overridden it? We > already do print "Committer:" when the ident was guessed from system > info, but I wonder if it would make sense to print when it was > forced. I dunno. I guess the time you most need the hint is when you > meant to use --committer and --author together, but only used > --author. But I don't know how Git would infer that case (versus the > normal case of you applying someone else's work and crediting them > with --author). Yup, guessing based on what you did _not_ give is always a hard task for any tool ;-). > I'm not sure if either is a useful direction, but they seemed > sufficiently not-dumb for me to at least type them out. ;) > > -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 20:16 ` Junio C Hamano @ 2025-11-11 21:33 ` Jeff King 2025-11-11 21:58 ` Junio C Hamano 2025-11-12 16:46 ` ZheNing Hu 2025-11-12 16:41 ` ZheNing Hu 1 sibling, 2 replies; 44+ messages in thread From: Jeff King @ 2025-11-11 21:33 UTC (permalink / raw) To: Junio C Hamano Cc: ZheNing Hu, brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git On Tue, Nov 11, 2025 at 12:16:40PM -0800, Junio C Hamano wrote: > Jeff King <peff@peff.net> writes: > > >> Sometimes it's because I forgot to configure the repository-level git user > >> config and started development first. Only when I tried to correct the > >> committer did I feel the pain. > > > > OK, this workflow does make sense to me. Fixing up an earlier mistake is > > inherently a one-off thing, and a command-line option is more ergonomic > > than using the environment variables. > > Not very much, at least to me. Fixing up an earlier mistake may be > one-shot thing but it is to correct multiple commits in one go, > which would be error prone if you do so with "git commit --option". > Either "fast-export | fast-import" pipe, or "git rebase" (which this > patch does not give --committer option, but it already knows how to > honor existing environment variables) would be used for that, no? I usually lean on "commit --amend" for this, coupled with rebase if there are multiple commits. So I've used: git rebase -x "git commit --no-edit --amend --author=..." or similar when fixing up incorrect application of somebody else's patches (e.g., if I ended up using "git apply" and tweaking the commit message myself, rather than using "git am"). > > Two small thoughts: > > > > - I suspect what you'd usually want there is for the committer and the > > author to match. We have --committer-date-is-author-date for rebase, > > and conceptually I think something like --committer-is-author would > > do what you want here. But obviously it's less flexible, and I don't > > know if it's that much easier to use. > > I am not sure how the user experience of this would look like. I just mean being able to do: git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author instead of: git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>' -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 21:33 ` Jeff King @ 2025-11-11 21:58 ` Junio C Hamano 2025-11-11 22:23 ` Jeff King 2025-11-12 16:48 ` ZheNing Hu 2025-11-12 16:46 ` ZheNing Hu 1 sibling, 2 replies; 44+ messages in thread From: Junio C Hamano @ 2025-11-11 21:58 UTC (permalink / raw) To: Jeff King Cc: ZheNing Hu, brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git Jeff King <peff@peff.net> writes: > I just mean being able to do: > > git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author > > instead of: > > git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>' Ah, I see. Like git -c user.name='Foo Bar' -c user.email=foo@example.com commit --amend Makes me wonder if we want user.ident that covers them both ;-) ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 21:58 ` Junio C Hamano @ 2025-11-11 22:23 ` Jeff King 2025-11-12 16:51 ` ZheNing Hu 2025-11-12 16:48 ` ZheNing Hu 1 sibling, 1 reply; 44+ messages in thread From: Jeff King @ 2025-11-11 22:23 UTC (permalink / raw) To: Junio C Hamano Cc: ZheNing Hu, brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git On Tue, Nov 11, 2025 at 01:58:21PM -0800, Junio C Hamano wrote: > Jeff King <peff@peff.net> writes: > > > I just mean being able to do: > > > > git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author > > > > instead of: > > > > git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>' > > Ah, I see. Like > > git -c user.name='Foo Bar' -c user.email=foo@example.com commit --amend > > Makes me wonder if we want user.ident that covers them both ;-) Hmm, I hadn't thought to use "-c" config for this. That makes me question the utility of --committer a little bit. ;) I guess it is slightly more convenient than "-c" in that it will trigger the find_author_by_nickname() magic. -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 22:23 ` Jeff King @ 2025-11-12 16:51 ` ZheNing Hu 0 siblings, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-12 16:51 UTC (permalink / raw) To: Jeff King Cc: Junio C Hamano, brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git Jeff King <peff@peff.net> 于2025年11月12日周三 06:23写道: > > On Tue, Nov 11, 2025 at 01:58:21PM -0800, Junio C Hamano wrote: > > > Jeff King <peff@peff.net> writes: > > > > > I just mean being able to do: > > > > > > git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author > > > > > > instead of: > > > > > > git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>' > > > > Ah, I see. Like > > > > git -c user.name='Foo Bar' -c user.email=foo@example.com commit --amend > > > > Makes me wonder if we want user.ident that covers them both ;-) > > Hmm, I hadn't thought to use "-c" config for this. That makes me > question the utility of --committer a little bit. ;) I guess it is > slightly more convenient than "-c" in that it will trigger the > find_author_by_nickname() magic. > The advantages of --committer over -c user.name -c user.email: 1. It's symmetric with --author 2. A single option is sufficient 3. As you mentioned, it can search for the committer in commit history (although that wasn't my original intention) > -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 21:58 ` Junio C Hamano 2025-11-11 22:23 ` Jeff King @ 2025-11-12 16:48 ` ZheNing Hu 1 sibling, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-12 16:48 UTC (permalink / raw) To: Junio C Hamano Cc: Jeff King, brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git Junio C Hamano <gitster@pobox.com> 于2025年11月12日周三 05:58写道: > > Jeff King <peff@peff.net> writes: > > > I just mean being able to do: > > > > git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author > > > > instead of: > > > > git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>' > > Ah, I see. Like > > git -c user.name='Foo Bar' -c user.email=foo@example.com commit --amend > You still need: git -c user.name ='Foo Bar' -c user.email=foo@example.com commit --amend --author It's hard to remember and not symmetric enough. > Makes me wonder if we want user.ident that covers them both ;-) ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 21:33 ` Jeff King 2025-11-11 21:58 ` Junio C Hamano @ 2025-11-12 16:46 ` ZheNing Hu 1 sibling, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-12 16:46 UTC (permalink / raw) To: Jeff King Cc: Junio C Hamano, brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git Jeff King <peff@peff.net> 于2025年11月12日周三 05:33写道: > > On Tue, Nov 11, 2025 at 12:16:40PM -0800, Junio C Hamano wrote: > > > Jeff King <peff@peff.net> writes: > > > > >> Sometimes it's because I forgot to configure the repository-level git user > > >> config and started development first. Only when I tried to correct the > > >> committer did I feel the pain. > > > > > > OK, this workflow does make sense to me. Fixing up an earlier mistake is > > > inherently a one-off thing, and a command-line option is more ergonomic > > > than using the environment variables. > > > > Not very much, at least to me. Fixing up an earlier mistake may be > > one-shot thing but it is to correct multiple commits in one go, > > which would be error prone if you do so with "git commit --option". > > Either "fast-export | fast-import" pipe, or "git rebase" (which this > > patch does not give --committer option, but it already knows how to > > honor existing environment variables) would be used for that, no? > > I usually lean on "commit --amend" for this, coupled with rebase if > there are multiple commits. So I've used: > > git rebase -x "git commit --no-edit --amend --author=..." > > or similar when fixing up incorrect application of somebody else's > patches (e.g., if I ended up using "git apply" and tweaking the commit > message myself, rather than using "git am"). > > > > Two small thoughts: > > > > > > - I suspect what you'd usually want there is for the committer and the > > > author to match. We have --committer-date-is-author-date for rebase, > > > and conceptually I think something like --committer-is-author would > > > do what you want here. But obviously it's less flexible, and I don't > > > know if it's that much easier to use. > > > > I am not sure how the user experience of this would look like. > > I just mean being able to do: > > git commit --amend --author='Foo Bar <foo@example.com>' --committer-is-author > > instead of: > > git commit --amend --author='Foo Bar <foo@example.com>' --committer='Foo Bar <foo@example.com>' > If I wanted an elegant parameter myself, it would definitely be `git commit --amend --user='Foo Bar <foo@example.com>'`, instead of `-A -B-is-A` > -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 20:16 ` Junio C Hamano 2025-11-11 21:33 ` Jeff King @ 2025-11-12 16:41 ` ZheNing Hu 1 sibling, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-12 16:41 UTC (permalink / raw) To: Junio C Hamano Cc: Jeff King, brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git Junio C Hamano <gitster@pobox.com> 于2025年11月12日周三 04:16写道: > > Jeff King <peff@peff.net> writes: > > >> Sometimes it's because I forgot to configure the repository-level git user > >> config and started development first. Only when I tried to correct the > >> committer did I feel the pain. > > > > OK, this workflow does make sense to me. Fixing up an earlier mistake is > > inherently a one-off thing, and a command-line option is more ergonomic > > than using the environment variables. > > Not very much, at least to me. Fixing up an earlier mistake may be > one-shot thing but it is to correct multiple commits in one go, > which would be error prone if you do so with "git commit --option". > Either "fast-export | fast-import" pipe, or "git rebase" (which this > patch does not give --committer option, but it already knows how to > honor existing environment variables) would be used for that, no? > Ha, perhaps this should be left to git rebase --committer --author in the future. GIT_AUTHOR_* and GIT_COMMITTER_* are indeed a bit cumbersome to use. > > Two small thoughts: > > > > - I suspect what you'd usually want there is for the committer and the > > author to match. We have --committer-date-is-author-date for rebase, > > and conceptually I think something like --committer-is-author would > > do what you want here. But obviously it's less flexible, and I don't > > know if it's that much easier to use. > > I am not sure how the user experience of this would look like. > > > - Because it's easy to make such mistakes, when you override the > > author (so that it doesn't match the committer), git-commit prints > > an extra "Author:" line in the output to make that more obvious. > > Should we do the same with committer when you've overridden it? We > > already do print "Committer:" when the ident was guessed from system > > info, but I wonder if it would make sense to print when it was > > forced. I dunno. I guess the time you most need the hint is when you > > meant to use --committer and --author together, but only used > > --author. But I don't know how Git would infer that case (versus the > > normal case of you applying someone else's work and crediting them > > with --author). > > Yup, guessing based on what you did _not_ give is always a hard task > for any tool ;-). > > > I'm not sure if either is a useful direction, but they seemed > > sufficiently not-dumb for me to at least type them out. ;) > > > > -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 19:15 ` Jeff King 2025-11-11 20:16 ` Junio C Hamano @ 2025-11-12 16:37 ` ZheNing Hu 1 sibling, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-12 16:37 UTC (permalink / raw) To: Jeff King Cc: brian m. carlson, phillip.wood, ZheNing Hu via GitGitGadget, git, Junio C Hamano Jeff King <peff@peff.net> 于2025年11月12日周三 03:15写道: > > On Tue, Nov 11, 2025 at 09:42:38PM +0800, ZheNing Hu wrote: > > > > Is a "--committer" option the best solution there, though? I'd think > > > you'd want to set user.* in the repo-level .git/config (or using a > > > dir-specific include) would be less error-prone. > > > > > > That doesn't help for using two identities for the same repo, but in my > > > experience it is easier to use two separate repositories for that to > > > match the organization of the work (even if you may sometimes fetch > > > between them). > > > > > > I'm not totally opposed to the new flag, and in general I'd defer to > > > people who say they find a new feature useful. I'm just having a hard > > > time imagining a scenario where it's the best option. > > > > Sometimes it's because I forgot to configure the repository-level git user > > config and started development first. Only when I tried to correct the > > committer did I feel the pain. > > OK, this workflow does make sense to me. Fixing up an earlier mistake is > inherently a one-off thing, and a command-line option is more ergonomic > than using the environment variables. > > Two small thoughts: > > - I suspect what you'd usually want there is for the committer and the > author to match. We have --committer-date-is-author-date for rebase, > and conceptually I think something like --committer-is-author would > do what you want here. But obviously it's less flexible, and I don't > know if it's that much easier to use. > Well, to be honest, I don't really like this kind of -A --BaseA option style. On the contrary, -A -B is actually simple and easy enough for me (or more symmetrical). However, it's true that git rebase probably doesn't have --author and --committer options. Perhaps this is also a pain point, or maybe something to look into for potential future contributions. > - Because it's easy to make such mistakes, when you override the > author (so that it doesn't match the committer), git-commit prints > an extra "Author:" line in the output to make that more obvious. > Should we do the same with committer when you've overridden it? We > already do print "Committer:" when the ident was guessed from system > info, but I wonder if it would make sense to print when it was > forced. I dunno. I guess the time you most need the hint is when you > meant to use --committer and --author together, but only used > --author. But I don't know how Git would infer that case (versus the > normal case of you applying someone else's work and crediting them > with --author). > Ah, you have a point. Perhaps the Commit should also be output, though it might be trivial (if you hadn't reminded me, I wouldn't have even noticed that git commit outputs Author when using --author). > I'm not sure if either is a useful direction, but they seemed > sufficiently not-dumb for me to at least type them out. ;) > > -Peff ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-10 16:50 ` Phillip Wood 2025-11-10 18:01 ` brian m. carlson @ 2025-11-11 13:01 ` ZheNing Hu 2025-11-11 14:38 ` Phillip Wood 1 sibling, 1 reply; 44+ messages in thread From: ZheNing Hu @ 2025-11-11 13:01 UTC (permalink / raw) To: phillip.wood; +Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano, Jeff King Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道: > > Hi ZheNing > > On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > > From: ZheNing Hu <adlternative@gmail.com> > > > > > > This patch introduces the --committer option to git-commit, providing: > > > > 1. Consistency with the existing --author option > > 2. A more convenient alternative to environment variables > > 3. Better support for automated workflows and scripts > > 4. Improved user experience when managing multiple identities > > What's the use case for the same person committing under different > identities? We already have a config mechanism to set different > identities for different repositories but I'm struggling to see why > someone would want to create commits under multiple identities in a > single repository. For scripts it easy enough to set the relevant > environment variables if a tool wants to create commits under its own > identity. > I frequently need to distinguish between different user.name and user.email configurations on our company's internal GitHub. The current problems are: When I misconfigure (which happens occasionally), git commit --author only fixes the author part, I still need to additionally set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables to fix the committer information These environment variables are painful to use, requiring manual setup every time If a --committer option could be provided to align with --author, users wouldn't need to remember and use these additional environment variables. This would greatly simplify the workflow and reduce cognitive overhead. > Thanks > > Phillip > > > The implementation follows the same pattern as the --author option, > > accepting the format "Name " and properly validating the input. > > > > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1 > > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1 > > Pull-Request: https://github.com/gitgitgadget/git/pull/1997 > > > > Documentation/git-commit.adoc | 9 +++- > > builtin/commit.c | 58 ++++++++++++++++++++++++- > > t/t7509-commit-authorship.sh | 80 +++++++++++++++++++++++++++++++++++ > > 3 files changed, 144 insertions(+), 3 deletions(-) > > > > diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc > > index 54c207ad45..a015c8328e 100644 > > --- a/Documentation/git-commit.adoc > > +++ b/Documentation/git-commit.adoc > > @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] > > [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] > > [-F <file> | -m <msg>] [--reset-author] [--allow-empty] > > [--allow-empty-message] [--no-verify] [-e] [--author=<author>] > > - [--date=<date>] [--cleanup=<mode>] [--[no-]status] > > + [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status] > > [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] > > [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] > > [--] [<pathspec>...] > > @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details. > > `--date=<date>`:: > > Override the author date used in the commit. > > > > +`--committer=<committer>`:: > > + Override the committer for the commit. Specify an explicit committer using the > > + standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_ > > + is assumed to be a pattern and is used to search for an existing > > + commit by that author (i.e. `git rev-list --all -i --author=<committer>`); > > + the commit author is then copied from the first such commit found. > > + > > `-m <msg>`:: > > `--message=<msg>`:: > > Use _<msg>_ as the commit message. > > diff --git a/builtin/commit.c b/builtin/commit.c > > index 0243f17d53..88e77cbaab 100644 > > --- a/builtin/commit.c > > +++ b/builtin/commit.c > > @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = { > > " [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n" > > " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" > > " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" > > - " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" > > + " [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n" > > " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" > > " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" > > " [--] [<pathspec>...]"), > > @@ -112,6 +112,7 @@ static enum { > > } commit_style; > > > > static const char *force_author; > > +static const char *force_committer; > > static char *logfile; > > static char *template_file; > > /* > > @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident) > > free(date); > > } > > > > +static void determine_committer_info(struct strbuf *committer_ident) > > +{ > > + char *name, *email, *date; > > + struct ident_split committer; > > + > > + name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); > > + email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); > > + date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); > > + > > + if (force_committer) { > > + struct ident_split ident; > > + > > + if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0) > > + die(_("malformed --committer parameter")); > > + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); > > + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); > > + > > + if (ident.date_begin) { > > + struct strbuf date_buf = STRBUF_INIT; > > + strbuf_addch(&date_buf, '@'); > > + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); > > + strbuf_addch(&date_buf, ' '); > > + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); > > + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > > + } > > + } > > + > > + if (force_date) { > > + struct strbuf date_buf = STRBUF_INIT; > > + if (parse_force_date(force_date, &date_buf)) > > + die(_("invalid date format: %s"), force_date); > > + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > > + } > > + > > + strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date, > > + IDENT_STRICT)); > > + assert_split_ident(&committer, committer_ident); > > + free(name); > > + free(email); > > + free(date); > > +} > > + > > static int author_date_is_interesting(void) > > { > > return author_message || force_date; > > @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[], > > if (force_author && renew_authorship) > > die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); > > > > + if (force_committer && !strchr(force_committer, '>')) > > + force_committer = find_author_by_nickname(force_committer); > > + > > if (logfile || have_option_m || use_message) > > use_editor = 0; > > > > @@ -1709,6 +1755,7 @@ int cmd_commit(int argc, > > OPT_FILENAME('F', "file", &logfile, N_("read message from file")), > > OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), > > OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), > > + OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")), > > OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), > > OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), > > OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), > > @@ -1785,6 +1832,7 @@ int cmd_commit(int argc, > > > > struct strbuf sb = STRBUF_INIT; > > struct strbuf author_ident = STRBUF_INIT; > > + struct strbuf committer_ident = STRBUF_INIT; > > const char *index_file, *reflog_msg; > > struct object_id oid; > > struct commit_list *parents = NULL; > > @@ -1930,8 +1978,13 @@ int cmd_commit(int argc, > > append_merge_tag_headers(parents, &tail); > > } > > > > + if (force_committer) { > > + determine_committer_info(&committer_ident); > > + } > > + > > if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, > > - parents, &oid, author_ident.buf, NULL, > > + parents, &oid, author_ident.buf, > > + force_committer ? committer_ident.buf : NULL, > > sign_commit, extra)) { > > rollback_index_files(); > > die(_("failed to write commit object")); > > @@ -1980,6 +2033,7 @@ cleanup: > > free_commit_extra_headers(extra); > > free_commit_list(parents); > > strbuf_release(&author_ident); > > + strbuf_release(&committer_ident); > > strbuf_release(&err); > > strbuf_release(&sb); > > free(logfile); > > diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh > > index 8e373b566b..45527f6a70 100755 > > --- a/t/t7509-commit-authorship.sh > > +++ b/t/t7509-commit-authorship.sh > > @@ -12,6 +12,11 @@ author_header () { > > sed -n -e '/^$/q' -e '/^author /p' > > } > > > > +committer_header () { > > + git cat-file commit "$1" | > > + sed -n -e '/^$/q' -e '/^committer /p' > > +} > > + > > message_body () { > > git cat-file commit "$1" | > > sed -e '1,/^$/d' > > @@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' > > test_cmp expect actual > > ' > > > > +test_expect_success '--committer option overrides committer' ' > > + git checkout Initial && > > + echo "Test --committer" >>foo && > > + test_tick && > > + git commit -a -m "test committer" --committer="Custom Committer <custom@committer.example>" && > > + committer_header HEAD >actual && > > + grep "Custom Committer <custom@committer.example>" actual > > +' > > + > > +test_expect_success '--committer with pattern search' ' > > + echo "Test committer pattern" >>foo && > > + test_tick && > > + git commit -a -m "test committer pattern" --committer="Frigate" && > > + committer_header HEAD >actual && > > + grep "Frigate <flying@over.world>" actual > > +' > > + > > +test_expect_success '--committer malformed parameter' ' > > + echo "Test malformed" >>foo && > > + test_tick && > > + test_must_fail git commit -a -m "test malformed" --committer="malformed committer" > > +' > > + > > +test_expect_success '--committer with --amend option' ' > > + git checkout -f Initial && > > + echo "Test committer with amend" >>foo && > > + test_tick && > > + git commit -a -m "initial commit for amend test" && > > + echo "Modified for amend" >>foo && > > + test_tick && > > + git commit -a --amend --no-edit \ > > + --author="Test Author <test@author.example>" \ > > + --committer="Test Committer <test@committer.example>" && > > + author_header HEAD >actual_author && > > + grep "Test Author <test@author.example>" actual_author && > > + committer_header HEAD >actual_committer && > > + grep "Test Committer <test@committer.example>" actual_committer > > +' > > + > > +test_expect_success 'GIT_COMMITTER_* environment variables' ' > > + git checkout -f Initial && > > + echo "Test env vars" >>foo && > > + test_tick && > > + GIT_COMMITTER_NAME="Env Committer" \ > > + GIT_COMMITTER_EMAIL="env@test.example" \ > > + git commit -a -m "test committer env vars" && > > + committer_header HEAD >actual && > > + grep "Env Committer <env@test.example>" actual > > +' > > + > > +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' ' > > + echo "Test override" >>foo && > > + test_tick && > > + GIT_COMMITTER_NAME="Env Committer" \ > > + GIT_COMMITTER_EMAIL="env@test.example" \ > > + git commit -a -m "test override" \ > > + --committer="Override Committer <override@test.example>" && > > + committer_header HEAD >actual && > > + grep "Override Committer <override@test.example>" actual > > +' > > + > > +test_expect_success '--date with --committer changes both author and committer dates' ' > > + git checkout -f Initial && > > + echo "Test date override" >>foo && > > + test_tick && > > + git commit -a -m "test date" \ > > + --author="Date Author <date@author.example>" \ > > + --committer="Date Committer <date@committer.example>" \ > > + --date="2024-06-15 10:30:00 +0800" && > > + git log -1 --format="%ai" >author_date && > > + git log -1 --format="%ci" >committer_date && > > + grep "2024-06-15 10:30:00 +0800" author_date && > > + grep "2024-06-15 10:30:00 +0800" committer_date > > +' > > + > > test_done > > > > base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84 > ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 13:01 ` ZheNing Hu @ 2025-11-11 14:38 ` Phillip Wood 2025-11-12 15:58 ` ZheNing Hu 0 siblings, 1 reply; 44+ messages in thread From: Phillip Wood @ 2025-11-11 14:38 UTC (permalink / raw) To: ZheNing Hu, phillip.wood Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano, Jeff King On 11/11/2025 13:01, ZheNing Hu wrote: > Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道: >> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: >>> From: ZheNing Hu <adlternative@gmail.com> >>> >>> >>> This patch introduces the --committer option to git-commit, providing: >>> >>> 1. Consistency with the existing --author option >>> 2. A more convenient alternative to environment variables >>> 3. Better support for automated workflows and scripts >>> 4. Improved user experience when managing multiple identities >> >> What's the use case for the same person committing under different >> identities? We already have a config mechanism to set different >> identities for different repositories but I'm struggling to see why >> someone would want to create commits under multiple identities in a >> single repository. For scripts it easy enough to set the relevant >> environment variables if a tool wants to create commits under its own >> identity. >> > > I frequently need to distinguish between different user.name and user.email > configurations on our company's internal GitHub. > > The current problems are: > > When I misconfigure (which happens occasionally), git commit --author only fixes > the author part, I still need to additionally set GIT_COMMITTER_NAME and > GIT_COMMITTER_EMAIL environment variables to fix the committer information > These environment variables are painful to use, requiring manual setup > every time I'm afraid I don't quite follow. If you are amending existing commits to fix them up after you have corrected your configuration then they will have the correct committer automatically when you run "git commit --amend --author=..." to correct the author. If you are committing before you have realized that user.{name,email} are misconfigured then I don't see how "--committer" helps because you have not yet realized anything is wrong. Thanks Phillip > If a --committer option could be provided to align with --author, users wouldn't > need to remember and use these additional environment variables. > This would greatly simplify the workflow and reduce cognitive overhead. > > >> Thanks >> >> Phillip >> >>> The implementation follows the same pattern as the --author option, >>> accepting the format "Name " and properly validating the input. >>> >>> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1 >>> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1 >>> Pull-Request: https://github.com/gitgitgadget/git/pull/1997 >>> >>> Documentation/git-commit.adoc | 9 +++- >>> builtin/commit.c | 58 ++++++++++++++++++++++++- >>> t/t7509-commit-authorship.sh | 80 +++++++++++++++++++++++++++++++++++ >>> 3 files changed, 144 insertions(+), 3 deletions(-) >>> >>> diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc >>> index 54c207ad45..a015c8328e 100644 >>> --- a/Documentation/git-commit.adoc >>> +++ b/Documentation/git-commit.adoc >>> @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] >>> [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] >>> [-F <file> | -m <msg>] [--reset-author] [--allow-empty] >>> [--allow-empty-message] [--no-verify] [-e] [--author=<author>] >>> - [--date=<date>] [--cleanup=<mode>] [--[no-]status] >>> + [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status] >>> [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] >>> [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] >>> [--] [<pathspec>...] >>> @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details. >>> `--date=<date>`:: >>> Override the author date used in the commit. >>> >>> +`--committer=<committer>`:: >>> + Override the committer for the commit. Specify an explicit committer using the >>> + standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_ >>> + is assumed to be a pattern and is used to search for an existing >>> + commit by that author (i.e. `git rev-list --all -i --author=<committer>`); >>> + the commit author is then copied from the first such commit found. >>> + >>> `-m <msg>`:: >>> `--message=<msg>`:: >>> Use _<msg>_ as the commit message. >>> diff --git a/builtin/commit.c b/builtin/commit.c >>> index 0243f17d53..88e77cbaab 100644 >>> --- a/builtin/commit.c >>> +++ b/builtin/commit.c >>> @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = { >>> " [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n" >>> " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" >>> " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" >>> - " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" >>> + " [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n" >>> " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" >>> " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" >>> " [--] [<pathspec>...]"), >>> @@ -112,6 +112,7 @@ static enum { >>> } commit_style; >>> >>> static const char *force_author; >>> +static const char *force_committer; >>> static char *logfile; >>> static char *template_file; >>> /* >>> @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident) >>> free(date); >>> } >>> >>> +static void determine_committer_info(struct strbuf *committer_ident) >>> +{ >>> + char *name, *email, *date; >>> + struct ident_split committer; >>> + >>> + name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); >>> + email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); >>> + date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); >>> + >>> + if (force_committer) { >>> + struct ident_split ident; >>> + >>> + if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0) >>> + die(_("malformed --committer parameter")); >>> + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); >>> + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); >>> + >>> + if (ident.date_begin) { >>> + struct strbuf date_buf = STRBUF_INIT; >>> + strbuf_addch(&date_buf, '@'); >>> + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); >>> + strbuf_addch(&date_buf, ' '); >>> + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); >>> + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); >>> + } >>> + } >>> + >>> + if (force_date) { >>> + struct strbuf date_buf = STRBUF_INIT; >>> + if (parse_force_date(force_date, &date_buf)) >>> + die(_("invalid date format: %s"), force_date); >>> + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); >>> + } >>> + >>> + strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date, >>> + IDENT_STRICT)); >>> + assert_split_ident(&committer, committer_ident); >>> + free(name); >>> + free(email); >>> + free(date); >>> +} >>> + >>> static int author_date_is_interesting(void) >>> { >>> return author_message || force_date; >>> @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[], >>> if (force_author && renew_authorship) >>> die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); >>> >>> + if (force_committer && !strchr(force_committer, '>')) >>> + force_committer = find_author_by_nickname(force_committer); >>> + >>> if (logfile || have_option_m || use_message) >>> use_editor = 0; >>> >>> @@ -1709,6 +1755,7 @@ int cmd_commit(int argc, >>> OPT_FILENAME('F', "file", &logfile, N_("read message from file")), >>> OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), >>> OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), >>> + OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")), >>> OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), >>> OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), >>> OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), >>> @@ -1785,6 +1832,7 @@ int cmd_commit(int argc, >>> >>> struct strbuf sb = STRBUF_INIT; >>> struct strbuf author_ident = STRBUF_INIT; >>> + struct strbuf committer_ident = STRBUF_INIT; >>> const char *index_file, *reflog_msg; >>> struct object_id oid; >>> struct commit_list *parents = NULL; >>> @@ -1930,8 +1978,13 @@ int cmd_commit(int argc, >>> append_merge_tag_headers(parents, &tail); >>> } >>> >>> + if (force_committer) { >>> + determine_committer_info(&committer_ident); >>> + } >>> + >>> if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, >>> - parents, &oid, author_ident.buf, NULL, >>> + parents, &oid, author_ident.buf, >>> + force_committer ? committer_ident.buf : NULL, >>> sign_commit, extra)) { >>> rollback_index_files(); >>> die(_("failed to write commit object")); >>> @@ -1980,6 +2033,7 @@ cleanup: >>> free_commit_extra_headers(extra); >>> free_commit_list(parents); >>> strbuf_release(&author_ident); >>> + strbuf_release(&committer_ident); >>> strbuf_release(&err); >>> strbuf_release(&sb); >>> free(logfile); >>> diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh >>> index 8e373b566b..45527f6a70 100755 >>> --- a/t/t7509-commit-authorship.sh >>> +++ b/t/t7509-commit-authorship.sh >>> @@ -12,6 +12,11 @@ author_header () { >>> sed -n -e '/^$/q' -e '/^author /p' >>> } >>> >>> +committer_header () { >>> + git cat-file commit "$1" | >>> + sed -n -e '/^$/q' -e '/^committer /p' >>> +} >>> + >>> message_body () { >>> git cat-file commit "$1" | >>> sed -e '1,/^$/d' >>> @@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' >>> test_cmp expect actual >>> ' >>> >>> +test_expect_success '--committer option overrides committer' ' >>> + git checkout Initial && >>> + echo "Test --committer" >>foo && >>> + test_tick && >>> + git commit -a -m "test committer" --committer="Custom Committer <custom@committer.example>" && >>> + committer_header HEAD >actual && >>> + grep "Custom Committer <custom@committer.example>" actual >>> +' >>> + >>> +test_expect_success '--committer with pattern search' ' >>> + echo "Test committer pattern" >>foo && >>> + test_tick && >>> + git commit -a -m "test committer pattern" --committer="Frigate" && >>> + committer_header HEAD >actual && >>> + grep "Frigate <flying@over.world>" actual >>> +' >>> + >>> +test_expect_success '--committer malformed parameter' ' >>> + echo "Test malformed" >>foo && >>> + test_tick && >>> + test_must_fail git commit -a -m "test malformed" --committer="malformed committer" >>> +' >>> + >>> +test_expect_success '--committer with --amend option' ' >>> + git checkout -f Initial && >>> + echo "Test committer with amend" >>foo && >>> + test_tick && >>> + git commit -a -m "initial commit for amend test" && >>> + echo "Modified for amend" >>foo && >>> + test_tick && >>> + git commit -a --amend --no-edit \ >>> + --author="Test Author <test@author.example>" \ >>> + --committer="Test Committer <test@committer.example>" && >>> + author_header HEAD >actual_author && >>> + grep "Test Author <test@author.example>" actual_author && >>> + committer_header HEAD >actual_committer && >>> + grep "Test Committer <test@committer.example>" actual_committer >>> +' >>> + >>> +test_expect_success 'GIT_COMMITTER_* environment variables' ' >>> + git checkout -f Initial && >>> + echo "Test env vars" >>foo && >>> + test_tick && >>> + GIT_COMMITTER_NAME="Env Committer" \ >>> + GIT_COMMITTER_EMAIL="env@test.example" \ >>> + git commit -a -m "test committer env vars" && >>> + committer_header HEAD >actual && >>> + grep "Env Committer <env@test.example>" actual >>> +' >>> + >>> +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' ' >>> + echo "Test override" >>foo && >>> + test_tick && >>> + GIT_COMMITTER_NAME="Env Committer" \ >>> + GIT_COMMITTER_EMAIL="env@test.example" \ >>> + git commit -a -m "test override" \ >>> + --committer="Override Committer <override@test.example>" && >>> + committer_header HEAD >actual && >>> + grep "Override Committer <override@test.example>" actual >>> +' >>> + >>> +test_expect_success '--date with --committer changes both author and committer dates' ' >>> + git checkout -f Initial && >>> + echo "Test date override" >>foo && >>> + test_tick && >>> + git commit -a -m "test date" \ >>> + --author="Date Author <date@author.example>" \ >>> + --committer="Date Committer <date@committer.example>" \ >>> + --date="2024-06-15 10:30:00 +0800" && >>> + git log -1 --format="%ai" >author_date && >>> + git log -1 --format="%ci" >committer_date && >>> + grep "2024-06-15 10:30:00 +0800" author_date && >>> + grep "2024-06-15 10:30:00 +0800" committer_date >>> +' >>> + >>> test_done >>> >>> base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84 >> > ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-11 14:38 ` Phillip Wood @ 2025-11-12 15:58 ` ZheNing Hu 2025-11-12 17:24 ` Junio C Hamano 2025-11-16 22:12 ` Matej Dujava 0 siblings, 2 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-12 15:58 UTC (permalink / raw) To: phillip.wood; +Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano, Jeff King Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道: > > On 11/11/2025 13:01, ZheNing Hu wrote: > > Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道: > >> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > >>> From: ZheNing Hu <adlternative@gmail.com> > >>> > >>> > >>> This patch introduces the --committer option to git-commit, providing: > >>> > >>> 1. Consistency with the existing --author option > >>> 2. A more convenient alternative to environment variables > >>> 3. Better support for automated workflows and scripts > >>> 4. Improved user experience when managing multiple identities > >> > >> What's the use case for the same person committing under different > >> identities? We already have a config mechanism to set different > >> identities for different repositories but I'm struggling to see why > >> someone would want to create commits under multiple identities in a > >> single repository. For scripts it easy enough to set the relevant > >> environment variables if a tool wants to create commits under its own > >> identity. > >> > > > > I frequently need to distinguish between different user.name and user.email > > configurations on our company's internal GitHub. > > > > The current problems are: > > > > When I misconfigure (which happens occasionally), git commit --author only fixes > > the author part, I still need to additionally set GIT_COMMITTER_NAME and > > GIT_COMMITTER_EMAIL environment variables to fix the committer information > > These environment variables are painful to use, requiring manual setup > > every time > > I'm afraid I don't quite follow. If you are amending existing commits to > fix them up after you have corrected your configuration then they will > have the correct committer automatically when you run "git commit > --amend --author=..." to correct the author. If you are committing > before you have realized that user.{name,email} are misconfigured then I > don't see how "--committer" helps because you have not yet realized > anything is wrong. > You're right that after realizing the misconfiguration and correcting the repository's user.name and user.email, running `git commit --amend` will fix the committer information, but the author remains unchanged. Users then need an additional `git commit --amend --author=...` to fix the author, which does work but requires an extra step. I see your point that this becomes more cumbersome when dealing with multiple commits. In such cases, users currently need to use something like: ``` GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \ GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \ git rebase -f <target> ``` This is indeed tedious and error-prone, especially when you want to quickly fix and push commits to the platform. `git commit --amend --author --committer` or a new `git rebase --author --committer` would provide a more user-friendly workflow for correcting identity information after misconfiguration, eliminating the need to manually set multiple environment variables or run multiple commands. > Thanks > > Phillip > Thanks ZheNing Hu > > If a --committer option could be provided to align with --author, users wouldn't > > need to remember and use these additional environment variables. > > This would greatly simplify the workflow and reduce cognitive overhead. > > > > > >> Thanks > >> > >> Phillip > >> > >>> The implementation follows the same pattern as the --author option, > >>> accepting the format "Name " and properly validating the input. > >>> > >>> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v1 > >>> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v1 > >>> Pull-Request: https://github.com/gitgitgadget/git/pull/1997 > >>> > >>> Documentation/git-commit.adoc | 9 +++- > >>> builtin/commit.c | 58 ++++++++++++++++++++++++- > >>> t/t7509-commit-authorship.sh | 80 +++++++++++++++++++++++++++++++++++ > >>> 3 files changed, 144 insertions(+), 3 deletions(-) > >>> > >>> diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc > >>> index 54c207ad45..a015c8328e 100644 > >>> --- a/Documentation/git-commit.adoc > >>> +++ b/Documentation/git-commit.adoc > >>> @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] > >>> [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] > >>> [-F <file> | -m <msg>] [--reset-author] [--allow-empty] > >>> [--allow-empty-message] [--no-verify] [-e] [--author=<author>] > >>> - [--date=<date>] [--cleanup=<mode>] [--[no-]status] > >>> + [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status] > >>> [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] > >>> [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] > >>> [--] [<pathspec>...] > >>> @@ -181,6 +181,13 @@ See linkgit:git-rebase[1] for details. > >>> `--date=<date>`:: > >>> Override the author date used in the commit. > >>> > >>> +`--committer=<committer>`:: > >>> + Override the committer for the commit. Specify an explicit committer using the > >>> + standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_ > >>> + is assumed to be a pattern and is used to search for an existing > >>> + commit by that author (i.e. `git rev-list --all -i --author=<committer>`); > >>> + the commit author is then copied from the first such commit found. > >>> + > >>> `-m <msg>`:: > >>> `--message=<msg>`:: > >>> Use _<msg>_ as the commit message. > >>> diff --git a/builtin/commit.c b/builtin/commit.c > >>> index 0243f17d53..88e77cbaab 100644 > >>> --- a/builtin/commit.c > >>> +++ b/builtin/commit.c > >>> @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = { > >>> " [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n" > >>> " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" > >>> " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" > >>> - " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" > >>> + " [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n" > >>> " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" > >>> " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" > >>> " [--] [<pathspec>...]"), > >>> @@ -112,6 +112,7 @@ static enum { > >>> } commit_style; > >>> > >>> static const char *force_author; > >>> +static const char *force_committer; > >>> static char *logfile; > >>> static char *template_file; > >>> /* > >>> @@ -690,6 +691,48 @@ static void determine_author_info(struct strbuf *author_ident) > >>> free(date); > >>> } > >>> > >>> +static void determine_committer_info(struct strbuf *committer_ident) > >>> +{ > >>> + char *name, *email, *date; > >>> + struct ident_split committer; > >>> + > >>> + name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); > >>> + email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); > >>> + date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); > >>> + > >>> + if (force_committer) { > >>> + struct ident_split ident; > >>> + > >>> + if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0) > >>> + die(_("malformed --committer parameter")); > >>> + set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); > >>> + set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); > >>> + > >>> + if (ident.date_begin) { > >>> + struct strbuf date_buf = STRBUF_INIT; > >>> + strbuf_addch(&date_buf, '@'); > >>> + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); > >>> + strbuf_addch(&date_buf, ' '); > >>> + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); > >>> + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > >>> + } > >>> + } > >>> + > >>> + if (force_date) { > >>> + struct strbuf date_buf = STRBUF_INIT; > >>> + if (parse_force_date(force_date, &date_buf)) > >>> + die(_("invalid date format: %s"), force_date); > >>> + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > >>> + } > >>> + > >>> + strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date, > >>> + IDENT_STRICT)); > >>> + assert_split_ident(&committer, committer_ident); > >>> + free(name); > >>> + free(email); > >>> + free(date); > >>> +} > >>> + > >>> static int author_date_is_interesting(void) > >>> { > >>> return author_message || force_date; > >>> @@ -1321,6 +1364,9 @@ static int parse_and_validate_options(int argc, const char *argv[], > >>> if (force_author && renew_authorship) > >>> die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); > >>> > >>> + if (force_committer && !strchr(force_committer, '>')) > >>> + force_committer = find_author_by_nickname(force_committer); > >>> + > >>> if (logfile || have_option_m || use_message) > >>> use_editor = 0; > >>> > >>> @@ -1709,6 +1755,7 @@ int cmd_commit(int argc, > >>> OPT_FILENAME('F', "file", &logfile, N_("read message from file")), > >>> OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), > >>> OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), > >>> + OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")), > >>> OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), > >>> OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), > >>> OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), > >>> @@ -1785,6 +1832,7 @@ int cmd_commit(int argc, > >>> > >>> struct strbuf sb = STRBUF_INIT; > >>> struct strbuf author_ident = STRBUF_INIT; > >>> + struct strbuf committer_ident = STRBUF_INIT; > >>> const char *index_file, *reflog_msg; > >>> struct object_id oid; > >>> struct commit_list *parents = NULL; > >>> @@ -1930,8 +1978,13 @@ int cmd_commit(int argc, > >>> append_merge_tag_headers(parents, &tail); > >>> } > >>> > >>> + if (force_committer) { > >>> + determine_committer_info(&committer_ident); > >>> + } > >>> + > >>> if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, > >>> - parents, &oid, author_ident.buf, NULL, > >>> + parents, &oid, author_ident.buf, > >>> + force_committer ? committer_ident.buf : NULL, > >>> sign_commit, extra)) { > >>> rollback_index_files(); > >>> die(_("failed to write commit object")); > >>> @@ -1980,6 +2033,7 @@ cleanup: > >>> free_commit_extra_headers(extra); > >>> free_commit_list(parents); > >>> strbuf_release(&author_ident); > >>> + strbuf_release(&committer_ident); > >>> strbuf_release(&err); > >>> strbuf_release(&sb); > >>> free(logfile); > >>> diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh > >>> index 8e373b566b..45527f6a70 100755 > >>> --- a/t/t7509-commit-authorship.sh > >>> +++ b/t/t7509-commit-authorship.sh > >>> @@ -12,6 +12,11 @@ author_header () { > >>> sed -n -e '/^$/q' -e '/^author /p' > >>> } > >>> > >>> +committer_header () { > >>> + git cat-file commit "$1" | > >>> + sed -n -e '/^$/q' -e '/^committer /p' > >>> +} > >>> + > >>> message_body () { > >>> git cat-file commit "$1" | > >>> sed -e '1,/^$/d' > >>> @@ -171,4 +176,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' > >>> test_cmp expect actual > >>> ' > >>> > >>> +test_expect_success '--committer option overrides committer' ' > >>> + git checkout Initial && > >>> + echo "Test --committer" >>foo && > >>> + test_tick && > >>> + git commit -a -m "test committer" --committer="Custom Committer <custom@committer.example>" && > >>> + committer_header HEAD >actual && > >>> + grep "Custom Committer <custom@committer.example>" actual > >>> +' > >>> + > >>> +test_expect_success '--committer with pattern search' ' > >>> + echo "Test committer pattern" >>foo && > >>> + test_tick && > >>> + git commit -a -m "test committer pattern" --committer="Frigate" && > >>> + committer_header HEAD >actual && > >>> + grep "Frigate <flying@over.world>" actual > >>> +' > >>> + > >>> +test_expect_success '--committer malformed parameter' ' > >>> + echo "Test malformed" >>foo && > >>> + test_tick && > >>> + test_must_fail git commit -a -m "test malformed" --committer="malformed committer" > >>> +' > >>> + > >>> +test_expect_success '--committer with --amend option' ' > >>> + git checkout -f Initial && > >>> + echo "Test committer with amend" >>foo && > >>> + test_tick && > >>> + git commit -a -m "initial commit for amend test" && > >>> + echo "Modified for amend" >>foo && > >>> + test_tick && > >>> + git commit -a --amend --no-edit \ > >>> + --author="Test Author <test@author.example>" \ > >>> + --committer="Test Committer <test@committer.example>" && > >>> + author_header HEAD >actual_author && > >>> + grep "Test Author <test@author.example>" actual_author && > >>> + committer_header HEAD >actual_committer && > >>> + grep "Test Committer <test@committer.example>" actual_committer > >>> +' > >>> + > >>> +test_expect_success 'GIT_COMMITTER_* environment variables' ' > >>> + git checkout -f Initial && > >>> + echo "Test env vars" >>foo && > >>> + test_tick && > >>> + GIT_COMMITTER_NAME="Env Committer" \ > >>> + GIT_COMMITTER_EMAIL="env@test.example" \ > >>> + git commit -a -m "test committer env vars" && > >>> + committer_header HEAD >actual && > >>> + grep "Env Committer <env@test.example>" actual > >>> +' > >>> + > >>> +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' ' > >>> + echo "Test override" >>foo && > >>> + test_tick && > >>> + GIT_COMMITTER_NAME="Env Committer" \ > >>> + GIT_COMMITTER_EMAIL="env@test.example" \ > >>> + git commit -a -m "test override" \ > >>> + --committer="Override Committer <override@test.example>" && > >>> + committer_header HEAD >actual && > >>> + grep "Override Committer <override@test.example>" actual > >>> +' > >>> + > >>> +test_expect_success '--date with --committer changes both author and committer dates' ' > >>> + git checkout -f Initial && > >>> + echo "Test date override" >>foo && > >>> + test_tick && > >>> + git commit -a -m "test date" \ > >>> + --author="Date Author <date@author.example>" \ > >>> + --committer="Date Committer <date@committer.example>" \ > >>> + --date="2024-06-15 10:30:00 +0800" && > >>> + git log -1 --format="%ai" >author_date && > >>> + git log -1 --format="%ci" >committer_date && > >>> + grep "2024-06-15 10:30:00 +0800" author_date && > >>> + grep "2024-06-15 10:30:00 +0800" committer_date > >>> +' > >>> + > >>> test_done > >>> > >>> base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84 > >> > > > ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-12 15:58 ` ZheNing Hu @ 2025-11-12 17:24 ` Junio C Hamano 2025-11-15 5:29 ` ZheNing Hu 2025-11-16 22:12 ` Matej Dujava 1 sibling, 1 reply; 44+ messages in thread From: Junio C Hamano @ 2025-11-12 17:24 UTC (permalink / raw) To: ZheNing Hu; +Cc: phillip.wood, ZheNing Hu via GitGitGadget, git, Jeff King ZheNing Hu <adlternative@gmail.com> writes: >> I'm afraid I don't quite follow. If you are amending existing commits to >> fix them up after you have corrected your configuration then they will >> have the correct committer automatically when you run "git commit >> --amend --author=..." to correct the author. If you are committing >> before you have realized that user.{name,email} are misconfigured then I >> don't see how "--committer" helps because you have not yet realized >> anything is wrong. >> > > You're right that after realizing the misconfiguration and correcting the > repository's user.name and user.email, running `git commit --amend` will > fix the committer information, but the author remains unchanged. Users > then need an additional `git commit --amend --author=...` to fix the author, > which does work but requires an extra step. Isn't it more like "You need to run with --author to correct the authorship by amending the commit *anyway*, but while doing so, the committer information will automatically be corrected"? As I said earlier in a separate message, the author and the committer are not symmetric, so having "--author" does make sense in the above picture, while "--committer", as Phillip points out, much less. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-12 17:24 ` Junio C Hamano @ 2025-11-15 5:29 ` ZheNing Hu 2025-11-16 1:06 ` Junio C Hamano 0 siblings, 1 reply; 44+ messages in thread From: ZheNing Hu @ 2025-11-15 5:29 UTC (permalink / raw) To: Junio C Hamano; +Cc: phillip.wood, ZheNing Hu via GitGitGadget, git, Jeff King Junio C Hamano <gitster@pobox.com> 于2025年11月13日周四 01:24写道: > > ZheNing Hu <adlternative@gmail.com> writes: > > >> I'm afraid I don't quite follow. If you are amending existing commits to > >> fix them up after you have corrected your configuration then they will > >> have the correct committer automatically when you run "git commit > >> --amend --author=..." to correct the author. If you are committing > >> before you have realized that user.{name,email} are misconfigured then I > >> don't see how "--committer" helps because you have not yet realized > >> anything is wrong. > >> > > > > You're right that after realizing the misconfiguration and correcting the > > repository's user.name and user.email, running `git commit --amend` will > > fix the committer information, but the author remains unchanged. Users > > then need an additional `git commit --amend --author=...` to fix the author, > > which does work but requires an extra step. > > Isn't it more like "You need to run with --author to correct the > authorship by amending the commit *anyway*, but while doing so, the > committer information will automatically be corrected"? As I said > earlier in a separate message, the author and the committer are not > symmetric, so having "--author" does make sense in the above picture, > while "--committer", as Phillip points out, much less. > Well, I admit that perhaps the design philosophy of author/committer is inconsistent (which has caused too much trouble), but for users, a consistent parameter interface is easier to understand and use. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-15 5:29 ` ZheNing Hu @ 2025-11-16 1:06 ` Junio C Hamano 2025-11-17 15:06 ` ZheNing Hu 0 siblings, 1 reply; 44+ messages in thread From: Junio C Hamano @ 2025-11-16 1:06 UTC (permalink / raw) To: ZheNing Hu; +Cc: phillip.wood, ZheNing Hu via GitGitGadget, git, Jeff King ZheNing Hu <adlternative@gmail.com> writes: >> Isn't it more like "You need to run with --author to correct the >> authorship by amending the commit *anyway*, but while doing so, the >> committer information will automatically be corrected"? As I said >> earlier in a separate message, the author and the committer are not >> symmetric, so having "--author" does make sense in the above picture, >> while "--committer", as Phillip points out, much less. >> > > Well, I admit that perhaps the design philosophy of author/committer is > inconsistent (which has caused too much trouble), but for users, a consistent > parameter interface is easier to understand and use. I do not think it is about design philosophy at all, though. The distinction comes from the difference between what "author" and "committer" fields record. The committer records the identity of the person who was at the keyboard when the commit object was created. The author records the identity of the person who wrote the change that the committer is turning into a commit. There is no symmetry between them, hence there is no inconsistency here. And it also comes from the actual human user behaviour. Many authors can pass their patches to a smaller number of committers who make them into part of the official project history, so when a commit is made, there is much stronger need to tweak who the author is for the commit than to tweak who the committer is. On the other hand, it is rare (if ever done) for multiple committers to share a single shell terminal session and take turns to make commit, where you would need to be able to say "this invocations of 'git commit' command is done by person X, who is different from the one who made the previous commit in this same shell session". ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-16 1:06 ` Junio C Hamano @ 2025-11-17 15:06 ` ZheNing Hu 0 siblings, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-17 15:06 UTC (permalink / raw) To: Junio C Hamano; +Cc: phillip.wood, ZheNing Hu via GitGitGadget, git, Jeff King Junio C Hamano <gitster@pobox.com> 于2025年11月16日周日 09:06写道: > > ZheNing Hu <adlternative@gmail.com> writes: > > >> Isn't it more like "You need to run with --author to correct the > >> authorship by amending the commit *anyway*, but while doing so, the > >> committer information will automatically be corrected"? As I said > >> earlier in a separate message, the author and the committer are not > >> symmetric, so having "--author" does make sense in the above picture, > >> while "--committer", as Phillip points out, much less. > >> > > > > Well, I admit that perhaps the design philosophy of author/committer is > > inconsistent (which has caused too much trouble), but for users, a consistent > > parameter interface is easier to understand and use. > > I do not think it is about design philosophy at all, though. > > The distinction comes from the difference between what "author" and > "committer" fields record. The committer records the identity of > the person who was at the keyboard when the commit object was > created. The author records the identity of the person who wrote > the change that the committer is turning into a commit. There is no > symmetry between them, hence there is no inconsistency here. > I understand the difference between committer and author, but some regular Git users don't really pay much attention to the distinction between the two. For these users, being able to quickly and easily correct user information is sufficient — it's just that some previous solutions were a bit complicated. > And it also comes from the actual human user behaviour. > > Many authors can pass their patches to a smaller number of > committers who make them into part of the official project history, > so when a commit is made, there is much stronger need to tweak who > the author is for the commit than to tweak who the committer is. On > the other hand, it is rare (if ever done) for multiple committers to > share a single shell terminal session and take turns to make commit, > where you would need to be able to say "this invocations of 'git > commit' command is done by person X, who is different from the one > who made the previous commit in this same shell session". > Yes, I believe users in this case are very rare. As I mentioned before, I just want to help those users who want to fix things after misconfiguration, making the fix a bit simpler. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-12 15:58 ` ZheNing Hu 2025-11-12 17:24 ` Junio C Hamano @ 2025-11-16 22:12 ` Matej Dujava 2025-11-17 14:27 ` Phillip Wood 2025-11-17 15:15 ` ZheNing Hu 1 sibling, 2 replies; 44+ messages in thread From: Matej Dujava @ 2025-11-16 22:12 UTC (permalink / raw) To: ZheNing Hu; +Cc: Phillip Wood, git, Junio C Hamano, Jeff King On Wed, Nov 12, 2025 at 11:58:02PM +0800, ZheNing Hu wrote: >Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道: >> >> On 11/11/2025 13:01, ZheNing Hu wrote: >> > Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道: >> >> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: >> >>> From: ZheNing Hu <adlternative@gmail.com> >> >>> >> >>> >> >>> This patch introduces the --committer option to git-commit, providing: >> >>> >> >>> 1. Consistency with the existing --author option >> >>> 2. A more convenient alternative to environment variables >> >>> 3. Better support for automated workflows and scripts >> >>> 4. Improved user experience when managing multiple identities >> >> >> >> What's the use case for the same person committing under different >> >> identities? We already have a config mechanism to set different >> >> identities for different repositories but I'm struggling to see why >> >> someone would want to create commits under multiple identities in a >> >> single repository. For scripts it easy enough to set the relevant >> >> environment variables if a tool wants to create commits under its own >> >> identity. >> >> >> > >> > I frequently need to distinguish between different user.name and user.email >> > configurations on our company's internal GitHub. >> > >> > The current problems are: >> > >> > When I misconfigure (which happens occasionally), git commit --author only fixes >> > the author part, I still need to additionally set GIT_COMMITTER_NAME and >> > GIT_COMMITTER_EMAIL environment variables to fix the committer information >> > These environment variables are painful to use, requiring manual setup >> > every time >> >> I'm afraid I don't quite follow. If you are amending existing commits to >> fix them up after you have corrected your configuration then they will >> have the correct committer automatically when you run "git commit >> --amend --author=..." to correct the author. If you are committing >> before you have realized that user.{name,email} are misconfigured then I >> don't see how "--committer" helps because you have not yet realized >> anything is wrong. >> > Hi I use includeIf pattern in a config to separate identities ~/.gitconfig: ``` [includeIf "gitdir:~/.local/src/personal/"] path ~/.gitconfig-personal [includeIf "gitdir:~/.local/src/companyA/"] path ~/.gitconfig-companyA [includeIf "gitdir:~/.local/src/companyB/"] path ~/.gitconfig-companyB ``` then each ~/.gitconfig-IDENTITY: ``` [user] name = ... email = ... signingkey = ... ``` >You're right that after realizing the misconfiguration and correcting the >repository's user.name and user.email, running `git commit --amend` will > fix the committer information, but the author remains unchanged. Users >then need an additional `git commit --amend --author=...` to fix the author, >which does work but requires an extra step. For just one commit, after you fix identity (update .git/config or move project so includeIf uses correct config) then `git commit --amend --reset-author` should get right identity for both commiter and author. > >I see your point that this becomes more cumbersome when dealing with >multiple commits. In such cases, users currently need to use something like: > >``` >GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \ >GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \ >git rebase -f <target> >``` In my test ^ (using 2.51.2) did not set specified AUTHOR identity, but using: git rebase <target> -fx "git commit --amend --no-edit --reset-author" is close to rewriting commits with new identity, but this will change both dates (committer, author). If --reset-author is not used but either GIT_AUTHOR_* are exported or --author '...' is used in a -x arg, then author date is kept untouched. > >This is indeed tedious and error-prone, especially when you want >to quickly fix and push commits to the platform. > >`git commit --amend --author --committer` or a new `git rebase >--author --committer` >would provide a more user-friendly workflow for correcting identity >information after misconfiguration, eliminating the need to manually >set multiple >environment variables or run multiple commands. > >> Thanks >> >> Phillip >> > >Thanks > >ZheNing Hu > -- Thanks, Matej ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-16 22:12 ` Matej Dujava @ 2025-11-17 14:27 ` Phillip Wood 2025-11-17 15:18 ` ZheNing Hu 2025-11-17 15:15 ` ZheNing Hu 1 sibling, 1 reply; 44+ messages in thread From: Phillip Wood @ 2025-11-17 14:27 UTC (permalink / raw) To: Matej Dujava, ZheNing Hu, git, Junio C Hamano, Jeff King On 16/11/2025 22:12, Matej Dujava wrote: > On Wed, Nov 12, 2025 at 11:58:02PM +0800, ZheNing Hu wrote: >> Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道: > > I use includeIf pattern in a config to separate identities > > ~/.gitconfig: > ``` > [includeIf "gitdir:~/.local/src/personal/"] > path ~/.gitconfig-personal > [includeIf "gitdir:~/.local/src/companyA/"] > path ~/.gitconfig-companyA > [includeIf "gitdir:~/.local/src/companyB/"] > path ~/.gitconfig-companyB > ``` > > then each > ~/.gitconfig-IDENTITY: > ``` > [user] > name = ... > email = ... > signingkey = ... > ``` I think that's a common pattern, so long as one can arrange the directory structure so that the repositories for each identity are under a different sub-directory it works well. >> You're right that after realizing the misconfiguration and correcting the >> repository's user.name and user.email, running `git commit --amend` will >> fix the committer information, but the author remains unchanged. Users >> then need an additional `git commit --amend --author=...` to fix the >> author, >> which does work but requires an extra step. > > For just one commit, after you fix identity (update .git/config or move > project so includeIf uses correct config) then `git commit --amend > --reset-author` should get right identity for both commiter and author. As you note below it also resets the author date which might to be desirable. >> I see your point that this becomes more cumbersome when dealing with >> multiple commits. In such cases, users currently need to use something >> like: >> >> ``` >> GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \ >> GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \ >> git rebase -f <target> >> ``` > > In my test ^ (using 2.51.2) did not set specified AUTHOR identity Indeed, rebase sets GIT_AUTHOR_{NAME,EMAIL,DATE} when running "git commit" to preserve the authorship of the commit being picked so any value that is set in the environment when running "git rebase" is ignored. > but using: > > git rebase <target> -fx "git commit --amend --no-edit --reset-author" > > is close to rewriting commits with new identity, but this will change > both dates (committer, author). > If --reset-author is not used but either GIT_AUTHOR_* are exported or > --author '...' is used in a -x arg, then author date is kept untouched. Yes "git rebase -x 'git commit --author=... --amend --no-edit'" is probably the easiest way to reset the author and committer. >> >> This is indeed tedious and error-prone, especially when you want >> to quickly fix and push commits to the platform. >> >> `git commit --amend --author --committer` or a new `git rebase >> --author --committer` >> would provide a more user-friendly workflow for correcting identity >> information after misconfiguration, eliminating the need to manually >> set multiple >> environment variables or run multiple commands. I'm still not sure why we need a "--committer" option when the committer identity is taken from the corrected config anyway. Thanks Phillip ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-17 14:27 ` Phillip Wood @ 2025-11-17 15:18 ` ZheNing Hu 0 siblings, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-17 15:18 UTC (permalink / raw) To: phillip.wood; +Cc: Matej Dujava, git, Junio C Hamano, Jeff King Phillip Wood <phillip.wood123@gmail.com> 于2025年11月17日周一 22:27写道: > > On 16/11/2025 22:12, Matej Dujava wrote: > > On Wed, Nov 12, 2025 at 11:58:02PM +0800, ZheNing Hu wrote: > >> Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道: > > > > I use includeIf pattern in a config to separate identities > > > > ~/.gitconfig: > > ``` > > [includeIf "gitdir:~/.local/src/personal/"] > > path ~/.gitconfig-personal > > [includeIf "gitdir:~/.local/src/companyA/"] > > path ~/.gitconfig-companyA > > [includeIf "gitdir:~/.local/src/companyB/"] > > path ~/.gitconfig-companyB > > ``` > > > > then each > > ~/.gitconfig-IDENTITY: > > ``` > > [user] > > name = ... > > email = ... > > signingkey = ... > > ``` > > I think that's a common pattern, so long as one can arrange the > directory structure so that the repositories for each identity are under > a different sub-directory it works well. > > >> You're right that after realizing the misconfiguration and correcting the > >> repository's user.name and user.email, running `git commit --amend` will > >> fix the committer information, but the author remains unchanged. Users > >> then need an additional `git commit --amend --author=...` to fix the > >> author, > >> which does work but requires an extra step. > > > > For just one commit, after you fix identity (update .git/config or move > > project so includeIf uses correct config) then `git commit --amend > > --reset-author` should get right identity for both commiter and author. > > As you note below it also resets the author date which might to be > desirable. > >> I see your point that this becomes more cumbersome when dealing with > >> multiple commits. In such cases, users currently need to use something > >> like: > >> > >> ``` > >> GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \ > >> GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \ > >> git rebase -f <target> > >> ``` > > > > In my test ^ (using 2.51.2) did not set specified AUTHOR identity > > Indeed, rebase sets GIT_AUTHOR_{NAME,EMAIL,DATE} when running "git > commit" to preserve the authorship of the commit being picked so any > value that is set in the environment when running "git rebase" is ignored. > > > but using: > > > > git rebase <target> -fx "git commit --amend --no-edit --reset-author" > > > > is close to rewriting commits with new identity, but this will change > > both dates (committer, author). > > If --reset-author is not used but either GIT_AUTHOR_* are exported or > > --author '...' is used in a -x arg, then author date is kept untouched. > > Yes "git rebase -x 'git commit --author=... --amend --no-edit'" is > probably the easiest way to reset the author and committer. > >> > >> This is indeed tedious and error-prone, especially when you want > >> to quickly fix and push commits to the platform. > >> > >> `git commit --amend --author --committer` or a new `git rebase > >> --author --committer` > >> would provide a more user-friendly workflow for correcting identity > >> information after misconfiguration, eliminating the need to manually > >> set multiple > >> environment variables or run multiple commands. > > I'm still not sure why we need a "--committer" option when the committer > identity is taken from the corrected config anyway. > Well, if the `git rebase --fx` approach is simple enough, I can also give up on introducing `--committer`. > Thanks > > Phillip > ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH] commit: add --committer option 2025-11-16 22:12 ` Matej Dujava 2025-11-17 14:27 ` Phillip Wood @ 2025-11-17 15:15 ` ZheNing Hu 1 sibling, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-17 15:15 UTC (permalink / raw) To: Matej Dujava, ZheNing Hu, Phillip Wood, git, Junio C Hamano, Jeff King Matej Dujava <mdujava@kocurkovo.cz> 于2025年11月17日周一 06:12写道: > > On Wed, Nov 12, 2025 at 11:58:02PM +0800, ZheNing Hu wrote: > >Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 22:38写道: > >> > >> On 11/11/2025 13:01, ZheNing Hu wrote: > >> > Phillip Wood <phillip.wood123@gmail.com> 于2025年11月11日周二 00:50写道: > >> >> On 09/11/2025 10:22, ZheNing Hu via GitGitGadget wrote: > >> >>> From: ZheNing Hu <adlternative@gmail.com> > >> >>> > >> >>> > >> >>> This patch introduces the --committer option to git-commit, providing: > >> >>> > >> >>> 1. Consistency with the existing --author option > >> >>> 2. A more convenient alternative to environment variables > >> >>> 3. Better support for automated workflows and scripts > >> >>> 4. Improved user experience when managing multiple identities > >> >> > >> >> What's the use case for the same person committing under different > >> >> identities? We already have a config mechanism to set different > >> >> identities for different repositories but I'm struggling to see why > >> >> someone would want to create commits under multiple identities in a > >> >> single repository. For scripts it easy enough to set the relevant > >> >> environment variables if a tool wants to create commits under its own > >> >> identity. > >> >> > >> > > >> > I frequently need to distinguish between different user.name and user.email > >> > configurations on our company's internal GitHub. > >> > > >> > The current problems are: > >> > > >> > When I misconfigure (which happens occasionally), git commit --author only fixes > >> > the author part, I still need to additionally set GIT_COMMITTER_NAME and > >> > GIT_COMMITTER_EMAIL environment variables to fix the committer information > >> > These environment variables are painful to use, requiring manual setup > >> > every time > >> > >> I'm afraid I don't quite follow. If you are amending existing commits to > >> fix them up after you have corrected your configuration then they will > >> have the correct committer automatically when you run "git commit > >> --amend --author=..." to correct the author. If you are committing > >> before you have realized that user.{name,email} are misconfigured then I > >> don't see how "--committer" helps because you have not yet realized > >> anything is wrong. > >> > > > > Hi > > I use includeIf pattern in a config to separate identities > > ~/.gitconfig: > ``` > [includeIf "gitdir:~/.local/src/personal/"] > path ~/.gitconfig-personal > [includeIf "gitdir:~/.local/src/companyA/"] > path ~/.gitconfig-companyA > [includeIf "gitdir:~/.local/src/companyB/"] > path ~/.gitconfig-companyB > ``` > > then each > ~/.gitconfig-IDENTITY: > ``` > [user] > name = ... > email = ... > signingkey = ... > ``` > This does indeed appear to be a very good standard, but unfortunately many users in the past and possibly even in the future may not be aware of such a good practice. > >You're right that after realizing the misconfiguration and correcting the > >repository's user.name and user.email, running `git commit --amend` will > > fix the committer information, but the author remains unchanged. Users > >then need an additional `git commit --amend --author=...` to fix the author, > >which does work but requires an extra step. > > For just one commit, after you fix identity (update .git/config or move > project so includeIf uses correct config) then `git commit --amend > --reset-author` should get right identity for both commiter and author. > Ok... > > > >I see your point that this becomes more cumbersome when dealing with > >multiple commits. In such cases, users currently need to use something like: > > > >``` > >GIT_AUTHOR_NAME="..." GIT_AUTHOR_EMAIL="..." \ > >GIT_COMMITTER_NAME="..." GIT_COMMITTER_EMAIL="..." \ > >git rebase -f <target> > >``` > > In my test ^ (using 2.51.2) did not set specified AUTHOR identity, but > using: > > git rebase <target> -fx "git commit --amend --no-edit --reset-author" > > is close to rewriting commits with new identity, but this will change > both dates (committer, author). > > If --reset-author is not used but either GIT_AUTHOR_* are exported or > --author '...' is used in a -x arg, then author date is kept untouched. > This does look like a good approach indeed, and it should pretty much meet the user's requirements. > > > >This is indeed tedious and error-prone, especially when you want > >to quickly fix and push commits to the platform. > > > >`git commit --amend --author --committer` or a new `git rebase > >--author --committer` > >would provide a more user-friendly workflow for correcting identity > >information after misconfiguration, eliminating the need to manually > >set multiple > >environment variables or run multiple commands. > > > >> Thanks > >> > >> Phillip > >> > > > >Thanks > > > >ZheNing Hu > > > > -- > Thanks, > Matej ^ permalink raw reply [flat|nested] 44+ messages in thread
* [PATCH v2] commit: add --committer option 2025-11-09 10:22 [PATCH] commit: add --committer option ZheNing Hu via GitGitGadget 2025-11-10 9:24 ` Patrick Steinhardt 2025-11-10 16:50 ` Phillip Wood @ 2025-11-10 16:56 ` ZheNing Hu via GitGitGadget 2025-11-10 19:22 ` Junio C Hamano 2025-11-12 16:55 ` [PATCH v3] " ZheNing Hu via GitGitGadget 2 siblings, 2 replies; 44+ messages in thread From: ZheNing Hu via GitGitGadget @ 2025-11-10 16:56 UTC (permalink / raw) To: git Cc: Junio C Hamano, Jeff King, Patrick Steinhardt, Phillip Wood, ZheNing Hu, ZheNing Hu From: ZheNing Hu <adlternative@gmail.com> Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables. Like --author, the --committer option supports two formats: - Explicit identity: --committer="Name <email@example.com>" - Pattern search: --committer="pattern" searches commit history for a matching committer and reuses that identity To share code with the existing --author option, this patch refactors: 1. find_author_by_nickname() into find_identity_by_nickname() which handles both author and committer searches through an is_author parameter. 2. determine_author_info() into determine_identity() which handles identity parsing and setting for both author and committer through an is_author parameter. Signed-off-by: ZheNing Hu <adlternative@gmail.com> Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com> --- commit: add --committer option Currently, when users need to override the committer identity in git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables, which can be cumbersome in scripting scenarios or when frequently switching committer identities. While git-commit already provides the --author option to conveniently override the author identity, there's no equivalent --committer option for the committer identity. This asymmetry creates an inconsistent user experience. This patch introduces the --committer option to git-commit, providing: 1. Consistency with the existing --author option 2. A more convenient alternative to environment variables 3. Better support for automated workflows and scripts 4. Improved user experience when managing multiple identities The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input. Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v2 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v2 Pull-Request: https://github.com/gitgitgadget/git/pull/1997 Range-diff vs v1: 1: 05e97b439f ! 1: 58e9e5c9d7 commit: add --committer option @@ Commit message committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables. - Signed-off-by: ZheNing Hu <adlternative@gmail.com> + Like --author, the --committer option supports two formats: + - Explicit identity: --committer="Name <email@example.com>" + - Pattern search: --committer="pattern" searches commit history for a + matching committer and reuses that identity + + To share code with the existing --author option, this patch refactors: + + 1. find_author_by_nickname() into find_identity_by_nickname() which + handles both author and committer searches through an is_author + parameter. + + 2. determine_author_info() into determine_identity() which handles + identity parsing and setting for both author and committer through + an is_author parameter. + Signed-off-by: ZheNing Hu <adlternative@gmail.com> Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com> ## Documentation/git-commit.adoc ## @@ Documentation/git-commit.adoc: git commit [-a | --interactive | --patch] [-s] [- [-F <file> | -m <msg>] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] - [--date=<date>] [--cleanup=<mode>] [--[no-]status] -+ [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status] ++ [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status] [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] [--] [<pathspec>...] @@ Documentation/git-commit.adoc: See linkgit:git-rebase[1] for details. - `--date=<date>`:: - Override the author date used in the commit. + commit by that author (i.e. `git rev-list --all -i --author=<author>`); + the commit author is then copied from the first such commit found. +`--committer=<committer>`:: + Override the committer for the commit. Specify an explicit committer using the -+ standard `A U Thor <committer@example.com>` format. Otherwise _<committer>_ ++ standard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_ + is assumed to be a pattern and is used to search for an existing -+ commit by that author (i.e. `git rev-list --all -i --author=<committer>`); -+ the commit author is then copied from the first such commit found. ++ commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`); ++ the commit committer is then copied from the first such commit found. + - `-m <msg>`:: - `--message=<msg>`:: - Use _<msg>_ as the commit message. + `--date=<date>`:: + Override the author date used in the commit. + ## builtin/commit.c ## @@ builtin/commit.c: static const char * const builtin_commit_usage[] = { @@ builtin/commit.c: static const char * const builtin_commit_usage[] = { " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" - " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" -+ " [--date=<date>] [--committer=<committer>] [--cleanup=<mode>] [--[no-]status]\n" ++ " [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" " [--] [<pathspec>...]"), @@ builtin/commit.c: static enum { static char *logfile; static char *template_file; /* -@@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident) - free(date); +@@ builtin/commit.c: static void set_ident_var(char **buf, char *val) + *buf = val; } -+static void determine_committer_info(struct strbuf *committer_ident) -+{ -+ char *name, *email, *date; -+ struct ident_split committer; -+ -+ name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); -+ email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); -+ date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); +-static void determine_author_info(struct strbuf *author_ident) ++static void determine_identity(struct strbuf *ident_str, int is_author) + { + char *name, *email, *date; +- struct ident_split author; +- +- name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); +- email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); +- date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); +- +- if (author_message) { +- struct ident_split ident; ++ struct ident_split ident; ++ const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME"; ++ const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL"; ++ const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE"; ++ const char *force_ident = is_author ? force_author : force_committer; ++ const char *param_name = is_author ? "--author" : "--committer"; ++ int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT; + -+ if (force_committer) { -+ struct ident_split ident; ++ name = xstrdup_or_null(getenv(env_name)); ++ email = xstrdup_or_null(getenv(env_email)); ++ date = xstrdup_or_null(getenv(env_date)); + -+ if (split_ident_line(&ident, force_committer, strlen(force_committer)) < 0) -+ die(_("malformed --committer parameter")); -+ set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); -+ set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); ++ if (is_author && author_message) { ++ struct ident_split msg_ident; + size_t len; + const char *a; + + a = find_commit_header(author_message_buffer, "author", &len); + if (!a) + die(_("commit '%s' lacks author header"), author_message); +- if (split_ident_line(&ident, a, len) < 0) ++ if (split_ident_line(&msg_ident, a, len) < 0) + die(_("commit '%s' has malformed author line"), author_message); + +- set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); +- set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); ++ set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin)); ++ set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin)); + +- if (ident.date_begin) { ++ if (msg_ident.date_begin) { + struct strbuf date_buf = STRBUF_INIT; + strbuf_addch(&date_buf, '@'); +- strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); ++ strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin); + strbuf_addch(&date_buf, ' '); +- strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); ++ strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin); + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); + } + } + +- if (force_author) { +- struct ident_split ident; ++ if (force_ident) { ++ struct ident_split force_ident_split; + -+ if (ident.date_begin) { ++ if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0) ++ die(_("malformed %s parameter"), param_name); ++ set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin)); ++ set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin)); + +- if (split_ident_line(&ident, force_author, strlen(force_author)) < 0) +- die(_("malformed --author parameter")); +- set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); +- set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); ++ if (!is_author && force_ident_split.date_begin) { + struct strbuf date_buf = STRBUF_INIT; + strbuf_addch(&date_buf, '@'); -+ strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); ++ strbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin); + strbuf_addch(&date_buf, ' '); -+ strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); ++ strbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin); + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); + } -+ } + } + + if (force_date) { +@@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident) + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); + } + +- strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, ++ strbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date, + IDENT_STRICT)); +- assert_split_ident(&author, author_ident); +- export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); +- export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); +- export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); ++ assert_split_ident(&ident, ident_str); + -+ if (force_date) { -+ struct strbuf date_buf = STRBUF_INIT; -+ if (parse_force_date(force_date, &date_buf)) -+ die(_("invalid date format: %s"), force_date); -+ set_ident_var(&date, strbuf_detach(&date_buf, NULL)); ++ if (is_author) { ++ export_one("GIT_AUTHOR_NAME", ident.name_begin, ident.name_end, 0); ++ export_one("GIT_AUTHOR_EMAIL", ident.mail_begin, ident.mail_end, 0); ++ export_one("GIT_AUTHOR_DATE", ident.date_begin, ident.tz_end, '@'); ++ } else { ++ export_one("GIT_COMMITTER_NAME", ident.name_begin, ident.name_end, 0); ++ export_one("GIT_COMMITTER_EMAIL", ident.mail_begin, ident.mail_end, 0); ++ export_one("GIT_COMMITTER_DATE", ident.date_begin, ident.tz_end, '@'); + } + -+ strbuf_addstr(committer_ident, fmt_ident(name, email, WANT_COMMITTER_IDENT, date, -+ IDENT_STRICT)); -+ assert_split_ident(&committer, committer_ident); -+ free(name); -+ free(email); -+ free(date); + free(name); + free(email); + free(date); + } + ++static void determine_author_info(struct strbuf *author_ident) ++{ ++ determine_identity(author_ident, 1); ++} ++ ++static void determine_committer_info(struct strbuf *committer_ident) ++{ ++ determine_identity(committer_ident, 0); +} + static int author_date_is_interesting(void) { return author_message || force_date; +@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix, + return 1; + } + +-static const char *find_author_by_nickname(const char *name) ++static const char *find_identity_by_nickname(const char *name, int is_author) + { + struct rev_info revs; + struct commit *commit; + struct strbuf buf = STRBUF_INIT; + const char *av[20]; + int ac = 0; ++ const char *field = is_author ? "author" : "committer"; ++ const char *format = is_author ? "%aN <%aE>" : "%cN <%cE>"; + + repo_init_revisions(the_repository, &revs, NULL); +- strbuf_addf(&buf, "--author=%s", name); ++ strbuf_addf(&buf, "--%s=%s", field, name); + av[++ac] = "--all"; + av[++ac] = "-i"; + av[++ac] = buf.buf; +@@ builtin/commit.c: static const char *find_author_by_nickname(const char *name) + ctx.date_mode.type = DATE_NORMAL; + strbuf_release(&buf); + repo_format_commit_message(the_repository, commit, +- "%aN <%aE>", &buf, &ctx); ++ format, &buf, &ctx); + release_revisions(&revs); + return strbuf_detach(&buf, NULL); + } +- die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name); ++ die(_("--%s '%s' is not 'Name <email>' and matches no existing %s"), ++ field, name, field); ++} ++ ++static const char *find_author_by_nickname(const char *name) ++{ ++ return find_identity_by_nickname(name, 1); ++} ++ ++static const char *find_committer_by_nickname(const char *name) ++{ ++ return find_identity_by_nickname(name, 0); + } + + static void handle_ignored_arg(struct wt_status *s) @@ builtin/commit.c: static int parse_and_validate_options(int argc, const char *argv[], if (force_author && renew_authorship) die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); + if (force_committer && !strchr(force_committer, '>')) -+ force_committer = find_author_by_nickname(force_committer); ++ force_committer = find_committer_by_nickname(force_committer); + if (logfile || have_option_m || use_message) use_editor = 0; @@ builtin/commit.c: int cmd_commit(int argc, append_merge_tag_headers(parents, &tail); } -+ if (force_committer) { ++ if (force_committer) + determine_committer_info(&committer_ident); -+ } + if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, - parents, &oid, author_ident.buf, NULL, @@ t/t7509-commit-authorship.sh: author_header () { message_body () { git cat-file commit "$1" | sed -e '1,/^$/d' + } + + test_expect_success '-C option copies authorship and message' ' +- test_commit --author Frigate\ \<flying@over.world\> \ ++ test_env GIT_COMMITTER_NAME="Frigate" \ ++ GIT_COMMITTER_EMAIL="flying@over.world" \ ++ test_commit --author Frigate\ \<flying@over.world\> \ + "Initial Commit" foo Initial Initial && + echo "Test 1" >>foo && + test_tick && @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' test_cmp expect actual ' @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PI + git checkout -f Initial && + echo "Test env vars" >>foo && + test_tick && -+ GIT_COMMITTER_NAME="Env Committer" \ -+ GIT_COMMITTER_EMAIL="env@test.example" \ -+ git commit -a -m "test committer env vars" && ++ test_env GIT_COMMITTER_NAME="Env Committer" \ ++ GIT_COMMITTER_EMAIL="env@test.example" \ ++ git commit -a -m "test committer env vars" && + committer_header HEAD >actual && + grep "Env Committer <env@test.example>" actual +' @@ t/t7509-commit-authorship.sh: test_expect_success '--reset-author with CHERRY_PI +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' ' + echo "Test override" >>foo && + test_tick && -+ GIT_COMMITTER_NAME="Env Committer" \ -+ GIT_COMMITTER_EMAIL="env@test.example" \ -+ git commit -a -m "test override" \ ++ test_env GIT_COMMITTER_NAME="Env Committer" \ ++ GIT_COMMITTER_EMAIL="env@test.example" \ ++ git commit -a -m "test override" \ + --committer="Override Committer <override@test.example>" && + committer_header HEAD >actual && + grep "Override Committer <override@test.example>" actual Documentation/git-commit.adoc | 9 ++- builtin/commit.c | 121 +++++++++++++++++++++++++--------- t/t7509-commit-authorship.sh | 84 ++++++++++++++++++++++- 3 files changed, 180 insertions(+), 34 deletions(-) diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc index 54c207ad45..ed4c54ae81 100644 --- a/Documentation/git-commit.adoc +++ b/Documentation/git-commit.adoc @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] [-F <file> | -m <msg>] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] - [--date=<date>] [--cleanup=<mode>] [--[no-]status] + [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status] [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] [--] [<pathspec>...] @@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details. commit by that author (i.e. `git rev-list --all -i --author=<author>`); the commit author is then copied from the first such commit found. +`--committer=<committer>`:: + Override the committer for the commit. Specify an explicit committer using the + standard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_ + is assumed to be a pattern and is used to search for an existing + commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`); + the commit committer is then copied from the first such commit found. + `--date=<date>`:: Override the author date used in the commit. diff --git a/builtin/commit.c b/builtin/commit.c index 0243f17d53..3b249dd878 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = { " [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n" " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" - " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" + " [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" " [--] [<pathspec>...]"), @@ -112,6 +112,7 @@ static enum { } commit_style; static const char *force_author; +static const char *force_committer; static char *logfile; static char *template_file; /* @@ -630,46 +631,61 @@ static void set_ident_var(char **buf, char *val) *buf = val; } -static void determine_author_info(struct strbuf *author_ident) +static void determine_identity(struct strbuf *ident_str, int is_author) { char *name, *email, *date; - struct ident_split author; - - name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); - email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); - date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); - - if (author_message) { - struct ident_split ident; + struct ident_split ident; + const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME"; + const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL"; + const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE"; + const char *force_ident = is_author ? force_author : force_committer; + const char *param_name = is_author ? "--author" : "--committer"; + int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT; + + name = xstrdup_or_null(getenv(env_name)); + email = xstrdup_or_null(getenv(env_email)); + date = xstrdup_or_null(getenv(env_date)); + + if (is_author && author_message) { + struct ident_split msg_ident; size_t len; const char *a; a = find_commit_header(author_message_buffer, "author", &len); if (!a) die(_("commit '%s' lacks author header"), author_message); - if (split_ident_line(&ident, a, len) < 0) + if (split_ident_line(&msg_ident, a, len) < 0) die(_("commit '%s' has malformed author line"), author_message); - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin)); + set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin)); - if (ident.date_begin) { + if (msg_ident.date_begin) { struct strbuf date_buf = STRBUF_INIT; strbuf_addch(&date_buf, '@'); - strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); + strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin); strbuf_addch(&date_buf, ' '); - strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); + strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin); set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } } - if (force_author) { - struct ident_split ident; + if (force_ident) { + struct ident_split force_ident_split; + + if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0) + die(_("malformed %s parameter"), param_name); + set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin)); + set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin)); - if (split_ident_line(&ident, force_author, strlen(force_author)) < 0) - die(_("malformed --author parameter")); - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + if (!is_author && force_ident_split.date_begin) { + struct strbuf date_buf = STRBUF_INIT; + strbuf_addch(&date_buf, '@'); + strbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin); + strbuf_addch(&date_buf, ' '); + strbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin); + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); + } } if (force_date) { @@ -679,17 +695,35 @@ static void determine_author_info(struct strbuf *author_ident) set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } - strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, + strbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date, IDENT_STRICT)); - assert_split_ident(&author, author_ident); - export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); - export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); - export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); + assert_split_ident(&ident, ident_str); + + if (is_author) { + export_one("GIT_AUTHOR_NAME", ident.name_begin, ident.name_end, 0); + export_one("GIT_AUTHOR_EMAIL", ident.mail_begin, ident.mail_end, 0); + export_one("GIT_AUTHOR_DATE", ident.date_begin, ident.tz_end, '@'); + } else { + export_one("GIT_COMMITTER_NAME", ident.name_begin, ident.name_end, 0); + export_one("GIT_COMMITTER_EMAIL", ident.mail_begin, ident.mail_end, 0); + export_one("GIT_COMMITTER_DATE", ident.date_begin, ident.tz_end, '@'); + } + free(name); free(email); free(date); } +static void determine_author_info(struct strbuf *author_ident) +{ + determine_identity(author_ident, 1); +} + +static void determine_committer_info(struct strbuf *committer_ident) +{ + determine_identity(committer_ident, 0); +} + static int author_date_is_interesting(void) { return author_message || force_date; @@ -1137,16 +1171,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 1; } -static const char *find_author_by_nickname(const char *name) +static const char *find_identity_by_nickname(const char *name, int is_author) { struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; const char *av[20]; int ac = 0; + const char *field = is_author ? "author" : "committer"; + const char *format = is_author ? "%aN <%aE>" : "%cN <%cE>"; repo_init_revisions(the_repository, &revs, NULL); - strbuf_addf(&buf, "--author=%s", name); + strbuf_addf(&buf, "--%s=%s", field, name); av[++ac] = "--all"; av[++ac] = "-i"; av[++ac] = buf.buf; @@ -1164,11 +1200,22 @@ static const char *find_author_by_nickname(const char *name) ctx.date_mode.type = DATE_NORMAL; strbuf_release(&buf); repo_format_commit_message(the_repository, commit, - "%aN <%aE>", &buf, &ctx); + format, &buf, &ctx); release_revisions(&revs); return strbuf_detach(&buf, NULL); } - die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name); + die(_("--%s '%s' is not 'Name <email>' and matches no existing %s"), + field, name, field); +} + +static const char *find_author_by_nickname(const char *name) +{ + return find_identity_by_nickname(name, 1); +} + +static const char *find_committer_by_nickname(const char *name) +{ + return find_identity_by_nickname(name, 0); } static void handle_ignored_arg(struct wt_status *s) @@ -1321,6 +1368,9 @@ static int parse_and_validate_options(int argc, const char *argv[], if (force_author && renew_authorship) die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); + if (force_committer && !strchr(force_committer, '>')) + force_committer = find_committer_by_nickname(force_committer); + if (logfile || have_option_m || use_message) use_editor = 0; @@ -1709,6 +1759,7 @@ int cmd_commit(int argc, OPT_FILENAME('F', "file", &logfile, N_("read message from file")), OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), + OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")), OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), @@ -1785,6 +1836,7 @@ int cmd_commit(int argc, struct strbuf sb = STRBUF_INIT; struct strbuf author_ident = STRBUF_INIT; + struct strbuf committer_ident = STRBUF_INIT; const char *index_file, *reflog_msg; struct object_id oid; struct commit_list *parents = NULL; @@ -1930,8 +1982,12 @@ int cmd_commit(int argc, append_merge_tag_headers(parents, &tail); } + if (force_committer) + determine_committer_info(&committer_ident); + if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, - parents, &oid, author_ident.buf, NULL, + parents, &oid, author_ident.buf, + force_committer ? committer_ident.buf : NULL, sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); @@ -1980,6 +2036,7 @@ cleanup: free_commit_extra_headers(extra); free_commit_list(parents); strbuf_release(&author_ident); + strbuf_release(&committer_ident); strbuf_release(&err); strbuf_release(&sb); free(logfile); diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh index 8e373b566b..7e163e02d1 100755 --- a/t/t7509-commit-authorship.sh +++ b/t/t7509-commit-authorship.sh @@ -12,13 +12,20 @@ author_header () { sed -n -e '/^$/q' -e '/^author /p' } +committer_header () { + git cat-file commit "$1" | + sed -n -e '/^$/q' -e '/^committer /p' +} + message_body () { git cat-file commit "$1" | sed -e '1,/^$/d' } test_expect_success '-C option copies authorship and message' ' - test_commit --author Frigate\ \<flying@over.world\> \ + test_env GIT_COMMITTER_NAME="Frigate" \ + GIT_COMMITTER_EMAIL="flying@over.world" \ + test_commit --author Frigate\ \<flying@over.world\> \ "Initial Commit" foo Initial Initial && echo "Test 1" >>foo && test_tick && @@ -171,4 +178,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' test_cmp expect actual ' +test_expect_success '--committer option overrides committer' ' + git checkout Initial && + echo "Test --committer" >>foo && + test_tick && + git commit -a -m "test committer" --committer="Custom Committer <custom@committer.example>" && + committer_header HEAD >actual && + grep "Custom Committer <custom@committer.example>" actual +' + +test_expect_success '--committer with pattern search' ' + echo "Test committer pattern" >>foo && + test_tick && + git commit -a -m "test committer pattern" --committer="Frigate" && + committer_header HEAD >actual && + grep "Frigate <flying@over.world>" actual +' + +test_expect_success '--committer malformed parameter' ' + echo "Test malformed" >>foo && + test_tick && + test_must_fail git commit -a -m "test malformed" --committer="malformed committer" +' + +test_expect_success '--committer with --amend option' ' + git checkout -f Initial && + echo "Test committer with amend" >>foo && + test_tick && + git commit -a -m "initial commit for amend test" && + echo "Modified for amend" >>foo && + test_tick && + git commit -a --amend --no-edit \ + --author="Test Author <test@author.example>" \ + --committer="Test Committer <test@committer.example>" && + author_header HEAD >actual_author && + grep "Test Author <test@author.example>" actual_author && + committer_header HEAD >actual_committer && + grep "Test Committer <test@committer.example>" actual_committer +' + +test_expect_success 'GIT_COMMITTER_* environment variables' ' + git checkout -f Initial && + echo "Test env vars" >>foo && + test_tick && + test_env GIT_COMMITTER_NAME="Env Committer" \ + GIT_COMMITTER_EMAIL="env@test.example" \ + git commit -a -m "test committer env vars" && + committer_header HEAD >actual && + grep "Env Committer <env@test.example>" actual +' + +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' ' + echo "Test override" >>foo && + test_tick && + test_env GIT_COMMITTER_NAME="Env Committer" \ + GIT_COMMITTER_EMAIL="env@test.example" \ + git commit -a -m "test override" \ + --committer="Override Committer <override@test.example>" && + committer_header HEAD >actual && + grep "Override Committer <override@test.example>" actual +' + +test_expect_success '--date with --committer changes both author and committer dates' ' + git checkout -f Initial && + echo "Test date override" >>foo && + test_tick && + git commit -a -m "test date" \ + --author="Date Author <date@author.example>" \ + --committer="Date Committer <date@committer.example>" \ + --date="2024-06-15 10:30:00 +0800" && + git log -1 --format="%ai" >author_date && + git log -1 --format="%ci" >committer_date && + grep "2024-06-15 10:30:00 +0800" author_date && + grep "2024-06-15 10:30:00 +0800" committer_date +' + test_done base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84 -- gitgitgadget ^ permalink raw reply related [flat|nested] 44+ messages in thread
* Re: [PATCH v2] commit: add --committer option 2025-11-10 16:56 ` [PATCH v2] " ZheNing Hu via GitGitGadget @ 2025-11-10 19:22 ` Junio C Hamano 2025-11-10 19:29 ` Junio C Hamano 2025-11-11 13:36 ` ZheNing Hu 2025-11-12 16:55 ` [PATCH v3] " ZheNing Hu via GitGitGadget 1 sibling, 2 replies; 44+ messages in thread From: Junio C Hamano @ 2025-11-10 19:22 UTC (permalink / raw) To: ZheNing Hu via GitGitGadget Cc: git, Jeff King, Patrick Steinhardt, Phillip Wood, ZheNing Hu "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes: > Signed-off-by: ZheNing Hu <adlternative@gmail.com> > Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com> What is this second author and how would its presence in the author list interact with your DCO obligation? How did you make sure that whatever is in this patch were not copied by the "agent" from somewhere that we cannot copy the code from before deciding to send this patch? The "cannot copy from" may come in different shapes, from "their code is proprietary" to "their licensing terms are not compatible with GPLv2" to "they welcome us borrowing but we must give credit to them", any of which we should be careful to avoid. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH v2] commit: add --committer option 2025-11-10 19:22 ` Junio C Hamano @ 2025-11-10 19:29 ` Junio C Hamano 2025-11-11 13:36 ` ZheNing Hu 1 sibling, 0 replies; 44+ messages in thread From: Junio C Hamano @ 2025-11-10 19:29 UTC (permalink / raw) To: ZheNing Hu via GitGitGadget Cc: git, Jeff King, Patrick Steinhardt, Phillip Wood, ZheNing Hu Junio C Hamano <gitster@pobox.com> writes: > "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes: > >> Signed-off-by: ZheNing Hu <adlternative@gmail.com> >> Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com> > > What is this second author and how would its presence in the author > list interact with your DCO obligation? > > How did you make sure that whatever is in this patch were not copied > by the "agent" from somewhere that we cannot copy the code from > before deciding to send this patch? The "cannot copy from" may come > in different shapes, from "their code is proprietary" to "their > licensing terms are not compatible with GPLv2" to "they welcome us > borrowing but we must give credit to them", any of which we should > be careful to avoid. Well, the last one is not something we should *avoid*. If their licensing terms are compatible with ours but they want to be credited, then we comply that request and credit them. But I think readers got the idea. Thanks. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH v2] commit: add --committer option 2025-11-10 19:22 ` Junio C Hamano 2025-11-10 19:29 ` Junio C Hamano @ 2025-11-11 13:36 ` ZheNing Hu 2025-11-11 15:40 ` Junio C Hamano 1 sibling, 1 reply; 44+ messages in thread From: ZheNing Hu @ 2025-11-11 13:36 UTC (permalink / raw) To: Junio C Hamano Cc: ZheNing Hu via GitGitGadget, git, Jeff King, Patrick Steinhardt, Phillip Wood Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 03:22写道: > > "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes: > > > Signed-off-by: ZheNing Hu <adlternative@gmail.com> > > Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com> > > What is this second author and how would its presence in the author > list interact with your DCO obligation? > > How did you make sure that whatever is in this patch were not copied > by the "agent" from somewhere that we cannot copy the code from > before deciding to send this patch? The "cannot copy from" may come > in different shapes, from "their code is proprietary" to "their > licensing terms are not compatible with GPLv2" to "they welcome us > borrowing but we must give credit to them", any of which we should > be careful to avoid. > > This was automatically added by some code assistance tools. I indeed forgot to consider its impact on the open source license. I'll remove it right away. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH v2] commit: add --committer option 2025-11-11 13:36 ` ZheNing Hu @ 2025-11-11 15:40 ` Junio C Hamano 2025-11-12 16:23 ` ZheNing Hu 0 siblings, 1 reply; 44+ messages in thread From: Junio C Hamano @ 2025-11-11 15:40 UTC (permalink / raw) To: ZheNing Hu Cc: ZheNing Hu via GitGitGadget, git, Jeff King, Patrick Steinhardt, Phillip Wood ZheNing Hu <adlternative@gmail.com> writes: > Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 03:22写道: >> >> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes: >> >> > Signed-off-by: ZheNing Hu <adlternative@gmail.com> >> > Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com> >> >> What is this second author and how would its presence in the author >> list interact with your DCO obligation? >> >> How did you make sure that whatever is in this patch were not copied >> by the "agent" from somewhere that we cannot copy the code from >> before deciding to send this patch? The "cannot copy from" may come >> in different shapes, from "their code is proprietary" to "their >> licensing terms are not compatible with GPLv2" to "they welcome us >> borrowing but we must give credit to them", any of which we should >> be careful to avoid. >> >> > > This was automatically added by some code assistance tools. > I indeed forgot to consider its impact on the open source license. > I'll remove it right away. Please don't silently remove it without answering the question you were asked. "The tool adds it but I disabled the agentic features of the tool and everything readers see in the submitted patch was what I typed, with no agent input" would be a perfect answer. "I did not consider the ramifications of use of the agentic tool, and I do not know where the code the tool added for me came from, so I cannot be sure I can contribute this patch to the project" would be a sad but may be an honest answer. "I cannot tell the origin but I can remove the line and claim I wrote everything myself" is not an answer that we want to see. I am sure that your answer would fall within the spectrum, and am hoping it would be the earlier, perfect one, or one close to it, but we need to hear it. Thanks. ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH v2] commit: add --committer option 2025-11-11 15:40 ` Junio C Hamano @ 2025-11-12 16:23 ` ZheNing Hu 0 siblings, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-12 16:23 UTC (permalink / raw) To: Junio C Hamano Cc: ZheNing Hu via GitGitGadget, git, Jeff King, Patrick Steinhardt, Phillip Wood Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 23:40写道: > > ZheNing Hu <adlternative@gmail.com> writes: > > > Junio C Hamano <gitster@pobox.com> 于2025年11月11日周二 03:22写道: > >> > >> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes: > >> > >> > Signed-off-by: ZheNing Hu <adlternative@gmail.com> > >> > Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com> > >> > >> What is this second author and how would its presence in the author > >> list interact with your DCO obligation? > >> > >> How did you make sure that whatever is in this patch were not copied > >> by the "agent" from somewhere that we cannot copy the code from > >> before deciding to send this patch? The "cannot copy from" may come > >> in different shapes, from "their code is proprietary" to "their > >> licensing terms are not compatible with GPLv2" to "they welcome us > >> borrowing but we must give credit to them", any of which we should > >> be careful to avoid. > >> > >> > > > > This was automatically added by some code assistance tools. > > I indeed forgot to consider its impact on the open source license. > > I'll remove it right away. > > Please don't silently remove it without answering the question you > were asked. "The tool adds it but I disabled the agentic features > of the tool and everything readers see in the submitted patch was > what I typed, with no agent input" would be a perfect answer. "I > did not consider the ramifications of use of the agentic tool, and I > do not know where the code the tool added for me came from, so I > cannot be sure I can contribute this patch to the project" would be > a sad but may be an honest answer. "I cannot tell the origin but I > can remove the line and claim I wrote everything myself" is not an > answer that we want to see. I am sure that your answer would fall > within the spectrum, and am hoping it would be the earlier, perfect > one, or one close to it, but we need to hear it. > Ha, I think my situation is closer to the second case, but I'm certain that identical code cannot be found on GitHub or any other platform. Moreover, this code is entirely modeled after Git's own code (e.g., determine_identity is based on determine_author_info). There's no possibility of copying code from other projects with licenses incompatible with Git. However, your reminder is valid, especially in this era where AI Agents are heavily infiltrating development. Any similar open-source contributions in the future will need to pay attention to this point. > Thanks. ^ permalink raw reply [flat|nested] 44+ messages in thread
* [PATCH v3] commit: add --committer option 2025-11-10 16:56 ` [PATCH v2] " ZheNing Hu via GitGitGadget 2025-11-10 19:22 ` Junio C Hamano @ 2025-11-12 16:55 ` ZheNing Hu via GitGitGadget 2025-11-12 18:56 ` Junio C Hamano 2025-11-15 15:43 ` [PATCH v4] " ZheNing Hu via GitGitGadget 1 sibling, 2 replies; 44+ messages in thread From: ZheNing Hu via GitGitGadget @ 2025-11-12 16:55 UTC (permalink / raw) To: git Cc: Junio C Hamano, Jeff King, Patrick Steinhardt, Phillip Wood, brian m. carlson, ZheNing Hu, ZheNing Hu From: ZheNing Hu <adlternative@gmail.com> Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables. Like --author, the --committer option supports two formats: - Explicit identity: --committer="Name <email@example.com>" - Pattern search: --committer="pattern" searches commit history for a matching committer and reuses that identity To share code with the existing --author option, this patch refactors: 1. find_author_by_nickname() into find_identity_by_nickname() which handles both author and committer searches through an is_author parameter. 2. determine_author_info() into determine_identity() which handles identity parsing and setting for both author and committer through an is_author parameter. Signed-off-by: ZheNing Hu <adlternative@gmail.com> --- commit: add --committer option Currently, when users need to override the committer identity in git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables, which can be cumbersome in scripting scenarios or when frequently switching committer identities. While git-commit already provides the --author option to conveniently override the author identity, there's no equivalent --committer option for the committer identity. This asymmetry creates an inconsistent user experience. This patch introduces the --committer option to git-commit, providing: 1. Consistency with the existing --author option 2. A more convenient alternative to environment variables 3. Better support for automated workflows and scripts 4. Improved user experience when managing multiple identities The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input. Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v3 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v3 Pull-Request: https://github.com/gitgitgadget/git/pull/1997 Range-diff vs v2: 1: 58e9e5c9d7 ! 1: acf724fad5 commit: add --committer option @@ Commit message an is_author parameter. Signed-off-by: ZheNing Hu <adlternative@gmail.com> - Co-authored-by: Aone-Agent <aone-agent@alibaba-inc.com> ## Documentation/git-commit.adoc ## @@ Documentation/git-commit.adoc: git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] Documentation/git-commit.adoc | 9 ++- builtin/commit.c | 121 +++++++++++++++++++++++++--------- t/t7509-commit-authorship.sh | 84 ++++++++++++++++++++++- 3 files changed, 180 insertions(+), 34 deletions(-) diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc index 54c207ad45..ed4c54ae81 100644 --- a/Documentation/git-commit.adoc +++ b/Documentation/git-commit.adoc @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] [-F <file> | -m <msg>] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] - [--date=<date>] [--cleanup=<mode>] [--[no-]status] + [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status] [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] [--] [<pathspec>...] @@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details. commit by that author (i.e. `git rev-list --all -i --author=<author>`); the commit author is then copied from the first such commit found. +`--committer=<committer>`:: + Override the committer for the commit. Specify an explicit committer using the + standard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_ + is assumed to be a pattern and is used to search for an existing + commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`); + the commit committer is then copied from the first such commit found. + `--date=<date>`:: Override the author date used in the commit. diff --git a/builtin/commit.c b/builtin/commit.c index 0243f17d53..3b249dd878 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = { " [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n" " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" - " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" + " [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" " [--] [<pathspec>...]"), @@ -112,6 +112,7 @@ static enum { } commit_style; static const char *force_author; +static const char *force_committer; static char *logfile; static char *template_file; /* @@ -630,46 +631,61 @@ static void set_ident_var(char **buf, char *val) *buf = val; } -static void determine_author_info(struct strbuf *author_ident) +static void determine_identity(struct strbuf *ident_str, int is_author) { char *name, *email, *date; - struct ident_split author; - - name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); - email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); - date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); - - if (author_message) { - struct ident_split ident; + struct ident_split ident; + const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME"; + const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL"; + const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE"; + const char *force_ident = is_author ? force_author : force_committer; + const char *param_name = is_author ? "--author" : "--committer"; + int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT; + + name = xstrdup_or_null(getenv(env_name)); + email = xstrdup_or_null(getenv(env_email)); + date = xstrdup_or_null(getenv(env_date)); + + if (is_author && author_message) { + struct ident_split msg_ident; size_t len; const char *a; a = find_commit_header(author_message_buffer, "author", &len); if (!a) die(_("commit '%s' lacks author header"), author_message); - if (split_ident_line(&ident, a, len) < 0) + if (split_ident_line(&msg_ident, a, len) < 0) die(_("commit '%s' has malformed author line"), author_message); - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin)); + set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin)); - if (ident.date_begin) { + if (msg_ident.date_begin) { struct strbuf date_buf = STRBUF_INIT; strbuf_addch(&date_buf, '@'); - strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); + strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin); strbuf_addch(&date_buf, ' '); - strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); + strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin); set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } } - if (force_author) { - struct ident_split ident; + if (force_ident) { + struct ident_split force_ident_split; + + if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0) + die(_("malformed %s parameter"), param_name); + set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin)); + set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin)); - if (split_ident_line(&ident, force_author, strlen(force_author)) < 0) - die(_("malformed --author parameter")); - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + if (!is_author && force_ident_split.date_begin) { + struct strbuf date_buf = STRBUF_INIT; + strbuf_addch(&date_buf, '@'); + strbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin); + strbuf_addch(&date_buf, ' '); + strbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin); + set_ident_var(&date, strbuf_detach(&date_buf, NULL)); + } } if (force_date) { @@ -679,17 +695,35 @@ static void determine_author_info(struct strbuf *author_ident) set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } - strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, + strbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date, IDENT_STRICT)); - assert_split_ident(&author, author_ident); - export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); - export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); - export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); + assert_split_ident(&ident, ident_str); + + if (is_author) { + export_one("GIT_AUTHOR_NAME", ident.name_begin, ident.name_end, 0); + export_one("GIT_AUTHOR_EMAIL", ident.mail_begin, ident.mail_end, 0); + export_one("GIT_AUTHOR_DATE", ident.date_begin, ident.tz_end, '@'); + } else { + export_one("GIT_COMMITTER_NAME", ident.name_begin, ident.name_end, 0); + export_one("GIT_COMMITTER_EMAIL", ident.mail_begin, ident.mail_end, 0); + export_one("GIT_COMMITTER_DATE", ident.date_begin, ident.tz_end, '@'); + } + free(name); free(email); free(date); } +static void determine_author_info(struct strbuf *author_ident) +{ + determine_identity(author_ident, 1); +} + +static void determine_committer_info(struct strbuf *committer_ident) +{ + determine_identity(committer_ident, 0); +} + static int author_date_is_interesting(void) { return author_message || force_date; @@ -1137,16 +1171,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 1; } -static const char *find_author_by_nickname(const char *name) +static const char *find_identity_by_nickname(const char *name, int is_author) { struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; const char *av[20]; int ac = 0; + const char *field = is_author ? "author" : "committer"; + const char *format = is_author ? "%aN <%aE>" : "%cN <%cE>"; repo_init_revisions(the_repository, &revs, NULL); - strbuf_addf(&buf, "--author=%s", name); + strbuf_addf(&buf, "--%s=%s", field, name); av[++ac] = "--all"; av[++ac] = "-i"; av[++ac] = buf.buf; @@ -1164,11 +1200,22 @@ static const char *find_author_by_nickname(const char *name) ctx.date_mode.type = DATE_NORMAL; strbuf_release(&buf); repo_format_commit_message(the_repository, commit, - "%aN <%aE>", &buf, &ctx); + format, &buf, &ctx); release_revisions(&revs); return strbuf_detach(&buf, NULL); } - die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name); + die(_("--%s '%s' is not 'Name <email>' and matches no existing %s"), + field, name, field); +} + +static const char *find_author_by_nickname(const char *name) +{ + return find_identity_by_nickname(name, 1); +} + +static const char *find_committer_by_nickname(const char *name) +{ + return find_identity_by_nickname(name, 0); } static void handle_ignored_arg(struct wt_status *s) @@ -1321,6 +1368,9 @@ static int parse_and_validate_options(int argc, const char *argv[], if (force_author && renew_authorship) die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); + if (force_committer && !strchr(force_committer, '>')) + force_committer = find_committer_by_nickname(force_committer); + if (logfile || have_option_m || use_message) use_editor = 0; @@ -1709,6 +1759,7 @@ int cmd_commit(int argc, OPT_FILENAME('F', "file", &logfile, N_("read message from file")), OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), + OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")), OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), @@ -1785,6 +1836,7 @@ int cmd_commit(int argc, struct strbuf sb = STRBUF_INIT; struct strbuf author_ident = STRBUF_INIT; + struct strbuf committer_ident = STRBUF_INIT; const char *index_file, *reflog_msg; struct object_id oid; struct commit_list *parents = NULL; @@ -1930,8 +1982,12 @@ int cmd_commit(int argc, append_merge_tag_headers(parents, &tail); } + if (force_committer) + determine_committer_info(&committer_ident); + if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, - parents, &oid, author_ident.buf, NULL, + parents, &oid, author_ident.buf, + force_committer ? committer_ident.buf : NULL, sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); @@ -1980,6 +2036,7 @@ cleanup: free_commit_extra_headers(extra); free_commit_list(parents); strbuf_release(&author_ident); + strbuf_release(&committer_ident); strbuf_release(&err); strbuf_release(&sb); free(logfile); diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh index 8e373b566b..7e163e02d1 100755 --- a/t/t7509-commit-authorship.sh +++ b/t/t7509-commit-authorship.sh @@ -12,13 +12,20 @@ author_header () { sed -n -e '/^$/q' -e '/^author /p' } +committer_header () { + git cat-file commit "$1" | + sed -n -e '/^$/q' -e '/^committer /p' +} + message_body () { git cat-file commit "$1" | sed -e '1,/^$/d' } test_expect_success '-C option copies authorship and message' ' - test_commit --author Frigate\ \<flying@over.world\> \ + test_env GIT_COMMITTER_NAME="Frigate" \ + GIT_COMMITTER_EMAIL="flying@over.world" \ + test_commit --author Frigate\ \<flying@over.world\> \ "Initial Commit" foo Initial Initial && echo "Test 1" >>foo && test_tick && @@ -171,4 +178,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' test_cmp expect actual ' +test_expect_success '--committer option overrides committer' ' + git checkout Initial && + echo "Test --committer" >>foo && + test_tick && + git commit -a -m "test committer" --committer="Custom Committer <custom@committer.example>" && + committer_header HEAD >actual && + grep "Custom Committer <custom@committer.example>" actual +' + +test_expect_success '--committer with pattern search' ' + echo "Test committer pattern" >>foo && + test_tick && + git commit -a -m "test committer pattern" --committer="Frigate" && + committer_header HEAD >actual && + grep "Frigate <flying@over.world>" actual +' + +test_expect_success '--committer malformed parameter' ' + echo "Test malformed" >>foo && + test_tick && + test_must_fail git commit -a -m "test malformed" --committer="malformed committer" +' + +test_expect_success '--committer with --amend option' ' + git checkout -f Initial && + echo "Test committer with amend" >>foo && + test_tick && + git commit -a -m "initial commit for amend test" && + echo "Modified for amend" >>foo && + test_tick && + git commit -a --amend --no-edit \ + --author="Test Author <test@author.example>" \ + --committer="Test Committer <test@committer.example>" && + author_header HEAD >actual_author && + grep "Test Author <test@author.example>" actual_author && + committer_header HEAD >actual_committer && + grep "Test Committer <test@committer.example>" actual_committer +' + +test_expect_success 'GIT_COMMITTER_* environment variables' ' + git checkout -f Initial && + echo "Test env vars" >>foo && + test_tick && + test_env GIT_COMMITTER_NAME="Env Committer" \ + GIT_COMMITTER_EMAIL="env@test.example" \ + git commit -a -m "test committer env vars" && + committer_header HEAD >actual && + grep "Env Committer <env@test.example>" actual +' + +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' ' + echo "Test override" >>foo && + test_tick && + test_env GIT_COMMITTER_NAME="Env Committer" \ + GIT_COMMITTER_EMAIL="env@test.example" \ + git commit -a -m "test override" \ + --committer="Override Committer <override@test.example>" && + committer_header HEAD >actual && + grep "Override Committer <override@test.example>" actual +' + +test_expect_success '--date with --committer changes both author and committer dates' ' + git checkout -f Initial && + echo "Test date override" >>foo && + test_tick && + git commit -a -m "test date" \ + --author="Date Author <date@author.example>" \ + --committer="Date Committer <date@committer.example>" \ + --date="2024-06-15 10:30:00 +0800" && + git log -1 --format="%ai" >author_date && + git log -1 --format="%ci" >committer_date && + grep "2024-06-15 10:30:00 +0800" author_date && + grep "2024-06-15 10:30:00 +0800" committer_date +' + test_done base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84 -- gitgitgadget ^ permalink raw reply related [flat|nested] 44+ messages in thread
* Re: [PATCH v3] commit: add --committer option 2025-11-12 16:55 ` [PATCH v3] " ZheNing Hu via GitGitGadget @ 2025-11-12 18:56 ` Junio C Hamano 2025-11-15 6:33 ` ZheNing Hu 2025-11-15 15:43 ` [PATCH v4] " ZheNing Hu via GitGitGadget 1 sibling, 1 reply; 44+ messages in thread From: Junio C Hamano @ 2025-11-12 18:56 UTC (permalink / raw) To: ZheNing Hu via GitGitGadget Cc: git, Jeff King, Patrick Steinhardt, Phillip Wood, brian m. carlson, ZheNing Hu "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes: > +`--committer=<committer>`:: > + Override the committer for the commit. Specify an explicit committer using the Isn't "set" or "use" more appropirate verb to use here? We already take the committer identity from multiple plases, like user.{name,email}, or GIT_COMMITTER_{NAME,EMAIL} configuration, and with the patch we also take it from a command line option, with the usual precedence order (i.e., command line trumps environment which trumps configuration). > -static void determine_author_info(struct strbuf *author_ident) > +static void determine_identity(struct strbuf *ident_str, int is_author) "is_author" does not sound grammatical for this case; if you are giving an ident of an unknown kind to this function and supplying another parameter to let it know which kind, "is_author" may make sense, but not here. As you will convert it into WANT_{AUTHOR,COMMITTER}_IDENT before using anyway, why not let the caller use the "enum want_ident" to tell this function what to do? > { > char *name, *email, *date; > - struct ident_split author; > - > - name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); > - email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); > - date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); > - > - if (author_message) { > - struct ident_split ident; > + struct ident_split ident; > + const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME"; > + const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL"; > + const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE"; > + const char *force_ident = is_author ? force_author : force_committer; > + const char *param_name = is_author ? "--author" : "--committer"; > + int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT; > + > + name = xstrdup_or_null(getenv(env_name)); > + email = xstrdup_or_null(getenv(env_email)); > + date = xstrdup_or_null(getenv(env_date)); > + > + if (is_author && author_message) { > + struct ident_split msg_ident; > size_t len; > const char *a; > > a = find_commit_header(author_message_buffer, "author", &len); > if (!a) > die(_("commit '%s' lacks author header"), author_message); > - if (split_ident_line(&ident, a, len) < 0) > + if (split_ident_line(&msg_ident, a, len) < 0) > die(_("commit '%s' has malformed author line"), author_message); > > - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); > - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); > + set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin)); > + set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin)); > > - if (ident.date_begin) { > + if (msg_ident.date_begin) { > struct strbuf date_buf = STRBUF_INIT; > strbuf_addch(&date_buf, '@'); > - strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); > + strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin); > strbuf_addch(&date_buf, ' '); > - strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); > + strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin); > set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > } > } The helper tries to be generic between both kinds of ident, but we still need conditional that says "this part of the function is only when we are looking for author", which is rather unsatisfactory. Also why do we need this much patch noise, only because you renamed one variable? I wonder if it would make it cleaner to move the body of this if() {} statement into a separate helper function, leaving only if (whose_ident == WANT_AUTHOR_IDENT) set_author_from_message(&name, &email, &date); or something simple here? ^ permalink raw reply [flat|nested] 44+ messages in thread
* Re: [PATCH v3] commit: add --committer option 2025-11-12 18:56 ` Junio C Hamano @ 2025-11-15 6:33 ` ZheNing Hu 0 siblings, 0 replies; 44+ messages in thread From: ZheNing Hu @ 2025-11-15 6:33 UTC (permalink / raw) To: Junio C Hamano Cc: ZheNing Hu via GitGitGadget, git, Jeff King, Patrick Steinhardt, Phillip Wood, brian m. carlson Junio C Hamano <gitster@pobox.com> 于2025年11月13日周四 02:56写道: > > "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes: > > > +`--committer=<committer>`:: > > + Override the committer for the commit. Specify an explicit committer using the > > Isn't "set" or "use" more appropirate verb to use here? > > We already take the committer identity from multiple plases, like > user.{name,email}, or GIT_COMMITTER_{NAME,EMAIL} configuration, and > with the patch we also take it from a command line option, with the > usual precedence order (i.e., command line trumps environment which > trumps configuration). > Since both `--author=<author>` and `--date=<date>` use the "Override the *" phrasing, `--committer` uses "Override the" just to maintain consistency, though changing it to "Set the" would also be acceptable. > > -static void determine_author_info(struct strbuf *author_ident) > > +static void determine_identity(struct strbuf *ident_str, int is_author) > > "is_author" does not sound grammatical for this case; if you are > giving an ident of an unknown kind to this function and supplying > another parameter to let it know which kind, "is_author" may make > sense, but not here. > > As you will convert it into WANT_{AUTHOR,COMMITTER}_IDENT before > using anyway, why not let the caller use the "enum want_ident" to > tell this function what to do? > Ok, will change. > > { > > char *name, *email, *date; > > - struct ident_split author; > > - > > - name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); > > - email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); > > - date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); > > - > > - if (author_message) { > > - struct ident_split ident; > > + struct ident_split ident; > > + const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME"; > > + const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL"; > > + const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE"; > > + const char *force_ident = is_author ? force_author : force_committer; > > + const char *param_name = is_author ? "--author" : "--committer"; > > + int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT; > > + > > + name = xstrdup_or_null(getenv(env_name)); > > + email = xstrdup_or_null(getenv(env_email)); > > + date = xstrdup_or_null(getenv(env_date)); > > + > > + if (is_author && author_message) { > > + struct ident_split msg_ident; > > size_t len; > > const char *a; > > > > a = find_commit_header(author_message_buffer, "author", &len); > > if (!a) > > die(_("commit '%s' lacks author header"), author_message); > > - if (split_ident_line(&ident, a, len) < 0) > > + if (split_ident_line(&msg_ident, a, len) < 0) > > die(_("commit '%s' has malformed author line"), author_message); > > > > - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); > > - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); > > + set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin)); > > + set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin)); > > > > - if (ident.date_begin) { > > + if (msg_ident.date_begin) { > > struct strbuf date_buf = STRBUF_INIT; > > strbuf_addch(&date_buf, '@'); > > - strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); > > + strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin); > > strbuf_addch(&date_buf, ' '); > > - strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); > > + strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin); > > set_ident_var(&date, strbuf_detach(&date_buf, NULL)); > > } > > } > > The helper tries to be generic between both kinds of ident, but we > still need conditional that says "this part of the function is only > when we are looking for author", which is rather unsatisfactory. > Indeed, some specific logic should be moved to determine_author_info()/determine_committer_info(). > Also why do we need this much patch noise, only because you renamed > one variable? I wonder if it would make it cleaner to move the body > of this if() {} statement into a separate helper function, leaving > only > > if (whose_ident == WANT_AUTHOR_IDENT) > set_author_from_message(&name, &email, &date); > Ok. > or something simple here? ^ permalink raw reply [flat|nested] 44+ messages in thread
* [PATCH v4] commit: add --committer option 2025-11-12 16:55 ` [PATCH v3] " ZheNing Hu via GitGitGadget 2025-11-12 18:56 ` Junio C Hamano @ 2025-11-15 15:43 ` ZheNing Hu via GitGitGadget 1 sibling, 0 replies; 44+ messages in thread From: ZheNing Hu via GitGitGadget @ 2025-11-15 15:43 UTC (permalink / raw) To: git Cc: Junio C Hamano, Jeff King, Patrick Steinhardt, Phillip Wood, brian m. carlson, ZheNing Hu, ZheNing Hu From: ZheNing Hu <adlternative@gmail.com> Add --committer option to git-commit, allowing users to override the committer identity similar to how --author works. This provides a more convenient alternative to setting GIT_COMMITTER_* environment variables. Like --author, the --committer option supports two formats: - Explicit identity: --committer="Name <email@example.com>" - Pattern search: --committer="pattern" searches commit history for a matching committer and reuses that identity To share code with the existing --author option, this patch refactors: 1. find_author_by_nickname() into find_identity_by_nickname() which handles both author and committer searches through an is_author parameter. 2. determine_author_info() into determine_identity() which handles identity parsing and setting for both author and committer through an is_author parameter. Signed-off-by: ZheNing Hu <adlternative@gmail.com> --- commit: add --committer option Currently, when users need to override the committer identity in git-commit, they have to set GIT_COMMITTER_NAME and GIT_COMMITTER_EMAIL environment variables, which can be cumbersome in scripting scenarios or when frequently switching committer identities. While git-commit already provides the --author option to conveniently override the author identity, there's no equivalent --committer option for the committer identity. This asymmetry creates an inconsistent user experience. This patch introduces the --committer option to git-commit, providing: 1. Consistency with the existing --author option 2. A more convenient alternative to environment variables 3. Better support for automated workflows and scripts 4. Improved user experience when managing multiple identities The implementation follows the same pattern as the --author option, accepting the format "Name " and properly validating the input. Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1997%2Fadlternative%2Fzh%2Fimplement-committer-option-v4 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1997/adlternative/zh/implement-committer-option-v4 Pull-Request: https://github.com/gitgitgadget/git/pull/1997 Range-diff vs v3: 1: acf724fad5 ! 1: bb17f810ef commit: add --committer option @@ Documentation/git-commit.adoc: See linkgit:git-rebase[1] for details. the commit author is then copied from the first such commit found. +`--committer=<committer>`:: -+ Override the committer for the commit. Specify an explicit committer using the ++ Set the committer for the commit. Specify an explicit committer using the + standard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_ + is assumed to be a pattern and is used to search for an existing + commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`); @@ builtin/commit.c: static void set_ident_var(char **buf, char *val) } -static void determine_author_info(struct strbuf *author_ident) -+static void determine_identity(struct strbuf *ident_str, int is_author) - { - char *name, *email, *date; +-{ +- char *name, *email, *date; - struct ident_split author; -- ++static void set_author_from_message(char **name, char **email, char **date) { ++ struct ident_split ident; ++ size_t len; ++ const char *a; + - name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); - email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); - date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); -- ++ if (!author_message) ++ return; + - if (author_message) { - struct ident_split ident; -+ struct ident_split ident; -+ const char *env_name = is_author ? "GIT_AUTHOR_NAME" : "GIT_COMMITTER_NAME"; -+ const char *env_email = is_author ? "GIT_AUTHOR_EMAIL" : "GIT_COMMITTER_EMAIL"; -+ const char *env_date = is_author ? "GIT_AUTHOR_DATE" : "GIT_COMMITTER_DATE"; -+ const char *force_ident = is_author ? force_author : force_committer; -+ const char *param_name = is_author ? "--author" : "--committer"; -+ int ident_flag = is_author ? WANT_AUTHOR_IDENT : WANT_COMMITTER_IDENT; -+ -+ name = xstrdup_or_null(getenv(env_name)); -+ email = xstrdup_or_null(getenv(env_email)); -+ date = xstrdup_or_null(getenv(env_date)); -+ -+ if (is_author && author_message) { -+ struct ident_split msg_ident; - size_t len; - const char *a; - - a = find_commit_header(author_message_buffer, "author", &len); - if (!a) - die(_("commit '%s' lacks author header"), author_message); +- size_t len; +- const char *a; +- +- a = find_commit_header(author_message_buffer, "author", &len); +- if (!a) +- die(_("commit '%s' lacks author header"), author_message); - if (split_ident_line(&ident, a, len) < 0) -+ if (split_ident_line(&msg_ident, a, len) < 0) - die(_("commit '%s' has malformed author line"), author_message); - +- die(_("commit '%s' has malformed author line"), author_message); +- - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); -+ set_ident_var(&name, xmemdupz(msg_ident.name_begin, msg_ident.name_end - msg_ident.name_begin)); -+ set_ident_var(&email, xmemdupz(msg_ident.mail_begin, msg_ident.mail_end - msg_ident.mail_begin)); - +- - if (ident.date_begin) { -+ if (msg_ident.date_begin) { - struct strbuf date_buf = STRBUF_INIT; - strbuf_addch(&date_buf, '@'); +- struct strbuf date_buf = STRBUF_INIT; +- strbuf_addch(&date_buf, '@'); - strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); -+ strbuf_add(&date_buf, msg_ident.date_begin, msg_ident.date_end - msg_ident.date_begin); - strbuf_addch(&date_buf, ' '); +- strbuf_addch(&date_buf, ' '); - strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); -+ strbuf_add(&date_buf, msg_ident.tz_begin, msg_ident.tz_end - msg_ident.tz_begin); - set_ident_var(&date, strbuf_detach(&date_buf, NULL)); - } +- set_ident_var(&date, strbuf_detach(&date_buf, NULL)); +- } ++ a = find_commit_header(author_message_buffer, "author", &len); ++ if (!a) ++ die(_("commit '%s' lacks author header"), author_message); ++ if (split_ident_line(&ident, a, len) < 0) ++ die(_("commit '%s' has malformed author line"), author_message); ++ ++ set_ident_var(name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); ++ set_ident_var(email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); ++ ++ if (ident.date_begin) { ++ struct strbuf date_buf = STRBUF_INIT; ++ strbuf_addch(&date_buf, '@'); ++ strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); ++ strbuf_addch(&date_buf, ' '); ++ strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); ++ set_ident_var(date, strbuf_detach(&date_buf, NULL)); } ++} - if (force_author) { - struct ident_split ident; ++static void determine_identity(struct strbuf *ident_str, enum want_ident whose_ident, ++ const char *env_name, const char *env_email, const char *env_date, ++ const char *force_ident, const char *param_name, ++ char *name, char *email, char *date) ++{ ++ struct ident_split ident; ++ ++ + if (force_ident) { + struct ident_split force_ident_split; -+ -+ if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0) -+ die(_("malformed %s parameter"), param_name); -+ set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin)); -+ set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin)); - if (split_ident_line(&ident, force_author, strlen(force_author)) < 0) - die(_("malformed --author parameter")); - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); -+ if (!is_author && force_ident_split.date_begin) { -+ struct strbuf date_buf = STRBUF_INIT; -+ strbuf_addch(&date_buf, '@'); -+ strbuf_add(&date_buf, force_ident_split.date_begin, force_ident_split.date_end - force_ident_split.date_begin); -+ strbuf_addch(&date_buf, ' '); -+ strbuf_add(&date_buf, force_ident_split.tz_begin, force_ident_split.tz_end - force_ident_split.tz_begin); -+ set_ident_var(&date, strbuf_detach(&date_buf, NULL)); -+ } ++ if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0) ++ die(_("malformed %s parameter"), param_name); ++ set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin)); ++ set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin)); } if (force_date) { @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident) } - strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, -+ strbuf_addstr(ident_str, fmt_ident(name, email, ident_flag, date, ++ strbuf_addstr(ident_str, fmt_ident(name, email, whose_ident, date, IDENT_STRICT)); - assert_split_ident(&author, author_ident); - export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident) - export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); + assert_split_ident(&ident, ident_str); + -+ if (is_author) { -+ export_one("GIT_AUTHOR_NAME", ident.name_begin, ident.name_end, 0); -+ export_one("GIT_AUTHOR_EMAIL", ident.mail_begin, ident.mail_end, 0); -+ export_one("GIT_AUTHOR_DATE", ident.date_begin, ident.tz_end, '@'); -+ } else { -+ export_one("GIT_COMMITTER_NAME", ident.name_begin, ident.name_end, 0); -+ export_one("GIT_COMMITTER_EMAIL", ident.mail_begin, ident.mail_end, 0); -+ export_one("GIT_COMMITTER_DATE", ident.date_begin, ident.tz_end, '@'); -+ } ++ export_one(env_name, ident.name_begin, ident.name_end, 0); ++ export_one(env_email, ident.mail_begin, ident.mail_end, 0); ++ export_one(env_date, ident.date_begin, ident.tz_end, '@'); + free(name); free(email); @@ builtin/commit.c: static void determine_author_info(struct strbuf *author_ident) +static void determine_author_info(struct strbuf *author_ident) +{ -+ determine_identity(author_ident, 1); ++ char *name, *email, *date; ++ ++ name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); ++ email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); ++ date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); ++ ++ set_author_from_message(&name, &email, &date); ++ ++ determine_identity(author_ident, WANT_AUTHOR_IDENT, ++ "GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_AUTHOR_DATE", ++ force_author, "--author", ++ name, email, date); +} + +static void determine_committer_info(struct strbuf *committer_ident) +{ -+ determine_identity(committer_ident, 0); ++ char *name, *email, *date; ++ ++ name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); ++ email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); ++ date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); ++ ++ determine_identity(committer_ident, WANT_COMMITTER_IDENT, ++ "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", "GIT_COMMITTER_DATE", ++ force_committer, "--committer", ++ name, email, date); +} + static int author_date_is_interesting(void) @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha } -static const char *find_author_by_nickname(const char *name) -+static const char *find_identity_by_nickname(const char *name, int is_author) ++static const char *find_identity_by_nickname(const char *name, enum want_ident whose_ident) { struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; const char *av[20]; int ac = 0; -+ const char *field = is_author ? "author" : "committer"; -+ const char *format = is_author ? "%aN <%aE>" : "%cN <%cE>"; ++ const char *field, *format; ++ ++ if (whose_ident != WANT_AUTHOR_IDENT && whose_ident != WANT_COMMITTER_IDENT) ++ BUG("find_identity_by_nickname requires WANT_AUTHOR_IDENT or WANT_COMMITTER_IDENT"); ++ ++ field = whose_ident == WANT_AUTHOR_IDENT ? "author" : "committer"; ++ format = whose_ident == WANT_AUTHOR_IDENT ? "%aN <%aE>" : "%cN <%cE>"; repo_init_revisions(the_repository, &revs, NULL); - strbuf_addf(&buf, "--author=%s", name); @@ builtin/commit.c: static const char *find_author_by_nickname(const char *name) + +static const char *find_author_by_nickname(const char *name) +{ -+ return find_identity_by_nickname(name, 1); ++ return find_identity_by_nickname(name, WANT_AUTHOR_IDENT); +} + +static const char *find_committer_by_nickname(const char *name) +{ -+ return find_identity_by_nickname(name, 0); ++ return find_identity_by_nickname(name, WANT_COMMITTER_IDENT); } static void handle_ignored_arg(struct wt_status *s) Documentation/git-commit.adoc | 9 +- builtin/commit.c | 155 ++++++++++++++++++++++++---------- t/t7509-commit-authorship.sh | 84 +++++++++++++++++- 3 files changed, 200 insertions(+), 48 deletions(-) diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc index 54c207ad45..a0c8a586de 100644 --- a/Documentation/git-commit.adoc +++ b/Documentation/git-commit.adoc @@ -12,7 +12,7 @@ git commit [-a | --interactive | --patch] [-s] [-v] [-u[<mode>]] [--amend] [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>] [-F <file> | -m <msg>] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] - [--date=<date>] [--cleanup=<mode>] [--[no-]status] + [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status] [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]] [--] [<pathspec>...] @@ -178,6 +178,13 @@ See linkgit:git-rebase[1] for details. commit by that author (i.e. `git rev-list --all -i --author=<author>`); the commit author is then copied from the first such commit found. +`--committer=<committer>`:: + Set the committer for the commit. Specify an explicit committer using the + standard `C O Mitter <committer@example.com>` format. Otherwise _<committer>_ + is assumed to be a pattern and is used to search for an existing + commit by that committer (i.e. `git rev-list --all -i --committer=<committer>`); + the commit committer is then copied from the first such commit found. + `--date=<date>`:: Override the author date used in the commit. diff --git a/builtin/commit.c b/builtin/commit.c index 0243f17d53..13138a3868 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -49,7 +49,7 @@ static const char * const builtin_commit_usage[] = { " [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>]\n" " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" - " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" + " [--committer=<committer>] [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" " [--] [<pathspec>...]"), @@ -112,6 +112,7 @@ static enum { } commit_style; static const char *force_author; +static const char *force_committer; static char *logfile; static char *template_file; /* @@ -630,46 +631,48 @@ static void set_ident_var(char **buf, char *val) *buf = val; } -static void determine_author_info(struct strbuf *author_ident) -{ - char *name, *email, *date; - struct ident_split author; +static void set_author_from_message(char **name, char **email, char **date) { + struct ident_split ident; + size_t len; + const char *a; - name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); - email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); - date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); + if (!author_message) + return; - if (author_message) { - struct ident_split ident; - size_t len; - const char *a; - - a = find_commit_header(author_message_buffer, "author", &len); - if (!a) - die(_("commit '%s' lacks author header"), author_message); - if (split_ident_line(&ident, a, len) < 0) - die(_("commit '%s' has malformed author line"), author_message); - - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); - - if (ident.date_begin) { - struct strbuf date_buf = STRBUF_INIT; - strbuf_addch(&date_buf, '@'); - strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); - strbuf_addch(&date_buf, ' '); - strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); - set_ident_var(&date, strbuf_detach(&date_buf, NULL)); - } + a = find_commit_header(author_message_buffer, "author", &len); + if (!a) + die(_("commit '%s' lacks author header"), author_message); + if (split_ident_line(&ident, a, len) < 0) + die(_("commit '%s' has malformed author line"), author_message); + + set_ident_var(name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); + set_ident_var(email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + + if (ident.date_begin) { + struct strbuf date_buf = STRBUF_INIT; + strbuf_addch(&date_buf, '@'); + strbuf_add(&date_buf, ident.date_begin, ident.date_end - ident.date_begin); + strbuf_addch(&date_buf, ' '); + strbuf_add(&date_buf, ident.tz_begin, ident.tz_end - ident.tz_begin); + set_ident_var(date, strbuf_detach(&date_buf, NULL)); } +} - if (force_author) { - struct ident_split ident; +static void determine_identity(struct strbuf *ident_str, enum want_ident whose_ident, + const char *env_name, const char *env_email, const char *env_date, + const char *force_ident, const char *param_name, + char *name, char *email, char *date) +{ + struct ident_split ident; + + + if (force_ident) { + struct ident_split force_ident_split; - if (split_ident_line(&ident, force_author, strlen(force_author)) < 0) - die(_("malformed --author parameter")); - set_ident_var(&name, xmemdupz(ident.name_begin, ident.name_end - ident.name_begin)); - set_ident_var(&email, xmemdupz(ident.mail_begin, ident.mail_end - ident.mail_begin)); + if (split_ident_line(&force_ident_split, force_ident, strlen(force_ident)) < 0) + die(_("malformed %s parameter"), param_name); + set_ident_var(&name, xmemdupz(force_ident_split.name_begin, force_ident_split.name_end - force_ident_split.name_begin)); + set_ident_var(&email, xmemdupz(force_ident_split.mail_begin, force_ident_split.mail_end - force_ident_split.mail_begin)); } if (force_date) { @@ -679,17 +682,49 @@ static void determine_author_info(struct strbuf *author_ident) set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } - strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, + strbuf_addstr(ident_str, fmt_ident(name, email, whose_ident, date, IDENT_STRICT)); - assert_split_ident(&author, author_ident); - export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); - export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); - export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@'); + assert_split_ident(&ident, ident_str); + + export_one(env_name, ident.name_begin, ident.name_end, 0); + export_one(env_email, ident.mail_begin, ident.mail_end, 0); + export_one(env_date, ident.date_begin, ident.tz_end, '@'); + free(name); free(email); free(date); } +static void determine_author_info(struct strbuf *author_ident) +{ + char *name, *email, *date; + + name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME")); + email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL")); + date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE")); + + set_author_from_message(&name, &email, &date); + + determine_identity(author_ident, WANT_AUTHOR_IDENT, + "GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_AUTHOR_DATE", + force_author, "--author", + name, email, date); +} + +static void determine_committer_info(struct strbuf *committer_ident) +{ + char *name, *email, *date; + + name = xstrdup_or_null(getenv("GIT_COMMITTER_NAME")); + email = xstrdup_or_null(getenv("GIT_COMMITTER_EMAIL")); + date = xstrdup_or_null(getenv("GIT_COMMITTER_DATE")); + + determine_identity(committer_ident, WANT_COMMITTER_IDENT, + "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", "GIT_COMMITTER_DATE", + force_committer, "--committer", + name, email, date); +} + static int author_date_is_interesting(void) { return author_message || force_date; @@ -1137,16 +1172,23 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 1; } -static const char *find_author_by_nickname(const char *name) +static const char *find_identity_by_nickname(const char *name, enum want_ident whose_ident) { struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; const char *av[20]; int ac = 0; + const char *field, *format; + + if (whose_ident != WANT_AUTHOR_IDENT && whose_ident != WANT_COMMITTER_IDENT) + BUG("find_identity_by_nickname requires WANT_AUTHOR_IDENT or WANT_COMMITTER_IDENT"); + + field = whose_ident == WANT_AUTHOR_IDENT ? "author" : "committer"; + format = whose_ident == WANT_AUTHOR_IDENT ? "%aN <%aE>" : "%cN <%cE>"; repo_init_revisions(the_repository, &revs, NULL); - strbuf_addf(&buf, "--author=%s", name); + strbuf_addf(&buf, "--%s=%s", field, name); av[++ac] = "--all"; av[++ac] = "-i"; av[++ac] = buf.buf; @@ -1164,11 +1206,22 @@ static const char *find_author_by_nickname(const char *name) ctx.date_mode.type = DATE_NORMAL; strbuf_release(&buf); repo_format_commit_message(the_repository, commit, - "%aN <%aE>", &buf, &ctx); + format, &buf, &ctx); release_revisions(&revs); return strbuf_detach(&buf, NULL); } - die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name); + die(_("--%s '%s' is not 'Name <email>' and matches no existing %s"), + field, name, field); +} + +static const char *find_author_by_nickname(const char *name) +{ + return find_identity_by_nickname(name, WANT_AUTHOR_IDENT); +} + +static const char *find_committer_by_nickname(const char *name) +{ + return find_identity_by_nickname(name, WANT_COMMITTER_IDENT); } static void handle_ignored_arg(struct wt_status *s) @@ -1321,6 +1374,9 @@ static int parse_and_validate_options(int argc, const char *argv[], if (force_author && renew_authorship) die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); + if (force_committer && !strchr(force_committer, '>')) + force_committer = find_committer_by_nickname(force_committer); + if (logfile || have_option_m || use_message) use_editor = 0; @@ -1709,6 +1765,7 @@ int cmd_commit(int argc, OPT_FILENAME('F', "file", &logfile, N_("read message from file")), OPT_STRING(0, "author", &force_author, N_("author"), N_("override author for commit")), OPT_STRING(0, "date", &force_date, N_("date"), N_("override date for commit")), + OPT_STRING(0, "committer", &force_committer, N_("committer"), N_("override committer for commit")), OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), @@ -1785,6 +1842,7 @@ int cmd_commit(int argc, struct strbuf sb = STRBUF_INIT; struct strbuf author_ident = STRBUF_INIT; + struct strbuf committer_ident = STRBUF_INIT; const char *index_file, *reflog_msg; struct object_id oid; struct commit_list *parents = NULL; @@ -1930,8 +1988,12 @@ int cmd_commit(int argc, append_merge_tag_headers(parents, &tail); } + if (force_committer) + determine_committer_info(&committer_ident); + if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, - parents, &oid, author_ident.buf, NULL, + parents, &oid, author_ident.buf, + force_committer ? committer_ident.buf : NULL, sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); @@ -1980,6 +2042,7 @@ cleanup: free_commit_extra_headers(extra); free_commit_list(parents); strbuf_release(&author_ident); + strbuf_release(&committer_ident); strbuf_release(&err); strbuf_release(&sb); free(logfile); diff --git a/t/t7509-commit-authorship.sh b/t/t7509-commit-authorship.sh index 8e373b566b..7e163e02d1 100755 --- a/t/t7509-commit-authorship.sh +++ b/t/t7509-commit-authorship.sh @@ -12,13 +12,20 @@ author_header () { sed -n -e '/^$/q' -e '/^author /p' } +committer_header () { + git cat-file commit "$1" | + sed -n -e '/^$/q' -e '/^committer /p' +} + message_body () { git cat-file commit "$1" | sed -e '1,/^$/d' } test_expect_success '-C option copies authorship and message' ' - test_commit --author Frigate\ \<flying@over.world\> \ + test_env GIT_COMMITTER_NAME="Frigate" \ + GIT_COMMITTER_EMAIL="flying@over.world" \ + test_commit --author Frigate\ \<flying@over.world\> \ "Initial Commit" foo Initial Initial && echo "Test 1" >>foo && test_tick && @@ -171,4 +178,79 @@ test_expect_success '--reset-author with CHERRY_PICK_HEAD' ' test_cmp expect actual ' +test_expect_success '--committer option overrides committer' ' + git checkout Initial && + echo "Test --committer" >>foo && + test_tick && + git commit -a -m "test committer" --committer="Custom Committer <custom@committer.example>" && + committer_header HEAD >actual && + grep "Custom Committer <custom@committer.example>" actual +' + +test_expect_success '--committer with pattern search' ' + echo "Test committer pattern" >>foo && + test_tick && + git commit -a -m "test committer pattern" --committer="Frigate" && + committer_header HEAD >actual && + grep "Frigate <flying@over.world>" actual +' + +test_expect_success '--committer malformed parameter' ' + echo "Test malformed" >>foo && + test_tick && + test_must_fail git commit -a -m "test malformed" --committer="malformed committer" +' + +test_expect_success '--committer with --amend option' ' + git checkout -f Initial && + echo "Test committer with amend" >>foo && + test_tick && + git commit -a -m "initial commit for amend test" && + echo "Modified for amend" >>foo && + test_tick && + git commit -a --amend --no-edit \ + --author="Test Author <test@author.example>" \ + --committer="Test Committer <test@committer.example>" && + author_header HEAD >actual_author && + grep "Test Author <test@author.example>" actual_author && + committer_header HEAD >actual_committer && + grep "Test Committer <test@committer.example>" actual_committer +' + +test_expect_success 'GIT_COMMITTER_* environment variables' ' + git checkout -f Initial && + echo "Test env vars" >>foo && + test_tick && + test_env GIT_COMMITTER_NAME="Env Committer" \ + GIT_COMMITTER_EMAIL="env@test.example" \ + git commit -a -m "test committer env vars" && + committer_header HEAD >actual && + grep "Env Committer <env@test.example>" actual +' + +test_expect_success '--committer overrides GIT_COMMITTER_* environment variables' ' + echo "Test override" >>foo && + test_tick && + test_env GIT_COMMITTER_NAME="Env Committer" \ + GIT_COMMITTER_EMAIL="env@test.example" \ + git commit -a -m "test override" \ + --committer="Override Committer <override@test.example>" && + committer_header HEAD >actual && + grep "Override Committer <override@test.example>" actual +' + +test_expect_success '--date with --committer changes both author and committer dates' ' + git checkout -f Initial && + echo "Test date override" >>foo && + test_tick && + git commit -a -m "test date" \ + --author="Date Author <date@author.example>" \ + --committer="Date Committer <date@committer.example>" \ + --date="2024-06-15 10:30:00 +0800" && + git log -1 --format="%ai" >author_date && + git log -1 --format="%ci" >committer_date && + grep "2024-06-15 10:30:00 +0800" author_date && + grep "2024-06-15 10:30:00 +0800" committer_date +' + test_done base-commit: 4badef0c3503dc29059d678abba7fac0f042bc84 -- gitgitgadget ^ permalink raw reply related [flat|nested] 44+ messages in thread
end of thread, other threads:[~2025-11-17 15:18 UTC | newest] Thread overview: 44+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-11-09 10:22 [PATCH] commit: add --committer option ZheNing Hu via GitGitGadget 2025-11-10 9:24 ` Patrick Steinhardt 2025-11-10 14:17 ` ZheNing Hu 2025-11-10 17:38 ` Junio C Hamano 2025-11-11 13:19 ` ZheNing Hu 2025-11-10 16:50 ` Phillip Wood 2025-11-10 18:01 ` brian m. carlson 2025-11-10 20:11 ` Jeff King 2025-11-10 22:06 ` Junio C Hamano 2025-11-11 6:54 ` Patrick Steinhardt 2025-11-11 14:53 ` Phillip Wood 2025-11-12 16:11 ` ZheNing Hu 2025-11-11 13:42 ` ZheNing Hu 2025-11-11 19:15 ` Jeff King 2025-11-11 20:16 ` Junio C Hamano 2025-11-11 21:33 ` Jeff King 2025-11-11 21:58 ` Junio C Hamano 2025-11-11 22:23 ` Jeff King 2025-11-12 16:51 ` ZheNing Hu 2025-11-12 16:48 ` ZheNing Hu 2025-11-12 16:46 ` ZheNing Hu 2025-11-12 16:41 ` ZheNing Hu 2025-11-12 16:37 ` ZheNing Hu 2025-11-11 13:01 ` ZheNing Hu 2025-11-11 14:38 ` Phillip Wood 2025-11-12 15:58 ` ZheNing Hu 2025-11-12 17:24 ` Junio C Hamano 2025-11-15 5:29 ` ZheNing Hu 2025-11-16 1:06 ` Junio C Hamano 2025-11-17 15:06 ` ZheNing Hu 2025-11-16 22:12 ` Matej Dujava 2025-11-17 14:27 ` Phillip Wood 2025-11-17 15:18 ` ZheNing Hu 2025-11-17 15:15 ` ZheNing Hu 2025-11-10 16:56 ` [PATCH v2] " ZheNing Hu via GitGitGadget 2025-11-10 19:22 ` Junio C Hamano 2025-11-10 19:29 ` Junio C Hamano 2025-11-11 13:36 ` ZheNing Hu 2025-11-11 15:40 ` Junio C Hamano 2025-11-12 16:23 ` ZheNing Hu 2025-11-12 16:55 ` [PATCH v3] " ZheNing Hu via GitGitGadget 2025-11-12 18:56 ` Junio C Hamano 2025-11-15 6:33 ` ZheNing Hu 2025-11-15 15:43 ` [PATCH v4] " ZheNing Hu via GitGitGadget
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).