Skip to content

Commit b4e2af3

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 48bbf81 commit b4e2af3

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
@@ -6465,28 +6465,49 @@ static int add_decorations_to_list(const struct commit *commit,
64656465
struct todo_add_branch_context *ctx)
64666466
{
64676467
const struct name_decoration *decoration = get_name_decoration(&commit->object);
6468-
const char *head_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
6469-
"HEAD",
6470-
RESOLVE_REF_READING,
6471-
NULL,
6472-
NULL);
6468+
struct ref_store *refs = get_main_ref_store(the_repository);
6469+
char *head_ref = refs_resolve_refdup(refs, "HEAD",
6470+
RESOLVE_REF_READING,
6471+
NULL, NULL);
64736472

64746473
while (decoration) {
64756474
struct todo_item *item;
64766475
const char *path;
6476+
const char *checked_ref;
6477+
char *resolved_ref;
6478+
int flags = 0;
64776479
size_t base_offset = ctx->buf->len;
64786480

64796481
/*
6480-
* If the branch is the current HEAD, then it will be
6481-
* updated by the default rebase behavior.
6482-
* Exclude it from the list of refs to update,
6483-
* as well as any non-branch decorations.
64846482
* Non-branch decorations may be present if the pretty format
64856483
* includes "%d", which would have loaded all refs
64866484
* into the global decoration table.
64876485
*/
6488-
if ((head_ref && !strcmp(head_ref, decoration->name)) ||
6489-
(decoration->type != DECORATION_REF_LOCAL)) {
6486+
if (decoration->type != DECORATION_REF_LOCAL) {
6487+
decoration = decoration->next;
6488+
continue;
6489+
}
6490+
6491+
resolved_ref = refs_resolve_refdup(refs, decoration->name,
6492+
RESOLVE_REF_READING,
6493+
NULL, &flags);
6494+
if (resolved_ref && (flags & REF_ISSYMREF) &&
6495+
starts_with(resolved_ref, "refs/heads/")) {
6496+
free(resolved_ref);
6497+
decoration = decoration->next;
6498+
continue;
6499+
}
6500+
6501+
checked_ref = (resolved_ref && (flags & REF_ISSYMREF)) ?
6502+
resolved_ref : decoration->name;
6503+
path = branch_checked_out(checked_ref);
6504+
6505+
/*
6506+
* If the branch is the current HEAD, then it will be
6507+
* updated by the default rebase behavior.
6508+
*/
6509+
if (!path && head_ref && !strcmp(head_ref, decoration->name)) {
6510+
free(resolved_ref);
64906511
decoration = decoration->next;
64916512
continue;
64926513
}
@@ -6498,7 +6519,7 @@ static int add_decorations_to_list(const struct commit *commit,
64986519
memset(item, 0, sizeof(*item));
64996520

65006521
/* If the branch is checked out, then leave a comment instead. */
6501-
if ((path = branch_checked_out(decoration->name))) {
6522+
if (path) {
65026523
item->command = TODO_COMMENT;
65036524
strbuf_commented_addf(ctx->buf, comment_line_str,
65046525
"Ref %s checked out at '%s'\n",
@@ -6518,9 +6539,11 @@ static int add_decorations_to_list(const struct commit *commit,
65186539
item->arg_len = ctx->buf->len - base_offset;
65196540
ctx->items_nr++;
65206541

6542+
free(resolved_ref);
65216543
decoration = decoration->next;
65226544
}
65236545

6546+
free(head_ref);
65246547
return 0;
65256548
}
65266549

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)