threads / discuss / 62032

`git rebase (--no-fork-point) --onto=<newbase> [<upstream> [<branch>]]` leaves HEAD detached and *HEAD not moved when <branch> is exactly `HEAD`

Subject: `git rebase (--no-fork-point) --onto=<newbase> [<upstream> [<branch>]]` leaves HEAD detached and *HEAD not moved when <branch> is exactly `HEAD`

## tl;dr

6 messages between Sep 1, 2024 and Sep 3, 2024.

replies: 5people: 3as markdown or json

Han Jiang· Sep 1, 2024, 11:02 UTC · lore

Thank you for filling out a Git bug report! Please answer the following questions to help us understand your issue.

What did you do before the bug happened? (Steps to reproduce your issue)

cd '/'; cd '/'; rm --force --recursive -- './test_git'; mkdir "$_"; cd "$_"; mkdir --parents -- './server' './client'; git -C './server' init --bare './repo.git' echo '1' >'./file'; git --git-dir='./server/repo.git' --work-tree='.' add './file'; git --git-dir='./server/repo.git' --work-tree='.' commit -m "$((++number))" echo '2' >'./file'; git --git-dir='./server/repo.git' --work-tree='.' add './file'; git --git-dir='./server/repo.git' --work-tree='.' commit -m "$((++number))" git -C './server/repo.git' log --graph --all --patch

git -C './client' clone '../server/repo.git' './repo' branch_default_path="$(git -C './client/repo' symbolic-ref HEAD)" branch_default_name="${branch_default_path#'refs/heads/'}" echo '3' >'./client/repo/file'; git -C './client/repo' add './file'; git -C './client/repo' commit -m "$((++number))" git -C './client/repo' log --graph --all --patch

git -C './server/repo.git' reset --soft HEAD~1 echo '4' >'./file'; git --git-dir='./server/repo.git' --work-tree='.' add './file'; git --git-dir='./server/repo.git' --work-tree='.' commit -m "$((++number))" git -C './server/repo.git' log --graph --all --patch

git -C './client/repo' fetch --all git -C './client/repo' log --graph --all --patch git -C './client/repo' -c 'core.editor=cat' rebase --onto=HEAD@{upstream} --interactive "$(git -C './client/repo' merge-base --fork-point HEAD@{upstream} HEAD)" HEAD git -C './client/repo' log --graph --all --patch echo '5' >'./client/repo/file'; git -C './client/repo' add './file'; git -C './client/repo' -c 'core.editor=:' rebase --continue git -C './client/repo' log --graph --all --patch git -C './client/repo' checkout -B "$branch_default_name" git -C './client/repo' log --graph --all --patch

What did you expect to happen? (Expected behavior)

HEAD points to GIT_DEFAULT_BRANCH and GIT_DEFAULT_BRANCH points to tip of origin's GIT_DEFAULT_BRANCH when rebase completes.

What happened instead? (Actual behavior)

HEAD is detached and *HEAD is not moved when rebase completes, so `git checkout -B` is needed.

What's different between what you expected and what actually happened?
Anything else you want to add:

Replacing `HEAD` with branch name (`"$(git -C './client/repo' branch --show-current)"`) works around the problem. cd '/'; cd '/'; rm --force --recursive -- './test_git'; mkdir "$_"; cd "$_"; mkdir --parents -- './server' './client'; git -C './server' init --bare './repo.git' echo '1' >'./file'; git --git-dir='./server/repo.git' --work-tree='.' add './file'; git --git-dir='./server/repo.git' --work-tree='.' commit -m "$((++number))" echo '2' >'./file'; git --git-dir='./server/repo.git' --work-tree='.' add './file'; git --git-dir='./server/repo.git' --work-tree='.' commit -m "$((++number))" git -C './server/repo.git' log --graph --all --patch

git -C './client' clone '../server/repo.git' './repo' echo '3' >'./client/repo/file'; git -C './client/repo' add './file'; git -C './client/repo' commit -m "$((++number))" git -C './client/repo' log --graph --all --patch

git -C './server/repo.git' reset --soft HEAD~1 echo '4' >'./file'; git --git-dir='./server/repo.git' --work-tree='.' add './file'; git --git-dir='./server/repo.git' --work-tree='.' commit -m "$((++number))" git -C './server/repo.git' log --graph --all --patch

git -C './client/repo' fetch --all git -C './client/repo' log --graph --all --patch git -C './client/repo' -c 'core.editor=cat' rebase --onto=HEAD@{upstream} --interactive "$(git -C './client/repo' merge-base --fork-point HEAD@{upstream} HEAD)" "$(git -C './client/repo' branch --show-current)" git -C './client/repo' log --graph --all --patch echo '5' >'./client/repo/file'; git -C './client/repo' add './file'; git -C './client/repo' -c 'core.editor=:' rebase --continue git -C './client/repo' log --graph --all --patch

