Merge branch 'ec/commit-fixup-options' into seen

Support for '-m', '-F', '-c', or '-C' options to supply a commit log
message from outside the editor has been added for all 'git commit
--fixup' variations.

* ec/commit-fixup-options:
  commit: allow -c/-C for all kinds of --fixup
  commit: allow -m/-F for all kinds of --fixup
seen
Junio C Hamano 2026-08-31 13:53:24 -07:00
commit bf65180f65
3 changed files with 133 additions and 44 deletions

View File

@ -102,21 +102,23 @@ include::diff-context-options.adoc[]
+ +
The commit created by plain `--fixup=<commit>` has a title The commit created by plain `--fixup=<commit>` has a title
composed of "fixup!" followed by the title of _<commit>_, composed of "fixup!" followed by the title of _<commit>_,
and is recognized specially by `git rebase --autosquash`. The `-m` and is recognized specially by `git rebase --autosquash`. The `-m`,
option may be used to supplement the log message of the created `-F`, `-C`, or `-c` option may be used to supplement the log message
commit, but the additional commentary will be thrown away once the of the created commit, but the additional commentary will be thrown
"fixup!" commit is squashed into _<commit>_ by away once the "fixup!" commit is squashed into _<commit>_ by
`git rebase --autosquash`. `git rebase --autosquash`.
+ +
The commit created by `--fixup=amend:<commit>` is similar but its The commit created by `--fixup=amend:<commit>` is similar but its
title is instead prefixed with "amend!". The log message of title is instead prefixed with "amend!". The log message of
_<commit>_ is copied into the log message of the "amend!" commit and _<commit>_ is copied into the log message of the "amend!" commit and
opened in an editor so it can be refined. When `git rebase opened in an editor so it can be refined. The replacement message may
--autosquash` squashes the "amend!" commit into _<commit>_, the also be supplied directly using `-m`, `-F`, or `-C`, bypassing the
log message of _<commit>_ is replaced by the refined log message need to open an editor, or using `-c` to open the editor pre-populated
from the "amend!" commit. It is an error for the "amend!" commit's with the referenced commit's message. When `git rebase
log message to be empty unless `--allow-empty-message` is --autosquash` squashes the "amend!" commit into _<commit>_, the log
specified. 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> `--fixup=reword:<commit>` is shorthand for `--fixup=amend:<commit>
--only`. It creates an "amend!" commit with only a log message --only`. It creates an "amend!" commit with only a log message

View File

@ -816,18 +816,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (have_option_m && !fixup_message) { if (have_option_m && !fixup_message) {
strbuf_addbuf(&sb, &message); strbuf_addbuf(&sb, &message);
hook_arg1 = "message"; hook_arg1 = "message";
} else if (logfile && !strcmp(logfile, "-")) { } else if (logfile && !fixup_message && !strcmp(logfile, "-")) {
if (isatty(0)) if (isatty(0))
fprintf(stderr, _("(reading log message from standard input)\n")); fprintf(stderr, _("(reading log message from standard input)\n"));
if (strbuf_read(&sb, 0, 0) < 0) if (strbuf_read(&sb, 0, 0) < 0)
die_errno(_("could not read log from standard input")); die_errno(_("could not read log from standard input"));
hook_arg1 = "message"; hook_arg1 = "message";
} else if (logfile) { } else if (logfile && !fixup_message) {
if (strbuf_read_file(&sb, logfile, 0) < 0) if (strbuf_read_file(&sb, logfile, 0) < 0)
die_errno(_("could not read log file '%s'"), die_errno(_("could not read log file '%s'"),
logfile); logfile);
hook_arg1 = "message"; hook_arg1 = "message";
} else if (use_message) { } else if (use_message && !fixup_message) {
const char *buffer; const char *buffer;
buffer = strstr(use_message_buffer, "\n\n"); buffer = strstr(use_message_buffer, "\n\n");
if (buffer) if (buffer)
@ -849,20 +849,26 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
hook_arg1 = "message"; hook_arg1 = "message";


/* /*
* Only `-m` commit message option is checked here, as * `-m`, `-F`, `-C`, and `-c` provide the message body.
* it supports `--fixup` to append the commit message. * If none was given and this is an amend, use the target
* * commit's body instead.
* 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.
*/ */
if (have_option_m && !strcmp(fixup_prefix, "fixup")) if (have_option_m) {
strbuf_addbuf(&sb, &message); strbuf_addbuf(&sb, &message);

} else if (logfile && !strcmp(logfile, "-")) {
if (!strcmp(fixup_prefix, "amend")) { if (isatty(0))
if (have_option_m) fprintf(stderr, _("(reading log message from standard input)\n"));
die(_("options '%s' and '%s:%s' cannot be used together"), "-m", "--fixup", fixup_message); if (strbuf_read(&sb, 0, 0) < 0)
die_errno(_("could not read log from standard input"));
} else if (logfile) {
if (strbuf_read_file(&sb, logfile, 0) < 0)
die_errno(_("could not read log file '%s'"), logfile);
} else if (use_message) {
struct commit *c = lookup_commit_reference_by_name(use_message);
if (!c)
die(_("could not lookup commit '%s'"), use_message);
prepare_amend_commit(c, &sb, &ctx);
} else if (!strcmp(fixup_prefix, "amend")) {
prepare_amend_commit(commit, &sb, &ctx); prepare_amend_commit(commit, &sb, &ctx);
} }
} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) { } else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {
@ -1365,10 +1371,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
} }
if (fixup_message && squash_message) if (fixup_message && squash_message)
die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup"); 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", !!edit_message, "-c",
!!logfile, "-F", !!logfile, "-F");
!!fixup_message, "--fixup");
die_for_incompatible_opt4(have_option_m, "-m", die_for_incompatible_opt4(have_option_m, "-m",
!!edit_message, "-c", !!edit_message, "-c",
!!use_message, "-C", !!use_message, "-C",

View File

@ -384,18 +384,24 @@ test_expect_success '--fixup=reword: ignores staged changes' '
test_cmp foo actual test_cmp foo actual
' '


test_expect_success '--fixup=reword: error out with -m option' ' test_expect_success 'commit --fixup=reword: works with -m' '
commit_for_rebase_autosquash_setup && commit_for_rebase_autosquash_setup &&
echo "fatal: options '\''-m'\'' and '\''--fixup:reword'\'' cannot be used together" >expect && git commit --fixup=reword:HEAD~ -m "reword commit message" &&
test_must_fail git commit --fixup=reword:HEAD~ -m "reword commit message" 2>actual && test_commit_message HEAD <<-EOF
test_cmp expect actual amend! $(git log -1 --format=%s HEAD~2)

reword commit message
EOF
' '


test_expect_success '--fixup=amend: error out with -m option' ' test_expect_success 'commit --fixup=amend: works with -m' '
commit_for_rebase_autosquash_setup && commit_for_rebase_autosquash_setup &&
echo "fatal: options '\''-m'\'' and '\''--fixup:amend'\'' cannot be used together" >expect && git commit --fixup=amend:HEAD~ -m "amend commit message" &&
test_must_fail git commit --fixup=amend:HEAD~ -m "amend commit message" 2>actual && test_commit_message HEAD <<-EOF
test_cmp expect actual amend! $(git log -1 --format=%s HEAD~2)

amend commit message
EOF
' '


test_expect_success 'consecutive amend! commits remove amend! line from commit msg body' ' test_expect_success 'consecutive amend! commits remove amend! line from commit msg body' '
@ -432,6 +438,13 @@ test_expect_success 'deny to create amend! commit if its commit msg body is empt
test_cmp expected actual test_cmp expected actual
' '


test_expect_success 'deny to create amend! commit if -m is empty' '
commit_for_rebase_autosquash_setup &&
echo "Aborting commit due to empty commit message body." >expect &&
test_must_fail git commit --fixup=amend:HEAD~ -m "" 2>actual &&
test_cmp expect actual
'

test_expect_success 'amend! commit allows empty commit msg body with --allow-empty-message' ' test_expect_success 'amend! commit allows empty commit msg body with --allow-empty-message' '
commit_for_rebase_autosquash_setup && commit_for_rebase_autosquash_setup &&
cat >expected <<-EOF && cat >expected <<-EOF &&
@ -468,10 +481,82 @@ test_expect_success '--fixup=reword: give error with pathsec' '
test_cmp expect actual test_cmp expect actual
' '


test_expect_success '--fixup=reword: -F give error message' ' test_expect_success 'commit --fixup works with -F' '
echo "fatal: options '\''-F'\'' and '\''--fixup'\'' cannot be used together" >expect && commit_for_rebase_autosquash_setup &&
test_must_fail git commit --fixup=reword:HEAD~ -F msg 2>actual && echo "message" >msgfile &&
test_cmp expect actual git commit --fixup HEAD~ -F msgfile &&
test_commit_message HEAD <<-EOF
fixup! $(git log -1 --format=%s HEAD~2)

message
EOF
'

test_expect_success 'commit --fixup works with -C' '
commit_for_rebase_autosquash_setup &&
git commit --fixup HEAD~ -C HEAD &&
test_commit_message HEAD <<-EOF
fixup! $(git log -1 --format=%s HEAD~2)

$(get_commit_msg HEAD~)
EOF
'

test_expect_success 'commit --fixup=amend: works with -c' '
commit_for_rebase_autosquash_setup &&
test_set_editor : &&
git commit --fixup=amend:HEAD -c HEAD~ &&
test_commit_message HEAD <<-EOF
amend! intermediate commit

target message subject line

target message body line 1
target message body line 2
EOF
'

test_expect_success 'commit --fixup=amend:HEAD with -C HEAD and without have the same message' '
commit_for_rebase_autosquash_setup &&
start=$(git rev-parse HEAD) &&

git commit --fixup=amend:HEAD -C HEAD &&
git commit --fixup=amend:HEAD -C HEAD &&
git log -1 --pretty=%B >with-c &&

git reset --hard "$start" &&
test_set_editor : &&
git commit --fixup=amend:HEAD &&
git commit --fixup=amend:HEAD &&
git log -1 --pretty=%B >without-c &&

test_cmp with-c without-c
'

test_expect_success 'commit --fixup=amend: with -C copies full subject + body of squash commit' '
commit_for_rebase_autosquash_setup &&
git commit --squash HEAD~ -m "inner body" &&
echo "extra" >>foo &&
git add foo &&
git commit --fixup=amend:HEAD -C HEAD &&
test_commit_message HEAD <<-EOF
amend! squash! $(git log -1 --format=%s HEAD~3)

squash! $(git log -1 --format=%s HEAD~3)

inner body
EOF
'

test_expect_success 'commit --fixup=reword: works with -F' '
commit_for_rebase_autosquash_setup &&
echo "message from file" >msgfile &&
git commit --fixup=reword:HEAD~ -F msgfile &&
test_commit_message HEAD <<-EOF
amend! $(git log -1 --format=%s HEAD~2)

$(cat msgfile)
EOF
' '


test_expect_success 'commit --squash works with -F' ' test_expect_success 'commit --squash works with -F' '
@ -524,10 +609,7 @@ test_expect_success 'invalid message options when using --fixup' '
echo changes >>foo && echo changes >>foo &&
echo "message" >log && echo "message" >log &&
git add foo && git add foo &&
test_must_fail git commit --fixup HEAD~1 --squash HEAD~2 && test_must_fail git commit --fixup HEAD~1 --squash HEAD~2
test_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&
test_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&
test_must_fail git commit --fixup HEAD~1 -F log
' '


cat >expected-template <<EOF cat >expected-template <<EOF