Skip to content

Commit c1e8828

Browse files
committed
rebase: skip branch symref aliases
git rebase --update-refs can fail after the normal rebase path has updated the current branch when another local branch is a symref to it. This can happen during a default-branch rename where refs/heads/main points at refs/heads/master while users migrate. The sequencer queues update-ref commands from local branch decorations. It already filters out decorations that are not local branches, but a branch symref is still a local branch decoration. Queuing the alias by its literal name makes the final update dereference it back to the current branch and fail the old-OID check because that branch moved earlier in the rebase. Resolve local branch symrefs to their referents before queuing update-ref commands. Skip aliases of HEAD and duplicate referents, leaving one old-OID-checked update for each concrete branch. Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
1 parent c69baaf commit c1e8828

2 files changed

Lines changed: 77 additions & 11 deletions

File tree

sequencer.c

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6445,28 +6445,67 @@ static int add_decorations_to_list(const struct commit *commit,
64456445
struct todo_add_branch_context *ctx)
64466446
{
64476447
const struct name_decoration *decoration = get_name_decoration(&commit->object);
6448-
const char *head_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
6449-
"HEAD",
6448+
struct ref_store *refs = get_main_ref_store(the_repository);
6449+
const char *head_ref = refs_resolve_ref_unsafe(refs, "HEAD",
64506450
RESOLVE_REF_READING,
6451-
NULL,
6452-
NULL);
6451+
NULL, NULL);
6452+
char *resolved_head_ref = refs_resolve_refdup(refs, "HEAD",
6453+
RESOLVE_REF_READING,
6454+
NULL, NULL);
6455+
struct strbuf update_ref = STRBUF_INIT;
64536456

64546457
while (decoration) {
64556458
struct todo_item *item;
64566459
const char *path;
6460+
const char *ref = decoration->name;
6461+
const char *resolved_ref;
6462+
int is_symref = 0;
6463+
int flags = 0;
64576464
size_t base_offset = ctx->buf->len;
64586465

64596466
/*
64606467
* If the branch is the current HEAD, then it will be
64616468
* updated by the default rebase behavior.
64626469
* Exclude it from the list of refs to update,
64636470
* as well as any non-branch decorations.
6471+
*
6472+
* Resolve branch symrefs after checking for the current HEAD so
6473+
* that aliases do not schedule duplicate updates for their
6474+
* referents.
6475+
*
64646476
* Non-branch decorations may be present if the pretty format
64656477
* includes "%d", which would have loaded all refs
64666478
* into the global decoration table.
64676479
*/
6468-
if ((head_ref && !strcmp(head_ref, decoration->name)) ||
6469-
(decoration->type != DECORATION_REF_LOCAL)) {
6480+
if (decoration->type != DECORATION_REF_LOCAL) {
6481+
decoration = decoration->next;
6482+
continue;
6483+
}
6484+
6485+
if (head_ref && !strcmp(head_ref, ref)) {
6486+
decoration = decoration->next;
6487+
continue;
6488+
}
6489+
6490+
strbuf_reset(&update_ref);
6491+
resolved_ref = refs_resolve_ref_unsafe(refs, ref,
6492+
RESOLVE_REF_READING |
6493+
RESOLVE_REF_NO_RECURSE,
6494+
NULL, &flags);
6495+
if ((flags & REF_ISSYMREF) && resolved_ref) {
6496+
if (!starts_with(resolved_ref, "refs/heads/")) {
6497+
decoration = decoration->next;
6498+
continue;
6499+
}
6500+
6501+
strbuf_addstr(&update_ref, resolved_ref);
6502+
ref = update_ref.buf;
6503+
is_symref = 1;
6504+
}
6505+
6506+
if ((is_symref && resolved_head_ref &&
6507+
!strcmp(resolved_head_ref, ref)) ||
6508+
string_list_has_string(&ctx->refs_to_oids, ref)) {
64706509
decoration = decoration->next;
64716510
continue;
64726511
}
@@ -6478,19 +6517,19 @@ static int add_decorations_to_list(const struct commit *commit,
64786517
memset(item, 0, sizeof(*item));
64796518

64806519
/* If the branch is checked out, then leave a comment instead. */
6481-
if ((path = branch_checked_out(decoration->name))) {
6520+
if ((path = branch_checked_out(ref))) {
64826521
item->command = TODO_COMMENT;
64836522
strbuf_commented_addf(ctx->buf, comment_line_str,
64846523
"Ref %s checked out at '%s'\n",
6485-
decoration->name, path);
6524+
ref, path);
64866525
} else {
64876526
struct string_list_item *sti;
64886527
item->command = TODO_UPDATE_REF;
6489-
strbuf_addf(ctx->buf, "%s\n", decoration->name);
6528+
strbuf_addf(ctx->buf, "%s\n", ref);
64906529

64916530
sti = string_list_insert(&ctx->refs_to_oids,
6492-
decoration->name);
6493-
sti->util = init_update_ref_record(decoration->name);
6531+
ref);
6532+
sti->util = init_update_ref_record(ref);
64946533
}
64956534

64966535
item->offset_in_buf = base_offset;
@@ -6501,6 +6540,8 @@ static int add_decorations_to_list(const struct commit *commit,
65016540
decoration = decoration->next;
65026541
}
65036542

6543+
strbuf_release(&update_ref);
6544+
free(resolved_head_ref);
65046545
return 0;
65056546
}
65066547

t/t3404-rebase-interactive.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,6 +1978,31 @@ test_expect_success '--update-refs ignores non-branch decorations' '
19781978
test_cmp expect actual
19791979
'
19801980

1981+
test_expect_success '--update-refs skips branch symrefs to current branch' '
1982+
test_when_finished "
1983+
test_might_fail git rebase --abort &&
1984+
git checkout primary &&
1985+
test_might_fail git symbolic-ref -d refs/heads/update-refs-symref-alias &&
1986+
test_might_fail git branch -D update-refs-symref update-refs-symref-base
1987+
" &&
1988+
git checkout -B update-refs-symref-base primary &&
1989+
test_commit --no-tag update-refs-symref-base symref-base.t &&
1990+
git checkout -B update-refs-symref &&
1991+
test_commit --no-tag update-refs-symref-topic symref-topic.t &&
1992+
git checkout update-refs-symref-base &&
1993+
test_commit --no-tag update-refs-symref-newbase symref-newbase.t &&
1994+
git checkout update-refs-symref &&
1995+
git symbolic-ref refs/heads/update-refs-symref-alias refs/heads/update-refs-symref &&
1996+
1997+
git rebase --update-refs update-refs-symref-base 2>err &&
1998+
1999+
test_cmp_rev update-refs-symref-base update-refs-symref^ &&
2000+
test_cmp_rev refs/heads/update-refs-symref refs/heads/update-refs-symref-alias &&
2001+
test_write_lines refs/heads/update-refs-symref >expect &&
2002+
git symbolic-ref refs/heads/update-refs-symref-alias >actual &&
2003+
test_cmp expect actual
2004+
'
2005+
19812006
test_expect_success '--update-refs updates refs correctly' '
19822007
git checkout -B update-refs no-conflict-branch &&
19832008
git branch -f base HEAD~4 &&

0 commit comments

Comments
 (0)