Skip to content

Commit 2b0dfbf

Browse files
committed
rebase: skip branch symref aliases
git rebase --update-refs can finish rewriting the current branch and then fail while updating a local branch that is a symbolic ref. This can happen during a default-branch rename where refs/heads/main points at refs/heads/master while users migrate. Fix this because the post-rebase failure leaves refs partially updated even though the main rebase has succeeded. The sequencer queues updates from local branch decorations. Commit 106b688 (rebase: ignore non-branch update-refs) filters out decorations such as HEAD and tags. A branch symref is still a local branch decoration, but refs_update_ref() dereferences it, so an alias to another branch duplicates the concrete branch update. Resolve local branch decorations before queuing them. Skip symrefs whose referents are under refs/heads/. Keep an owned copy of the resolved HEAD and skip the current branch before checked-out handling so later ref resolution cannot change the comparison. This prevents a successful rebase from being followed by a failed, partially applied ref update while preserving each alias as a symref. Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
1 parent 48bbf81 commit 2b0dfbf

3 files changed

Lines changed: 49 additions & 13 deletions

File tree

sequencer.c

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6465,40 +6465,58 @@ 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+
char *resolved_ref;
6477+
int flags = 0;
64776478
size_t base_offset = ctx->buf->len;
64786479

64796480
/*
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.
64846481
* Non-branch decorations may be present if the pretty format
64856482
* includes "%d", which would have loaded all refs
64866483
* into the global decoration table.
64876484
*/
6488-
if ((head_ref && !strcmp(head_ref, decoration->name)) ||
6489-
(decoration->type != DECORATION_REF_LOCAL)) {
6485+
if (decoration->type != DECORATION_REF_LOCAL) {
6486+
decoration = decoration->next;
6487+
continue;
6488+
}
6489+
6490+
resolved_ref = refs_resolve_refdup(refs, decoration->name,
6491+
RESOLVE_REF_READING,
6492+
NULL, &flags);
6493+
if (resolved_ref && (flags & REF_ISSYMREF) &&
6494+
starts_with(resolved_ref, "refs/heads/")) {
6495+
free(resolved_ref);
6496+
decoration = decoration->next;
6497+
continue;
6498+
}
6499+
6500+
/*
6501+
* If the branch is the current HEAD, then it will be
6502+
* updated by the default rebase behavior.
6503+
*/
6504+
if (head_ref && !strcmp(head_ref, decoration->name)) {
6505+
free(resolved_ref);
64906506
decoration = decoration->next;
64916507
continue;
64926508
}
64936509

6510+
path = branch_checked_out(decoration->name);
6511+
64946512
ALLOC_GROW(ctx->items,
64956513
ctx->items_nr + 1,
64966514
ctx->items_alloc);
64976515
item = &ctx->items[ctx->items_nr];
64986516
memset(item, 0, sizeof(*item));
64996517

65006518
/* If the branch is checked out, then leave a comment instead. */
6501-
if ((path = branch_checked_out(decoration->name))) {
6519+
if (path) {
65026520
item->command = TODO_COMMENT;
65036521
strbuf_commented_addf(ctx->buf, comment_line_str,
65046522
"Ref %s checked out at '%s'\n",
@@ -6518,9 +6536,11 @@ static int add_decorations_to_list(const struct commit *commit,
65186536
item->arg_len = ctx->buf->len - base_offset;
65196537
ctx->items_nr++;
65206538

6539+
free(resolved_ref);
65216540
decoration = decoration->next;
65226541
}
65236542

6543+
free(head_ref);
65246544
return 0;
65256545
}
65266546

t/t3400-rebase.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ test_expect_success 'git rebase --update-ref with core.commentChar and branch on
471471
GIT_SEQUENCE_EDITOR="cat >actual" git -c core.commentChar=% \
472472
rebase -i --update-refs base &&
473473
test_grep "% Ref refs/heads/wt-topic checked out at" actual &&
474-
test_grep "% Ref refs/heads/topic2 checked out at" actual
474+
test_grep ! "% Ref refs/heads/topic2 checked out at" actual
475475
'
476476

477477
test_done

t/t3404-rebase-interactive.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,24 +1975,40 @@ test_expect_success '--update-refs ignores non-branch decorations' '
19751975
) &&
19761976
grep ^update-ref todo >actual &&
19771977
test_write_lines "update-ref refs/heads/no-conflict-branch" >expect &&
1978+
test_grep ! "^# Ref refs/heads/update-refs checked out" todo &&
19781979
test_cmp expect actual
19791980
'
19801981

19811982
test_expect_success '--update-refs updates refs correctly' '
1983+
test_when_finished "
1984+
test_might_fail git symbolic-ref -d refs/heads/no-conflict-branch-alias &&
1985+
test_might_fail git symbolic-ref -d refs/heads/second-alias
1986+
" &&
19821987
git checkout -B update-refs no-conflict-branch &&
19831988
git branch -f base HEAD~4 &&
19841989
git branch -f first HEAD~3 &&
19851990
git branch -f second HEAD~3 &&
19861991
git branch -f third HEAD~1 &&
1992+
git symbolic-ref refs/heads/no-conflict-branch-alias \
1993+
refs/heads/no-conflict-branch &&
1994+
git symbolic-ref refs/heads/second-alias refs/heads/second &&
19871995
test_commit extra2 fileX &&
19881996
git commit --amend --fixup=L &&
19891997
19901998
git rebase -i --autosquash --update-refs primary 2>err &&
19911999
19922000
test_cmp_rev HEAD~3 refs/heads/first &&
19932001
test_cmp_rev HEAD~3 refs/heads/second &&
2002+
test_cmp_rev HEAD~3 refs/heads/second-alias &&
19942003
test_cmp_rev HEAD~1 refs/heads/third &&
19952004
test_cmp_rev HEAD refs/heads/no-conflict-branch &&
2005+
test_cmp_rev HEAD refs/heads/no-conflict-branch-alias &&
2006+
test_write_lines refs/heads/no-conflict-branch >expect &&
2007+
git symbolic-ref refs/heads/no-conflict-branch-alias >actual &&
2008+
test_cmp expect actual &&
2009+
test_write_lines refs/heads/second >expect &&
2010+
git symbolic-ref refs/heads/second-alias >actual &&
2011+
test_cmp expect actual &&
19962012
19972013
q_to_tab >expect <<-\EOF &&
19982014
Successfully rebased and updated refs/heads/update-refs.

0 commit comments

Comments
 (0)