{"thread":{"id":"65653","subject":"[PATCH 0/1] commit: allow -m/-F with --fixup=amend: or reword:","startedAt":"2026-05-18T11:23:22Z","lastAt":"2026-09-22T13:07:41Z","messageCount":13,"participants":["erik@cervined.in","Junio C Hamano","Phillip Wood","Erik Cervin Edin"],"isPatch":true,"patchVersion":1,"patchTotal":1},"messages":[{"id":"543526","messageId":"20260518112225.73172-2-erik@cervined.in","threadId":"65653","inReplyTo":null,"subject":"[PATCH 0/1] commit: allow -m/-F with --fixup=amend: or reword:","fromName":"","fromEmail":"erik@cervined.in","sentAt":"2026-05-18T11:22:24Z","receivedAt":"2026-05-18T11:23:22Z","isPatch":true,"body":"From: Erik Cervin-Edin <erik@cervined.in>\n\nThe commit --fixup=reword: (and --fixup:amend) options are powerful but\ncurrently not well-suited for non-interactive workflows.\n\nI often find myself hacking away on a branch and the last thing I do is\nfinalize and formulate the commit messages. One of the current ways of\ndoing this is running an interactive rebase and picking the commits in\nyour branch to reword. However, doing this requires you to linearly go\nthrough the messages and edit them one by one. The other options which\nallows more flexible editing is to generate linear patches -- but this\ntrades editing freedom for branch topology freedom and has its own\ndrawbacks.\n\nThe --fixup=reword: flag introduced in 494d314a05 (commit: add\namend suboption to --fixup to create amend! commit, 2021-03-15),\nadds a third workflow which allows rewording commits without initiating\na rebase and from the comfort of the HEAD of the branch. However, doing\nsuch editing is only possible using $EDITOR, which restricts its use in\nsome workflows.\n\nWhen amend:/reword: were introduced in Charvi's series, -m support\nfor amend fixups was discussed but not pursued\n(xmqqwnuvsw0d.fsf@gitster.g and xmqqczwmsjzl.fsf@gitster.g):\n\nOn Fri, 26 Feb 2021 11:32:30 -0800, Junio C Hamano wrote:\n> >> > +                     if (have_option_m)\n> >> > +                             die(_(\"cannot combine -m with --fixup:%s\"), fixup_message);\n> >> > +                     else\n> >> > +                             prepare_amend_commit(commit, &sb, &ctx);\n> >>\n> >> Hmph, why is -m so special?  Should we allow --fixup=amend:<cmd>\n> >> with -F (or -c/-C for that matter), or are these other options\n> >> caught at a lot higher layer already and we do not have to check\n> >> them here?\n> >\n> > yes, those options are caught earlier and give the error as below:\n> > \"Only one of -c/-C/-F/--fixup can be used.\"\n> > and only `-m` is checked over here.\n>\n> And the reason why -m cannot be checked early is because we do not\n> recognize which kind of \"fixup\" we are doing when \"only one of\n> -c/-C/-F/--fixup\" check is made before this function is called?\n>\n> OK.  I wonder if we can tell which kind of fixup we are doing much\n> earlier, though.  Then we could extend it to say \"Only one of\n> -c/-C/-F/-m/--fixup=amend:<commit> can be used\", etc., and we do not\n> have to have this \"only -m is checked here, everything else is\n> checked earlier\" curiosity.  But I do not know if such a change is\n> necessarily an improvement.  I guess a better \"fix\" would probably\n> be to add a comment to this function where it only checks for \"-m\"\n> and tell readers why -c/-C/-F do not have to be checked here.\n\nThis patch picks up that thread by allowing both -m and -F for\namend/reword fixups, bypassing the need for an interactive editor.\nThis makes it practical to, for example, write replacement messages in\nfiles and batch-apply them as reword fixups without stepping through\neach one interactively. It's also friendly to AI agents who have a hard time\nediting text using a non-interactive $EDITOR.\n\nAllowing -c/-C was also considered but left out of this patch -- it can\nbe added in a re-roll if reviewers think it's worthwhile. I could see it\nbeing useful, for example if you want to use git notes as a re-write\ncommit message channel. Since this is my first patch I intentionally\nthought it best to start small.\n\nErik Cervin-Edin (1):\n  commit: allow -m/-F with --fixup=amend: or reword:\n\n Documentation/git-commit.adoc             | 13 +++--\n builtin/commit.c                          | 41 ++++++++++----\n t/t7500-commit-template-squash-signoff.sh | 67 +++++++++++++++++++----\n 3 files changed, 92 insertions(+), 29 deletions(-)\n\n-- \n2.54.0.772.g683d7313b1\n\n"},{"id":"543527","messageId":"20260518112225.73172-4-erik@cervined.in","threadId":"65653","inReplyTo":"20260518112225.73172-2-erik@cervined.in","subject":"[PATCH 1/1] commit: allow -m/-F with --fixup=amend: or reword:","fromName":"","fromEmail":"erik@cervined.in","sentAt":"2026-05-18T11:22:26Z","receivedAt":"2026-05-18T11:23:42Z","isPatch":true,"body":"From: Erik Cervin-Edin <erik@cervined.in>\n\n--fixup=amend: and --fixup=reword: require an editor to supply the\nreplacement commit message. The -m and -F flags are rejected: -m is\ncaught by a die() in prepare_to_commit(), and -F is caught by\ndie_for_incompatible_opt4() which groups -F with --fixup as mutually\nexclusive. This makes these modes unusable in non-interactive\nworkflows -- notably AI coding agents.\n\nWhen the amend suboption was introduced in 494d314a05 (commit: add\namend suboption to --fixup to create amend! commit, 2021-03-15),\n-m support for amend fixups was discussed but not pursued, and -F\nwas already caught by the higher-layer incompatibility check grouping\nit with --fixup.\n\nAllow -m and -F to supply the replacement message body for amend and\nreword fixups. When provided, bypass the editor and directly use the\nuser's message as the body, replacing the original commit's message. For\n-F, the file contents are read into the message strbuf and then handled\nidentically to -m.\n\nPlain --fixup (without amend: or reword:) continues to reject -F but\nstill accepts -m (even though it's practically a no-op).\n\nSigned-off-by: Erik Cervin Edin <erik@cervined.in>\n---\n Documentation/git-commit.adoc             | 13 +++--\n builtin/commit.c                          | 41 ++++++++++----\n t/t7500-commit-template-squash-signoff.sh | 67 +++++++++++++++++++----\n 3 files changed, 92 insertions(+), 29 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 8329c1034b..9478d5d265 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -111,12 +111,13 @@ commit, but the additional commentary will be thrown away once the\n The commit created by `--fixup=amend:<commit>` is similar but its\n title is instead prefixed with \"amend!\". The log message of\n _<commit>_ is copied into the log message of the \"amend!\" commit and\n-opened in an editor so it can be refined. When `git rebase\n---autosquash` squashes the \"amend!\" commit into _<commit>_, the\n-log message of _<commit>_ is replaced by the refined log message\n-from the \"amend!\" commit. It is an error for the \"amend!\" commit's\n-log message to be empty unless `--allow-empty-message` is\n-specified.\n+opened in an editor so it can be refined. The replacement message may\n+also be supplied directly using `-m` or `-F`, bypassing the need to open\n+an editor. When `git rebase --autosquash` squashes the \"amend!\" commit\n+into _<commit>_, the log message of _<commit>_ is replaced by the\n+refined log message from the \"amend!\" commit. It is an error for the\n+\"amend!\" commit's log message to be empty unless `--allow-empty-message`\n+is specified.\n +\n `--fixup=reword:<commit>` is shorthand for `--fixup=amend:<commit>\n  --only`. It creates an \"amend!\" commit with only a log message\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 28f6174503..269c2d782b 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -837,21 +837,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\thook_arg1 = \"message\";\n \n \t\t/*\n-\t\t * Only `-m` commit message option is checked here, as\n-\t\t * it supports `--fixup` to append the commit message.\n-\t\t *\n-\t\t * The other commit message options `-c`/`-C`/`-F` are\n-\t\t * incompatible with all the forms of `--fixup` and\n-\t\t * have already errored out while parsing the `git commit`\n-\t\t * options.\n+\t\t * `-m` (and `-F`, converted to `-m` earlier for\n+\t\t * amend/reword) appends the message body here.\n+\t\t * `-c`/`-C` are still incompatible with all forms\n+\t\t * of `--fixup`.\n \t\t */\n \t\tif (have_option_m && !strcmp(fixup_prefix, \"fixup\"))\n \t\t\tstrbuf_addbuf(&sb, &message);\n \n \t\tif (!strcmp(fixup_prefix, \"amend\")) {\n \t\t\tif (have_option_m)\n-\t\t\t\tdie(_(\"options '%s' and '%s:%s' cannot be used together\"), \"-m\", \"--fixup\", fixup_message);\n-\t\t\tprepare_amend_commit(commit, &sb, &ctx);\n+\t\t\t\tstrbuf_addbuf(&sb, &message);\n+\t\t\telse\n+\t\t\t\tprepare_amend_commit(commit, &sb, &ctx);\n \t\t}\n \t} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {\n \t\tsize_t merge_msg_start;\n@@ -1338,10 +1336,12 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \t}\n \tif (fixup_message && squash_message)\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n-\tdie_for_incompatible_opt4(!!use_message, \"-C\",\n+\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n \t\t\t\t  !!edit_message, \"-c\",\n-\t\t\t\t  !!logfile, \"-F\",\n \t\t\t\t  !!fixup_message, \"--fixup\");\n+\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n+\t\t\t\t  !!edit_message, \"-c\",\n+\t\t\t\t  !!logfile, \"-F\");\n \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n \t\t\t\t  !!edit_message, \"-c\",\n \t\t\t\t  !!use_message, \"-C\",\n@@ -1410,6 +1410,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \t\t}\n \t}\n \n+\tif (logfile && fixup_message && !strcmp(fixup_prefix, \"fixup\"))\n+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"-F\", \"--fixup\");\n+\n \tif (0 <= edit_flag)\n \t\tuse_editor = edit_flag;\n \n@@ -1821,6 +1824,22 @@ int cmd_commit(int argc,\n \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n \t\t\t\t\t  builtin_commit_usage,\n \t\t\t\t\t  prefix, current_head, &s);\n+\n+\tif (logfile && fixup_message && !strcmp(fixup_prefix, \"amend\")) {\n+\t\tif (!strcmp(logfile, \"-\")) {\n+\t\t\tif (isatty(0))\n+\t\t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n+\t\t\tif (strbuf_read(&message, 0, 0) < 0)\n+\t\t\t\tdie_errno(_(\"could not read log from standard input\"));\n+\t\t} else {\n+\t\t\tif (strbuf_read_file(&message, logfile, 0) < 0)\n+\t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n+\t\t}\n+\t\tstrbuf_complete_line(&message);\n+\t\thave_option_m = 1;\n+\t\tFREE_AND_NULL(logfile);\n+\t}\n+\n \tif (trailer_args.nr)\n \t\ttrailer_config_init();\n \ndiff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh\nindex 66aff8e097..b7579ad789 100755\n--- a/t/t7500-commit-template-squash-signoff.sh\n+++ b/t/t7500-commit-template-squash-signoff.sh\n@@ -384,18 +384,28 @@ test_expect_success '--fixup=reword: ignores staged changes' '\n \ttest_cmp foo actual\n '\n \n-test_expect_success '--fixup=reword: error out with -m option' '\n+test_expect_success '--fixup=amend: with -m option' '\n \tcommit_for_rebase_autosquash_setup &&\n-\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:reword'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=reword:HEAD~ -m \"reword commit message\" 2>actual &&\n-\ttest_cmp expect actual\n+\tcat >expected <<-EOF &&\n+\tamend! $(git log -1 --format=%s HEAD~)\n+\n+\tamend commit message\n+\tEOF\n+\tgit commit --fixup=amend:HEAD~ -m \"amend commit message\" &&\n+\tget_commit_msg HEAD >actual &&\n+\ttest_cmp expected actual\n '\n \n-test_expect_success '--fixup=amend: error out with -m option' '\n+test_expect_success '--fixup=reword: with -m option' '\n \tcommit_for_rebase_autosquash_setup &&\n-\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:amend'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"amend commit message\" 2>actual &&\n-\ttest_cmp expect actual\n+\tcat >expected <<-EOF &&\n+\tamend! $(git log -1 --format=%s HEAD~)\n+\n+\treword commit message\n+\tEOF\n+\tgit commit --fixup=reword:HEAD~ -m \"reword commit message\" &&\n+\tget_commit_msg HEAD >actual &&\n+\ttest_cmp expected actual\n '\n \n test_expect_success 'consecutive amend! commits remove amend! line from commit msg body' '\n@@ -432,6 +442,12 @@ test_expect_success 'deny to create amend! commit if its commit msg body is empt\n \ttest_cmp expected actual\n '\n \n+test_expect_success '--fixup=amend: -m with empty message aborts' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"\" 2>err &&\n+\ttest_grep \"empty commit message body\" err\n+'\n+\n test_expect_success 'amend! commit allows empty commit msg body with --allow-empty-message' '\n \tcommit_for_rebase_autosquash_setup &&\n \tcat >expected <<-EOF &&\n@@ -468,10 +484,37 @@ test_expect_success '--fixup=reword: give error with pathsec' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--fixup=reword: -F give error message' '\n-\techo \"fatal: options '\\''-F'\\'' and '\\''--fixup'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=reword:HEAD~ -F msg  2>actual &&\n-\ttest_cmp expect actual\n+test_expect_success '--fixup=reword: with -F option' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"message from file\" >msgfile &&\n+\tcat >expected <<-EOF &&\n+\tamend! $(git log -1 --format=%s HEAD~)\n+\n+\tmessage from file\n+\tEOF\n+\tgit commit --fixup=reword:HEAD~ -F msgfile &&\n+\tget_commit_msg HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '--fixup=amend: with -F option' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"amend message from file\" >msgfile &&\n+\tcat >expected <<-EOF &&\n+\tamend! $(git log -1 --format=%s HEAD~)\n+\n+\tamend message from file\n+\tEOF\n+\tgit commit --fixup=amend:HEAD~ -F msgfile &&\n+\tget_commit_msg HEAD >actual &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success '-F with plain --fixup still errors' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"message\" >msgfile &&\n+\ttest_must_fail git commit --fixup HEAD~ -F msgfile 2>err &&\n+\ttest_grep \"cannot be used together\" err\n '\n \n test_expect_success 'commit --squash works with -F' '\n-- \n2.54.0.772.g683d7313b1\n\n"},{"id":"543531","messageId":"xmqqik8kc2nj.fsf@gitster.g","threadId":"65653","inReplyTo":"20260518112225.73172-4-erik@cervined.in","subject":"Re: [PATCH 1/1] commit: allow -m/-F with --fixup=amend: or reword:","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-18T12:39:44Z","receivedAt":"2026-05-18T12:39:47Z","isPatch":true,"body":"erik@cervined.in writes:\n\n> From: Erik Cervin-Edin <erik@cervined.in>\n\nThe name on this overriding in-body From: line, and the name on\nSigned-off-by: line below, must match.  Please pick a name with or\nwithout hyphen and stick to it.\n\n> --fixup=amend: and --fixup=reword: require an editor to supply the\n> replacement commit message. The -m and -F flags are rejected: -m is\n> caught by a die() in prepare_to_commit(), and -F is caught by\n> die_for_incompatible_opt4() which groups -F with --fixup as mutually\n> exclusive. This makes these modes unusable in non-interactive\n> workflows -- notably AI coding agents.\n\n\"Unusable\" may be stronger than reality, as you can make creatie use\nof GIT_EDITOR to achieve what you want.  \"awkward\" or \"poorly suited\"\nwould be more fitting.\n\n> Plain --fixup (without amend: or reword:) continues to reject -F but\n> still accepts -m (even though it's practically a no-op).\n\nIs it \"practically a no-op\"?  Wouldn't\n\n   $ git commit --fixup <commit> -m \"message body\"\n\nbe useful to leave a message in the resulting commit, which is later\nto be squashed into the named <commit>?  Actually squashing with \"fixup!\"\nmay lose the message supplied here, but wouldn't people use this\nfacility to more easily identify what each of the fixups are about?\n\nFor the same reason, \"-F\" would be just as useful as \"-m\" in this context,\nand it feels a bit inconsistent to allow one while rejecting the other.\n\n> diff --git a/builtin/commit.c b/builtin/commit.c\n> index 28f6174503..269c2d782b 100644\n> --- a/builtin/commit.c\n> +++ b/builtin/commit.c\n> @@ -837,21 +837,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n>  \t\thook_arg1 = \"message\";\n>  \n>  \t\t/*\n> -\t\t * Only `-m` commit message option is checked here, as\n> -\t\t * it supports `--fixup` to append the commit message.\n> -\t\t *\n> -\t\t * The other commit message options `-c`/`-C`/`-F` are\n> -\t\t * incompatible with all the forms of `--fixup` and\n> -\t\t * have already errored out while parsing the `git commit`\n> -\t\t * options.\n> +\t\t * `-m` (and `-F`, converted to `-m` earlier for\n> +\t\t * amend/reword) appends the message body here.\n> +\t\t * `-c`/`-C` are still incompatible with all forms\n> +\t\t * of `--fixup`.\n>  \t\t */\n>  \t\tif (have_option_m && !strcmp(fixup_prefix, \"fixup\"))\n>  \t\t\tstrbuf_addbuf(&sb, &message);\n>  \n>  \t\tif (!strcmp(fixup_prefix, \"amend\")) {\n>  \t\t\tif (have_option_m)\n> -\t\t\t\tdie(_(\"options '%s' and '%s:%s' cannot be used together\"), \"-m\", \"--fixup\", fixup_message);\n\nGood that you got rid of this overly long die() message line.\n\n> -\t\t\tprepare_amend_commit(commit, &sb, &ctx);\n> +\t\t\t\tstrbuf_addbuf(&sb, &message);\n> +\t\t\telse\n> +\t\t\t\tprepare_amend_commit(commit, &sb, &ctx);\n>  \t\t}\n>  \t} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {\n>  \t\tsize_t merge_msg_start;\n> @@ -1338,10 +1336,12 @@ static int parse_and_validate_options(int argc, const char *argv[],\n>  \t}\n>  \tif (fixup_message && squash_message)\n>  \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n> -\tdie_for_incompatible_opt4(!!use_message, \"-C\",\n> +\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n>  \t\t\t\t  !!edit_message, \"-c\",\n> -\t\t\t\t  !!logfile, \"-F\",\n>  \t\t\t\t  !!fixup_message, \"--fixup\");\n> +\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n> +\t\t\t\t  !!edit_message, \"-c\",\n> +\t\t\t\t  !!logfile, \"-F\");\n>  \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n>  \t\t\t\t  !!edit_message, \"-c\",\n>  \t\t\t\t  !!use_message, \"-C\",\n> @@ -1410,6 +1410,9 @@ static int parse_and_validate_options(int argc, const char *argv[],\n>  \t\t}\n>  \t}\n>  \n> +\tif (logfile && fixup_message && !strcmp(fixup_prefix, \"fixup\"))\n> +\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"-F\", \"--fixup\");\n> +\n>  \tif (0 <= edit_flag)\n>  \t\tuse_editor = edit_flag;\n>  \n> @@ -1821,6 +1824,22 @@ int cmd_commit(int argc,\n>  \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n>  \t\t\t\t\t  builtin_commit_usage,\n>  \t\t\t\t\t  prefix, current_head, &s);\n> +\n> +\tif (logfile && fixup_message && !strcmp(fixup_prefix, \"amend\")) {\n> +\t\tif (!strcmp(logfile, \"-\")) {\n> +\t\t\tif (isatty(0))\n> +\t\t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n> +\t\t\tif (strbuf_read(&message, 0, 0) < 0)\n> +\t\t\t\tdie_errno(_(\"could not read log from standard input\"));\n> +\t\t} else {\n> +\t\t\tif (strbuf_read_file(&message, logfile, 0) < 0)\n> +\t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n> +\t\t}\n> +\t\tstrbuf_complete_line(&message);\n> +\t\thave_option_m = 1;\n> +\t\tFREE_AND_NULL(logfile);\n> +\t}\n> +\n\nIt is curious that for this new feature alone, but not the other\nexisting code paths, \"-m\" and \"-F\" options reads from file in the\nnew code here, instead of letting the existing code for \"-F\" to read\n(which happens inside prepare_to_commit(), I presume?).\n\nA potential problem of the above code is if we find something wrong\nin message and complain later in the control flow, we have long lost\nwhere the message came from, as the point of the above code is\nexactly to pretend that \"--fixup:amend/reword -F\" message did *not*\ncome from a file with the \"-F\" option, but from the command line via\nthe \"-m\" option.\n\n> +test_expect_success '--fixup=amend: with -m option' '\n>  \tcommit_for_rebase_autosquash_setup &&\n> -\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:reword'\\'' cannot be used together\" >expect &&\n> -\ttest_must_fail git commit --fixup=reword:HEAD~ -m \"reword commit message\" 2>actual &&\n> -\ttest_cmp expect actual\n> +\tcat >expected <<-EOF &&\n\nThis comment is not about the added logic, but I notice that among\n86 hits with string \"expect\" in this file in today's \"master\", only\n14 hits are with string \"expected\", i.e., the prevalent name for the\n\"golden copy result\" that is compared with the actula result (called\n\"actual\") is \"expect\", not \"expected\".  Please do not make the\nsituation worse.\n\n> -\ttest_cmp expect actual\n> +\tcat >expected <<-EOF &&\n\nDitto.\n"},{"id":"543537","messageId":"ac6aaaca-2b7c-4892-ba93-0dc3e3c18ff7@gmail.com","threadId":"65653","inReplyTo":"xmqqik8kc2nj.fsf@gitster.g","subject":"Re: [PATCH 1/1] commit: allow -m/-F with --fixup=amend: or reword:","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-18T15:27:24Z","receivedAt":"2026-05-18T15:27:28Z","isPatch":true,"body":"On 18/05/2026 13:39, Junio C Hamano wrote:\n> erik@cervined.in writes:\n> \n>> From: Erik Cervin-Edin <erik@cervined.in>\n> \n> The name on this overriding in-body From: line, and the name on\n> Signed-off-by: line below, must match.  Please pick a name with or\n> without hyphen and stick to it.\n> \n>> --fixup=amend: and --fixup=reword: require an editor to supply the\n>> replacement commit message. The -m and -F flags are rejected: -m is\n>> caught by a die() in prepare_to_commit(), and -F is caught by\n>> die_for_incompatible_opt4() which groups -F with --fixup as mutually\n>> exclusive. This makes these modes unusable in non-interactive\n>> workflows -- notably AI coding agents.\n> \n> \"Unusable\" may be stronger than reality, as you can make creatie use\n> of GIT_EDITOR to achieve what you want.  \"awkward\" or \"poorly suited\"\n> would be more fitting.\n\nIndeed\n\n>> Plain --fixup (without amend: or reword:) continues to reject -F but\n>> still accepts -m (even though it's practically a no-op).\n> \n> Is it \"practically a no-op\"?  Wouldn't\n> \n>     $ git commit --fixup <commit> -m \"message body\"\n> \n> be useful to leave a message in the resulting commit, which is later\n> to be squashed into the named <commit>?  Actually squashing with \"fixup!\"\n> may lose the message supplied here, but wouldn't people use this\n> facility to more easily identify what each of the fixups are about?\n\nYes, I find it quite useful to make a note of what the fixup is doing if \nI know I'm not going to squash it for a while.\n\n> For the same reason, \"-F\" would be just as useful as \"-m\" in this context,\n> and it feels a bit inconsistent to allow one while rejecting the other.\n\nYes, looking at the way the code is structured I wonder if these options \nwere made incompatible to simplify the implementation, or maybe the \nimplementation merely reflects those restrictions.\n\n>> @@ -1821,6 +1824,22 @@ int cmd_commit(int argc,\n>>   \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n>>   \t\t\t\t\t  builtin_commit_usage,\n>>   \t\t\t\t\t  prefix, current_head, &s);\n>> +\n>> +\tif (logfile && fixup_message && !strcmp(fixup_prefix, \"amend\")) {\n>> +\t\tif (!strcmp(logfile, \"-\")) {\n>> +\t\t\tif (isatty(0))\n>> +\t\t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n>> +\t\t\tif (strbuf_read(&message, 0, 0) < 0)\n>> +\t\t\t\tdie_errno(_(\"could not read log from standard input\"));\n>> +\t\t} else {\n>> +\t\t\tif (strbuf_read_file(&message, logfile, 0) < 0)\n>> +\t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n>> +\t\t}\n>> +\t\tstrbuf_complete_line(&message);\n>> +\t\thave_option_m = 1;\n>> +\t\tFREE_AND_NULL(logfile);\n>> +\t}\n>> +\n> \n> It is curious that for this new feature alone, but not the other\n> existing code paths, \"-m\" and \"-F\" options reads from file in the\n> new code here, instead of letting the existing code for \"-F\" to read\n> (which happens inside prepare_to_commit(), I presume?).\n> \n> A potential problem of the above code is if we find something wrong\n> in message and complain later in the control flow, we have long lost\n> where the message came from, as the point of the above code is\n> exactly to pretend that \"--fixup:amend/reword -F\" message did *not*\n> come from a file with the \"-F\" option, but from the command line via\n> the \"-m\" option.\n\nIt is indeed unfortunate that we end up duplicating the code to read the \nlogfile here. I wonder how hard it would be to refactor \nprepare_to_commit() so that it can accommodate \"--fixup=amend:<commit> -F\"\n\n>> +test_expect_success '--fixup=amend: with -m option' '\n>>   \tcommit_for_rebase_autosquash_setup &&\n>> -\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:reword'\\'' cannot be used together\" >expect &&\n>> -\ttest_must_fail git commit --fixup=reword:HEAD~ -m \"reword commit message\" 2>actual &&\n>> -\ttest_cmp expect actual\n>> +\tcat >expected <<-EOF &&\n> \n> This comment is not about the added logic, but I notice that among\n> 86 hits with string \"expect\" in this file in today's \"master\", only\n> 14 hits are with string \"expected\", i.e., the prevalent name for the\n> \"golden copy result\" that is compared with the actula result (called\n> \"actual\") is \"expect\", not \"expected\".  Please do not make the\n> situation worse.\n\nIn this case it would be better to use\n\n\ttest_commit_message HEAD <<-EOF\n\tamend! $(git log -1 --format=%s HEAD~)\n\n\tamend commit message\n\tEOF\n\nand avoid creating actual and expect all together.\n\nThanks\n\nPhillip\n\n"},{"id":"544001","messageId":"aguM7UIbAo19Zojv@mbp","threadId":"65653","inReplyTo":"ac6aaaca-2b7c-4892-ba93-0dc3e3c18ff7@gmail.com","subject":"Re: [PATCH 1/1] commit: allow -m/-F with --fixup=amend: or reword:","fromName":"Erik Cervin Edin","fromEmail":"erik@cervined.in","sentAt":"2026-05-24T15:00:26Z","receivedAt":"2026-05-24T15:00:31Z","isPatch":true,"body":"> > > --fixup=amend: and --fixup=reword: require an editor to supply the\n> > > replacement commit message. The -m and -F flags are rejected: -m is\n> > > caught by a die() in prepare_to_commit(), and -F is caught by\n> > > die_for_incompatible_opt4() which groups -F with --fixup as mutually\n> > > exclusive. This makes these modes unusable in non-interactive\n> > > workflows -- notably AI coding agents.\n> > \n> > \"Unusable\" may be stronger than reality, as you can make creatie use\n> > of GIT_EDITOR to achieve what you want.  \"awkward\" or \"poorly suited\"\n> > would be more fitting.\n> \n> Indeed\n\nFair, \"poorly suited\" is more accurate. It's not impossible, just very\nawkward.\n\n> > > Plain --fixup (without amend: or reword:) continues to reject -F but\n> > > still accepts -m (even though it's practically a no-op).\n> > \n> > Is it \"practically a no-op\"?\n\nNo, I was mistaken. The message is kept until autosquash.\n\n    The `-m` option may be used to supplement the log message of the\n    created commit, but the additional commentary will be thrown away\n    once the \"fixup!\" commit is squashed into _<commit>_ by `git rebase\n    --autosquash`.\n\nI was trying to fill in the gaps here on the intent of the pre-existing\nbehavior (to reject -F with --fixup) and I kind of assumed the message\nwas being discarded.\n\n> > For the same reason, \"-F\" would be just as useful as \"-m\" in this context,\n> > and it feels a bit inconsistent to allow one while rejecting the other.\n> \n> Yes, looking at the way the code is structured I wonder if these options\n> were made incompatible to simplify the implementation, or maybe the\n> implementation merely reflects those restrictions.\n\nI think it would. I kept the pre-existing behavior because I wasn't sure\nif the rejection meant \"Error. You are doing something that doesn't make\nsense -- you probably meant to do something else\" or \"Sorry. What you're\ntrying to do is not supported\"\n\nA closer look at the original implementation 30884c9afc (commit: add\nsupport for --fixup <commit> -m\"<extra message>\", 2017-12-22) makes it\nclear the intent here is the latter:\n\n    Those options could also support combining with -m, but given what\n    they do I can't think of a good use-case for doing that, so I have not\n    made the more invasive change of splitting up the logic in commit.c to\n    first act on those, and then on -m options.\n\nThere is a case to not reject them, it was just deemed unnecessary\ncomplex for something without a clear use-case.\n\nIn the ideal case, given that -m works (and does something useful), it's\nreasonable to expect -F to do the same (for the same reasons as\n--fixup=reword:.) Although, it's arguably less crucial in this\nusecase. Given what its ephemeral nature, such a message is likely a\nterse comment, -m \"forgot to format\" or similar.\n\nI think it makes sense to allow -F for all --fixup variations, for\nconsistency. For the plain --fixup, -c/-C are probably less justifiable,\nbut -F mirroring -m seems worthwhile for consistency's sake in all\nvariations.\n\n> > A potential problem of the above code is if we find something wrong\n> > in message and complain later in the control flow\n> > in message and complain later in the control flow, we have long lost\n> > where the message came from, as the point of the above code is\n> > exactly to pretend that \"--fixup:amend/reword -F\" message did *not*\n> > come from a file with the \"-F\" option, but from the command line via\n> > the \"-m\" option.\n\nNow that you mention this, I guess a message on stdin can be arbitrarily\nlarge, have null bytes and maybe some other oddities which the -m\nwould never have.\n\n> I wonder how hard it would be to refactor prepare_to_commit()\n> so that it can accommodate \"--fixup=amend:<commit> -F\"\n\nI think this is doable.\n\n> > > +test_expect_success '--fixup=amend: with -m option' '\n> > >   \tcommit_for_rebase_autosquash_setup &&\n> > > -\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:reword'\\'' cannot be used together\" >expect &&\n> > > -\ttest_must_fail git commit --fixup=reword:HEAD~ -m \"reword commit message\" 2>actual &&\n> > > -\ttest_cmp expect actual\n> > > +\tcat >expected <<-EOF &&\n> > \n> > This comment is not about the added logic, but I notice that among\n> > 86 hits with string \"expect\" in this file in today's \"master\", only\n\n> > 14 hits are with string \"expected\", i.e., the prevalent name for the\n> > \"golden copy result\" that is compared with the actula result (called\n> > \"actual\") is \"expect\", not \"expected\".  Please do not make the\n> > situation worse.\n\nMea culpa. I overlooked this distinction.\n\n> In this case it would be better to use\n> \n> \ttest_commit_message HEAD <<-EOF\n> \tamend! $(git log -1 --format=%s HEAD~)\n> \n> \tamend commit message\n> \tEOF\n> \n> and avoid creating actual and expect all together.\n\nThat would also work (except it has to be HEAD~2, since the reword\ncommit advances HEAD by one)\n\nThank you both for the review. I will reroll as a V2 taking your\nsuggestions into account.\n\n- Erik\n"},{"id":"544106","messageId":"cover.1779792311.git.erik@cervined.in","threadId":"65653","inReplyTo":"20260518112225.73172-2-erik@cervined.in","subject":"[PATCH v2 0/2] commit: allow -m/-F/-c/-C for all --fixup variations","fromName":"","fromEmail":"erik@cervined.in","sentAt":"2026-05-26T10:47:42Z","receivedAt":"2026-05-26T10:48:03Z","isPatch":true,"body":"From: Erik Cervin-Edin <erik@cervined.in>\n\nV1 was a single patch and only addressed -m/-F.  V2 makes two\nsubstantive changes.\n\nFirst, the refactor.  V1 added a special-cased file slurp in\ncmd_commit() that pretended -F had been spelled -m.  Junio noted\nthis loses the \"this came from a file\" origin if anything goes\nwrong later in the control flow [1], and Phillip suggested\nrefactoring prepare_to_commit() [2].  V2 does that: prepare_to_commit()\nnow consults fixup_message at each message-source branch and routes the\nmessage origin flags through the same code path for all --fixup\nvariations.\n\nSecond, scope extended to -c/-C.  In the V1 thread I noted [3]\nthat -F made sense across all --fixup variants for consistency,\nbut that -c/-C felt \"probably less justifiable\" for plain --fixup.\nLooking at it more while refactoring, the existing patchwork of\nwhich flag works with which variant looked less and less like a\ndesign and more like an accident -- once -F was threaded through\nprepare_to_commit(), -c/-C fell out of the same path naturally\n(blocked by the same die_for_incompatible_opt4() grouping that\ncaught -F).  This lives in its own patch (2/2) -- I won't object\nif reviewers prefer to drop it or rolling it separately.\n\nThere is one wrinkle worth flagging on 2/2.  When -c/-C names a\nsource commit whose message starts with \"amend! \",\nprepare_amend_commit() strips that line -- the same stripping that\nhappens for a no-source --fixup=amend:<commit>.  This is independent\nof which --fixup variant is being produced (the target); it depends\nonly on whether the -c/-C source is itself an amend!/reword! commit.\nThe upside is that\n\n    git commit --fixup=amend:foo -C foo\n\nand\n\n    GIT_EDITOR=: git commit --fixup=amend:foo\n\nproduce the same commit.  If reviewers would rather -c/-C take the\nsource message verbatim, that's a small change and I'm open to it.\n\nSmaller fixes from V1 review:\n\n  * \"unusable\" softened to \"poorly suited\" in the rationale [1].\n\n  * Dropped the incorrect claim that plain --fixup -m is\n    \"practically a no-op\"\n\n  * Adopted Phillip's suggestion to use test_commit_message in the\n    new --fixup=amend: -m test, which also resolves the\n    expected/expect golden-file naming Junio called out [1][2].\n\n[1] https://lore.kernel.org/git/xmqqik8kc2nj.fsf@gitster.g/\n[2] https://lore.kernel.org/git/ac6aaaca-2b7c-4892-ba93-0dc3e3c18ff7@gmail.com/\n[3] https://lore.kernel.org/git/aguM7UIbAo19Zojv@mbp/\n\nErik Cervin-Edin (2):\n  commit: allow -m/-F for all kinds of --fixup\n  commit: allow -c/-C for all kinds of --fixup\n\n Documentation/git-commit.adoc             |  22 +++--\n builtin/commit.c                          |  41 ++++----\n t/t7500-commit-template-squash-signoff.sh | 114 +++++++++++++++++++---\n 3 files changed, 133 insertions(+), 44 deletions(-)\n\nRange-diff against v1:\n1:  49e202f04b ! 1:  e9f07d49ee commit: allow -m/-F with --fixup=amend: or reword:\n    @@ Metadata\n     Author: Erik Cervin-Edin <erik@cervined.in>\n     \n      ## Commit message ##\n    -    commit: allow -m/-F with --fixup=amend: or reword:\n    +    commit: allow -m/-F for all kinds of --fixup\n     \n    -    commit: allow -m/-F with --fixup=amend: or reword:\n    +    The ability to provide a commit message for git commit --fixup and its\n    +    variations is limited:\n     \n    -    --fixup=amend: and --fixup=reword: require an editor to supply the\n    -    replacement commit message. The -m and -F flags are rejected: -m is\n    -    caught by a die() in prepare_to_commit(), and -F is caught by\n    +      * Plain --fixup only allows using the -m flag\n    +\n    +      * The amend/reword --fixup variants only allow supplying the message\n    +        using an editor\n    +\n    +    For amend/reword, the -m and -F flags are rejected: -m is caught by a\n    +    die() in prepare_to_commit(), and -F is caught by\n         die_for_incompatible_opt4() which groups -F with --fixup as mutually\n    -    exclusive. This makes these modes unusable in non-interactive\n    -    workflows -- notably AI coding agents.\n    +    exclusive.  This makes these modes poorly suited for non-interactive\n    +    workflows -- notably when using AI coding agents.\n    +\n    +    When support to use the -m option was introduced in [1] it was noted\n    +    that there could be support for other options but at the time the use\n    +    case was deemed too niche.  Later, when the amend suboption was\n    +    introduced in [2] -m support for amend fixups was discussed but not\n    +    pursued, and -F was already caught by the higher-layer incompatibility\n    +    check grouping it with --fixup.\n    +\n    +    The rejections of these options hark back to when --fixup was\n    +    introduced in [3] and as noted in [1] -- there's nothing inherently\n    +    preventing support for them.  The current patchwork of which flags\n    +    work with which --fixup variants has no strong logic to it, and\n    +    allowing all of them simplifies both the code and the interface.\n     \n    -    When the amend suboption was introduced in 494d314a05 (commit: add\n    -    amend suboption to --fixup to create amend! commit, 2021-03-15),\n    -    -m support for amend fixups was discussed but not pursued, and -F\n    -    was already caught by the higher-layer incompatibility check grouping\n    -    it with --fixup.\n    +    Allow -m and -F to supply the message body for all --fixup variations,\n    +    mirroring the flow of a regular commit.  -c and -C, which are blocked\n    +    by the same incompatibility check, are handled in the next commit.\n     \n    -    Allow -m and -F to supply the replacement message body for amend and\n    -    reword fixups. When provided, bypass the editor and directly use the\n    -    user's message as the body, replacing the original commit's message. For\n    -    -F, the file contents are read into the message strbuf and then handled\n    -    identically to -m.\n    +    1. 30884c9afc (commit: add support for --fixup <commit> -m\"<extra\n    +       message>\", 2017-12-22)\n     \n    -    Plain --fixup (without amend: or reword:) continues to reject -F but\n    -    still accepts -m (even though it's practically a no-op).\n    +    2. 494d314a05 (commit: add amend suboption to --fixup to create amend!\n    +       commit, 2021-03-15)\n     \n    -    Signed-off-by: Erik Cervin Edin <erik@cervined.in>\n    +    3. d71b8ba7c9 (commit: --fixup option for use with rebase --autosquash,\n    +       2010-11-02)\n    +\n    +    Helped-by: Junio C Hamano <gitster@pobox.com>\n    +    Suggested-by: Phillip Wood <phillip.wood123@gmail.com>\n    +    Signed-off-by: Erik Cervin-Edin <erik@cervined.in>\n     \n      ## Documentation/git-commit.adoc ##\n    -@@ Documentation/git-commit.adoc: commit, but the additional commentary will be thrown away once the\n    +@@ Documentation/git-commit.adoc: include::diff-context-options.adoc[]\n    + The commit created by plain `--fixup=<commit>` has a title\n    + composed of \"fixup!\" followed by the title of _<commit>_,\n    + and is recognized specially by `git rebase --autosquash`. The `-m`\n    +-option may be used to supplement the log message of the created\n    +-commit, but the additional commentary will be thrown away once the\n    +-\"fixup!\" commit is squashed into _<commit>_ by\n    ++or `-F` option may be used to supplement the log message\n    ++of the created commit, but the additional commentary will be thrown\n    ++away once the \"fixup!\" commit is squashed into _<commit>_ by\n    + `git rebase --autosquash`.\n    + +\n      The commit created by `--fixup=amend:<commit>` is similar but its\n      title is instead prefixed with \"amend!\". The log message of\n      _<commit>_ is copied into the log message of the \"amend!\" commit and\n    @@ Documentation/git-commit.adoc: commit, but the additional commentary will be thr\n     -log message to be empty unless `--allow-empty-message` is\n     -specified.\n     +opened in an editor so it can be refined. The replacement message may\n    -+also be supplied directly using `-m` or `-F`, bypassing the need to open\n    -+an editor. When `git rebase --autosquash` squashes the \"amend!\" commit\n    -+into _<commit>_, the log message of _<commit>_ is replaced by the\n    -+refined log message from the \"amend!\" commit. It is an error for the\n    -+\"amend!\" commit's log message to be empty unless `--allow-empty-message`\n    -+is specified.\n    ++also be supplied directly using `-m` or `-F`, bypassing the\n    ++need to open an editor. When `git rebase\n    ++--autosquash` squashes the \"amend!\" commit into _<commit>_, the log\n    ++message of _<commit>_ is replaced by the refined log message from the\n    ++\"amend!\" commit. It is an error for the \"amend!\" commit's log message\n    ++to be empty unless `--allow-empty-message` is specified.\n      +\n      `--fixup=reword:<commit>` is shorthand for `--fixup=amend:<commit>\n       --only`. It creates an \"amend!\" commit with only a log message\n     \n      ## builtin/commit.c ##\n    +@@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,\n    + \tif (have_option_m && !fixup_message) {\n    + \t\tstrbuf_addbuf(&sb, &message);\n    + \t\thook_arg1 = \"message\";\n    +-\t} else if (logfile && !strcmp(logfile, \"-\")) {\n    ++\t} else if (logfile && !fixup_message && !strcmp(logfile, \"-\")) {\n    + \t\tif (isatty(0))\n    + \t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n    + \t\tif (strbuf_read(&sb, 0, 0) < 0)\n    + \t\t\tdie_errno(_(\"could not read log from standard input\"));\n    + \t\thook_arg1 = \"message\";\n    +-\t} else if (logfile) {\n    ++\t} else if (logfile && !fixup_message) {\n    + \t\tif (strbuf_read_file(&sb, logfile, 0) < 0)\n    + \t\t\tdie_errno(_(\"could not read log file '%s'\"),\n    + \t\t\t\t  logfile);\n    + \t\thook_arg1 = \"message\";\n    +-\t} else if (use_message) {\n    ++\t} else if (use_message && !fixup_message) {\n    + \t\tconst char *buffer;\n    + \t\tbuffer = strstr(use_message_buffer, \"\\n\\n\");\n    + \t\tif (buffer)\n     @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const char *prefix,\n      \t\thook_arg1 = \"message\";\n      \n    @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n     -\t\t * incompatible with all the forms of `--fixup` and\n     -\t\t * have already errored out while parsing the `git commit`\n     -\t\t * options.\n    -+\t\t * `-m` (and `-F`, converted to `-m` earlier for\n    -+\t\t * amend/reword) appends the message body here.\n    -+\t\t * `-c`/`-C` are still incompatible with all forms\n    -+\t\t * of `--fixup`.\n    ++\t\t * Only `-m` and `-F` are handled here. `-c`/`-C` are\n    ++\t\t * incompatible with --fixup and have already errored out\n    ++\t\t * during option parsing.\n      \t\t */\n    - \t\tif (have_option_m && !strcmp(fixup_prefix, \"fixup\"))\n    +-\t\tif (have_option_m && !strcmp(fixup_prefix, \"fixup\"))\n    ++\t\tif (have_option_m) {\n      \t\t\tstrbuf_addbuf(&sb, &message);\n    - \n    - \t\tif (!strcmp(fixup_prefix, \"amend\")) {\n    - \t\t\tif (have_option_m)\n    +-\n    +-\t\tif (!strcmp(fixup_prefix, \"amend\")) {\n    +-\t\t\tif (have_option_m)\n     -\t\t\t\tdie(_(\"options '%s' and '%s:%s' cannot be used together\"), \"-m\", \"--fixup\", fixup_message);\n    --\t\t\tprepare_amend_commit(commit, &sb, &ctx);\n    -+\t\t\t\tstrbuf_addbuf(&sb, &message);\n    -+\t\t\telse\n    -+\t\t\t\tprepare_amend_commit(commit, &sb, &ctx);\n    ++\t\t} else if (logfile && !strcmp(logfile, \"-\")) {\n    ++\t\t\tif (isatty(0))\n    ++\t\t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n    ++\t\t\tif (strbuf_read(&sb, 0, 0) < 0)\n    ++\t\t\t\tdie_errno(_(\"could not read log from standard input\"));\n    ++\t\t} else if (logfile) {\n    ++\t\t\tif (strbuf_read_file(&sb, logfile, 0) < 0)\n    ++\t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n    ++\t\t} else if (!strcmp(fixup_prefix, \"amend\")) {\n    + \t\t\tprepare_amend_commit(commit, &sb, &ctx);\n      \t\t}\n      \t} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {\n    - \t\tsize_t merge_msg_start;\n     @@ builtin/commit.c: static int parse_and_validate_options(int argc, const char *argv[],\n      \t}\n      \tif (fixup_message && squash_message)\n    @@ builtin/commit.c: static int parse_and_validate_options(int argc, const char *ar\n      \t\t\t\t  !!edit_message, \"-c\",\n     -\t\t\t\t  !!logfile, \"-F\",\n      \t\t\t\t  !!fixup_message, \"--fixup\");\n    -+\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n    -+\t\t\t\t  !!edit_message, \"-c\",\n    -+\t\t\t\t  !!logfile, \"-F\");\n      \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n      \t\t\t\t  !!edit_message, \"-c\",\n    - \t\t\t\t  !!use_message, \"-C\",\n    -@@ builtin/commit.c: static int parse_and_validate_options(int argc, const char *argv[],\n    - \t\t}\n    - \t}\n    - \n    -+\tif (logfile && fixup_message && !strcmp(fixup_prefix, \"fixup\"))\n    -+\t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"-F\", \"--fixup\");\n    -+\n    - \tif (0 <= edit_flag)\n    - \t\tuse_editor = edit_flag;\n    - \n    -@@ builtin/commit.c: int cmd_commit(int argc,\n    - \targc = parse_and_validate_options(argc, argv, builtin_commit_options,\n    - \t\t\t\t\t  builtin_commit_usage,\n    - \t\t\t\t\t  prefix, current_head, &s);\n    -+\n    -+\tif (logfile && fixup_message && !strcmp(fixup_prefix, \"amend\")) {\n    -+\t\tif (!strcmp(logfile, \"-\")) {\n    -+\t\t\tif (isatty(0))\n    -+\t\t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n    -+\t\t\tif (strbuf_read(&message, 0, 0) < 0)\n    -+\t\t\t\tdie_errno(_(\"could not read log from standard input\"));\n    -+\t\t} else {\n    -+\t\t\tif (strbuf_read_file(&message, logfile, 0) < 0)\n    -+\t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n    -+\t\t}\n    -+\t\tstrbuf_complete_line(&message);\n    -+\t\thave_option_m = 1;\n    -+\t\tFREE_AND_NULL(logfile);\n    -+\t}\n    -+\n    - \tif (trailer_args.nr)\n    - \t\ttrailer_config_init();\n    - \n     \n      ## t/t7500-commit-template-squash-signoff.sh ##\n     @@ t/t7500-commit-template-squash-signoff.sh: test_expect_success '--fixup=reword: ignores staged changes' '\n    @@ t/t7500-commit-template-squash-signoff.sh: test_expect_success '--fixup=reword:\n      '\n      \n     -test_expect_success '--fixup=reword: error out with -m option' '\n    -+test_expect_success '--fixup=amend: with -m option' '\n    ++test_expect_success 'commit --fixup=reword: works with -m' '\n      \tcommit_for_rebase_autosquash_setup &&\n     -\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:reword'\\'' cannot be used together\" >expect &&\n     -\ttest_must_fail git commit --fixup=reword:HEAD~ -m \"reword commit message\" 2>actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\tamend! $(git log -1 --format=%s HEAD~)\n    +-\ttest_cmp expect actual\n    ++\tgit commit --fixup=reword:HEAD~ -m \"reword commit message\" &&\n    ++\ttest_commit_message HEAD <<-EOF\n    ++\tamend! $(git log -1 --format=%s HEAD~2)\n     +\n    -+\tamend commit message\n    ++\treword commit message\n     +\tEOF\n    -+\tgit commit --fixup=amend:HEAD~ -m \"amend commit message\" &&\n    -+\tget_commit_msg HEAD >actual &&\n    - \ttest_cmp expect actual\n      '\n      \n     -test_expect_success '--fixup=amend: error out with -m option' '\n    -+test_expect_success '--fixup=reword: with -m option' '\n    ++test_expect_success 'commit --fixup=amend: works with -m' '\n      \tcommit_for_rebase_autosquash_setup &&\n     -\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:amend'\\'' cannot be used together\" >expect &&\n     -\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"amend commit message\" 2>actual &&\n    -+\tcat >expect <<-EOF &&\n    -+\tamend! $(git log -1 --format=%s HEAD~)\n    +-\ttest_cmp expect actual\n    ++\tgit commit --fixup=amend:HEAD~ -m \"amend commit message\" &&\n    ++\ttest_commit_message HEAD <<-EOF\n    ++\tamend! $(git log -1 --format=%s HEAD~2)\n     +\n    -+\treword commit message\n    ++\tamend commit message\n     +\tEOF\n    -+\tgit commit --fixup=reword:HEAD~ -m \"reword commit message\" &&\n    -+\tget_commit_msg HEAD >actual &&\n    - \ttest_cmp expect actual\n      '\n      \n    + test_expect_success 'consecutive amend! commits remove amend! line from commit msg body' '\n     @@ t/t7500-commit-template-squash-signoff.sh: test_expect_success 'deny to create amend! commit if its commit msg body is empt\n      \ttest_cmp expected actual\n      '\n      \n    -+test_expect_success '--fixup=amend: -m with empty message aborts' '\n    ++test_expect_success 'deny to create amend! commit if -m is empty' '\n     +\tcommit_for_rebase_autosquash_setup &&\n    -+\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"\" 2>err &&\n    -+\ttest_grep \"empty commit message body\" err\n    ++\techo \"Aborting commit due to empty commit message body.\" >expect &&\n    ++\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"\" 2>actual &&\n    ++\ttest_cmp expect actual\n     +'\n     +\n      test_expect_success 'amend! commit allows empty commit msg body with --allow-empty-message' '\n    @@ t/t7500-commit-template-squash-signoff.sh: test_expect_success '--fixup=reword:\n     -test_expect_success '--fixup=reword: -F give error message' '\n     -\techo \"fatal: options '\\''-F'\\'' and '\\''--fixup'\\'' cannot be used together\" >expect &&\n     -\ttest_must_fail git commit --fixup=reword:HEAD~ -F msg  2>actual &&\n    -+test_expect_success '--fixup=reword: with -F option' '\n    -+\tcommit_for_rebase_autosquash_setup &&\n    -+\techo \"message from file\" >msgfile &&\n    -+\tcat >expect <<-EOF &&\n    -+\tamend! $(git log -1 --format=%s HEAD~)\n    -+\n    -+\tmessage from file\n    -+\tEOF\n    -+\tgit commit --fixup=reword:HEAD~ -F msgfile &&\n    -+\tget_commit_msg HEAD >actual &&\n    - \ttest_cmp expect actual\n    - '\n    - \n    -+test_expect_success '--fixup=amend: with -F option' '\n    +-\ttest_cmp expect actual\n    ++test_expect_success 'commit --fixup works with -F' '\n     +\tcommit_for_rebase_autosquash_setup &&\n    -+\techo \"amend message from file\" >msgfile &&\n    -+\tcat >expect <<-EOF &&\n    -+\tamend! $(git log -1 --format=%s HEAD~)\n    ++\techo \"message\" >msgfile &&\n    ++\tgit commit --fixup HEAD~ -F msgfile &&\n    ++\ttest_commit_message HEAD <<-EOF\n    ++\tfixup! $(git log -1 --format=%s HEAD~2)\n     +\n    -+\tamend message from file\n    ++\tmessage\n     +\tEOF\n    -+\tgit commit --fixup=amend:HEAD~ -F msgfile &&\n    -+\tget_commit_msg HEAD >actual &&\n    -+\ttest_cmp expect actual\n     +'\n     +\n    -+test_expect_success '-F with plain --fixup still errors' '\n    ++test_expect_success 'commit --fixup=reword: works with -F' '\n     +\tcommit_for_rebase_autosquash_setup &&\n    -+\techo \"message\" >msgfile &&\n    -+\ttest_must_fail git commit --fixup HEAD~ -F msgfile 2>err &&\n    -+\ttest_grep \"cannot be used together\" err\n    -+'\n    ++\techo \"message from file\" >msgfile &&\n    ++\tgit commit --fixup=reword:HEAD~ -F msgfile &&\n    ++\ttest_commit_message HEAD <<-EOF\n    ++\tamend! $(git log -1 --format=%s HEAD~2)\n     +\n    ++\t$(cat msgfile)\n    ++\tEOF\n    + '\n    + \n      test_expect_success 'commit --squash works with -F' '\n    - \tcommit_for_rebase_autosquash_setup &&\n    - \techo \"log message from file\" >msgfile &&\n    +@@ t/t7500-commit-template-squash-signoff.sh: test_expect_success 'invalid message options when using --fixup' '\n    + \tgit add foo &&\n    + \ttest_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&\n    + \ttest_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&\n    +-\ttest_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&\n    +-\ttest_must_fail git commit --fixup HEAD~1 -F log\n    ++\ttest_must_fail git commit --fixup HEAD~1 -c HEAD~2\n    + '\n    + \n    + cat >expected-template <<EOF\n-:  ---------- > 2:  b3fc743abf commit: allow -c/-C for all kinds of --fixup\n\nbase-commit: 208068f2d8ae29d7edaa245d9975b1b22ec65738\n-- \n2.54.0.1014.g842965a2d5\n\n"},{"id":"544107","messageId":"e9f07d49ee2b4a19e7039b6da11be502a8d33fa8.1779792311.git.erik@cervined.in","threadId":"65653","inReplyTo":"cover.1779792311.git.erik@cervined.in","subject":"[PATCH v2 1/2] commit: allow -m/-F for all kinds of --fixup","fromName":"","fromEmail":"erik@cervined.in","sentAt":"2026-05-26T10:47:43Z","receivedAt":"2026-05-26T10:48:16Z","isPatch":true,"body":"From: Erik Cervin-Edin <erik@cervined.in>\n\nThe ability to provide a commit message for git commit --fixup and its\nvariations is limited:\n\n  * Plain --fixup only allows using the -m flag\n\n  * The amend/reword --fixup variants only allow supplying the message\n    using an editor\n\nFor amend/reword, the -m and -F flags are rejected: -m is caught by a\ndie() in prepare_to_commit(), and -F is caught by\ndie_for_incompatible_opt4() which groups -F with --fixup as mutually\nexclusive.  This makes these modes poorly suited for non-interactive\nworkflows -- notably when using AI coding agents.\n\nWhen support to use the -m option was introduced in [1] it was noted\nthat there could be support for other options but at the time the use\ncase was deemed too niche.  Later, when the amend suboption was\nintroduced in [2] -m support for amend fixups was discussed but not\npursued, and -F was already caught by the higher-layer incompatibility\ncheck grouping it with --fixup.\n\nThe rejections of these options hark back to when --fixup was\nintroduced in [3] and as noted in [1] -- there's nothing inherently\npreventing support for them.  The current patchwork of which flags\nwork with which --fixup variants has no strong logic to it, and\nallowing all of them simplifies both the code and the interface.\n\nAllow -m and -F to supply the message body for all --fixup variations,\nmirroring the flow of a regular commit.  -c and -C, which are blocked\nby the same incompatibility check, are handled in the next commit.\n\n1. 30884c9afc (commit: add support for --fixup <commit> -m\"<extra\n   message>\", 2017-12-22)\n\n2. 494d314a05 (commit: add amend suboption to --fixup to create amend!\n   commit, 2021-03-15)\n\n3. d71b8ba7c9 (commit: --fixup option for use with rebase --autosquash,\n   2010-11-02)\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSuggested-by: Phillip Wood <phillip.wood123@gmail.com>\nSigned-off-by: Erik Cervin-Edin <erik@cervined.in>\n---\n Documentation/git-commit.adoc             | 19 ++++----\n builtin/commit.c                          | 34 +++++++-------\n t/t7500-commit-template-squash-signoff.sh | 56 +++++++++++++++++------\n 3 files changed, 69 insertions(+), 40 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 8329c1034b..61efd29e66 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -103,20 +103,21 @@ include::diff-context-options.adoc[]\n The commit created by plain `--fixup=<commit>` has a title\n composed of \"fixup!\" followed by the title of _<commit>_,\n and is recognized specially by `git rebase --autosquash`. The `-m`\n-option may be used to supplement the log message of the created\n-commit, but the additional commentary will be thrown away once the\n-\"fixup!\" commit is squashed into _<commit>_ by\n+or `-F` option may be used to supplement the log message\n+of the created commit, but the additional commentary will be thrown\n+away once the \"fixup!\" commit is squashed into _<commit>_ by\n `git rebase --autosquash`.\n +\n The commit created by `--fixup=amend:<commit>` is similar but its\n title is instead prefixed with \"amend!\". The log message of\n _<commit>_ is copied into the log message of the \"amend!\" commit and\n-opened in an editor so it can be refined. When `git rebase\n---autosquash` squashes the \"amend!\" commit into _<commit>_, the\n-log message of _<commit>_ is replaced by the refined log message\n-from the \"amend!\" commit. It is an error for the \"amend!\" commit's\n-log message to be empty unless `--allow-empty-message` is\n-specified.\n+opened in an editor so it can be refined. The replacement message may\n+also be supplied directly using `-m` or `-F`, bypassing the\n+need to open an editor. When `git rebase\n+--autosquash` squashes the \"amend!\" commit into _<commit>_, the log\n+message of _<commit>_ is replaced by the refined log message from the\n+\"amend!\" commit. It is an error for the \"amend!\" commit's log message\n+to be empty unless `--allow-empty-message` is specified.\n +\n `--fixup=reword:<commit>` is shorthand for `--fixup=amend:<commit>\n  --only`. It creates an \"amend!\" commit with only a log message\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 28f6174503..3f1fca2919 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -804,18 +804,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tif (have_option_m && !fixup_message) {\n \t\tstrbuf_addbuf(&sb, &message);\n \t\thook_arg1 = \"message\";\n-\t} else if (logfile && !strcmp(logfile, \"-\")) {\n+\t} else if (logfile && !fixup_message && !strcmp(logfile, \"-\")) {\n \t\tif (isatty(0))\n \t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n \t\tif (strbuf_read(&sb, 0, 0) < 0)\n \t\t\tdie_errno(_(\"could not read log from standard input\"));\n \t\thook_arg1 = \"message\";\n-\t} else if (logfile) {\n+\t} else if (logfile && !fixup_message) {\n \t\tif (strbuf_read_file(&sb, logfile, 0) < 0)\n \t\t\tdie_errno(_(\"could not read log file '%s'\"),\n \t\t\t\t  logfile);\n \t\thook_arg1 = \"message\";\n-\t} else if (use_message) {\n+\t} else if (use_message && !fixup_message) {\n \t\tconst char *buffer;\n \t\tbuffer = strstr(use_message_buffer, \"\\n\\n\");\n \t\tif (buffer)\n@@ -837,20 +837,21 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\thook_arg1 = \"message\";\n \n \t\t/*\n-\t\t * Only `-m` commit message option is checked here, as\n-\t\t * it supports `--fixup` to append the commit message.\n-\t\t *\n-\t\t * The other commit message options `-c`/`-C`/`-F` are\n-\t\t * incompatible with all the forms of `--fixup` and\n-\t\t * have already errored out while parsing the `git commit`\n-\t\t * options.\n+\t\t * Only `-m` and `-F` are handled here. `-c`/`-C` are\n+\t\t * incompatible with --fixup and have already errored out\n+\t\t * during option parsing.\n \t\t */\n-\t\tif (have_option_m && !strcmp(fixup_prefix, \"fixup\"))\n+\t\tif (have_option_m) {\n \t\t\tstrbuf_addbuf(&sb, &message);\n-\n-\t\tif (!strcmp(fixup_prefix, \"amend\")) {\n-\t\t\tif (have_option_m)\n-\t\t\t\tdie(_(\"options '%s' and '%s:%s' cannot be used together\"), \"-m\", \"--fixup\", fixup_message);\n+\t\t} else if (logfile && !strcmp(logfile, \"-\")) {\n+\t\t\tif (isatty(0))\n+\t\t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n+\t\t\tif (strbuf_read(&sb, 0, 0) < 0)\n+\t\t\t\tdie_errno(_(\"could not read log from standard input\"));\n+\t\t} else if (logfile) {\n+\t\t\tif (strbuf_read_file(&sb, logfile, 0) < 0)\n+\t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n+\t\t} else if (!strcmp(fixup_prefix, \"amend\")) {\n \t\t\tprepare_amend_commit(commit, &sb, &ctx);\n \t\t}\n \t} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {\n@@ -1338,9 +1339,8 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \t}\n \tif (fixup_message && squash_message)\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n-\tdie_for_incompatible_opt4(!!use_message, \"-C\",\n+\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n \t\t\t\t  !!edit_message, \"-c\",\n-\t\t\t\t  !!logfile, \"-F\",\n \t\t\t\t  !!fixup_message, \"--fixup\");\n \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n \t\t\t\t  !!edit_message, \"-c\",\ndiff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh\nindex 66aff8e097..01c7400136 100755\n--- a/t/t7500-commit-template-squash-signoff.sh\n+++ b/t/t7500-commit-template-squash-signoff.sh\n@@ -384,18 +384,24 @@ test_expect_success '--fixup=reword: ignores staged changes' '\n \ttest_cmp foo actual\n '\n \n-test_expect_success '--fixup=reword: error out with -m option' '\n+test_expect_success 'commit --fixup=reword: works with -m' '\n \tcommit_for_rebase_autosquash_setup &&\n-\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:reword'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=reword:HEAD~ -m \"reword commit message\" 2>actual &&\n-\ttest_cmp expect actual\n+\tgit commit --fixup=reword:HEAD~ -m \"reword commit message\" &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! $(git log -1 --format=%s HEAD~2)\n+\n+\treword commit message\n+\tEOF\n '\n \n-test_expect_success '--fixup=amend: error out with -m option' '\n+test_expect_success 'commit --fixup=amend: works with -m' '\n \tcommit_for_rebase_autosquash_setup &&\n-\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:amend'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"amend commit message\" 2>actual &&\n-\ttest_cmp expect actual\n+\tgit commit --fixup=amend:HEAD~ -m \"amend commit message\" &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! $(git log -1 --format=%s HEAD~2)\n+\n+\tamend commit message\n+\tEOF\n '\n \n test_expect_success 'consecutive amend! commits remove amend! line from commit msg body' '\n@@ -432,6 +438,13 @@ test_expect_success 'deny to create amend! commit if its commit msg body is empt\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'deny to create amend! commit if -m is empty' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"Aborting commit due to empty commit message body.\" >expect &&\n+\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"\" 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'amend! commit allows empty commit msg body with --allow-empty-message' '\n \tcommit_for_rebase_autosquash_setup &&\n \tcat >expected <<-EOF &&\n@@ -468,10 +481,26 @@ test_expect_success '--fixup=reword: give error with pathsec' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--fixup=reword: -F give error message' '\n-\techo \"fatal: options '\\''-F'\\'' and '\\''--fixup'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=reword:HEAD~ -F msg  2>actual &&\n-\ttest_cmp expect actual\n+test_expect_success 'commit --fixup works with -F' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"message\" >msgfile &&\n+\tgit commit --fixup HEAD~ -F msgfile &&\n+\ttest_commit_message HEAD <<-EOF\n+\tfixup! $(git log -1 --format=%s HEAD~2)\n+\n+\tmessage\n+\tEOF\n+'\n+\n+test_expect_success 'commit --fixup=reword: works with -F' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"message from file\" >msgfile &&\n+\tgit commit --fixup=reword:HEAD~ -F msgfile &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! $(git log -1 --format=%s HEAD~2)\n+\n+\t$(cat msgfile)\n+\tEOF\n '\n \n test_expect_success 'commit --squash works with -F' '\n@@ -526,8 +555,7 @@ test_expect_success 'invalid message options when using --fixup' '\n \tgit add foo &&\n \ttest_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&\n \ttest_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&\n-\ttest_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&\n-\ttest_must_fail git commit --fixup HEAD~1 -F log\n+\ttest_must_fail git commit --fixup HEAD~1 -c HEAD~2\n '\n \n cat >expected-template <<EOF\n-- \n2.54.0.1014.g842965a2d5\n\n"},{"id":"544108","messageId":"b3fc743abf5d35fbb23c7483836de292ad5bffda.1779792311.git.erik@cervined.in","threadId":"65653","inReplyTo":"cover.1779792311.git.erik@cervined.in","subject":"[PATCH v2 2/2] commit: allow -c/-C for all kinds of --fixup","fromName":"","fromEmail":"erik@cervined.in","sentAt":"2026-05-26T10:47:44Z","receivedAt":"2026-05-26T10:48:23Z","isPatch":true,"body":"From: Erik Cervin-Edin <erik@cervined.in>\n\nThe previous commit allowed -m and -F for all --fixup variations.  The\n-c/-C flags were blocked by the same higher-layer incompatibility check\nthat previously caught -F, namely die_for_incompatible_opt4() grouping\nthem with --fixup.\n\nDrop --fixup from that check and route the resolved commit through\nprepare_amend_commit() in the fixup path, mirroring the no-message-source\nbehaviour of --fixup=amend.  With this in place, -m/-F/-c/-C all behave\nconsistently across the plain, amend, and reword --fixup forms.\n\nSigned-off-by: Erik Cervin-Edin <erik@cervined.in>\n---\n Documentation/git-commit.adoc             |  9 ++--\n builtin/commit.c                          | 13 +++--\n t/t7500-commit-template-squash-signoff.sh | 60 +++++++++++++++++++++--\n 3 files changed, 71 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 61efd29e66..98c50a3be5 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -102,8 +102,8 @@ include::diff-context-options.adoc[]\n +\n The commit created by plain `--fixup=<commit>` has a title\n composed of \"fixup!\" followed by the title of _<commit>_,\n-and is recognized specially by `git rebase --autosquash`. The `-m`\n-or `-F` option may be used to supplement the log message\n+and is recognized specially by `git rebase --autosquash`. The `-m`,\n+`-F`, `-C`, or `-c` option may be used to supplement the log message\n of the created commit, but the additional commentary will be thrown\n away once the \"fixup!\" commit is squashed into _<commit>_ by\n `git rebase --autosquash`.\n@@ -112,8 +112,9 @@ The commit created by `--fixup=amend:<commit>` is similar but its\n title is instead prefixed with \"amend!\". The log message of\n _<commit>_ is copied into the log message of the \"amend!\" commit and\n opened in an editor so it can be refined. The replacement message may\n-also be supplied directly using `-m` or `-F`, bypassing the\n-need to open an editor. When `git rebase\n+also be supplied directly using `-m`, `-F`, or `-C`, bypassing the\n+need to open an editor, or using `-c` to open the editor pre-populated\n+with the referenced commit's message. When `git rebase\n --autosquash` squashes the \"amend!\" commit into _<commit>_, the log\n message of _<commit>_ is replaced by the refined log message from the\n \"amend!\" commit. It is an error for the \"amend!\" commit's log message\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 3f1fca2919..fcf148eb21 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -837,9 +837,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\thook_arg1 = \"message\";\n \n \t\t/*\n-\t\t * Only `-m` and `-F` are handled here. `-c`/`-C` are\n-\t\t * incompatible with --fixup and have already errored out\n-\t\t * during option parsing.\n+\t\t * `-m`, `-F`, `-C`, and `-c` provide the message body.\n+\t\t * If none was given and this is an amend, use the target\n+\t\t * commit's body instead.\n \t\t */\n \t\tif (have_option_m) {\n \t\t\tstrbuf_addbuf(&sb, &message);\n@@ -851,6 +851,11 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\t} else if (logfile) {\n \t\t\tif (strbuf_read_file(&sb, logfile, 0) < 0)\n \t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n+\t\t} else if (use_message) {\n+\t\t\tstruct commit *c = lookup_commit_reference_by_name(use_message);\n+\t\t\tif (!c)\n+\t\t\t\tdie(_(\"could not lookup commit '%s'\"), use_message);\n+\t\t\tprepare_amend_commit(c, &sb, &ctx);\n \t\t} else if (!strcmp(fixup_prefix, \"amend\")) {\n \t\t\tprepare_amend_commit(commit, &sb, &ctx);\n \t\t}\n@@ -1341,7 +1346,7 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n \tdie_for_incompatible_opt3(!!use_message, \"-C\",\n \t\t\t\t  !!edit_message, \"-c\",\n-\t\t\t\t  !!fixup_message, \"--fixup\");\n+\t\t\t\t  !!logfile, \"-F\");\n \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n \t\t\t\t  !!edit_message, \"-c\",\n \t\t\t\t  !!use_message, \"-C\",\ndiff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh\nindex 01c7400136..48e1247d9e 100755\n--- a/t/t7500-commit-template-squash-signoff.sh\n+++ b/t/t7500-commit-template-squash-signoff.sh\n@@ -492,6 +492,62 @@ test_expect_success 'commit --fixup works with -F' '\n \tEOF\n '\n \n+test_expect_success 'commit --fixup works with -C' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\tgit commit --fixup HEAD~ -C HEAD &&\n+\ttest_commit_message HEAD <<-EOF\n+\tfixup! $(git log -1 --format=%s HEAD~2)\n+\n+\t$(get_commit_msg HEAD~)\n+\tEOF\n+'\n+\n+test_expect_success 'commit --fixup=amend: works with -c' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\ttest_set_editor : &&\n+\tgit commit --fixup=amend:HEAD -c HEAD~ &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! intermediate commit\n+\n+\ttarget message subject line\n+\n+\ttarget message body line 1\n+\ttarget message body line 2\n+\tEOF\n+'\n+\n+test_expect_success 'commit --fixup=amend:HEAD with -C HEAD and without have the same message' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\tstart=$(git rev-parse HEAD) &&\n+\n+\tgit commit --fixup=amend:HEAD -C HEAD &&\n+\tgit commit --fixup=amend:HEAD -C HEAD &&\n+\tgit log -1 --pretty=%B >with-c &&\n+\n+\tgit reset --hard \"$start\" &&\n+\ttest_set_editor : &&\n+\tgit commit --fixup=amend:HEAD &&\n+\tgit commit --fixup=amend:HEAD &&\n+\tgit log -1 --pretty=%B >without-c &&\n+\n+\ttest_cmp with-c without-c\n+'\n+\n+test_expect_success 'commit --fixup=amend: with -C copies full subject + body of squash commit' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\tgit commit --squash HEAD~ -m \"inner body\" &&\n+\techo \"extra\" >>foo &&\n+\tgit add foo &&\n+\tgit commit --fixup=amend:HEAD -C HEAD &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! squash! $(git log -1 --format=%s HEAD~3)\n+\n+\tsquash! $(git log -1 --format=%s HEAD~3)\n+\n+\tinner body\n+\tEOF\n+'\n+\n test_expect_success 'commit --fixup=reword: works with -F' '\n \tcommit_for_rebase_autosquash_setup &&\n \techo \"message from file\" >msgfile &&\n@@ -553,9 +609,7 @@ test_expect_success 'invalid message options when using --fixup' '\n \techo changes >>foo &&\n \techo \"message\" >log &&\n \tgit add foo &&\n-\ttest_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&\n-\ttest_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&\n-\ttest_must_fail git commit --fixup HEAD~1 -c HEAD~2\n+\ttest_must_fail git commit --fixup HEAD~1 --squash HEAD~2\n '\n \n cat >expected-template <<EOF\n-- \n2.54.0.1014.g842965a2d5\n\n"},{"id":"551317","messageId":"xmqq1pbkefh0.fsf@gitster.g","threadId":"65653","inReplyTo":"b3fc743abf5d35fbb23c7483836de292ad5bffda.1779792311.git.erik@cervined.in","subject":"Re: [PATCH v2 2/2] commit: allow -c/-C for all kinds of --fixup","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-26T21:31:23Z","receivedAt":"2026-08-26T21:31:25Z","isPatch":true,"body":"erik@cervined.in writes:\n\n> From: Erik Cervin-Edin <erik@cervined.in>\n>\n> The previous commit allowed -m and -F for all --fixup variations.  The\n> -c/-C flags were blocked by the same higher-layer incompatibility check\n> that previously caught -F, namely die_for_incompatible_opt4() grouping\n> them with --fixup.\n>\n> Drop --fixup from that check and route the resolved commit through\n> prepare_amend_commit() in the fixup path, mirroring the no-message-source\n> behaviour of --fixup=amend.  With this in place, -m/-F/-c/-C all behave\n> consistently across the plain, amend, and reword --fixup forms.\n>\n> Signed-off-by: Erik Cervin-Edin <erik@cervined.in>\n> ---\n>  Documentation/git-commit.adoc             |  9 ++--\n>  builtin/commit.c                          | 13 +++--\n>  t/t7500-commit-template-squash-signoff.sh | 60 +++++++++++++++++++++--\n>  3 files changed, 71 insertions(+), 11 deletions(-)\n\nSorry, but this topic has been hanging without getting any reviews\non the list for quite a while.\n\nAny takers?\n\n\nOne small thing I noticed appears here:\n\n> @@ -1341,7 +1346,7 @@ static int parse_and_validate_options(int argc, const char *argv[],\n>  \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n>  \tdie_for_incompatible_opt3(!!use_message, \"-C\",\n>  \t\t\t\t  !!edit_message, \"-c\",\n> -\t\t\t\t  !!fixup_message, \"--fixup\");\n> +\t\t\t\t  !!logfile, \"-F\");\n>  \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n>  \t\t\t\t  !!edit_message, \"-c\",\n>  \t\t\t\t  !!use_message, \"-C\",\n\nThis blocks -C, -c, -F from being used together.  But the next\nopt4() covers these combinations already:\n\n  \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n  \t\t\t\t  !!edit_message, \"-c\",\n  \t\t\t\t  !!use_message, \"-C\",\n  \t\t\t\t  !!logfile, \"-F\")\n"},{"id":"551993","messageId":"CA+JQ7M__GOnM9LHt0txry-G2z2CKhdZr0b-rU=Yd_A0gCEwmaQ@mail.gmail.com","threadId":"65653","inReplyTo":"xmqq1pbkefh0.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] commit: allow -c/-C for all kinds of --fixup","fromName":"Erik Cervin Edin","fromEmail":"erik@cervined.in","sentAt":"2026-09-04T18:47:35Z","receivedAt":"2026-09-04T18:48:15Z","isPatch":true,"body":"On Wed, 26 Aug 2026 at 23:31, Junio C Hamano <gitster@pobox.com> wrote:\n> Sorry, but this topic has been hanging without getting any reviews\n> on the list for quite a while.\n>\n> Any takers?\n\nI'm a little unfamiliar with this part of the mailing list, but yes,\nit's been a while. I've been assuming people are busy with other\nthings, which is ofc okay. If there's anything I can do on my side to\nhelp make this easier I'm happy to. I may be slow to respond since I\ncheck this thread less frequently these days.\n\nFWIW I've been dogfooding these options to reword commit messages the\nlast few months and found it pretty useful so far.\n\n> One small thing I noticed appears here:\n>\n> > @@ -1341,7 +1346,7 @@ static int parse_and_validate_options(int argc, const char *argv[],\n> >               die(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n> >       die_for_incompatible_opt3(!!use_message, \"-C\",\n> >                                 !!edit_message, \"-c\",\n> > -                               !!fixup_message, \"--fixup\");\n> > +                               !!logfile, \"-F\");\n> >       die_for_incompatible_opt4(have_option_m, \"-m\",\n> >                                 !!edit_message, \"-c\",\n> >                                 !!use_message, \"-C\",\n>\n> This blocks -C, -c, -F from being used together.  But the next\n> opt4() covers these combinations already:\n>\n>         die_for_incompatible_opt4(have_option_m, \"-m\",\n>                                   !!edit_message, \"-c\",\n>                                   !!use_message, \"-C\",\n>                                   !!logfile, \"-F\")\n\nThat looks like it's worth a second look. I'll be away for a few weeks\nbut I'll take a look at that when I get back.\n"},{"id":"552983","messageId":"cover.1790082176.git.erik@cervined.in","threadId":"65653","inReplyTo":"cover.1779792311.git.erik@cervined.in","subject":"[PATCH v3 0/2] commit: allow -m/-F/-c/-C for all --fixup variations","fromName":"","fromEmail":"erik@cervined.in","sentAt":"2026-09-22T13:04:32Z","receivedAt":"2026-09-22T13:06:28Z","isPatch":true,"body":"From: Erik Cervin-Edin <erik@cervined.in>\n\nChanges since v2:\n\n  * 2/2: Junio noted [1] that after dropping --fixup from the\n    -C/-c/--fixup incompatibility check, what remained (-C, -c, -F)\n    was already rejected by the neighbouring die_for_incompatible_opt4()\n    that groups those options with -m.  The check is now removed\n    entirely instead of being narrowed.\n\n  * 2/2: reworded the log message to lead with why -c/-C are the odd\n    ones out once 1/2 is in place, rather than with the check that\n    happened to block them.\n\n  * Rebased onto current master.  1/2 is unchanged apart from that.\n\nBackground\n----------\n\nToday only -m works with plain --fixup, and neither -m nor -F works\nwith --fixup=amend: or --fixup=reword:, which makes those modes\nawkward to drive non-interactively.  1/2 threads -m and -F through\nprepare_to_commit() for all --fixup variations.  2/2 does the same for\n-c/-C, which were blocked by the same incompatibility check that\ncaught -F, so that all four message sources behave consistently across\nthe plain, amend, and reword forms.\n\n2/2 lives in its own patch and I won't object if reviewers would\nrather drop it or take it separately.\n\nA note on one design decision in 2/2.  -c/-C go through\nprepare_amend_commit(), so if the source commit is itself an \"amend!\"\ncommit, its subject line is dropped and only the body is copied.  This\nis what a no-source --fixup=amend:<commit> already does.\n\nI kept that on purpose.  Under --fixup the subject is always generated\nby git, so whatever -C supplies ends up in the body, and the body of an\namend! commit is the message destined for its target.  Copying the\n\"amend! X\" line along with it would leave that line in the final\nmessage after autosquash.  It also means\n\n    git commit --fixup=amend:foo -C foo\n\nand\n\n    GIT_EDITOR=: git commit --fixup=amend:foo\n\nproduce the same commit.\n\nThe helper only knows about \"amend!\" and not \"fixup!\" or \"squash!\".\nThat is pre-existing and I have left it alone here.\n\nThe v1 -> v2 changes (refactoring prepare_to_commit() instead of the\nspecial-cased file slurp in cmd_commit(), extending scope to -c/-C,\nand the smaller review fixes) are described in the v2 cover letter [2].\n\n[1] https://lore.kernel.org/git/xmqq1pbkefh0.fsf@gitster.g/\n[2] https://lore.kernel.org/git/cover.1779792311.git.erik@cervined.in/\n\nErik Cervin-Edin (2):\n  commit: allow -m/-F for all kinds of --fixup\n  commit: allow -c/-C for all kinds of --fixup\n\n Documentation/git-commit.adoc             |  22 +++--\n builtin/commit.c                          |  40 ++++----\n t/t7500-commit-template-squash-signoff.sh | 114 +++++++++++++++++++---\n 3 files changed, 131 insertions(+), 45 deletions(-)\n\nRange-diff against v2:\n1:  e9f07d49ee = 1:  4581f97cb7 commit: allow -m/-F for all kinds of --fixup\n2:  b3fc743abf ! 2:  39e408e147 commit: allow -c/-C for all kinds of --fixup\n    @@ Metadata\n      ## Commit message ##\n         commit: allow -c/-C for all kinds of --fixup\n     \n    -    The previous commit allowed -m and -F for all --fixup variations.  The\n    -    -c/-C flags were blocked by the same higher-layer incompatibility check\n    -    that previously caught -F, namely die_for_incompatible_opt4() grouping\n    -    them with --fixup.\n    +    The fixup code path in prepare_to_commit() only knows how to take a\n    +    message body from -m or -F.  Now that the previous commit allows -m and\n    +    -F for all --fixup variations, -c/-C are the odd ones out.\n     \n    -    Drop --fixup from that check and route the resolved commit through\n    -    prepare_amend_commit() in the fixup path, mirroring the no-message-source\n    -    behaviour of --fixup=amend.  With this in place, -m/-F/-c/-C all behave\n    -    consistently across the plain, amend, and reword --fixup forms.\n    +    Teach the fixup path to look up the ref given to -c/-C and seed its\n    +    message when preparing to amend the commit, mirroring what --fixup=amend\n    +    does when no message source is given.  With this in place, -m/-F/-c/-C\n    +    all behave consistently across the plain, amend, and reword --fixup\n    +    forms.\n     \n    +    The incompatibility check grouping -C, -c, and --fixup can go away\n    +    entirely, as -C and -c are already rejected together by the check that\n    +    groups them with -m and -F.\n    +\n    +    Helped-by: Junio C Hamano <gitster@pobox.com>\n         Signed-off-by: Erik Cervin-Edin <erik@cervined.in>\n     \n      ## Documentation/git-commit.adoc ##\n    @@ builtin/commit.c: static int prepare_to_commit(const char *index_file, const cha\n      \t\t\tprepare_amend_commit(commit, &sb, &ctx);\n      \t\t}\n     @@ builtin/commit.c: static int parse_and_validate_options(int argc, const char *argv[],\n    + \t}\n    + \tif (fixup_message && squash_message)\n      \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n    - \tdie_for_incompatible_opt3(!!use_message, \"-C\",\n    - \t\t\t\t  !!edit_message, \"-c\",\n    +-\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n    +-\t\t\t\t  !!edit_message, \"-c\",\n     -\t\t\t\t  !!fixup_message, \"--fixup\");\n    -+\t\t\t\t  !!logfile, \"-F\");\n      \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n      \t\t\t\t  !!edit_message, \"-c\",\n      \t\t\t\t  !!use_message, \"-C\",\n\nbase-commit: d38352cd43ab9745686d697872408bc3249a153f\n-- \n2.55.0.770.g0473647dd8\n\n"},{"id":"552984","messageId":"4581f97cb737c701081614be0e222ee4c860ce1c.1790082176.git.erik@cervined.in","threadId":"65653","inReplyTo":"cover.1790082176.git.erik@cervined.in","subject":"[PATCH v3 1/2] commit: allow -m/-F for all kinds of --fixup","fromName":"","fromEmail":"erik@cervined.in","sentAt":"2026-09-22T13:04:35Z","receivedAt":"2026-09-22T13:07:22Z","isPatch":true,"body":"From: Erik Cervin-Edin <erik@cervined.in>\n\nThe ability to provide a commit message for git commit --fixup and its\nvariations is limited:\n\n  * Plain --fixup only allows using the -m flag\n\n  * The amend/reword --fixup variants only allow supplying the message\n    using an editor\n\nFor amend/reword, the -m and -F flags are rejected: -m is caught by a\ndie() in prepare_to_commit(), and -F is caught by\ndie_for_incompatible_opt4() which groups -F with --fixup as mutually\nexclusive.  This makes these modes poorly suited for non-interactive\nworkflows -- notably when using AI coding agents.\n\nWhen support to use the -m option was introduced in [1] it was noted\nthat there could be support for other options but at the time the use\ncase was deemed too niche.  Later, when the amend suboption was\nintroduced in [2] -m support for amend fixups was discussed but not\npursued, and -F was already caught by the higher-layer incompatibility\ncheck grouping it with --fixup.\n\nThe rejections of these options hark back to when --fixup was\nintroduced in [3] and as noted in [1] -- there's nothing inherently\npreventing support for them.  The current patchwork of which flags\nwork with which --fixup variants has no strong logic to it, and\nallowing all of them simplifies both the code and the interface.\n\nAllow -m and -F to supply the message body for all --fixup variations,\nmirroring the flow of a regular commit.  -c and -C, which are blocked\nby the same incompatibility check, are handled in the next commit.\n\n1. 30884c9afc (commit: add support for --fixup <commit> -m\"<extra\n   message>\", 2017-12-22)\n\n2. 494d314a05 (commit: add amend suboption to --fixup to create amend!\n   commit, 2021-03-15)\n\n3. d71b8ba7c9 (commit: --fixup option for use with rebase --autosquash,\n   2010-11-02)\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSuggested-by: Phillip Wood <phillip.wood123@gmail.com>\nSigned-off-by: Erik Cervin-Edin <erik@cervined.in>\n---\n Documentation/git-commit.adoc             | 19 ++++----\n builtin/commit.c                          | 34 +++++++-------\n t/t7500-commit-template-squash-signoff.sh | 56 +++++++++++++++++------\n 3 files changed, 69 insertions(+), 40 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 8329c1034b..61efd29e66 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -103,20 +103,21 @@ include::diff-context-options.adoc[]\n The commit created by plain `--fixup=<commit>` has a title\n composed of \"fixup!\" followed by the title of _<commit>_,\n and is recognized specially by `git rebase --autosquash`. The `-m`\n-option may be used to supplement the log message of the created\n-commit, but the additional commentary will be thrown away once the\n-\"fixup!\" commit is squashed into _<commit>_ by\n+or `-F` option may be used to supplement the log message\n+of the created commit, but the additional commentary will be thrown\n+away once the \"fixup!\" commit is squashed into _<commit>_ by\n `git rebase --autosquash`.\n +\n The commit created by `--fixup=amend:<commit>` is similar but its\n title is instead prefixed with \"amend!\". The log message of\n _<commit>_ is copied into the log message of the \"amend!\" commit and\n-opened in an editor so it can be refined. When `git rebase\n---autosquash` squashes the \"amend!\" commit into _<commit>_, the\n-log message of _<commit>_ is replaced by the refined log message\n-from the \"amend!\" commit. It is an error for the \"amend!\" commit's\n-log message to be empty unless `--allow-empty-message` is\n-specified.\n+opened in an editor so it can be refined. The replacement message may\n+also be supplied directly using `-m` or `-F`, bypassing the\n+need to open an editor. When `git rebase\n+--autosquash` squashes the \"amend!\" commit into _<commit>_, the log\n+message of _<commit>_ is replaced by the refined log message from the\n+\"amend!\" commit. It is an error for the \"amend!\" commit's log message\n+to be empty unless `--allow-empty-message` is specified.\n +\n `--fixup=reword:<commit>` is shorthand for `--fixup=amend:<commit>\n  --only`. It creates an \"amend!\" commit with only a log message\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 205fbd57e3..ffb8644653 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -816,18 +816,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \tif (have_option_m && !fixup_message) {\n \t\tstrbuf_addbuf(&sb, &message);\n \t\thook_arg1 = \"message\";\n-\t} else if (logfile && !strcmp(logfile, \"-\")) {\n+\t} else if (logfile && !fixup_message && !strcmp(logfile, \"-\")) {\n \t\tif (isatty(0))\n \t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n \t\tif (strbuf_read(&sb, 0, 0) < 0)\n \t\t\tdie_errno(_(\"could not read log from standard input\"));\n \t\thook_arg1 = \"message\";\n-\t} else if (logfile) {\n+\t} else if (logfile && !fixup_message) {\n \t\tif (strbuf_read_file(&sb, logfile, 0) < 0)\n \t\t\tdie_errno(_(\"could not read log file '%s'\"),\n \t\t\t\t  logfile);\n \t\thook_arg1 = \"message\";\n-\t} else if (use_message) {\n+\t} else if (use_message && !fixup_message) {\n \t\tconst char *buffer;\n \t\tbuffer = strstr(use_message_buffer, \"\\n\\n\");\n \t\tif (buffer)\n@@ -849,20 +849,21 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\thook_arg1 = \"message\";\n \n \t\t/*\n-\t\t * Only `-m` commit message option is checked here, as\n-\t\t * it supports `--fixup` to append the commit message.\n-\t\t *\n-\t\t * The other commit message options `-c`/`-C`/`-F` are\n-\t\t * incompatible with all the forms of `--fixup` and\n-\t\t * have already errored out while parsing the `git commit`\n-\t\t * options.\n+\t\t * Only `-m` and `-F` are handled here. `-c`/`-C` are\n+\t\t * incompatible with --fixup and have already errored out\n+\t\t * during option parsing.\n \t\t */\n-\t\tif (have_option_m && !strcmp(fixup_prefix, \"fixup\"))\n+\t\tif (have_option_m) {\n \t\t\tstrbuf_addbuf(&sb, &message);\n-\n-\t\tif (!strcmp(fixup_prefix, \"amend\")) {\n-\t\t\tif (have_option_m)\n-\t\t\t\tdie(_(\"options '%s' and '%s:%s' cannot be used together\"), \"-m\", \"--fixup\", fixup_message);\n+\t\t} else if (logfile && !strcmp(logfile, \"-\")) {\n+\t\t\tif (isatty(0))\n+\t\t\t\tfprintf(stderr, _(\"(reading log message from standard input)\\n\"));\n+\t\t\tif (strbuf_read(&sb, 0, 0) < 0)\n+\t\t\t\tdie_errno(_(\"could not read log from standard input\"));\n+\t\t} else if (logfile) {\n+\t\t\tif (strbuf_read_file(&sb, logfile, 0) < 0)\n+\t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n+\t\t} else if (!strcmp(fixup_prefix, \"amend\")) {\n \t\t\tprepare_amend_commit(commit, &sb, &ctx);\n \t\t}\n \t} else if (!stat(git_path_merge_msg(the_repository), &statbuf)) {\n@@ -1365,9 +1366,8 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \t}\n \tif (fixup_message && squash_message)\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n-\tdie_for_incompatible_opt4(!!use_message, \"-C\",\n+\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n \t\t\t\t  !!edit_message, \"-c\",\n-\t\t\t\t  !!logfile, \"-F\",\n \t\t\t\t  !!fixup_message, \"--fixup\");\n \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n \t\t\t\t  !!edit_message, \"-c\",\ndiff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh\nindex 5e782ad8d7..9b2ecb1356 100755\n--- a/t/t7500-commit-template-squash-signoff.sh\n+++ b/t/t7500-commit-template-squash-signoff.sh\n@@ -384,18 +384,24 @@ test_expect_success '--fixup=reword: ignores staged changes' '\n \ttest_cmp foo actual\n '\n \n-test_expect_success '--fixup=reword: error out with -m option' '\n+test_expect_success 'commit --fixup=reword: works with -m' '\n \tcommit_for_rebase_autosquash_setup &&\n-\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:reword'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=reword:HEAD~ -m \"reword commit message\" 2>actual &&\n-\ttest_cmp expect actual\n+\tgit commit --fixup=reword:HEAD~ -m \"reword commit message\" &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! $(git log -1 --format=%s HEAD~2)\n+\n+\treword commit message\n+\tEOF\n '\n \n-test_expect_success '--fixup=amend: error out with -m option' '\n+test_expect_success 'commit --fixup=amend: works with -m' '\n \tcommit_for_rebase_autosquash_setup &&\n-\techo \"fatal: options '\\''-m'\\'' and '\\''--fixup:amend'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"amend commit message\" 2>actual &&\n-\ttest_cmp expect actual\n+\tgit commit --fixup=amend:HEAD~ -m \"amend commit message\" &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! $(git log -1 --format=%s HEAD~2)\n+\n+\tamend commit message\n+\tEOF\n '\n \n test_expect_success 'consecutive amend! commits remove amend! line from commit msg body' '\n@@ -432,6 +438,13 @@ test_expect_success 'deny to create amend! commit if its commit msg body is empt\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'deny to create amend! commit if -m is empty' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"Aborting commit due to empty commit message body.\" >expect &&\n+\ttest_must_fail git commit --fixup=amend:HEAD~ -m \"\" 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'amend! commit allows empty commit msg body with --allow-empty-message' '\n \tcommit_for_rebase_autosquash_setup &&\n \tcat >expected <<-EOF &&\n@@ -468,10 +481,26 @@ test_expect_success '--fixup=reword: give error with pathsec' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--fixup=reword: -F give error message' '\n-\techo \"fatal: options '\\''-F'\\'' and '\\''--fixup'\\'' cannot be used together\" >expect &&\n-\ttest_must_fail git commit --fixup=reword:HEAD~ -F msg  2>actual &&\n-\ttest_cmp expect actual\n+test_expect_success 'commit --fixup works with -F' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"message\" >msgfile &&\n+\tgit commit --fixup HEAD~ -F msgfile &&\n+\ttest_commit_message HEAD <<-EOF\n+\tfixup! $(git log -1 --format=%s HEAD~2)\n+\n+\tmessage\n+\tEOF\n+'\n+\n+test_expect_success 'commit --fixup=reword: works with -F' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\techo \"message from file\" >msgfile &&\n+\tgit commit --fixup=reword:HEAD~ -F msgfile &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! $(git log -1 --format=%s HEAD~2)\n+\n+\t$(cat msgfile)\n+\tEOF\n '\n \n test_expect_success 'commit --squash works with -F' '\n@@ -526,8 +555,7 @@ test_expect_success 'invalid message options when using --fixup' '\n \tgit add foo &&\n \ttest_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&\n \ttest_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&\n-\ttest_must_fail git commit --fixup HEAD~1 -c HEAD~2 &&\n-\ttest_must_fail git commit --fixup HEAD~1 -F log\n+\ttest_must_fail git commit --fixup HEAD~1 -c HEAD~2\n '\n \n cat >expected-template <<EOF\n-- \n2.55.0.770.g0473647dd8\n\n"},{"id":"552985","messageId":"39e408e14718f533edc6ed2de6ff41ed961c6e05.1790082176.git.erik@cervined.in","threadId":"65653","inReplyTo":"cover.1790082176.git.erik@cervined.in","subject":"[PATCH v3 2/2] commit: allow -c/-C for all kinds of --fixup","fromName":"","fromEmail":"erik@cervined.in","sentAt":"2026-09-22T13:04:37Z","receivedAt":"2026-09-22T13:07:41Z","isPatch":true,"body":"From: Erik Cervin-Edin <erik@cervined.in>\n\nThe fixup code path in prepare_to_commit() only knows how to take a\nmessage body from -m or -F.  Now that the previous commit allows -m and\n-F for all --fixup variations, -c/-C are the odd ones out.\n\nTeach the fixup path to look up the ref given to -c/-C and seed its\nmessage when preparing to amend the commit, mirroring what --fixup=amend\ndoes when no message source is given.  With this in place, -m/-F/-c/-C\nall behave consistently across the plain, amend, and reword --fixup\nforms.\n\nThe incompatibility check grouping -C, -c, and --fixup can go away\nentirely, as -C and -c are already rejected together by the check that\ngroups them with -m and -F.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Erik Cervin-Edin <erik@cervined.in>\n---\n Documentation/git-commit.adoc             |  9 ++--\n builtin/commit.c                          | 14 +++---\n t/t7500-commit-template-squash-signoff.sh | 60 +++++++++++++++++++++--\n 3 files changed, 70 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc\nindex 61efd29e66..98c50a3be5 100644\n--- a/Documentation/git-commit.adoc\n+++ b/Documentation/git-commit.adoc\n@@ -102,8 +102,8 @@ include::diff-context-options.adoc[]\n +\n The commit created by plain `--fixup=<commit>` has a title\n composed of \"fixup!\" followed by the title of _<commit>_,\n-and is recognized specially by `git rebase --autosquash`. The `-m`\n-or `-F` option may be used to supplement the log message\n+and is recognized specially by `git rebase --autosquash`. The `-m`,\n+`-F`, `-C`, or `-c` option may be used to supplement the log message\n of the created commit, but the additional commentary will be thrown\n away once the \"fixup!\" commit is squashed into _<commit>_ by\n `git rebase --autosquash`.\n@@ -112,8 +112,9 @@ The commit created by `--fixup=amend:<commit>` is similar but its\n title is instead prefixed with \"amend!\". The log message of\n _<commit>_ is copied into the log message of the \"amend!\" commit and\n opened in an editor so it can be refined. The replacement message may\n-also be supplied directly using `-m` or `-F`, bypassing the\n-need to open an editor. When `git rebase\n+also be supplied directly using `-m`, `-F`, or `-C`, bypassing the\n+need to open an editor, or using `-c` to open the editor pre-populated\n+with the referenced commit's message. When `git rebase\n --autosquash` squashes the \"amend!\" commit into _<commit>_, the log\n message of _<commit>_ is replaced by the refined log message from the\n \"amend!\" commit. It is an error for the \"amend!\" commit's log message\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex ffb8644653..25341cc3e7 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -849,9 +849,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\thook_arg1 = \"message\";\n \n \t\t/*\n-\t\t * Only `-m` and `-F` are handled here. `-c`/`-C` are\n-\t\t * incompatible with --fixup and have already errored out\n-\t\t * during option parsing.\n+\t\t * `-m`, `-F`, `-C`, and `-c` provide the message body.\n+\t\t * If none was given and this is an amend, use the target\n+\t\t * commit's body instead.\n \t\t */\n \t\tif (have_option_m) {\n \t\t\tstrbuf_addbuf(&sb, &message);\n@@ -863,6 +863,11 @@ static int prepare_to_commit(const char *index_file, const char *prefix,\n \t\t} else if (logfile) {\n \t\t\tif (strbuf_read_file(&sb, logfile, 0) < 0)\n \t\t\t\tdie_errno(_(\"could not read log file '%s'\"), logfile);\n+\t\t} else if (use_message) {\n+\t\t\tstruct commit *c = lookup_commit_reference_by_name(use_message);\n+\t\t\tif (!c)\n+\t\t\t\tdie(_(\"could not lookup commit '%s'\"), use_message);\n+\t\t\tprepare_amend_commit(c, &sb, &ctx);\n \t\t} else if (!strcmp(fixup_prefix, \"amend\")) {\n \t\t\tprepare_amend_commit(commit, &sb, &ctx);\n \t\t}\n@@ -1366,9 +1371,6 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \t}\n \tif (fixup_message && squash_message)\n \t\tdie(_(\"options '%s' and '%s' cannot be used together\"), \"--squash\", \"--fixup\");\n-\tdie_for_incompatible_opt3(!!use_message, \"-C\",\n-\t\t\t\t  !!edit_message, \"-c\",\n-\t\t\t\t  !!fixup_message, \"--fixup\");\n \tdie_for_incompatible_opt4(have_option_m, \"-m\",\n \t\t\t\t  !!edit_message, \"-c\",\n \t\t\t\t  !!use_message, \"-C\",\ndiff --git a/t/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh\nindex 9b2ecb1356..de3221ea52 100755\n--- a/t/t7500-commit-template-squash-signoff.sh\n+++ b/t/t7500-commit-template-squash-signoff.sh\n@@ -492,6 +492,62 @@ test_expect_success 'commit --fixup works with -F' '\n \tEOF\n '\n \n+test_expect_success 'commit --fixup works with -C' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\tgit commit --fixup HEAD~ -C HEAD &&\n+\ttest_commit_message HEAD <<-EOF\n+\tfixup! $(git log -1 --format=%s HEAD~2)\n+\n+\t$(get_commit_msg HEAD~)\n+\tEOF\n+'\n+\n+test_expect_success 'commit --fixup=amend: works with -c' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\ttest_set_editor : &&\n+\tgit commit --fixup=amend:HEAD -c HEAD~ &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! intermediate commit\n+\n+\ttarget message subject line\n+\n+\ttarget message body line 1\n+\ttarget message body line 2\n+\tEOF\n+'\n+\n+test_expect_success 'commit --fixup=amend:HEAD with -C HEAD and without have the same message' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\tstart=$(git rev-parse HEAD) &&\n+\n+\tgit commit --fixup=amend:HEAD -C HEAD &&\n+\tgit commit --fixup=amend:HEAD -C HEAD &&\n+\tgit log -1 --pretty=%B >with-c &&\n+\n+\tgit reset --hard \"$start\" &&\n+\ttest_set_editor : &&\n+\tgit commit --fixup=amend:HEAD &&\n+\tgit commit --fixup=amend:HEAD &&\n+\tgit log -1 --pretty=%B >without-c &&\n+\n+\ttest_cmp with-c without-c\n+'\n+\n+test_expect_success 'commit --fixup=amend: with -C copies full subject + body of squash commit' '\n+\tcommit_for_rebase_autosquash_setup &&\n+\tgit commit --squash HEAD~ -m \"inner body\" &&\n+\techo \"extra\" >>foo &&\n+\tgit add foo &&\n+\tgit commit --fixup=amend:HEAD -C HEAD &&\n+\ttest_commit_message HEAD <<-EOF\n+\tamend! squash! $(git log -1 --format=%s HEAD~3)\n+\n+\tsquash! $(git log -1 --format=%s HEAD~3)\n+\n+\tinner body\n+\tEOF\n+'\n+\n test_expect_success 'commit --fixup=reword: works with -F' '\n \tcommit_for_rebase_autosquash_setup &&\n \techo \"message from file\" >msgfile &&\n@@ -553,9 +609,7 @@ test_expect_success 'invalid message options when using --fixup' '\n \techo changes >>foo &&\n \techo \"message\" >log &&\n \tgit add foo &&\n-\ttest_must_fail git commit --fixup HEAD~1 --squash HEAD~2 &&\n-\ttest_must_fail git commit --fixup HEAD~1 -C HEAD~2 &&\n-\ttest_must_fail git commit --fixup HEAD~1 -c HEAD~2\n+\ttest_must_fail git commit --fixup HEAD~1 --squash HEAD~2\n '\n \n cat >expected-template <<EOF\n-- \n2.55.0.770.g0473647dd8\n\n"}]}