Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is git rebase discarding my commits?

Tags:

git

git-rebase

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
like image 629
Ryan Lundy Avatar asked Apr 01 '14 15:04

Ryan Lundy


People also ask

How do I stop rebase abortion?

You can run git rebase --abort to completely undo the rebase. Git will return you to your branch's state as it was before git rebase was called. You can run git rebase --skip to completely skip the commit.

What is the problem with git rebase?

git rebase rewrites the commit history. It can be harmful to do it in shared branches. It can cause complex and hard to resolve merge conflicts.

Will rebase remove my changes?

The rebase will continue moving your commits up to HEAD . At any point, you may or may not run into more conflicts. If you don't, you'll see that the rebase worked successfully and then you'll be able to push your changes to your branch.

Why is rebase destructive?

First of all, you must understand that Git rebase is a destructive operation. Git generates new commits based on your previous commits onto the target branch. Your former commits will, therefore, be destroyed. Basically, you rewrite your Git history!


2 Answers

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:

  • Understanding "git pull --rebase" vs "git rebase"

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.

like image 111
Luke Usherwood Avatar answered Sep 19 '22 12:09

Luke Usherwood


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?

like image 26
Jack O'Connor Avatar answered Sep 18 '22 12:09

Jack O'Connor