Skip to content

Commit a653f56

Browse files
committed
rebase: guard non-branch symref targets
A local branch symbolic ref may point outside refs/heads/. Such an alias cannot be skipped like a branch-to-branch alias because its concrete target ref is absent from the local branch decoration list. However, queuing each alias independently can update the same target ref more than once and make the second compare-and-swap fail. A reservation from another worktree can also name either an alias or its resolved target ref, so checking only one form can miss an in-progress update. Fix these cases by checking both the literal alias and its resolved target ref against checked-out reservations. Deduplicate updates by target ref. Also reserve both forms when loading another worktree's update-refs state. This makes different aliases honor the same in-progress update. This keeps non-branch symrefs supported without allowing duplicate or cross-worktree ref updates. Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
1 parent b9a01e9 commit a653f56

3 files changed

Lines changed: 106 additions & 0 deletions

File tree

branch.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,10 +442,25 @@ static void prepare_checked_out_branches(void)
442442
&update_refs)) {
443443
struct string_list_item *item;
444444
for_each_string_list_item(item, &update_refs) {
445+
char *resolved_ref;
446+
int flags = 0;
447+
445448
old = strmap_put(&current_checked_out_branches,
446449
item->string,
447450
xstrdup(wt->path));
448451
free(old);
452+
453+
resolved_ref = refs_resolve_refdup(
454+
get_main_ref_store(the_repository),
455+
item->string, RESOLVE_REF_READING,
456+
NULL, &flags);
457+
if (resolved_ref && (flags & REF_ISSYMREF)) {
458+
old = strmap_put(
459+
&current_checked_out_branches,
460+
resolved_ref, xstrdup(wt->path));
461+
free(old);
462+
}
463+
free(resolved_ref);
449464
}
450465
string_list_clear(&update_refs, 1);
451466
}

sequencer.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6459,6 +6459,7 @@ struct todo_add_branch_context {
64596459
size_t items_alloc;
64606460
struct strbuf *buf;
64616461
struct string_list refs_to_oids;
6462+
struct string_list symref_update_targets;
64626463
};
64636464

