From: Phillip Wood <phillip.wood123@gmail.com>
To: Son Luong Ngoc via GitGitGadget <gitgitgadget@gmail.com>,
git@vger.kernel.org
Cc: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
Son Luong Ngoc <sluongng@gmail.com>
Subject: Re: [PATCH v3 1/2] rebase: skip branch symref aliases
Date: Fri, 24 Jul 2026 10:55:18 +0100 [thread overview]
Message-ID: <00e529b6-7ae7-463f-a4b3-0991e9411aba@gmail.com> (raw)
In-Reply-To: <5bece313-6ffb-450b-add1-29652b64de10@gmail.com>
On 23/07/2026 19:58, Phillip Wood wrote:
> On 22/07/2026 09:15, Son Luong Ngoc via GitGitGadget wrote:
>> From: Son Luong Ngoc <sluongng@gmail.com>
>>
>> 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.
>>
>> The problem is a partially applied ref update: the main rebase has
>> already succeeded when the later ref update fails.
>>
>> The sequencer queues updates from local branch decorations. Commit
>> 106b6885c7 (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
>> targets are under refs/heads/ so that only the concrete branch update is
>> queued. 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.
>
> Thanks for re-rolling I'm pretty sure the logic is sound now but I'm a
> bit confused by a couple of things - see my comments below.
>
>> Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
>> ---
>> sequencer.c | 44 +++++++++++++++++++++++++----------
>> t/t3400-rebase.sh | 2 +-
>> t/t3404-rebase-interactive.sh | 16 +++++++++++++
>> 3 files changed, 49 insertions(+), 13 deletions(-)
>>
>> diff --git a/sequencer.c b/sequencer.c
>> index 1355a99a09..63aba60a08 100644
>> --- a/sequencer.c
>> +++ b/sequencer.c
>> @@ -6465,32 +6465,50 @@ static int add_decorations_to_list(const
>> struct commit *commit,
>> struct todo_add_branch_context *ctx)
>> {
>> const struct name_decoration *decoration =
>> get_name_decoration(&commit->object);
>> - const char *head_ref =
>> refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
>> - "HEAD",
>> - RESOLVE_REF_READING,
>> - NULL,
>> - NULL);
>> + struct ref_store *refs = get_main_ref_store(the_repository);
>> + char *head_ref = refs_resolve_refdup(refs, "HEAD",
>> + RESOLVE_REF_READING,
>> + NULL, NULL);
>> while (decoration) {
>> struct todo_item *item;
>> const char *path;
>> + char *resolved_ref;
>> + int flags = 0;
>> 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
>> * includes "%d", which would have loaded all refs
>> * into the global decoration table.
>> */
>> - if ((head_ref && !strcmp(head_ref, decoration->name)) ||
>> - (decoration->type != DECORATION_REF_LOCAL)) {
>> + if (decoration->type != DECORATION_REF_LOCAL) {
>> + decoration = decoration->next;
>> + continue;
>> + }
>
> It would be nice to have a comment here explaining what we're doing.
> Also I don't think we need to copy the refname so it would be more
> efficient to use refs_resolve_ref_unsafe().
Looking at this again we cannot use refs_resolve_ref_unsafe() because
the result would be overwritten by the call to refs_resolve_refdup() in
branch_checked_out().
>> + 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;
>> + }
>
> We skip any symbolic refs that point to another branch which is good.
>
>> + /*
>> + * 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;
>> }
>
> Then we check to see if the decoration matches HEAD which we used to do
> above - I'm not clear why we have moved this check.
Should we be using "resolved_ref" instead of "decoration->name"? That
would explain why this was moved and would makes sense as we resolve
symrefs when reading HEAD. When HEAD points outside "refs/heads/" we'd
then skip updating any symrefs under "refs/heads/" that pointed to the
same ref as HEAD.
Thanks
Phillip
>
>> + path = branch_checked_out(decoration->name);
>> +
>
> This belongs in the next patch I think.
>
>> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
>> index e62e07b894..1a02f6546b 100755
>> --- a/t/t3400-rebase.sh
>> +++ b/t/t3400-rebase.sh
>> @@ -471,7 +471,7 @@ test_expect_success 'git rebase --update-ref with
>> core.commentChar and branch on
>
> Adding an extra context line shows
>
> git checkout topic2> GIT_SEQUENCE_EDITOR="cat >actual" git -c
> core.commentChar=% \
>> rebase -i --update-refs base &&
>> 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
>
> As topic2 is checked out in the worktree where the rebase is running why
> did this line appear before?
>
>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-
>> interactive.sh
>> index e64816770a..11afa8be56 100755
>> --- a/t/t3404-rebase-interactive.sh
>> +++ b/t/t3404-rebase-interactive.sh
>> @@ -1975,15 +1975,23 @@ test_expect_success '--update-refs ignores
>> non-branch decorations' '
>> ) &&
>> grep ^update-ref todo >actual &&
>> test_write_lines "update-ref refs/heads/no-conflict-branch"
>> >expect &&
>> + test_grep ! "^# Ref refs/heads/update-refs checked out" todo &&
>
> Lets move this line below test_cmp so we keep that line next to the ones
> that create the files that are being compared. Is this another case
> where we used to add this comment and no longer do so?
>
>> test_cmp expect actual
>> '
>> 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 branch -f base HEAD~4 &&
>> git branch -f first HEAD~3 &&
>> git branch -f second HEAD~3 &&
>> 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 &&
>> git commit --amend --fixup=L &&
>> @@ -1991,8 +1999,16 @@ test_expect_success '--update-refs updates refs
>> correctly' '
>> test_cmp_rev HEAD~3 refs/heads/first &&
>> 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 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 &&
>
> This looks good - we check that "rebase --update-refs" succeeds withh
> branches that are symrefs and also that those refs are untouched by the
> rebase.
>
> Thanks
>
> Phillip
>
>> q_to_tab >expect <<-\EOF &&
>> Successfully rebased and updated refs/heads/update-refs.
>
next prev parent reply other threads:[~2026-07-24 9:55 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-28 5:41 [PATCH 0/2] rebase: handle --update-refs branch symrefs Son Luong Ngoc via GitGitGadget
2026-05-28 5:42 ` [PATCH 1/2] t3404: add failing branch symref test Son Luong Ngoc via GitGitGadget
2026-06-01 13:52 ` Phillip Wood
2026-05-28 5:42 ` [PATCH 2/2] rebase: skip branch symref aliases Son Luong Ngoc via GitGitGadget
2026-05-28 7:08 ` Kristoffer Haugsbakk
2026-06-01 14:10 ` Phillip Wood
2026-05-28 20:42 ` [PATCH 0/2] rebase: handle --update-refs branch symrefs Junio C Hamano
2026-06-03 10:27 ` [PATCH v2] rebase: skip branch symref aliases Son Luong Ngoc via GitGitGadget
2026-06-04 15:37 ` Phillip Wood
2026-07-22 8:16 ` Son Luong Ngoc
2026-07-22 8:15 ` [PATCH v3 0/2] rebase: handle --update-refs branch symrefs Son Luong Ngoc via GitGitGadget
2026-07-22 8:15 ` [PATCH v3 1/2] rebase: skip branch symref aliases Son Luong Ngoc via GitGitGadget
2026-07-23 18:58 ` Phillip Wood
2026-07-24 9:55 ` Phillip Wood [this message]
2026-07-25 11:21 ` Erik Cervin-Edin
2026-07-26 15:42 ` Junio C Hamano
2026-07-22 8:15 ` [PATCH v3 2/2] rebase: guard non-branch symref targets Son Luong Ngoc via GitGitGadget
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=00e529b6-7ae7-463f-a4b3-0991e9411aba@gmail.com \
--to=phillip.wood123@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitgitgadget@gmail.com \
--cc=kristofferhaugsbakk@fastmail.com \
--cc=phillip.wood@dunelm.org.uk \
--cc=sluongng@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox