Question

I'm trying to rebase a branch on top of master, something I've done a thousand times before. But today, it's not working:

> git status
On branch mystuff
Your branch and 'master' have diverged,
and have 6 and 2 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

nothing to commit, working directory clean

> git rebase
First, rewinding head to replay your work on top of it...

> git status
On branch mystuff
Your branch is up-to-date with 'master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    [a directory from the project]

nothing added to commit but untracked files present (use "git add" to track)

>

Everything starts like normal, but then Git finishes the rebase without putting any of my commits there; my branch mystuff ends up on the same commit as master.

The obvious conclusion would be that my commits are already in master somewhere. But they're not, I swear. I've gone back through the history. The commits are on a couple of other feature branches, but they're not in the history of master anywhere. (And I can tell they're not in master anyway by the state of the files when I have master checked out.)

So, if the commits aren't already in my upstream history, why else would git rebase refuse to stack my commits on top?

Oddly enough, if I cherry-pick the commits onto master one-by-one, that works. And then I can move my mystuff branch to the end, and back master up to where it was. (But why would I need to do it that way?)

EDIT:

The documentation on git rebase says this:

The current branch is reset to <upstream>, or <newbase> if the --onto option was supplied. This has the exact same effect as git reset --hard <upstream> (or <newbase>). ORIG_HEAD is set to point at the tip of the branch before the reset.

The commits that were previously saved into the temporary area are then reapplied to the current branch, one by one, in order. Note that any commits in HEAD which introduce the same textual changes as a commit in HEAD..<upstream> are omitted (i.e., a patch already accepted upstream with a different commit message or timestamp will be skipped).

This would be consistent with the behavior I'm seeing if the commits actually existed upstream...but they don't. And as mentioned in the comments, git rebase master works correctly and applies all the commits. But git rebase without master doesn't, even though master is set as the upstream branch.

Configuration of my branches:

[branch "master"]
    remote = origin
    merge = refs/heads/master
[branch "mystuff"]
    remote = .
    merge = refs/heads/master
Was it helpful?

Solution

This has bitten me at least a dozen times after a certain git upgrade. There is now a difference between git rebase and git rebase master: the former was changed to use same fancy "fork-point" machinery. There is a detailed explanation in answer to this question:

Today for the first time I figured out concrete steps to reproduce it.

MY SCENARIO: I have 4 commits on master which I've decided should now move into a topic branch, plus I want to reorder them. If I do it this way...

  1. Create a new topic branch, tracking the current branch (master)

    git checkout -b topic -t
    
  2. Rewind master back :

    git checkout master
    git reset --hard origin/master
    
  3. Reorder the commits on topic

    git checkout topic
    git status  # "Your branch is ahead of 'master' by 4 commits" Good!
    git rebase --interactive
    

... then the interactive rebase screen comes up with this ominous list of commits:

    # no-op

Uh-oh... I save the file and continue anyway. Yup, looks like git's thrown away my work again. topic now points to the same commit as master and origin/master.

So I presume what triggered this for you is:

  1. upgrading git

  2. You had done something like my step 2 on your master branch.

In my lay-man's understanding, the fork-point machinery searches back through the reflog and notices that those commits had been removed from the upstream branch, and comes to the conclusion that to bring your topic branch "up to date" they should be removed there too.

The solution is, instead of:

git rebase

Use one of:

git rebase --no-fork-point
git rebase master

But I suspect like me you wouldn't do this every time. (I mean, we set an upstream branch for a reason, right?) So you'd just learn to recognise when this disaster strikes, use git reflog and git reset --hard to recover, and then use the above command.

Still, you need to be careful now - I think this is extremely dangerous. I have had times when I've rebased a large branch and a few commits silently disappeared from the beginning, and I didn't notice for days! I'm fairly comfortable mining git reflog to do disaster recovery, but I'm not sure everyone is. I wonder if git has started being too clever here.

OTHER TIPS

Run git branch -vv to see upstream branches. It sounds like your upstream for mystuff isn't what you think it is. Maybe you had an editing accident and ended up with multiple [branch "mystuff"] entries in your config?

I was going to ask this question myself, but as I tried to ask, found the answer.

There are 2 situations you might find yourself in when in need of a rebase. Consider the following history:

A---B---C master

Collaborator #1 changes commit C, adds commit D and does git push -f:

A---B---C'---D master

Collaborator #2 adds commit E and does git fetch:

A---B---C'---D origin/master
     \
      C---E master

You might say, "B is the last common ancestor, C and C' differ, I want to preserve all the changes." Then your expected history is:

A---B---C'---D---C"---E" master

Another way of thinking about it is, "The other guy changed the C commit, there must be a reason, I want this new version of C in place of mine." Then what you have in mind is this:

A---B---C'---D---E" master

Which means when commits are deleted upstream, so happens locally when you do a rebase.

The former mode is chosen when you specify the upstream explicitly:

$ git rebase origin/master

The latter (the "--fork-point" mode):

$ git -c pull.rebase=true pull
$ git pull --rebase
$ git rebase
$ git rebase --fork-point origin/master

A more detailed description is given here. A script that reproduces the issue.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top