{"thread":{"id":"65600","subject":"[PATCH] rebase: ignore non-branch update-refs","startedAt":"2026-05-06T02:41:53Z","lastAt":"2026-05-15T15:40:33Z","messageCount":14,"participants":["mail@abhinavg.net","Phillip Wood","Junio C Hamano","Abhinav Gupta"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"542798","messageId":"20260506023944.90691-1-mail@abhinavg.net","threadId":"65600","inReplyTo":null,"subject":"[PATCH] rebase: ignore non-branch update-refs","fromName":"","fromEmail":"mail@abhinavg.net","sentAt":"2026-05-06T02:39:43Z","receivedAt":"2026-05-06T02:41:53Z","isPatch":true,"body":"From: Abhinav Gupta <mail@abhinavg.net>\n\nThe following Git configuration breaks git rebase --update-refs:\n\n    [rebase]\n        instructionFormat = %s%d\n\nThe '%d' format requests all available decorations for a commit,\nfilling the global decoration table with all of them,\nwhich --update-refs then uses to populate 'update-ref' instructions\nin the rebase todo list.\n\nSpecifically, this results in the following instruction:\n\n    update-ref HEAD\n\nThe todo parser then rejects the instruction:\n\n    error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD\n    error: invalid line 3: update-ref HEAD\n\nTo fix, ignore decorations that are not local branches\nwhen scanning through the table.\n\nThis filtering matches the documented contract:\n\n    Automatically force-update any branches that point to commits [..]\n\nSigned-off-by: Abhinav Gupta <mail@abhinavg.net>\n---\n sequencer.c                   | 10 ++++++++++\n t/t3404-rebase-interactive.sh | 22 ++++++++++++++++++++++\n 2 files changed, 32 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..25bcfc5da0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -6428,6 +6428,16 @@ static int add_decorations_to_list(const struct commit *commit,\n \t\tconst char *path;\n \t\tsize_t base_offset = ctx->buf->len;\n \n+\t\t/*\n+\t\t * The global decoration table may contain names loaded by\n+\t\t * a previous pretty format such as \"%d\".\n+\t\t * This will result in refs such as \"HEAD\" being present.\n+\t\t */\n+\t\tif (decoration->type != DECORATION_REF_LOCAL) {\n+\t\t\tdecoration = decoration->next;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\t/*\n \t\t * If the branch is the current HEAD, then it will be\n \t\t * updated by the default rebase behavior.\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 3e44562afa..d58236f0eb 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1960,6 +1960,28 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t)\n '\n \n+test_expect_success '--update-refs ignores non-branch decorations' '\n+\ttest_when_finished \"git branch -D update-refs\" &&\n+\ttest_when_finished \"git branch -D third\" &&\n+\ttest_when_finished \"git checkout primary\" &&\n+\tgit checkout -B update-refs no-conflict-branch &&\n+\tgit branch -f third HEAD~1 &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\t# rebase.instructionFormat=%d loads normal log decorations before\n+\t\t# --update-refs adds its branch placeholders.\n+\t\t# The placeholder scan must still ignore symbolic decorations,\n+\t\t# because \"update-ref HEAD\" is not a valid branch update.\n+\t\ttest_must_fail git -c rebase.instructionFormat=\"%s%d\" \\\n+\t\t\trebase -i --update-refs primary >todo &&\n+\n+\t\ttest_grep \"^update-ref refs/heads/third$\" todo &&\n+\t\ttest_grep ! \"^update-ref refs/heads/update-refs$\" todo &&\n+\t\ttest_grep ! \"^update-ref HEAD$\" todo\n+\t)\n+'\n+\n test_expect_success '--update-refs updates refs correctly' '\n \tgit checkout -B update-refs no-conflict-branch &&\n \tgit branch -f base HEAD~4 &&\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n-- \n2.54.0\n\n"},{"id":"542862","messageId":"dc7c32f7-d56e-4dd6-977e-cced6155e758@gmail.com","threadId":"65600","inReplyTo":"20260506023944.90691-1-mail@abhinavg.net","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-07T16:08:24Z","receivedAt":"2026-05-07T16:08:23Z","isPatch":true,"body":"Hi Abhinav\n\nOn 06/05/2026 03:39, mail@abhinavg.net wrote:\n> From: Abhinav Gupta <mail@abhinavg.net>\n> \n> The following Git configuration breaks git rebase --update-refs:\n> \n>      [rebase]\n>          instructionFormat = %s%d\n> \n> The '%d' format requests all available decorations for a commit,\n> filling the global decoration table with all of them,\n> which --update-refs then uses to populate 'update-ref' instructions\n> in the rebase todo list.\n> \n> Specifically, this results in the following instruction:\n> \n>      update-ref HEAD\n> \n> The todo parser then rejects the instruction:\n> \n>      error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD\n>      error: invalid line 3: update-ref HEAD\n> \n> To fix, ignore decorations that are not local branches\n> when scanning through the table.\n\nThanks for the clear explanation, the solution makes sense\n\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 3e44562afa..d58236f0eb 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1960,6 +1960,28 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n>   \t)\n>   '\n>   \n> +test_expect_success '--update-refs ignores non-branch decorations' '\n> +\ttest_when_finished \"git branch -D update-refs\" &&\n> +\ttest_when_finished \"git branch -D third\" &&\n\nI'm not sure that we really need to create this branch in the first\nplace. We already create update-refs from no-conflict-branch so we can\ncheck that no-conflict-branch appears and update-refs does not. In\naddition to checking that HEAD does not appear we should also check that\nwe don't try to update any tags (HEAD matches tag M). I think the\nsimplest way is to check the output of \"grep ^update-ref todo\" so we'd\nhave\n\ntest_expect_success '--update-refs ignores non-branch decorations' '\n\ttest_when_finished \"git branch -D update-refs\" &&\n\ttest_when_finished \"git checkout primary\" &&\n\tgit checkout -B update-refs no-conflict-branch &&\n\t(\n\t\tset_cat_todo_editor &&\n\n\t\t# rebase.instructionFormat=%d loads normal log decorations before\n\t\t# --update-refs adds its branch placeholders so we must ignore\n\t\t# all non-local decorations.\n\t\ttest_must_fail git -c rebase.instructionFormat=\"%s%d\" \\\n\t\t\trebase -i --update-refs HEAD^ >todo\n\t) &&\n\tgrep ^update-ref todo >actual &&\n\ttest_write_lines \"update-ref refs/heads/no-conflict-branch\" >expect &&\n\ttest_cmp expect actual\n'\n\nThanks for working on this\n\nPhillip\n\n> +\ttest_when_finished \"git checkout primary\" &&\n> +\tgit checkout -B update-refs no-conflict-branch &&\n> +\tgit branch -f third HEAD~1 &&\n> +\t(\n> +\t\tset_cat_todo_editor &&\n> +\n> +\t\t# rebase.instructionFormat=%d loads normal log decorations before\n> +\t\t# --update-refs adds its branch placeholders.\n> +\t\t# The placeholder scan must still ignore symbolic decorations,\n> +\t\t# because \"update-ref HEAD\" is not a valid branch update.\n> +\t\ttest_must_fail git -c rebase.instructionFormat=\"%s%d\" \\\n> +\t\t\trebase -i --update-refs primary >todo &&\n> +\n> +\t\ttest_grep \"^update-ref refs/heads/third$\" todo &&\n> +\t\ttest_grep ! \"^update-ref refs/heads/update-refs$\" todo &&\n> +\t\ttest_grep ! \"^update-ref HEAD$\" todo\n> +\t)\n> +'\n> +\n>   test_expect_success '--update-refs updates refs correctly' '\n>   \tgit checkout -B update-refs no-conflict-branch &&\n>   \tgit branch -f base HEAD~4 &&\n> \n> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n\n"},{"id":"542882","messageId":"20260508015817.86177-1-mail@abhinavg.net","threadId":"65600","inReplyTo":"20260506023944.90691-1-mail@abhinavg.net","subject":"[PATCH v2] rebase: ignore non-branch update-refs","fromName":"","fromEmail":"mail@abhinavg.net","sentAt":"2026-05-08T01:58:17Z","receivedAt":"2026-05-08T01:58:56Z","isPatch":true,"body":"From: Abhinav Gupta <mail@abhinavg.net>\n\nThe following Git configuration breaks git rebase --update-refs:\n\n    [rebase]\n        instructionFormat = %s%d\n\nThe '%d' format requests all available decorations for a commit,\nfilling the global decoration table with all of them,\nwhich --update-refs then uses to populate 'update-ref' instructions\nin the rebase todo list.\n\nSpecifically, this results in the following instruction:\n\n    update-ref HEAD\n\nThe todo parser then rejects the instruction:\n\n    error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD\n    error: invalid line 3: update-ref HEAD\n\nTo fix, ignore decorations that are not local branches\nwhen scanning through the table.\n\nThis matches the documented contract:\nit moves branch refs under refs/heads/\nand leaves display-only decorations (HEAD, tags, etc.) alone.\n\nVerification:\nA regression test that fails without this fix is included.\n\nSigned-off-by: Abhinav Gupta <mail@abhinavg.net>\n---\nUpdates:\n    v2: incorporate suggestions to simplify the test\n\n sequencer.c                   | 10 ++++++++++\n t/t3404-rebase-interactive.sh | 18 ++++++++++++++++++\n 2 files changed, 28 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..25bcfc5da0 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -6428,6 +6428,16 @@ static int add_decorations_to_list(const struct commit *commit,\n \t\tconst char *path;\n \t\tsize_t base_offset = ctx->buf->len;\n \n+\t\t/*\n+\t\t * The global decoration table may contain names loaded by\n+\t\t * a previous pretty format such as \"%d\".\n+\t\t * This will result in refs such as \"HEAD\" being present.\n+\t\t */\n+\t\tif (decoration->type != DECORATION_REF_LOCAL) {\n+\t\t\tdecoration = decoration->next;\n+\t\t\tcontinue;\n+\t\t}\n+\n \t\t/*\n \t\t * If the branch is the current HEAD, then it will be\n \t\t * updated by the default rebase behavior.\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 3e44562afa..58b3bb0c27 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1960,6 +1960,24 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t)\n '\n \n+test_expect_success '--update-refs ignores non-branch decorations' '\n+\ttest_when_finished \"git branch -D update-refs\" &&\n+\ttest_when_finished \"git checkout primary\" &&\n+\tgit checkout -B update-refs no-conflict-branch &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\t# rebase.instructionFormat=%d loads normal log decorations before\n+\t\t# --update-refs adds its branch placeholders so we must ignore\n+\t\t# all non-local decorations.\n+\t\ttest_must_fail git -c rebase.instructionFormat=\"%s%d\" \\\n+\t\t\trebase -i --update-refs HEAD^ >todo\n+\t) &&\n+\tgrep ^update-ref todo >actual &&\n+\ttest_write_lines \"update-ref refs/heads/no-conflict-branch\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success '--update-refs updates refs correctly' '\n \tgit checkout -B update-refs no-conflict-branch &&\n \tgit branch -f base HEAD~4 &&\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n-- \n2.54.0\n\n"},{"id":"542905","messageId":"d9128068-d5e3-4a81-a4d6-b94bfc7d3717@gmail.com","threadId":"65600","inReplyTo":"20260508015817.86177-1-mail@abhinavg.net","subject":"Re: [PATCH v2] rebase: ignore non-branch update-refs","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-08T10:07:08Z","receivedAt":"2026-05-08T10:07:05Z","isPatch":true,"body":"Hi Abhinav\n\nThanks for re-working the test - this looks great.\n\nPhillip\n\nOn 08/05/2026 02:58, mail@abhinavg.net wrote:\n> From: Abhinav Gupta <mail@abhinavg.net>\n> \n> The following Git configuration breaks git rebase --update-refs:\n> \n>      [rebase]\n>          instructionFormat = %s%d\n> \n> The '%d' format requests all available decorations for a commit,\n> filling the global decoration table with all of them,\n> which --update-refs then uses to populate 'update-ref' instructions\n> in the rebase todo list.\n> \n> Specifically, this results in the following instruction:\n> \n>      update-ref HEAD\n> \n> The todo parser then rejects the instruction:\n> \n>      error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD\n>      error: invalid line 3: update-ref HEAD\n> \n> To fix, ignore decorations that are not local branches\n> when scanning through the table.\n> \n> This matches the documented contract:\n> it moves branch refs under refs/heads/\n> and leaves display-only decorations (HEAD, tags, etc.) alone.\n> \n> Verification:\n> A regression test that fails without this fix is included.\n> \n> Signed-off-by: Abhinav Gupta <mail@abhinavg.net>\n> ---\n> Updates:\n>      v2: incorporate suggestions to simplify the test\n> \n>   sequencer.c                   | 10 ++++++++++\n>   t/t3404-rebase-interactive.sh | 18 ++++++++++++++++++\n>   2 files changed, 28 insertions(+)\n> \n> diff --git a/sequencer.c b/sequencer.c\n> index b7d8dca47f..25bcfc5da0 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -6428,6 +6428,16 @@ static int add_decorations_to_list(const struct commit *commit,\n>   \t\tconst char *path;\n>   \t\tsize_t base_offset = ctx->buf->len;\n>   \n> +\t\t/*\n> +\t\t * The global decoration table may contain names loaded by\n> +\t\t * a previous pretty format such as \"%d\".\n> +\t\t * This will result in refs such as \"HEAD\" being present.\n> +\t\t */\n> +\t\tif (decoration->type != DECORATION_REF_LOCAL) {\n> +\t\t\tdecoration = decoration->next;\n> +\t\t\tcontinue;\n> +\t\t}\n> +\n>   \t\t/*\n>   \t\t * If the branch is the current HEAD, then it will be\n>   \t\t * updated by the default rebase behavior.\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index 3e44562afa..58b3bb0c27 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1960,6 +1960,24 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n>   \t)\n>   '\n>   \n> +test_expect_success '--update-refs ignores non-branch decorations' '\n> +\ttest_when_finished \"git branch -D update-refs\" &&\n> +\ttest_when_finished \"git checkout primary\" &&\n> +\tgit checkout -B update-refs no-conflict-branch &&\n> +\t(\n> +\t\tset_cat_todo_editor &&\n> +\n> +\t\t# rebase.instructionFormat=%d loads normal log decorations before\n> +\t\t# --update-refs adds its branch placeholders so we must ignore\n> +\t\t# all non-local decorations.\n> +\t\ttest_must_fail git -c rebase.instructionFormat=\"%s%d\" \\\n> +\t\t\trebase -i --update-refs HEAD^ >todo\n> +\t) &&\n> +\tgrep ^update-ref todo >actual &&\n> +\ttest_write_lines \"update-ref refs/heads/no-conflict-branch\" >expect &&\n> +\ttest_cmp expect actual\n> +'\n> +\n>   test_expect_success '--update-refs updates refs correctly' '\n>   \tgit checkout -B update-refs no-conflict-branch &&\n>   \tgit branch -f base HEAD~4 &&\n> \n> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n\n"},{"id":"542961","messageId":"xmqqqznk9ih8.fsf@gitster.g","threadId":"65600","inReplyTo":"20260506023944.90691-1-mail@abhinavg.net","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-10T01:11:15Z","receivedAt":"2026-05-10T01:11:17Z","isPatch":true,"body":"mail@abhinavg.net writes:\n\n> diff --git a/sequencer.c b/sequencer.c\n> index b7d8dca47f..25bcfc5da0 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -6428,6 +6428,16 @@ static int add_decorations_to_list(const struct commit *commit,\n>  \t\tconst char *path;\n>  \t\tsize_t base_offset = ctx->buf->len;\n>  \n> +\t\t/*\n> +\t\t * The global decoration table may contain names loaded by\n> +\t\t * a previous pretty format such as \"%d\".\n> +\t\t * This will result in refs such as \"HEAD\" being present.\n> +\t\t */\n\nYour long topic branch may have local unannotated tags that point\ninto the middle of it, marking strategic points in the topic.\n\nWith this change, the command no longer moves them when it rebases\nthe entire topic.  Isn't it a regression?\n\n> +\t\tif (decoration->type != DECORATION_REF_LOCAL) {\n> +\t\t\tdecoration = decoration->next;\n> +\t\t\tcontinue;\n> +\t\t}\n\nIn other words, what you want to prevent from appearing in the insn\nstream may be \"HEAD\", but if so, \"must be DECORATION_REF_LOCAL\" is\ntoo broad a net to catch it, and causing unintended collateral damage.\n\nAs to the style, as the body of the new conditional works\nidentically with the existing code to exclude the current branch, I\nwonder why it shouldn't read more like this?  The following\nillustration still uses \"must be DECORATION_REF_LOCAL\" and that may\nhave to be corrected, of course.\n\n sequencer.c | 8 +++++---\n 1 file changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git i/sequencer.c w/sequencer.c\nindex b7d8dca47f..1ba95fbae1 100644\n--- i/sequencer.c\n+++ w/sequencer.c\n@@ -6429,10 +6429,12 @@ static int add_decorations_to_list(const struct commit *commit,\n \t\tsize_t base_offset = ctx->buf->len;\n \n \t\t/*\n-\t\t * If the branch is the current HEAD, then it will be\n-\t\t * updated by the default rebase behavior.\n+\t\t * Exclude the \"current\" branch, which will be updated\n+\t\t * by the default rebase behavior.  Exclude non-branch\n+\t\t * decorations as well.\n \t\t */\n-\t\tif (head_ref && !strcmp(head_ref, decoration->name)) {\n+\t\tif ((head_ref && !strcmp(head_ref, decoration->name)) ||\n+\t\t    (decoration->type != DECORATION_REF_LOCAL)) {\n \t\t\tdecoration = decoration->next;\n \t\t\tcontinue;\n \t\t}\n"},{"id":"542969","messageId":"0911df2d-aaa2-456e-a678-345239cefc67@gmail.com","threadId":"65600","inReplyTo":"xmqqqznk9ih8.fsf@gitster.g","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-10T13:37:21Z","receivedAt":"2026-05-10T13:37:27Z","isPatch":true,"body":"On 10/05/2026 02:11, Junio C Hamano wrote:\n> mail@abhinavg.net writes:\n> \n>> diff --git a/sequencer.c b/sequencer.c\n>> index b7d8dca47f..25bcfc5da0 100644\n>> --- a/sequencer.c\n>> +++ b/sequencer.c\n>> @@ -6428,6 +6428,16 @@ static int add_decorations_to_list(const struct commit *commit,\n>>   \t\tconst char *path;\n>>   \t\tsize_t base_offset = ctx->buf->len;\n>>   \n>> +\t\t/*\n>> +\t\t * The global decoration table may contain names loaded by\n>> +\t\t * a previous pretty format such as \"%d\".\n>> +\t\t * This will result in refs such as \"HEAD\" being present.\n>> +\t\t */\n> \n> Your long topic branch may have local unannotated tags that point\n> into the middle of it, marking strategic points in the topic.\n> \n> With this change, the command no longer moves them when it rebases\n> the entire topic.  Isn't it a regression?\n\nsequencer.c:todo_list_add_update_ref_commands() calls \nload_branch_decorations() so it does not update tags and the patch is \ncorrect.\n\nLooking at make_script_with_merges() it also calls \nload_branch_decorations() so we should probably add something like the \ndiff below. Having said that this patch is a strict improvement so we \ncan always fix make_script_with_merges() as a follow up.\n\nThanks\n\nPhillip\n\n---- 8< ----\n\ndiff --git b/sequencer.c b/sequencer.c\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5982,6 +5982,15 @@ static int make_script_with_merges(struct \npretty_print_context *pp,\n  \t\t\tconst char *label = label_from_message.buf;\n  \t\t\tconst struct name_decoration *decoration =\n  \t\t\t\tget_name_decoration(&to_merge->item->object);\n+\n+\t\t\t/*\n+\t\t\t * If rebase.instructionFormat includes \"%d\"\n+\t\t\t * then we to skip non-local decorations as\n+\t\t\t * we're only interested in branch names\n+\t\t\t */\n+\t\t\twhile (decoration &&\n+\t\t\t       decoration->type != DECORATION_REF_LOCAL)\n+\t\t\t\tdecoration = decoration->next;\n\n  \t\t\tif (decoration)\n  \t\t\t\tskip_prefix(decoration->name, \"refs/heads/\",\n\n"},{"id":"542978","messageId":"20260510224111.64467-1-mail@abhinavg.net","threadId":"65600","inReplyTo":"20260508015817.86177-1-mail@abhinavg.net","subject":"[PATCH v3 0/1] rebase: ignore non-branch update-refs","fromName":"","fromEmail":"mail@abhinavg.net","sentAt":"2026-05-10T22:41:10Z","receivedAt":"2026-05-10T22:41:37Z","isPatch":true,"body":"From: Abhinav Gupta <mail@abhinavg.net>\n\nUpdated per suggestion to merge the conditionals.\n\nPhillip wrote:\n> On 10/05/2026 02:11, Junio C Hamano wrote:\n> > Your long topic branch may have local unannotated tags that point\n> > into the middle of it, marking strategic points in the topic.\n> >\n> > With this change, the command no longer moves them when it rebases\n> > the entire topic.  Isn't it a regression?\n>\n> sequencer.c:todo_list_add_update_ref_commands() calls\n> load_branch_decorations() so it does not update tags and the patch is\n> correct.\n\nThat's right, the documented contract is that only branches are updated.\nWithout '%d' triggering a load_ref_decorations,\nload_branch_decorations would be called and only branch refs\nwould be added to the rebase todo list.\n\nPhillip wrote:\n> Looking at make_script_with_merges() it also calls\n> load_branch_decorations() so we should probably add something like the\n> diff below.\n\nThinking out loud:\nInstead of caller-side filtering, another option might be\nto replace load_branch_decorations with a branch-specialized iterator\nthat relies on load_ref_decorations and silently skips non-branch decorations.\nThat's a more invasive change, though.\n\nThanks!\n\nAbhinav Gupta (1):\n  rebase: ignore non-branch update-refs\n\n sequencer.c                   |  8 +++++++-\n t/t3404-rebase-interactive.sh | 18 ++++++++++++++++++\n 2 files changed, 25 insertions(+), 1 deletion(-)\n\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n-- \n2.54.0\n\n"},{"id":"542979","messageId":"20260510224111.64467-2-mail@abhinavg.net","threadId":"65600","inReplyTo":"20260510224111.64467-1-mail@abhinavg.net","subject":"[PATCH v3 1/1] rebase: ignore non-branch update-refs","fromName":"","fromEmail":"mail@abhinavg.net","sentAt":"2026-05-10T22:41:11Z","receivedAt":"2026-05-10T22:41:48Z","isPatch":true,"body":"From: Abhinav Gupta <mail@abhinavg.net>\n\nThe following Git configuration breaks git rebase --update-refs:\n\n    [rebase]\n        instructionFormat = %s%d\n\nThe '%d' format requests all available decorations for a commit,\nfilling the global decoration table with all of them,\nwhich --update-refs then uses to populate 'update-ref' instructions\nin the rebase todo list.\n\nSpecifically, this results in the following instruction:\n\n    update-ref HEAD\n\nThe todo parser then rejects the instruction:\n\n    error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD\n    error: invalid line 3: update-ref HEAD\n\nTo fix, ignore decorations that are not local branches\nwhen scanning through the table.\n\nThis matches the documented contract:\nit moves branch refs under refs/heads/\nand leaves display-only decorations (HEAD, tags, etc.) alone.\n\nVerification:\nA regression test that fails without this fix is included.\n\nSigned-off-by: Abhinav Gupta <mail@abhinavg.net>\n---\nUpdates:\n    v2: incorporate suggestions to simplify the test\n    v3: merge two if statements into one\n\n sequencer.c                   |  8 +++++++-\n t/t3404-rebase-interactive.sh | 18 ++++++++++++++++++\n 2 files changed, 25 insertions(+), 1 deletion(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..ca3ea863d6 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -6431,8 +6431,14 @@ static int add_decorations_to_list(const struct commit *commit,\n \t\t/*\n \t\t * If the branch is the current HEAD, then it will be\n \t\t * updated by the default rebase behavior.\n+\t\t * Exclude it from the list of refs to update,\n+\t\t * as well as any non-branch decorations.\n+\t\t * Non-branch decorations may be present if the pretty format\n+\t\t * includes \"%d\", which would have loaded all refs\n+\t\t * into the global decoration table.\n \t\t */\n-\t\tif (head_ref && !strcmp(head_ref, decoration->name)) {\n+\t\tif ((head_ref && !strcmp(head_ref, decoration->name)) ||\n+\t\t    (decoration->type != DECORATION_REF_LOCAL)) {\n \t\t\tdecoration = decoration->next;\n \t\t\tcontinue;\n \t\t}\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 3e44562afa..58b3bb0c27 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1960,6 +1960,24 @@ test_expect_success '--update-refs adds commands with --rebase-merges' '\n \t)\n '\n \n+test_expect_success '--update-refs ignores non-branch decorations' '\n+\ttest_when_finished \"git branch -D update-refs\" &&\n+\ttest_when_finished \"git checkout primary\" &&\n+\tgit checkout -B update-refs no-conflict-branch &&\n+\t(\n+\t\tset_cat_todo_editor &&\n+\n+\t\t# rebase.instructionFormat=%d loads normal log decorations before\n+\t\t# --update-refs adds its branch placeholders so we must ignore\n+\t\t# all non-local decorations.\n+\t\ttest_must_fail git -c rebase.instructionFormat=\"%s%d\" \\\n+\t\t\trebase -i --update-refs HEAD^ >todo\n+\t) &&\n+\tgrep ^update-ref todo >actual &&\n+\ttest_write_lines \"update-ref refs/heads/no-conflict-branch\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success '--update-refs updates refs correctly' '\n \tgit checkout -B update-refs no-conflict-branch &&\n \tgit branch -f base HEAD~4 &&\n-- \n2.54.0\n\n"},{"id":"542981","messageId":"xmqqv7cu96q4.fsf@gitster.g","threadId":"65600","inReplyTo":"0911df2d-aaa2-456e-a678-345239cefc67@gmail.com","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-10T23:37:23Z","receivedAt":"2026-05-10T23:37:25Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> Your long topic branch may have local unannotated tags that point\n>> into the middle of it, marking strategic points in the topic.\n>> \n>> With this change, the command no longer moves them when it rebases\n>> the entire topic.  Isn't it a regression?\n>\n> sequencer.c:todo_list_add_update_ref_commands() calls \n> load_branch_decorations() so it does not update tags and the patch is \n> correct.\n\nOK.  And with \"%d\", the existing versions of Git would have produced\nsomething like\n\n    pick 31e8fcabd8 # rebase: update-refs (HEAD -> rebase, tag: mark)\n    update-ref HEAD\n    update-ref refs/heads/rebase\n    update-ref refs/tags/mark\n\nit would have failed to work due to the \"HEAD\" thing, so even though\nexisting versions of Git may have added such local tags to the insn\nsequence, it would not have been a workable configuration anyway.\n\nOK.  If we never supported such a workflow to use local tags as\nmarkers, then the strategy taken by the posted patch to limit us to\nlocal branch refs is a very good thing, I think.\n"},{"id":"542990","messageId":"63e04828-5895-4deb-a698-5d6f494d23f3@app.fastmail.com","threadId":"65600","inReplyTo":"xmqqv7cu96q4.fsf@gitster.g","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Abhinav Gupta","fromEmail":"mail@abhinavg.net","sentAt":"2026-05-11T00:15:42Z","receivedAt":"2026-05-11T00:16:26Z","isPatch":true,"body":"\nOn Sun, May 10, 2026, at 16:37, Junio C Hamano wrote:\n> it would have failed to work due to the \"HEAD\" thing, so even though\n> existing versions of Git may have added such local tags to the insn\n> sequence, it would not have been a workable configuration anyway.\n\nYeah. One additional data point:\nnon-interactive rebase is also broken under this configuration.\nGiven a branch off main~1, it runs into the same issue:\n\n    $ git checkout -b foo main~1\n    $ git commit --allow-empty -m 'do things'\n    $ git rebase main\n      # ...\n    error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD\n    error: invalid line 2: update-ref HEAD\n    You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.\n    Or you can abort the rebase with 'git rebase --abort'.\n\nI'm guessing non-interactive rebase works off the same todo list so that makes sense.\n"},{"id":"542991","messageId":"xmqqmry6x0dm.fsf@gitster.g","threadId":"65600","inReplyTo":"63e04828-5895-4deb-a698-5d6f494d23f3@app.fastmail.com","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-11T00:20:37Z","receivedAt":"2026-05-11T00:20:40Z","isPatch":true,"body":"\"Abhinav Gupta\" <mail@abhinavg.net> writes:\n\n> On Sun, May 10, 2026, at 16:37, Junio C Hamano wrote:\n>> it would have failed to work due to the \"HEAD\" thing, so even though\n>> existing versions of Git may have added such local tags to the insn\n>> sequence, it would not have been a workable configuration anyway.\n>\n> Yeah. One additional data point:\n> non-interactive rebase is also broken under this configuration.\n> Given a branch off main~1, it runs into the same issue:\n>\n>     $ git checkout -b foo main~1\n>     $ git commit --allow-empty -m 'do things'\n>     $ git rebase main\n>       # ...\n>     error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD\n>     error: invalid line 2: update-ref HEAD\n>     You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.\n>     Or you can abort the rebase with 'git rebase --abort'.\n>\n> I'm guessing non-interactive rebase works off the same todo list so that makes sense.\n\nI smell that you'd be suggesting to replace the patch we have\ndiscussed with another one that declares that it is a bug to use %d\nin insn format?  I do not think how well it would fly.\n"},{"id":"542993","messageId":"cc21dea8-2184-4db9-9aaf-6cd6910095f8@app.fastmail.com","threadId":"65600","inReplyTo":"xmqqmry6x0dm.fsf@gitster.g","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Abhinav Gupta","fromEmail":"mail@abhinavg.net","sentAt":"2026-05-11T00:33:34Z","receivedAt":"2026-05-11T00:33:55Z","isPatch":true,"body":"\nOn Sun, May 10, 2026, at 17:20, Junio C Hamano wrote:\n> I smell that you'd be suggesting to replace the patch we have\n> discussed with another one that declares that it is a bug to use %d\n> in insn format?  I do not think how well it would fly.\n\nNo, I didn't meant to imply that. I'm happy with the patch as-is.\n\nUsing %d in insn format is desirable, and I would not try to remove that.\nI ran into this issue because of my own use of %d in insn format.\n\nI was sharing an additional example for how this currently misbehaves:\nnot only does it generate the 'update-ref HEAD' in interactive contexts,\nbut it also breaks all non-interactive rebases with a non-empty todo list.\n"},{"id":"543187","messageId":"5331661b-f0a3-4e70-bd2e-c642337deb8f@gmail.com","threadId":"65600","inReplyTo":"63e04828-5895-4deb-a698-5d6f494d23f3@app.fastmail.com","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-12T15:10:51Z","receivedAt":"2026-05-12T15:10:56Z","isPatch":true,"body":"On 11/05/2026 01:15, Abhinav Gupta wrote:\n> \n> On Sun, May 10, 2026, at 16:37, Junio C Hamano wrote:\n>> it would have failed to work due to the \"HEAD\" thing, so even though\n>> existing versions of Git may have added such local tags to the insn\n>> sequence, it would not have been a workable configuration anyway.\n> \n> Yeah. One additional data point:\n> non-interactive rebase is also broken under this configuration.\n\nI assume you mean that rebase.instructionFormat includes \"%d\" and \nrebase.updateRefs is true below.\n\n> Given a branch off main~1, it runs into the same issue:\n> \n>      $ git checkout -b foo main~1\n>      $ git commit --allow-empty -m 'do things'\n>      $ git rebase main\n>        # ...\n>      error: update-ref requires a fully qualified refname e.g. refs/heads/HEAD\n>      error: invalid line 2: update-ref HEAD\n>      You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'.\n>      Or you can abort the rebase with 'git rebase --abort'.\n> \n> I'm guessing non-interactive rebase works off the same todo list so that makes sense.\n\nYes, barring some special cases where we don't support updating refs\n\t\"git rebase <options>\"\nis essentially\n\tGIT_SEQUENCE_EDITOR=: git rebase -i <options>\n\nThanks\n\nPhillip\n\n"},{"id":"543404","messageId":"956baa65-6448-448f-9f02-fc90068a9413@gmail.com","threadId":"65600","inReplyTo":"0911df2d-aaa2-456e-a678-345239cefc67@gmail.com","subject":"Re: [PATCH] rebase: ignore non-branch update-refs","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-15T15:40:29Z","receivedAt":"2026-05-15T15:40:33Z","isPatch":true,"body":"On 10/05/2026 14:37, Phillip Wood wrote:\n> \n> Looking at make_script_with_merges() it also calls \n> load_branch_decorations() so we should probably add something like the \n> diff below. Having said that this patch is a strict improvement so we \n> can always fix make_script_with_merges() as a follow up.\n\nI've just had another look at this and even though we call \nload_branch_decorations() after calling setup_revisions_from_strvec() \nand prepare_revision_walk() we only load branch decorations. It turns \nout that \"%d\" calls load_ref_decorations() the first time it formats a \ncommit and because we call load_branch_decorations() before the first \ncall to get_revisions() we haven't formatted any commits yet. So we \ndon't need to worry about rebase.instructionFormat changing the \ndecorations that get loaded when generating the todo list with \nmake_script_with_merges(). \"rebase --update-refs\" without \"-r\" only \ncalls load_branch_decorations() after we've formatted a commit which is \nwhy it is affected.\n\nThanks\n\nPhillip\n\n> \n> Thanks\n> \n> Phillip\n> \n> ---- 8< ----\n> \n> diff --git b/sequencer.c b/sequencer.c\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -5982,6 +5982,15 @@ static int make_script_with_merges(struct \n> pretty_print_context *pp,\n>               const char *label = label_from_message.buf;\n>               const struct name_decoration *decoration =\n>                   get_name_decoration(&to_merge->item->object);\n> +\n> +            /*\n> +             * If rebase.instructionFormat includes \"%d\"\n> +             * then we to skip non-local decorations as\n> +             * we're only interested in branch names\n> +             */\n> +            while (decoration &&\n> +                   decoration->type != DECORATION_REF_LOCAL)\n> +                decoration = decoration->next;\n> \n>               if (decoration)\n>                   skip_prefix(decoration->name, \"refs/heads/\",\n> \n> \n\n"}]}