Search code examples
gitrebase

Git rebase checks out parent commit


Git is behaving weirdly in a particular case and I am able to reproduce the issue. I have two branches, say master and feature, pointing to the same commit. When I reset the master to a parent commit and try to rebase feature on top of master, the feature branch also points to the parent commit although I expect rebase to do nothing. I am not sure why it's happening.

Steps to reproduce:

Run the following commands to reproduce the initial state:

deepakgupta @ git init
Initialized empty Git repository in /tmp/hello/.git/

deepakgupta (master)@ touch hello

deepakgupta (master)@ git add hello

deepakgupta (master)@ git commit -m "First commit"
[master (root-commit) 1750f9c] First commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 hello

deepakgupta (master)@ touch world

deepakgupta (master)@ git add world

deepakgupta (master)@ git commit -m "Second commit"
[master 7411ad0] Second commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 world

deepakgupta (master)@ git branch feature

At this point of time, this is how git log looks:

commit 7411ad0984841b28dca630d54c3b079ebd01c7e9 (HEAD -> master, feature)
Author: Deepak Gupta <[email protected]>
Date:   Tue Apr 7 13:58:09 2020 +0530

    Second commit

commit 1750f9c5ba95f53742005657e9cee41390165dc7
Author: Deepak Gupta <[email protected]>
Date:   Tue Apr 7 13:57:54 2020 +0530

    First commit

Now try out:

git reset --hard HEAD^
git checkout feature
git branch --set-upstream-to master

This is now git log looks now:

commit 7411ad0984841b28dca630d54c3b079ebd01c7e9 (HEAD -> feature)
Author: Deepak Gupta <[email protected]>
Date:   Tue Apr 7 13:58:09 2020 +0530

    Second commit

commit 1750f9c5ba95f53742005657e9cee41390165dc7 (master)
Author: Deepak Gupta <[email protected]>
Date:   Tue Apr 7 13:57:54 2020 +0530

    First commit

What should happen when I try git rebase? I feel that the state should remain same. but it changes to:

commit 1750f9c5ba95f53742005657e9cee41390165dc7 (HEAD -> feature, master)
Author: Deepak Gupta <[email protected]>
Date:   Tue Apr 7 13:57:54 2020 +0530

    First commit
(END)

Can someone explain the behavior?


Solution

  • This problem—or feature, as the Git folks prefer to refer to it—is due to Git's fork-point mode. The git rebase documentation describes this as follows:

    If upstream is not specified, the upstream configured in branch.name.remote and branch.name.merge options will be used (see git-config[1] for details) and the --fork-point option is assumed. If you are currently not on any branch or if the current branch does not have a configured upstream, the rebase will abort.

    (formatting, especially the bold part, is mine here). The next paragraph offers this cryptic summary:

    All changes made by commits in the current branch but that are not in upstream are saved to a temporary area. This is the same set of commits that would be shown by git log <upstream>..HEAD; or by git log 'fork_point'..HEAD, if --fork-point is active (see the description on --fork-point below); or by git log HEAD, if the --root option is specified.

    (This description omits a few important items: in particular, merges and commits that are patch-ID equivalent are also discarded by default. But neither of these affects your particular case.)

    The fork-point description is partway down the page. It mentions that:

    fork_point is the result of git merge-base --fork-point <upstream> <branch> command (see git-merge-base[1]). If fork_point ends up being empty, the upstream will be used as a fallback.

    The upstream is master and the branch is the current branch, feature. In my repository, I now run:

    $ git merge-base --fork-point master feature
    af07c3c48ff0d499400ac539aa9c665a8dddcd6f
    

    (using the hash IDs in the repository I made while reproducing your example). Let's use git log on my repository to see the two commits:

    $ git log --decorate --oneline
    af07c3c (HEAD -> feature) Second commit
    dbf1741 (master) First commit
    

    So, given this output from git merge-base --fork-point, rebase promises that the commits that it will copy are those that would be shown by git log af07c3c48ff0d499400ac539aa9c665a8dddcd6f..HEAD:

    $ git log af07c3c48ff0d499400ac539aa9c665a8dddcd6f..HEAD
    $
    

    No commits are listed, so no commits are copied.

    How to fix the problem

    To disable fork-point mode, run:

    git rebase --no-fork-point
    

    or:

    git rebase master
    

    Even though the upstream of the current branch feature is master, an explicit git rebase master disables fork-point mode by default. To force Git to use fork-point mode when using an explicit branch name—should you wish to do this; in this case, you presumably don't want it—you could also run:

    git rebase --fork-point master
    

    With no arguments at all, you get the effect of git rebase --fork-point master.