[PATCH 2/2] rebase: skip branch symref aliases
- From
Son Luong Ngoc via GitGitGadget <gitgitgadget@gmail.com>
- Date
- May 28, 2026, 05:42 UTC
- Message-ID
- <0ab0a717441e9fc7c494da194065a948a35a7f01.1779946921.git.gitgitgadget@gmail.com>
- In-Reply-To
- <pull.2126.git.1779946921.gitgitgadget@gmail.com>
From: Son Luong Ngoc <sluongng@gmail.com>
rebase --update-refs records local branch decorations before replaying commits. If a decoration is a symbolic branch such as refs/heads/main pointing at refs/heads/master, updating it later dereferences back to master and can fail because the normal rebase path already moved that branch.
Resolve local branch symref decorations to their referents before queuing update-ref commands, and skip duplicates. This keeps branch aliases from scheduling a second update for the same underlying branch while still using the existing old-OID check for the single queued update.
Signed-off-by: Son Luong Ngoc <sluongng@gmail.com> --- sequencer.c | 63 +++++++++++++++++++++++++++++------ t/t3404-rebase-interactive.sh | 2 +- 2 files changed, 53 insertions(+), 12 deletions(-)
diff --git a/sequencer.c b/sequencer.c index 1ee4b2875b..4a83d1337c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -6445,15 +6445,22 @@ static int add_decorations_to_list(const struct commit *commit, struct todo_add_branch_context *ctx) { const struct name_decoration *decoration = get_name_decoration(&commit->object); - const char *head_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), - "HEAD", + struct ref_store *refs = get_main_ref_store(the_repository); + const char *head_ref = refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING, - NULL, - NULL); + NULL, NULL); + char *resolved_head_ref = refs_resolve_refdup(refs, "HEAD", + RESOLVE_REF_READING, + NULL, NULL); + struct strbuf update_ref = STRBUF_INIT; while (decoration) { struct todo_item *item; const char *path; + const char *ref = decoration->name; + const char *resolved_ref; + int is_symref = 0; + int flags = 0; size_t base_offset = ctx->buf->len; /* @@ -6461,12 +6468,44 @@ static int add_decorations_to_list(const struct commit *commit, * updated by the default rebase behavior. * Exclude it from the list of refs to update, * as well as any non-branch decorations. + * + * Resolve branch symrefs after checking for the current HEAD so + * that aliases do not schedule duplicate updates for their + * referents. + * * Non-branch decorations may be present if the pretty format * includes "%d", which would have loaded all refs * into the global decoration table. */ - if ((head_ref && !strcmp(head_ref, decoration->name)) || - (decoration->type != DECORATION_REF_LOCAL)) { + if (decoration->type != DECORATION_REF_LOCAL) { + decoration = decoration->next; + continue; + } + + if (head_ref && !strcmp(head_ref, ref)) { + decoration = decoration->next; + continue; + } + + strbuf_reset(&update_ref); + resolved_ref = refs_resolve_ref_unsafe(refs, ref, + RESOLVE_REF_READING | + RESOLVE_REF_NO_RECURSE, + NULL, &flags); + if ((flags & REF_ISSYMREF) && resolved_ref) { + if (!starts_with(resolved_ref, "refs/heads/")) { + decoration = decoration->next; + continue; + } + + strbuf_addstr(&update_ref, resolved_ref); + ref = update_ref.buf; + is_symref = 1; + } + + if ((is_symref && resolved_head_ref && + !strcmp(resolved_head_ref, ref)) || + string_list_has_string(&ctx->refs_to_oids, ref)) { decoration = decoration->next; continue; } @@ -6478,19 +6517,19 @@ static int add_decorations_to_list(const struct commit *commit, memset(item, 0, sizeof(*item)); /* If the branch is checked out, then leave a comment instead. */ - if ((path = branch_checked_out(decoration->name))) { + if ((path = branch_checked_out(ref))) { item->command = TODO_COMMENT; strbuf_commented_addf(ctx->buf, comment_line_str, "Ref %s checked out at '%s'\n", - decoration->name, path); + ref, path); } else { struct string_list_item *sti; item->command = TODO_UPDATE_REF; - strbuf_addf(ctx->buf, "%s\n", decoration->name); + strbuf_addf(ctx->buf, "%s\n", ref); sti = string_list_insert(&ctx->refs_to_oids, - decoration->name); - sti->util = init_update_ref_record(decoration->name); + ref); + sti->util = init_update_ref_record(ref); } item->offset_in_buf = base_offset; @@ -6501,6 +6540,8 @@ static int add_decorations_to_list(const struct commit *commit, decoration = decoration->next; } + strbuf_release(&update_ref); + free(resolved_head_ref); return 0; } diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 42ba8cc313..29447c0fc3 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1978,7 +1978,7 @@ test_expect_success '--update-refs ignores non-branch decorations' ' test_cmp expect actual ' -test_expect_failure '--update-refs skips branch symrefs to current branch' ' +test_expect_success '--update-refs skips branch symrefs to current branch' ' test_when_finished " test_might_fail git rebase --abort && git checkout primary &&
-- gitgitgadget