Please review the rest of the bug report below. You can delete any lines you don't wish to share.

[System Info]
git version:
git version 2.46.0.windows.1
cpu: x86_64
built from commit: 2e6a859ffc0471f60f79c1256f766042b0d5d17d
sizeof-long: 4
sizeof-size_t: 8
shell-path: D:/git-sdk-64-build-installers/usr/bin/sh
feature: fsmonitor--daemon
libcurl: 8.9.0
OpenSSL: OpenSSL 3.2.2 4 Jun 2024
zlib: 1.3.1
uname: Windows 10.0 22631
compiler info: gnuc: 14.1
libc info: no libc information available
$SHELL (typically, interactive shell): C:\Program Files\Git\usr\bin\bash.exe

[Enabled Hooks] not run from a git repository - no hooks to show

Phillip Wood· Sep 1, 2024, 15:26 UTC · re: Han Jiang · lore

Re: `git rebase (--no-fork-point) --onto=<newbase> [<upstream> [<branch>]]` leaves HEAD detached and *HEAD not moved when <branch> is exactly `HEAD`

On 01/09/2024 12:02, Han Jiang wrote:
> What did you do before the bug happened? (Steps to reproduce your issue)
> git -C './client/repo' -c 'core.editor=cat' rebase
> --onto=HEAD@{upstream} --interactive "$(git -C './client/repo'
> merge-base --fork-point HEAD@{upstream} HEAD)" HEAD

"git rebase <upstream> <branch>" is designed to switch to a different branch before rebasing it. If you do not want to switch branches you should use "git rebase <upstream>". "<branch>" is expected to be a branch name, not a symbolic ref to the branch like "HEAD".

> Replacing `HEAD` with branch name (`"$(git -C './client/repo' branch
> --show-current)"`) works around the problem.
This is working as expected.

"git checkout HEAD" is a no-op so "git rebase <upstream> HEAD" is behaving differently to "git checkout HEAD && git rebase <upstream>". We could look at changing that but it would be a breaking change for anyone relying on the current behavior to detach HEAD before rebasing.

Best Wishes
Phillip
Junio C Hamano· Sep 1, 2024, 15:42 UTC · re: Phillip Wood · lore

Re: `git rebase (--no-fork-point) --onto=<newbase> [<upstream> [<branch>]]` leaves HEAD detached and *HEAD not moved when <branch> is exactly `HEAD`

Phillip Wood <phillip.wood123@gmail.com> writes:
> "git rebase <upstream> <branch>" is designed to switch to a different
> branch before rebasing it. If you do not want to switch branches you
> should use "git rebase <upstream>".
Correct.
> "<branch>" is expected to be a
> branch name, not a symbolic ref to the branch like "HEAD".

I question the correctness of this, though. The "what to rebase" argument can be any arbitrary commit-ish, and if it is a name of a local branch, that branch is rebased. If it is not, the HEAD is detached and that detached HEAD state is rebased. The latter is handy when you are unsure if you want to really touch the branch. i.e. "git rebase master topic^0"---this way you'd end up on a detached HEAD that shows what would happen if you really rebased "topic" branch, but if you do not like the result, you can just discard the state by e.g., checking out some branch, and you do not even contaminate the reflog of the "topic" branch with the record of this failed exeriment.

I have a mild suspicion that the "rebase" command might have changed its behaviour since the days back when it was implemented as a shell script, when the "what to rebase" argument is HEAD, as the most natural implementation to do this "optionally first switch to it when the argument is given" in the scripted Porcelain is to actually run "git checkout HEAD", which should be a somewhat noisy no-op. Apparently today's "git rebase" does not work that way and seems to detach HEAD instead and then rebases it. As you said, that is the behaviour most users are familiar with and it is probably too late to change, even if (I didn't check) an ancient version of "rebase" did not work that way and instead rebased the current branch.

Thanks.
Han Jiang· Sep 1, 2024, 22:05 UTC · re: Junio C Hamano · lore

Re: `git rebase (--no-fork-point) --onto=<newbase> [<upstream> [<branch>]]` leaves HEAD detached and *HEAD not moved when <branch> is exactly `HEAD`

@Phillip Wood @Junio C Hamano I finally got it. Thank you both for your explanation!

On Mon, Sep 2, 2024 at 3:42 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 38 quoted lines
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> > "git rebase <upstream> <branch>" is designed to switch to a different
> > branch before rebasing it. If you do not want to switch branches you
> > should use "git rebase <upstream>".
>
> Correct.
>
> > "<branch>" is expected to be a
> > branch name, not a symbolic ref to the branch like "HEAD".
>
> I question the correctness of this, though.  The "what to rebase"
> argument can be any arbitrary commit-ish, and if it is a name of a
> local branch, that branch is rebased.  If it is not, the HEAD is
> detached and that detached HEAD state is rebased.  The latter is
> handy when you are unsure if you want to really touch the branch.
> i.e. "git rebase master topic^0"---this way you'd end up on a
> detached HEAD that shows what would happen if you really rebased
> "topic" branch, but if you do not like the result, you can just
> discard the state by e.g., checking out some branch, and you do not
> even contaminate the reflog of the "topic" branch with the record of
> this failed exeriment.
>
> I have a mild suspicion that the "rebase" command might have changed
> its behaviour since the days back when it was implemented as a shell
> script, when the "what to rebase" argument is HEAD, as the most
> natural implementation to do this "optionally first switch to it
> when the argument is given" in the scripted Porcelain is to actually
> run "git checkout HEAD", which should be a somewhat noisy no-op.
> Apparently today's "git rebase" does not work that way and seems to
> detach HEAD instead and then rebases it.  As you said, that is the
> behaviour most users are familiar with and it is probably too late
> to change, even if (I didn't check) an ancient version of "rebase"
> did not work that way and instead rebased the current branch.
>
> Thanks.
>
Phillip Wood· Sep 2, 2024, 09:04 UTC · re: Junio C Hamano · lore

Re: `git rebase (--no-fork-point) --onto=<newbase> [<upstream> [<branch>]]` leaves HEAD detached and *HEAD not moved when <branch> is exactly `HEAD`

Hi Junio
On 01/09/2024 16:42, Junio C Hamano wrote:
Show 6 quoted lines
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>> "<branch>" is expected to be a
>> branch name, not a symbolic ref to the branch like "HEAD".
> 
> I question the correctness of this, though.
Yes as you explain below, it is a bit of an over simplification
Show 11 quoted lines
> The "what to rebase"
> argument can be any arbitrary commit-ish, and if it is a name of a
> local branch, that branch is rebased.  If it is not, the HEAD is
> detached and that detached HEAD state is rebased.  The latter is
> handy when you are unsure if you want to really touch the branch.
> i.e. "git rebase master topic^0"---this way you'd end up on a
> detached HEAD that shows what would happen if you really rebased
> "topic" branch, but if you do not like the result, you can just
> discard the state by e.g., checking out some branch, and you do not
> even contaminate the reflog of the "topic" branch with the record of
> this failed exeriment.
 >
Show 6 quoted lines
> I have a mild suspicion that the "rebase" command might have changed
> its behaviour since the days back when it was implemented as a shell
> script, when the "what to rebase" argument is HEAD, as the most
> natural implementation to do this "optionally first switch to it
> when the argument is given" in the scripted Porcelain is to actually
> run "git checkout HEAD", which should be a somewhat noisy no-op.j

It appears to have changed in 0cb06644a5 (rebase [--onto O] A B: omit needless checkout, 2008-03-15). That also changed the reflog messages written by "git rebase <upstream> <branch>" so that "git checkout @{n}" does not see the new branch being checked out.

Best Wishes
Phillip
Junio C Hamano· Sep 3, 2024, 15:38 UTC · re: Phillip Wood · lore

Re: `git rebase (--no-fork-point) --onto=<newbase> [<upstream> [<branch>]]` leaves HEAD detached and *HEAD not moved when <branch> is exactly `HEAD`

Phillip Wood <phillip.wood123@gmail.com> writes:
Show 11 quoted lines
>> I have a mild suspicion that the "rebase" command might have changed
>> its behaviour since the days back when it was implemented as a shell
>> script, when the "what to rebase" argument is HEAD, as the most
>> natural implementation to do this "optionally first switch to it
>> when the argument is given" in the scripted Porcelain is to actually
>> run "git checkout HEAD", which should be a somewhat noisy no-op.j
>
> It appears to have changed in 0cb06644a5 (rebase [--onto O] A B: omit
> needless checkout, 2008-03-15). That also changed the reflog messages
> written by "git rebase <upstream> <branch>" so that "git checkout
> @{n}" does not see the new branch being checked out.

Thanks for digging. Even if we now know that it was a regression in the early times of the current code, it is way too old to change it now.

Unless we want to do so at a major version bump, but I do not know if this is big enough to worth writing a release note entry for. Users who do want to run a trial rebase of the current branch on a detached HEAD have all been already using HEAD^0 as the <branch> argument and they have been fine without complaints.

Making it consistent in the opposite direction by changinging "git checkout HEAD" to detach HEAD is unthinkable. So what we have is fine, I would think.

Thanks.

← back to recent threads