Merge branch 'sn/rebase-update-refs-symrefs' into seen
'git rebase --update-refs' has been taught to resolve local branch symrefs to their referents before queuing updates, ensuring aliases of the current branch are skipped and duplicate updates are avoided to prevent failures when branch aliases are present. * sn/rebase-update-refs-symrefs: rebase: guard non-branch symref targets rebase: skip branch symref aliasesseen
commit
00e21f202c
15
branch.c
15
branch.c
|
|
@ -466,9 +466,24 @@ static void prepare_checked_out_branches(void)
|
||||||
&update_refs)) {
|
&update_refs)) {
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
for_each_string_list_item(item, &update_refs) {
|
for_each_string_list_item(item, &update_refs) {
|
||||||
|
char *resolved_ref;
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
register_checked_out_branch("", item->string,
|
register_checked_out_branch("", item->string,
|
||||||
wt->path,
|
wt->path,
|
||||||
BRANCH_CHECKOUT_KIND_UPDATE_REF);
|
BRANCH_CHECKOUT_KIND_UPDATE_REF);
|
||||||
|
|
||||||
|
resolved_ref = refs_resolve_refdup(
|
||||||
|
get_main_ref_store(the_repository),
|
||||||
|
item->string, RESOLVE_REF_READING,
|
||||||
|
NULL, &flags);
|
||||||
|
if (resolved_ref && (flags & REF_ISSYMREF)) {
|
||||||
|
char *old = strmap_put(
|
||||||
|
¤t_checked_out_branches,
|
||||||
|
resolved_ref, xstrdup(wt->path));
|
||||||
|
free(old);
|
||||||
|
}
|
||||||
|
free(resolved_ref);
|
||||||
}
|
}
|
||||||
string_list_clear(&update_refs, 1);
|
string_list_clear(&update_refs, 1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
63
sequencer.c
63
sequencer.c
|
|
@ -6548,34 +6548,65 @@ struct todo_add_branch_context {
|
||||||
size_t items_alloc;
|
size_t items_alloc;
|
||||||
struct strbuf *buf;
|
struct strbuf *buf;
|
||||||
struct string_list refs_to_oids;
|
struct string_list refs_to_oids;
|
||||||
|
struct string_list symref_update_targets;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int add_decorations_to_list(const struct commit *commit,
|
static int add_decorations_to_list(const struct commit *commit,
|
||||||
struct todo_add_branch_context *ctx)
|
struct todo_add_branch_context *ctx)
|
||||||
{
|
{
|
||||||
const struct name_decoration *decoration = get_name_decoration(&commit->object);
|
const struct name_decoration *decoration = get_name_decoration(&commit->object);
|
||||||
const char *head_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
|
struct ref_store *refs = get_main_ref_store(the_repository);
|
||||||
"HEAD",
|
char *head_ref = refs_resolve_refdup(refs, "HEAD",
|
||||||
RESOLVE_REF_READING,
|
RESOLVE_REF_READING,
|
||||||
NULL,
|
NULL, NULL);
|
||||||
NULL);
|
|
||||||
|
|
||||||
while (decoration) {
|
while (decoration) {
|
||||||
struct todo_item *item;
|
struct todo_item *item;
|
||||||
const char *path;
|
const char *path;
|
||||||
|
const char *checked_ref;
|
||||||
|
char *resolved_ref;
|
||||||
|
int flags = 0;
|
||||||
size_t base_offset = ctx->buf->len;
|
size_t base_offset = ctx->buf->len;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the branch is the current HEAD, then it will be
|
|
||||||
* updated by the default rebase behavior.
|
|
||||||
* Exclude it from the list of refs to update,
|
|
||||||
* as well as any non-branch decorations.
|
|
||||||
* Non-branch decorations may be present if the pretty format
|
* Non-branch decorations may be present if the pretty format
|
||||||
* includes "%d", which would have loaded all refs
|
* includes "%d", which would have loaded all refs
|
||||||
* into the global decoration table.
|
* into the global decoration table.
|
||||||
*/
|
*/
|
||||||
if ((head_ref && !strcmp(head_ref, decoration->name)) ||
|
if (decoration->type != DECORATION_REF_LOCAL) {
|
||||||
(decoration->type != DECORATION_REF_LOCAL)) {
|
decoration = decoration->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved_ref = refs_resolve_refdup(refs, decoration->name,
|
||||||
|
RESOLVE_REF_READING,
|
||||||
|
NULL, &flags);
|
||||||
|
if (resolved_ref && (flags & REF_ISSYMREF) &&
|
||||||
|
starts_with(resolved_ref, "refs/heads/")) {
|
||||||
|
free(resolved_ref);
|
||||||
|
decoration = decoration->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the branch is the current HEAD, then it will be
|
||||||
|
* updated by the default rebase behavior.
|
||||||
|
*/
|
||||||
|
if (head_ref && !strcmp(head_ref, decoration->name)) {
|
||||||
|
free(resolved_ref);
|
||||||
|
decoration = decoration->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
path = branch_checked_out(decoration->name);
|
||||||
|
if (!path && resolved_ref && (flags & REF_ISSYMREF)) {
|
||||||
|
checked_ref = resolved_ref;
|
||||||
|
path = branch_checked_out(checked_ref);
|
||||||
|
}
|
||||||
|
if (!path && resolved_ref && (flags & REF_ISSYMREF) &&
|
||||||
|
string_list_has_string(&ctx->symref_update_targets,
|
||||||
|
resolved_ref)) {
|
||||||
|
free(resolved_ref);
|
||||||
decoration = decoration->next;
|
decoration = decoration->next;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -6587,13 +6618,17 @@ static int add_decorations_to_list(const struct commit *commit,
|
||||||
memset(item, 0, sizeof(*item));
|
memset(item, 0, sizeof(*item));
|
||||||
|
|
||||||
/* If the branch is checked out, then leave a comment instead. */
|
/* If the branch is checked out, then leave a comment instead. */
|
||||||
if ((path = branch_checked_out(decoration->name))) {
|
if (path) {
|
||||||
item->command = TODO_COMMENT;
|
item->command = TODO_COMMENT;
|
||||||
strbuf_commented_addf(ctx->buf, comment_line_str,
|
strbuf_commented_addf(ctx->buf, comment_line_str,
|
||||||
"Ref %s checked out at '%s'\n",
|
"Ref %s checked out at '%s'\n",
|
||||||
decoration->name, path);
|
decoration->name, path);
|
||||||
} else {
|
} else {
|
||||||
struct string_list_item *sti;
|
struct string_list_item *sti;
|
||||||
|
|
||||||
|
if (resolved_ref && (flags & REF_ISSYMREF))
|
||||||
|
string_list_insert(&ctx->symref_update_targets,
|
||||||
|
resolved_ref);
|
||||||
item->command = TODO_UPDATE_REF;
|
item->command = TODO_UPDATE_REF;
|
||||||
strbuf_addf(ctx->buf, "%s\n", decoration->name);
|
strbuf_addf(ctx->buf, "%s\n", decoration->name);
|
||||||
|
|
||||||
|
|
@ -6607,9 +6642,11 @@ static int add_decorations_to_list(const struct commit *commit,
|
||||||
item->arg_len = ctx->buf->len - base_offset;
|
item->arg_len = ctx->buf->len - base_offset;
|
||||||
ctx->items_nr++;
|
ctx->items_nr++;
|
||||||
|
|
||||||
|
free(resolved_ref);
|
||||||
decoration = decoration->next;
|
decoration = decoration->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(head_ref);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6623,6 +6660,7 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)
|
||||||
struct todo_add_branch_context ctx = {
|
struct todo_add_branch_context ctx = {
|
||||||
.buf = &todo_list->buf,
|
.buf = &todo_list->buf,
|
||||||
.refs_to_oids = STRING_LIST_INIT_DUP,
|
.refs_to_oids = STRING_LIST_INIT_DUP,
|
||||||
|
.symref_update_targets = STRING_LIST_INIT_DUP,
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.items_alloc = 2 * todo_list->nr + 1;
|
ctx.items_alloc = 2 * todo_list->nr + 1;
|
||||||
|
|
@ -6648,6 +6686,7 @@ static int todo_list_add_update_ref_commands(struct todo_list *todo_list)
|
||||||
res = write_update_refs_state(&ctx.refs_to_oids);
|
res = write_update_refs_state(&ctx.refs_to_oids);
|
||||||
|
|
||||||
string_list_clear(&ctx.refs_to_oids, 1);
|
string_list_clear(&ctx.refs_to_oids, 1);
|
||||||
|
string_list_clear(&ctx.symref_update_targets, 0);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
/* we failed, so clean up the new list. */
|
/* we failed, so clean up the new list. */
|
||||||
|
|
|
||||||
|
|
@ -483,7 +483,7 @@ test_expect_success 'git rebase --update-ref with core.commentChar and branch on
|
||||||
GIT_SEQUENCE_EDITOR="cat >actual" git -c core.commentChar=% \
|
GIT_SEQUENCE_EDITOR="cat >actual" git -c core.commentChar=% \
|
||||||
rebase -i --update-refs base &&
|
rebase -i --update-refs base &&
|
||||||
test_grep "% Ref refs/heads/wt-topic checked out at" actual &&
|
test_grep "% Ref refs/heads/wt-topic checked out at" actual &&
|
||||||
test_grep "% Ref refs/heads/topic2 checked out at" actual
|
test_grep ! "% Ref refs/heads/topic2 checked out at" actual
|
||||||
'
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
|
||||||
|
|
@ -2124,15 +2124,23 @@ test_expect_success '--update-refs ignores non-branch decorations' '
|
||||||
) &&
|
) &&
|
||||||
grep ^update-ref todo >actual &&
|
grep ^update-ref todo >actual &&
|
||||||
test_write_lines "update-ref refs/heads/no-conflict-branch" >expect &&
|
test_write_lines "update-ref refs/heads/no-conflict-branch" >expect &&
|
||||||
|
test_grep ! "^# Ref refs/heads/update-refs checked out" todo &&
|
||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success '--update-refs updates refs correctly' '
|
test_expect_success '--update-refs updates refs correctly' '
|
||||||
|
test_when_finished "
|
||||||
|
test_might_fail git symbolic-ref -d refs/heads/no-conflict-branch-alias &&
|
||||||
|
test_might_fail git symbolic-ref -d refs/heads/second-alias
|
||||||
|
" &&
|
||||||
git checkout -B update-refs no-conflict-branch &&
|
git checkout -B update-refs no-conflict-branch &&
|
||||||
git branch -f base HEAD~4 &&
|
git branch -f base HEAD~4 &&
|
||||||
git branch -f first HEAD~3 &&
|
git branch -f first HEAD~3 &&
|
||||||
git branch -f second HEAD~3 &&
|
git branch -f second HEAD~3 &&
|
||||||
git branch -f third HEAD~1 &&
|
git branch -f third HEAD~1 &&
|
||||||
|
git symbolic-ref refs/heads/no-conflict-branch-alias \
|
||||||
|
refs/heads/no-conflict-branch &&
|
||||||
|
git symbolic-ref refs/heads/second-alias refs/heads/second &&
|
||||||
test_commit extra2 fileX &&
|
test_commit extra2 fileX &&
|
||||||
git commit --amend --fixup=L &&
|
git commit --amend --fixup=L &&
|
||||||
|
|
||||||
|
|
@ -2140,8 +2148,16 @@ test_expect_success '--update-refs updates refs correctly' '
|
||||||
|
|
||||||
test_cmp_rev HEAD~3 refs/heads/first &&
|
test_cmp_rev HEAD~3 refs/heads/first &&
|
||||||
test_cmp_rev HEAD~3 refs/heads/second &&
|
test_cmp_rev HEAD~3 refs/heads/second &&
|
||||||
|
test_cmp_rev HEAD~3 refs/heads/second-alias &&
|
||||||
test_cmp_rev HEAD~1 refs/heads/third &&
|
test_cmp_rev HEAD~1 refs/heads/third &&
|
||||||
test_cmp_rev HEAD refs/heads/no-conflict-branch &&
|
test_cmp_rev HEAD refs/heads/no-conflict-branch &&
|
||||||
|
test_cmp_rev HEAD refs/heads/no-conflict-branch-alias &&
|
||||||
|
test_write_lines refs/heads/no-conflict-branch >expect &&
|
||||||
|
git symbolic-ref refs/heads/no-conflict-branch-alias >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_write_lines refs/heads/second >expect &&
|
||||||
|
git symbolic-ref refs/heads/second-alias >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
|
||||||
q_to_tab >expect <<-\EOF &&
|
q_to_tab >expect <<-\EOF &&
|
||||||
Successfully rebased and updated refs/heads/update-refs.
|
Successfully rebased and updated refs/heads/update-refs.
|
||||||
|
|
@ -2157,6 +2173,78 @@ test_expect_success '--update-refs updates refs correctly' '
|
||||||
test_cmp expect err.trimmed
|
test_cmp expect err.trimmed
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success '--update-refs checks resolved non-branch symref target' '
|
||||||
|
test_when_finished "
|
||||||
|
git worktree remove --force checked-out-target-wt &&
|
||||||
|
git symbolic-ref -d refs/heads/non-branch-alias &&
|
||||||
|
git tag -d checked-out-target
|
||||||
|
" &&
|
||||||
|
git tag checked-out-target HEAD~1 &&
|
||||||
|
git symbolic-ref refs/heads/non-branch-alias refs/tags/checked-out-target &&
|
||||||
|
git worktree add --detach checked-out-target-wt checked-out-target &&
|
||||||
|
git -C checked-out-target-wt symbolic-ref HEAD refs/tags/checked-out-target &&
|
||||||
|
|
||||||
|
GIT_SEQUENCE_EDITOR="cat >todo" git rebase -i --update-refs HEAD~2 &&
|
||||||
|
|
||||||
|
test_grep "^# Ref refs/heads/non-branch-alias checked out at" todo &&
|
||||||
|
test_write_lines refs/tags/checked-out-target >expect &&
|
||||||
|
git symbolic-ref refs/heads/non-branch-alias >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--update-refs deduplicates non-branch symref targets' '
|
||||||
|
test_when_finished "
|
||||||
|
git symbolic-ref -d refs/heads/non-branch-alias-one &&
|
||||||
|
git symbolic-ref -d refs/heads/non-branch-alias-two &&
|
||||||
|
git tag -d shared-non-branch-target
|
||||||
|
" &&
|
||||||
|
git tag shared-non-branch-target HEAD~1 &&
|
||||||
|
git symbolic-ref refs/heads/non-branch-alias-one \
|
||||||
|
refs/tags/shared-non-branch-target &&
|
||||||
|
git symbolic-ref refs/heads/non-branch-alias-two \
|
||||||
|
refs/tags/shared-non-branch-target &&
|
||||||
|
|
||||||
|
GIT_SEQUENCE_EDITOR=: git rebase -i --force-rebase --update-refs HEAD~2 &&
|
||||||
|
|
||||||
|
test_cmp_rev HEAD~1 refs/heads/non-branch-alias-one &&
|
||||||
|
test_cmp_rev HEAD~1 refs/heads/non-branch-alias-two &&
|
||||||
|
test_write_lines refs/tags/shared-non-branch-target >expect &&
|
||||||
|
git symbolic-ref refs/heads/non-branch-alias-one >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git symbolic-ref refs/heads/non-branch-alias-two >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--update-refs honors non-branch symref reservations' '
|
||||||
|
test_when_finished "
|
||||||
|
test_might_fail git worktree remove --force reserved-target-wt &&
|
||||||
|
test_might_fail git symbolic-ref -d \
|
||||||
|
refs/heads/reserved-non-branch-alias-one &&
|
||||||
|
test_might_fail git symbolic-ref -d \
|
||||||
|
refs/heads/reserved-non-branch-alias-two &&
|
||||||
|
test_might_fail git tag -d reserved-non-branch-target
|
||||||
|
" &&
|
||||||
|
git tag reserved-non-branch-target HEAD~1 &&
|
||||||
|
git symbolic-ref refs/heads/reserved-non-branch-alias-one \
|
||||||
|
refs/tags/reserved-non-branch-target &&
|
||||||
|
git symbolic-ref refs/heads/reserved-non-branch-alias-two \
|
||||||
|
refs/tags/reserved-non-branch-target &&
|
||||||
|
git worktree add --detach reserved-target-wt HEAD &&
|
||||||
|
wt_gitdir=$(git -C reserved-target-wt rev-parse --absolute-git-dir) &&
|
||||||
|
mkdir -p "$wt_gitdir/rebase-merge" &&
|
||||||
|
old_oid=$(git rev-parse refs/heads/reserved-non-branch-alias-one) &&
|
||||||
|
test_write_lines refs/heads/reserved-non-branch-alias-one \
|
||||||
|
"$old_oid" "$old_oid" >"$wt_gitdir/rebase-merge/update-refs" &&
|
||||||
|
|
||||||
|
GIT_SEQUENCE_EDITOR="cat >todo" git rebase -i --update-refs HEAD~2 &&
|
||||||
|
|
||||||
|
test_grep "^# Ref refs/heads/reserved-non-branch-alias-one checked out at" \
|
||||||
|
todo &&
|
||||||
|
test_grep "^# Ref refs/heads/reserved-non-branch-alias-two checked out at" \
|
||||||
|
todo &&
|
||||||
|
test_grep ! "^update-ref refs/heads/reserved-non-branch-alias" todo
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'respect user edits to update-ref steps' '
|
test_expect_success 'respect user edits to update-ref steps' '
|
||||||
git checkout -B update-refs-break no-conflict-branch &&
|
git checkout -B update-refs-break no-conflict-branch &&
|
||||||
git branch -f base HEAD~4 &&
|
git branch -f base HEAD~4 &&
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue