rebase: handle --update-refs branch symrefs#2126
Conversation
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>
9938d7e to
0ab0a71
Compare
|
/preview |
|
Preview email sent as pull.2126.git.1779908024.gitgitgadget@gmail.com |
|
/preview |
|
Preview email sent as pull.2126.git.1779943633.gitgitgadget@gmail.com |
|
/preview |
|
Preview email sent as pull.2126.git.1779943735.gitgitgadget@gmail.com |
|
/preview |
|
Preview email sent as pull.2126.git.1779944585.gitgitgadget@gmail.com |
|
/submit |
|
Submitted as pull.2126.git.1779946921.gitgitgadget@gmail.com To fetch this version into To fetch this version to local tag |
| @@ -6445,28 +6445,67 @@ static int add_decorations_to_list(const struct commit *commit, | |||
| struct todo_add_branch_context *ctx) | |||
There was a problem hiding this comment.
"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|
User |
|
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?
|
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:
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:
cc: "Kristoffer Haugsbakk" kristofferhaugsbakk@fastmail.com