[PATCH 1/1] commit: allow -m/-F with --fixup=amend: or reword:
- From
- erik@cervined.in <erik@cervined.in>
- Date
- May 18, 2026, 11:22 UTC
- Message-ID
- <20260518112225.73172-4-erik@cervined.in>
- In-Reply-To
- <20260518112225.73172-2-erik@cervined.in>
From: Erik Cervin-Edin <erik@cervined.in>
--fixup=amend: and --fixup=reword: require an editor to supply the replacement commit message. The -m and -F flags are rejected: -m is caught by a die() in prepare_to_commit(), and -F is caught by die_for_incompatible_opt4() which groups -F with --fixup as mutually exclusive. This makes these modes unusable in non-interactive workflows -- notably AI coding agents.
When the amend suboption was introduced in 494d314a05 (commit: add amend suboption to --fixup to create amend! commit, 2021-03-15), -m support for amend fixups was discussed but not pursued, and -F was already caught by the higher-layer incompatibility check grouping it with --fixup.
Allow -m and -F to supply the replacement message body for amend and reword fixups. When provided, bypass the editor and directly use the user's message as the body, replacing the original commit's message. For -F, the file contents are read into the message strbuf and then handled identically to -m.
Plain --fixup (without amend: or reword:) continues to reject -F but still accepts -m (even though it's practically a no-op).
Signed-off-by: Erik Cervin Edin <erik@cervined.in> --- Documentation/git-commit.adoc | 13 +++-- builtin/commit.c | 41 ++++++++++---- t/t7500-commit-template-squash-signoff.sh | 67 +++++++++++++++++++---- 3 files changed, 92 insertions(+), 29 deletions(-)
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc index 8329c1034b..9478d5d265 100644 --- a/Documentation/git-commit.adoc +++ b/Documentation/git-commit.adoc @@ -111,12 +111,13 @@ commit, but the additional commentary will be thrown away once the The commit created by `--fixup=amend:<commit>` is similar but its title is instead prefixed with "amend!". The log message of _<commit>_ is copied into the log message of the "amend!" commit and -opened in an editor so it can be refined. When `git rebase ---autosquash` squashes the "amend!" commit into _<commit>_, the -log message of _<commit>_ is replaced by the refined log message -from the "amend!" commit. It is an error for the "amend!" commit's -log message to be empty unless `--allow-empty-message` is -specified. +opened in an editor so it can be refined. The replacement message may +also be supplied directly using `-m` or `-F`, bypassing the need to open +an editor. When `git rebase --autosquash` squashes the "amend!" commit +into _<commit>_, the log message of _<commit>_ is replaced by the +refined log message from the "amend!" commit. It is an error for the +"amend!" commit's log message to be empty unless `--allow-empty-message` +is specified. + `--fixup=reword:<commit>` is shorthand for `--fixup=amend:<commit> --only`. It creates an "amend!" commit with only a log message diff --git a/builtin/commit.c b/builtin/commit.c index 28f6174503..269c2d782b 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -837,21 +837,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix, hook_arg1 = "message"; /* - * Only `-m` commit message option is checked here, as - * it supports `--fixup` to append the commit message. - * - * The other commit message options `-c`/`-C`/`-F` are - * incompatible with all the forms of `--fixup` and - * have already errored out while parsing the `git commit` - * options. + * `-m` (and `-F`, converted to `-m` earlier for + * amend/reword) appends the message body here. + * `-c`/`-C` are still incompatible with all forms + * of `--fixup`. */ if (have_option_m && !strcmp(fixup_prefix, "fixup")) strbuf_addbuf(&sb, &message); if (!strcmp(fixup_prefix, "amend")) { if (have_option_m) - die(_("options '%s' and '%s:%s' cannot be used together"), "-m", "--fixup", fixup_message); - prepare_amend_commit(commit, &sb, &ctx); + strbuf_addbuf(&sb, &message); + else + prepare_amend_commit(commit, &sb, &ctx); } } else if (!stat(git_path_merge_msg(the_repository), &statbuf)) { size_t merge_msg_start; @@ -1338,10 +1336,12 @@ static int parse_and_validate_options(int argc, const char *argv[], } if (fixup_message && squash_message) die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup"); - die_for_incompatible_opt4(!!use_message, "-C", + die_for_incompatible_opt3(!!use_message, "-C", !!edit_message, "-c", - !!logfile, "-F", !!fixup_message, "--fixup"); + die_for_incompatible_opt3(!!use_message, "-C", + !!edit_message, "-c", + !!logfile, "-F"); die_for_incompatible_opt4(have_option_m, "-m", !!edit_message, "-c", !!use_message, "-C", @@ -1410,6 +1410,9 @@ static int parse_and_validate_options(int argc, const char *argv[], } } + if (logfile && fixup_message && !strcmp(fixup_prefix, "fixup")) + die(_("options '%s' and '%s' cannot be used together"), "-F", "--fixup"); + if (0 <= edit_flag) use_editor = edit_flag; @@ -1821,6 +1824,22 @@ int cmd_commit(int argc, argc = parse_and_validate_options(argc, argv, builtin_commit_options, builtin_commit_usage, prefix, current_head, &s); + + if (logfile && fixup_message && !strcmp(fixup_prefix, "amend")) { + if (!strcmp(logfile, "-")) { + if (isatty(0)) + fprintf(stderr, _("(reading log message from standard input)\n")); + if (strbuf_read(&message, 0, 0) < 0) + die_errno(_("could not read log from standard input")); + } else { + if (strbuf_read_file(&message, logfile, 0) < 0) + die_errno(_("could not read log file '%s'"), logfile); + } + strbuf_complete_line(&message); + have_option_m = 1; + FREE_AND_NULL(logfile); + } + if (trailer_args.nr) trailer_config_init(); diff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh index 66aff8e097..b7579ad789 100755 --- a/t/t7500-commit-template-squash-signoff.sh +++ b/t/t7500-commit-template-squash-signoff.sh @@ -384,18 +384,28 @@ test_expect_success '--fixup=reword: ignores staged changes' ' test_cmp foo actual ' -test_expect_success '--fixup=reword: error out with -m option' ' +test_expect_success '--fixup=amend: with -m option' ' commit_for_rebase_autosquash_setup && - echo "fatal: options '\''-m'\'' and '\''--fixup:reword'\'' cannot be used together" >expect && - test_must_fail git commit --fixup=reword:HEAD~ -m "reword commit message" 2>actual && - test_cmp expect actual + cat >expected <<-EOF && + amend! $(git log -1 --format=%s HEAD~) + + amend commit message + EOF + git commit --fixup=amend:HEAD~ -m "amend commit message" && + get_commit_msg HEAD >actual && + test_cmp expected actual ' -test_expect_success '--fixup=amend: error out with -m option' ' +test_expect_success '--fixup=reword: with -m option' ' commit_for_rebase_autosquash_setup && - echo "fatal: options '\''-m'\'' and '\''--fixup:amend'\'' cannot be used together" >expect && - test_must_fail git commit --fixup=amend:HEAD~ -m "amend commit message" 2>actual && - test_cmp expect actual + cat >expected <<-EOF && + amend! $(git log -1 --format=%s HEAD~) + + reword commit message + EOF + git commit --fixup=reword:HEAD~ -m "reword commit message" && + get_commit_msg HEAD >actual && + test_cmp expected actual ' test_expect_success 'consecutive amend! commits remove amend! line from commit msg body' ' @@ -432,6 +442,12 @@ test_expect_success 'deny to create amend! commit if its commit msg body is empt test_cmp expected actual ' +test_expect_success '--fixup=amend: -m with empty message aborts' ' + commit_for_rebase_autosquash_setup && + test_must_fail git commit --fixup=amend:HEAD~ -m "" 2>err && + test_grep "empty commit message body" err +' + test_expect_success 'amend! commit allows empty commit msg body with --allow-empty-message' ' commit_for_rebase_autosquash_setup && cat >expected <<-EOF && @@ -468,10 +484,37 @@ test_expect_success '--fixup=reword: give error with pathsec' ' test_cmp expect actual ' -test_expect_success '--fixup=reword: -F give error message' ' - echo "fatal: options '\''-F'\'' and '\''--fixup'\'' cannot be used together" >expect && - test_must_fail git commit --fixup=reword:HEAD~ -F msg 2>actual && - test_cmp expect actual +test_expect_success '--fixup=reword: with -F option' ' + commit_for_rebase_autosquash_setup && + echo "message from file" >msgfile && + cat >expected <<-EOF && + amend! $(git log -1 --format=%s HEAD~) + + message from file + EOF + git commit --fixup=reword:HEAD~ -F msgfile && + get_commit_msg HEAD >actual && + test_cmp expected actual +' + +test_expect_success '--fixup=amend: with -F option' ' + commit_for_rebase_autosquash_setup && + echo "amend message from file" >msgfile && + cat >expected <<-EOF && + amend! $(git log -1 --format=%s HEAD~) + + amend message from file + EOF + git commit --fixup=amend:HEAD~ -F msgfile && + get_commit_msg HEAD >actual && + test_cmp expected actual +' + +test_expect_success '-F with plain --fixup still errors' ' + commit_for_rebase_autosquash_setup && + echo "message" >msgfile && + test_must_fail git commit --fixup HEAD~ -F msgfile 2>err && + test_grep "cannot be used together" err ' test_expect_success 'commit --squash works with -F' '
-- 2.54.0.772.g683d7313b1