Skip to content

Commit 07e863d

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. Commit 106b688 (rebase: ignore non-branch update-refs) filters out decorations that are not local branches, such as HEAD and tags. A branch symref is different: it is still a local branch decoration, but if it resolves to another branch then that target branch is itself present in the decoration list and will be updated as a concrete branch. Skip branch decorations whose symrefs resolve to refs/heads/*, because those targets are already represented by concrete branch decorations. This prevents aliases from scheduling a second update for the same branch. Resolve remaining local branch decorations before checking whether they are checked out, but only skip the current branch when it is not reported as checked out. This preserves the existing todo-list comment for checked-out branches while still avoiding an update-ref command for the current branch when no checked-out protection is needed. Symrefs to other branches are skipped directly. Other symrefs stay on the existing path, but checked-out protection is applied to the resolved target rather than to the symref name. Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
1 parent f60db8d commit 07e863d

2 files changed

Lines changed: 50 additions & 12 deletions

File tree

sequencer.c

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6460,28 +6460,49 @@ static int add_decorations_to_list(const struct commit *commit,
64606460
struct todo_add_branch_context *ctx)
64616461
{
64626462
const struct name_decoration *decoration = get_name_decoration(&commit->object);
6463-
const char *head_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
6464-
"HEAD",
6465-
RESOLVE_REF_READING,
6466-
NULL,
6467-
NULL);
6463+
struct ref_store *refs = get_main_ref_store(the_repository);
6464+
char *head_ref = refs_resolve_refdup(refs, "HEAD",
6465+
RESOLVE_REF_READING,
6466+
NULL, NULL);
64686467

64696468
while (decoration) {
64706469
struct todo_item *item;
64716470
const char *path;
6471+
const char *checked_ref;
6472+
char *resolved_ref;
6473+
int flags = 0;
64726474
size_t base_offset = ctx->buf->len;
64736475

64746476
/*
6475-
* If the branch is the current HEAD, then it will be
6476-
* updated by the default rebase behavior.
6477-
* Exclude it from the list of refs to update,
6478-
* as well as any non-branch decorations.
64796477
* Non-branch decorations may be present if the pretty format
64806478
* includes "%d", which would have loaded all refs
64816479
* into the global decoration table.
64826480
*/
6483-
if ((head_ref && !strcmp(head_ref, decoration->name)) ||
6484-
(decoration->type != DECORATION_REF_LOCAL)) {
6481+
if (decoration->type != DECORATION_REF_LOCAL) {
6482+
decoration = decoration->next;
6483+
continue;
6484+
}
6485+
6486+
resolved_ref = refs_resolve_refdup(refs, decoration->name,
6487+
RESOLVE_REF_READING,
6488+
NULL, &flags);
6489+
if (resolved_ref && (flags & REF_ISSYMREF) &&
6490+
starts_with(resolved_ref, "refs/heads/")) {
6491+
free(resolved_ref);
6492+
decoration = decoration->next;
6493+
continue;
6494+
}
6495+
6496+
checked_ref = (resolved_ref && (flags & REF_ISSYMREF)) ?
6497+
resolved_ref : decoration->name;
6498+
path = branch_checked_out(checked_ref);
6499+
6500+
/*
6501+
* If the branch is the current HEAD, then it will be
6502+
* updated by the default rebase behavior.
6503+
*/
6504+
if (!path && head_ref && !strcmp(head_ref, decoration->name)) {
6505+
free(resolved_ref);
64856506
decoration = decoration->next;
64866507
continue;
64876508
}
@@ -6493,7 +6514,7 @@ static int add_decorations_to_list(const struct commit *commit,
64936514
memset(item, 0, sizeof(*item));
64946515

64956516
/* If the branch is checked out, then leave a comment instead. */
6496-
if ((path = branch_checked_out(decoration->name))) {
6517+
if (path) {
64976518
item->command = TODO_COMMENT;
64986519
strbuf_commented_addf(ctx->buf, comment_line_str,
64996520
"Ref %s checked out at '%s'\n",
@@ -6513,9 +6534,11 @@ static int add_decorations_to_list(const struct commit *commit,
65136534
item->arg_len = ctx->buf->len - base_offset;
65146535
ctx->items_nr++;
65156536

6537+
free(resolved_ref);
65166538
decoration = decoration->next;
65176539
}
65186540

6541+
free(head_ref);
65196542
return 0;
65206543
}
65216544

t/t3404-rebase-interactive.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,20 +1979,35 @@ test_expect_success '--update-refs ignores non-branch decorations' '
19791979
'
19801980

19811981
test_expect_success '--update-refs updates refs correctly' '
1982+
test_when_finished "
1983+
test_might_fail git symbolic-ref -d refs/heads/no-conflict-branch-alias &&
1984+
test_might_fail git symbolic-ref -d refs/heads/second-alias
1985+
" &&
19821986
git checkout -B update-refs no-conflict-branch &&
19831987
git branch -f base HEAD~4 &&
19841988
git branch -f first HEAD~3 &&
19851989
git branch -f second HEAD~3 &&
19861990
git branch -f third HEAD~1 &&
1991+
git symbolic-ref refs/heads/no-conflict-branch-alias \
1992+
refs/heads/no-conflict-branch &&
1993+
git symbolic-ref refs/heads/second-alias refs/heads/second &&
19871994
test_commit extra2 fileX &&
19881995
git commit --amend --fixup=L &&
19891996
19901997
git rebase -i --autosquash --update-refs primary 2>err &&
19911998
19921999
test_cmp_rev HEAD~3 refs/heads/first &&
19932000
test_cmp_rev HEAD~3 refs/heads/second &&
2001+
test_cmp_rev HEAD~3 refs/heads/second-alias &&
19942002
test_cmp_rev HEAD~1 refs/heads/third &&
19952003
test_cmp_rev HEAD refs/heads/no-conflict-branch &&
2004+
test_cmp_rev HEAD refs/heads/no-conflict-branch-alias &&
2005+
test_write_lines refs/heads/no-conflict-branch >expect &&
2006+
git symbolic-ref refs/heads/no-conflict-branch-alias >actual &&
2007+
test_cmp expect actual &&
2008+
test_write_lines refs/heads/second >expect &&
2009+
git symbolic-ref refs/heads/second-alias >actual &&
2010+
test_cmp expect actual &&
19962011
19972012
q_to_tab >expect <<-\EOF &&
19982013
Successfully rebased and updated refs/heads/update-refs.

0 commit comments

Comments
 (0)