Skip to content

rebase: handle --update-refs branch symrefs#2126

Open
sluongng wants to merge 2 commits into
gitgitgadget:masterfrom
sluongng:sl/rebase-update-refs-symrefs
Open

rebase: handle --update-refs branch symrefs#2126
sluongng wants to merge 2 commits into
gitgitgadget:masterfrom
sluongng:sl/rebase-update-refs-symrefs

Conversation

@sluongng
Copy link
Copy Markdown

@sluongng sluongng commented May 27, 2026

git rebase --update-refs can fail after the normal rebase path has
successfully updated the current branch when another local branch is a
symbolic ref to it.

One practical way to arrive at that setup is a default branch rename from
master to main. While the migration is in progress, a user may keep
refs/heads/main as a symbolic ref to refs/heads/master so that both names
continue to work locally.

If pull.rebase is enabled, a plain git pull can then finish the rebase of
master and still fail while trying to update the main alias. The reported
failure looked like this, with line breaks adjusted for the cover letter:

Successfully rebased and updated refs/heads/master.
error: update_ref failed for ref 'refs/heads/main':
cannot lock ref 'refs/heads/main':
is at fc2c7bd5f17abec7861ef759edcd33a1e16662a1
but expected 531cabdfb49098d6ffa502ed4bf91d1b35edfcfa
Updated the following refs with --update-refs:
Failed to update the following refs with --update-refs:
        refs/heads/main

The sequencer builds its update-ref todo commands from local branch
decorations. It excludes the current branch by comparing the literal
decoration name with the resolved HEAD ref, so refs/heads/main is not
recognized as the current branch alias. The final update then dereferences
the alias back to master, but master has already moved, so the old-OID check
fails.

This series keeps that failure mode explicit by adding a known-breakage test
first, so that the behavioral expectation is clear before the fix. The fix
then resolves local branch symref decorations before queuing update-ref
commands, skips aliases of the current branch, and skips duplicate
referents. That keeps the existing old-OID protection on the single real
branch update. It also avoids teaching the final ref update step to treat
this race-looking mismatch as success.

Patch overview:

  • Patch 1 records the branch-symref failure in t3404.
  • Patch 2 flips the test and canonicalizes sequencer behavior.

cc: "Kristoffer Haugsbakk" kristofferhaugsbakk@fastmail.com

sluongng added 2 commits May 27, 2026 15:22
rebase --update-refs queues local branch decorations by their literal
refnames. When a branch such as refs/heads/main is a symbolic ref to
the current branch, the normal rebase path first updates the current
branch and the queued symref update later tries to update the same
referent with the old value it recorded before the rebase.

Add a known-breakage test that exercises this case so that the fix can
flip it to test_expect_success. The expected behavior is that the branch
symref keeps pointing at the rebased current branch.

Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
rebase --update-refs records local branch decorations before replaying
commits. If a decoration is a symbolic branch such as refs/heads/main
pointing at refs/heads/master, updating it later dereferences back to
master and can fail because the normal rebase path already moved that
branch.

Resolve local branch symref decorations to their referents before
queuing update-ref commands, and skip duplicates. This keeps branch
aliases from scheduling a second update for the same underlying branch
while still using the existing old-OID check for the single queued
update.

Signed-off-by: Son Luong Ngoc <sluongng@gmail.com>
@sluongng sluongng force-pushed the sl/rebase-update-refs-symrefs branch from 9938d7e to 0ab0a71 Compare May 27, 2026 14:14
@sluongng
Copy link
Copy Markdown
Author

/preview

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget Bot commented May 27, 2026

Preview email sent as pull.2126.git.1779908024.gitgitgadget@gmail.com

@sluongng
Copy link
Copy Markdown
Author

/preview

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget Bot commented May 28, 2026

Preview email sent as pull.2126.git.1779943633.gitgitgadget@gmail.com

@sluongng
Copy link
Copy Markdown
Author

/preview

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget Bot commented May 28, 2026

Preview email sent as pull.2126.git.1779943735.gitgitgadget@gmail.com

@sluongng
Copy link
Copy Markdown
Author

/preview

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget Bot commented May 28, 2026

Preview email sent as pull.2126.git.1779944585.gitgitgadget@gmail.com

@sluongng
Copy link
Copy Markdown
Author

/submit

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget Bot commented May 28, 2026

Submitted as pull.2126.git.1779946921.gitgitgadget@gmail.com

To fetch this version into FETCH_HEAD:

git fetch https://github.com/gitgitgadget/git/ pr-2126/sluongng/sl/rebase-update-refs-symrefs-v1

To fetch this version to local tag pr-2126/sluongng/sl/rebase-update-refs-symrefs-v1:

git fetch --no-tags https://github.com/gitgitgadget/git/ tag pr-2126/sluongng/sl/rebase-update-refs-symrefs-v1

Comment thread sequencer.c
@@ -6445,28 +6445,67 @@ static int add_decorations_to_list(const struct commit *commit,
struct todo_add_branch_context *ctx)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Kristoffer Haugsbakk" wrote on the Git mailing list (how to reply to this email):

On Thu, May 28, 2026, at 07:42, Son Luong Ngoc via GitGitGadget wrote:
>[snip]
> -test_expect_failure '--update-refs skips branch symrefs to current branch' '
> +test_expect_success '--update-refs skips branch symrefs to current branch' '
>  	test_when_finished "
>  		test_might_fail git rebase --abort &&
>  		git checkout primary &&

This style of fixing a bug by:

• Add failing test `test_expect_failure` in the first commit
• Fix the bug in the next commit and flip to `test_expect_success`

Is legitimate and makes it easier to verify that the test really
exercises the regression. But in this project it is preferred to
just do the bug fix + regression test in one patch.

See https://lore.kernel.org/git/xmqqfrdk3aqy.fsf@gitster.g/

> --
> gitgitgadget

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget Bot commented May 28, 2026

User "Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> has been added to the cc: list.

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget Bot commented May 28, 2026

Junio C Hamano wrote on the Git mailing list (how to reply to this email):

"Son Luong Ngoc via GitGitGadget" <gitgitgadget@gmail.com> writes:

> git rebase --update-refs can fail after the normal rebase path has
> successfully updated the current branch when another local branch is a
> symbolic ref to it.
>
> One practical way to arrive at that setup is a default branch rename from
> master to main. While the migration is in progress, a user may keep
> refs/heads/main as a symbolic ref to refs/heads/master so that both names
> continue to work locally.
>
> If pull.rebase is enabled, a plain git pull can then finish the rebase of
> master and still fail while trying to update the main alias. The reported
> failure looked like this, with line breaks adjusted for the cover letter:
>
> Successfully rebased and updated refs/heads/master.
> error: update_ref failed for ref 'refs/heads/main':
> cannot lock ref 'refs/heads/main':
> is at fc2c7bd5f17abec7861ef759edcd33a1e16662a1
> but expected 531cabdfb49098d6ffa502ed4bf91d1b35edfcfa
> Updated the following refs with --update-refs:
> Failed to update the following refs with --update-refs:
>         refs/heads/main

I vaguely recall we saw a different topic that dealt with a
situation somewhat similar to this topic (I think it was about
'describe' giving a name that is not a branch).  How would this mesh
with what the other topic wanted to do?  Instead of filtering out
non-branch names (which the other topic did), here we want to filter
out names that are not concrete branches but pointers to something
else.  Would it mean that the logic here is more broad (i.e., both
wants to filter out names of non-branches), making the other topic
unnecessary?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant