9

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
Community
  • 1
  • 1
Ryan Lundy
  • 204,559
  • 37
  • 180
  • 211
  • What is the `git status` after `git rebase` has returned? – lrineau Apr 01 '14 at 16:00
  • What happens when you do `git rebase master` or `git rebase master `? –  Apr 01 '14 at 16:25
  • Is sharing your whole directory a possibility? If you're _that_ sure you're making things OK, it may help to find a bug or anything. – mgarciaisaia Apr 01 '14 at 16:53
  • @Irineau, see the edit. The untracked directory is because one of the commits that didn't get rebased was a change to .gitignore to ignore that directory. – Ryan Lundy Apr 01 '14 at 17:08
  • @Cupcake, good question. `git rebase` and `git rebase --onto master` both fail as above...but `git rebase master` works! But why? If master is the upstream, then `git rebase` should work without the branch argument, shouldn't it? – Ryan Lundy Apr 01 '14 at 17:12
  • Can you include the git configuration for your `mystuff` branch? – joshtkling Apr 01 '14 at 19:57
  • @joshtkling, sure. See above. – Ryan Lundy Apr 01 '14 at 20:28
  • Not a clue, based on that. I see no good reason why `git rebase` would act any differently than `git rebase master`. – joshtkling Apr 01 '14 at 20:33
  • What version of git are you using? There have been several updates/bugfixes recently to rebase over the last couple of versions of git that might help. – onionjake Apr 03 '14 at 17:20
  • @onionjake, 1.9.0.msysgit.0. I did look over recent release notes to see if there was anything relevant, but I didn't find anything. – Ryan Lundy Apr 03 '14 at 17:56
  • No need to swear. Just checkout your branch and run 'git cherry origin/master'. It will tell you if your commits are already in the origin/master. – the.malkolm Apr 08 '14 at 17:22
  • Another confirmation that this used to work: I use Jenkins to do builds on my machine, and I had just `git rebase` in the step after I pulled from our main repository. I never used to have to say what branch. I've been using this since the end of 2013 without a problem until recently. – Ryan Lundy Apr 08 '14 at 19:38
  • I suggest to make a sync from master branch: git pull --rebase master – enrique-carbonell Apr 23 '14 at 17:47
  • Sounds like a bug in `git` triggered by the use of `remote = .`. I'd suggest doing `git fsck` and if nothing turns out, try reporting it to the git developers. If you cannot share the whole repo with them, I guess they might have some scripts to anonymize a copy of the repo. – Mikko Rantalainen May 12 '14 at 06:33
  • Try to specify the rebase branch in the rebase commit. – Mohanraj May 18 '14 at 10:07
  • I would try to do `git rebase -i origin/master` where `-i` stands for "interactive" to see which commits are shown on the list. – Namek Jul 11 '14 at 13:29
  • My guess is that you started to make changes in the `master` branch, realized that, did [`git checkout -tb topic && git branch -f master HEAD~... && git rebase`](https://gist.github.com/x-yuri/b5d4c22709ea5651cc19884e1ba6c9c3). On a side note, I wonder why you'd like to have `master` a `topic`'s upstream... – x-yuri Jan 13 '21 at 22:52
  • @x-yuri Nope, that's not what I did. – Ryan Lundy Jan 14 '21 at 05:38

3 Answers3

5

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.

Community
  • 1
  • 1
Luke Usherwood
  • 3,082
  • 1
  • 28
  • 35
  • Next question: is there a git configuration option to change the behaviour back? (Bonus points: maybe just for local branches?) I couldn't find mention of one when searching `git help`. – Luke Usherwood Nov 30 '16 at 11:24
  • 3
    Aha! Yes, that *is* the problem. It might be re-characterized from "notices that the commits have been *removed* from there" to "notices that the commits were previously *shared* with the upstream, and then *assumes* they were replaced with 'improved copies' there". There is no automatic `--no-fork-point` option, but you could add one: see the rebase script, around line 454, with `test "$fork_point" = auto && fork_point=t`. Replace with "if set to auto, check config for final value". – torek Nov 30 '16 at 14:00
  • `--no-fork-point` didn't change the outcome for me, but this explanation was still helpful. after recovering my commit with `reflog`, i ended up just cherry-picking my commit to a new branch i made off of master. – allieferr Oct 03 '18 at 20:07
  • In the end I avoid this by aliasing `git reup` to mean `git rebase @{u}` and simply never rebasing without either calling that alias or specifying a different upstream branch. – Ryan Lundy Jan 13 '21 at 17:42
  • Why would you make `master` the upstream of `topic`? O.o – x-yuri Jan 13 '21 at 22:10
  • @x-yuri Because eventually topic will be merged into master. I'm not sure what's confusing you about this. – Ryan Lundy Jan 14 '21 at 05:39
  • @RyanLundy Well, usually upstreams are the remote branches. What benefits does your setup provides? – x-yuri Jan 14 '21 at 19:06
  • @x-yuri I can still easily push to the remote: `git push origin`. As long as I have an upstream branch specified, even if it's another local branch instead of a remote branch, Git will happily push from my local branch to the matching-name remote branch. So this way I can see where I stand in relation to the branch I'll eventually merge into (with `git status`), while I can push to the remote with no trouble. – Ryan Lundy Jan 14 '21 at 20:10
1

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?

Jack O'Connor
  • 10,068
  • 4
  • 48
  • 53
1

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.

x-yuri
  • 16,722
  • 15
  • 114
  • 161