64646465
static int add_decorations_to_list(const struct commit *commit,
@@ -6473,6 +6474,7 @@ static int add_decorations_to_list(const struct commit *commit,
64736474
while (decoration) {
64746475
struct todo_item *item;
64756476
const char *path;
6477+
const char *checked_ref;
64766478
char *resolved_ref;
64776479
int flags = 0;
64786480
size_t base_offset = ctx->buf->len;
@@ -6508,6 +6510,17 @@ static int add_decorations_to_list(const struct commit *commit,
65086510
}
65096511

65106512
path = branch_checked_out(decoration->name);
6513+
if (!path && resolved_ref && (flags & REF_ISSYMREF)) {
6514+
checked_ref = resolved_ref;
6515+
path = branch_checked_out(checked_ref);
6516+
}
6517+
if (!path && resolved_ref && (flags & REF_ISSYMREF) &&
6518+
string_list_has_string(&ctx->symref_update_targets,
6519+
resolved_ref)) {
6520+
free(resolved_ref);
6521+
decoration = decoration->next;
6522+
continue;
6523+
}
65116524

65126525
ALLOC_GROW(ctx->items,
65136526
ctx->items_nr + 1,
@@ -6523,6 +6536,10 @@ static int add_decorations_to_list(const struct commit *commit,
65236536
decoration->name, path);
65246537
} else {
65256538
struct string_list_item *sti;
6539+
6540+
if (resolved_ref && (flags & REF_ISSYMREF))
6541+
string_list_insert(&ctx->symref_update_targets,
6542+
resolved_ref);
65266543
item->command = TODO_UPDATE_REF;
65276544
strbuf_addf(ctx->buf, "%s\n", decoration->name);
65286545

@@ -6554,6 +6571,7 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)
65546571
struct todo_add_branch_context ctx = {
65556572
.buf = &todo_list->buf,
65566573
.refs_to_oids = STRING_LIST_INIT_DUP,
6574+
.symref_update_targets = STRING_LIST_INIT_DUP,
65576575
};
65586576

65596577
ctx.items_alloc = 2 * todo_list->nr + 1;
@@ -6579,6 +6597,7 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)
65796597
res = write_update_refs_state(&ctx.refs_to_oids);
65806598

65816599
string_list_clear(&ctx.refs_to_oids, 1);
6600+
string_list_clear(&ctx.symref_update_targets, 0);
65826601

65836602
if (res) {
65846603
/* we failed, so clean up the new list. */

t/t3404-rebase-interactive.sh

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,6 +2024,78 @@ test_expect_success '--update-refs updates refs correctly' '
20242024
test_cmp expect err.trimmed
20252025
'
20262026

2027+
test_expect_success '--update-refs checks resolved non-branch symref target' '
2028+
test_when_finished "
2029+
git worktree remove --force checked-out-target-wt &&
2030+
git symbolic-ref -d refs/heads/non-branch-alias &&
2031+
git tag -d checked-out-target
2032+
" &&
2033+
git tag checked-out-target HEAD~1 &&
2034+
git symbolic-ref refs/heads/non-branch-alias refs/tags/checked-out-target &&
2035+
git worktree add --detach checked-out-target-wt checked-out-target &&
2036+
git -C checked-out-target-wt symbolic-ref HEAD refs/tags/checked-out-target &&
2037+
2038+
GIT_SEQUENCE_EDITOR="cat >todo" git rebase -i --update-refs HEAD~2 &&
2039+
2040+
test_grep "^# Ref refs/heads/non-branch-alias checked out at" todo &&
2041+
test_write_lines refs/tags/checked-out-target >expect &&
2042+
git symbolic-ref refs/heads/non-branch-alias >actual &&
2043+
test_cmp expect actual
2044+
'
2045+
2046+
test_expect_success '--update-refs deduplicates non-branch symref targets' '
2047+
test_when_finished "
2048+
git symbolic-ref -d refs/heads/non-branch-alias-one &&
2049+
git symbolic-ref -d refs/heads/non-branch-alias-two &&
2050+
git tag -d shared-non-branch-target
2051+
" &&
2052+
git tag shared-non-branch-target HEAD~1 &&
2053+
git symbolic-ref refs/heads/non-branch-alias-one \
2054+
refs/tags/shared-non-branch-target &&
2055+
git symbolic-ref refs/heads/non-branch-alias-two \
2056+
refs/tags/shared-non-branch-target &&
2057+
2058+
GIT_SEQUENCE_EDITOR=: git rebase -i --force-rebase --update-refs HEAD~2 &&
2059+
2060+
test_cmp_rev HEAD~1 refs/heads/non-branch-alias-one &&
2061+
test_cmp_rev HEAD~1 refs/heads/non-branch-alias-two &&
2062+
test_write_lines refs/tags/shared-non-branch-target >expect &&
2063+
git symbolic-ref refs/heads/non-branch-alias-one >actual &&
2064+
test_cmp expect actual &&
2065+
git symbolic-ref refs/heads/non-branch-alias-two >actual &&
2066+
test_cmp expect actual
2067+
'
2068+
2069+
test_expect_success '--update-refs honors non-branch symref reservations' '
2070+
test_when_finished "
2071+
test_might_fail git worktree remove --force reserved-target-wt &&
2072+
test_might_fail git symbolic-ref -d \
2073+
refs/heads/reserved-non-branch-alias-one &&
2074+
test_might_fail git symbolic-ref -d \
2075+
refs/heads/reserved-non-branch-alias-two &&
2076+
test_might_fail git tag -d reserved-non-branch-target
2077+
" &&
2078+
git tag reserved-non-branch-target HEAD~1 &&
2079+
git symbolic-ref refs/heads/reserved-non-branch-alias-one \
2080+
refs/tags/reserved-non-branch-target &&
2081+
git symbolic-ref refs/heads/reserved-non-branch-alias-two \
2082+
refs/tags/reserved-non-branch-target &&
2083+
git worktree add --detach reserved-target-wt HEAD &&
2084+
wt_gitdir=$(git -C reserved-target-wt rev-parse --absolute-git-dir) &&
2085+
mkdir -p "$wt_gitdir/rebase-merge" &&
2086+
old_oid=$(git rev-parse refs/heads/reserved-non-branch-alias-one) &&
2087+
test_write_lines refs/heads/reserved-non-branch-alias-one \
2088+
"$old_oid" "$old_oid" >"$wt_gitdir/rebase-merge/update-refs" &&
2089+
2090+
GIT_SEQUENCE_EDITOR="cat >todo" git rebase -i --update-refs HEAD~2 &&
2091+
2092+
test_grep "^# Ref refs/heads/reserved-non-branch-alias-one checked out at" \
2093+
todo &&
2094+
test_grep "^# Ref refs/heads/reserved-non-branch-alias-two checked out at" \
2095+
todo &&
2096+
test_grep ! "^update-ref refs/heads/reserved-non-branch-alias" todo
2097+
'
2098+
20272099
test_expect_success 'respect user edits to update-ref steps' '
20282100
git checkout -B update-refs-break no-conflict-branch &&
20292101
git branch -f base HEAD~4 &&

0 commit comments

Comments
 (0)