Reputation: 580
I always do a git pull --rebase even if I do not have a single pending commit.
I just did a pull.
Suddenly, when I run git status, I am seeing that my local master is 11 commits ahead of the remote master.
I dont know where they came from and want to get rid of them.
What do I do?
Upvotes: 0
Views: 97
Reputation: 489233
It's clear that you're a victim of what is usually called an "upstream rebase". You can jump right to the solution below but this background is pretty important.
This why I hate git pull
. It's not git pull
's fault, really, but because git pull
is a convenience command that simply runs git fetch
followed by a second command, it winds up hiding the two underlying Git commands from new Git users. The fact that there are two different second commands, both of which can mess things up in different ways, is spectacularly unhelpful.
Fortunately, the first part—the git fetch
step—almost always works.1 It's the second step where, if all goes well, everyone says "oh that was easy" and thinks that git pull
is great and then when it explodes, as it always does eventually, they're in trouble, because they have never had to deal with the second part failing. And now it's an emergency and they have to spend a bunch of hours learning a bunch of complicated stuff: the way to recover from failures, and how merge and rebase interact with the git fetch
step.
Anyway, this time you are the one stuck with the messy situation, so let's just press on with it. The second half of what git pull
does is either git merge
or git rebase
. Exactly which command it does when is beyond the scope of this answer, except that we can say for sure that if you add --rebase
, it always uses git rebase
. So you only have one complicated Git command to learn in a hurry. (Yay? :-) )
Hence, as Tim Biegeleisen answered, these new mystery commits came from using git rebase
. So it's time now to learn all2 about rebase, and how it interacts with git fetch
.
1There are still a few ways it can go wrong, but usually the one that actually happens is "the network is down and thus we can't get anything from origin
" which is really obvious and makes everything stop right at the beginning, and you just try again once the network is fixed.
2Well, let's not go that far. :-)
Rebase can be complicated but we can sum it all up very simply: git rebase
copies commits.
That's it. Rebase copies commits. That's actually not bad at all, is it? Well, it's not bad when they are your commits, but you just had it copy someone else's commits. The tricky part here is defining precisely which commits get copied, when, and how. This is where git fetch
comes into the picture again.
Tim's drawing here covers the more normal case, when you have made some commits. I'm going to make my own drawing though. Let's say there are a bunch of commits, ending with a series F--G--H
, when you start out. That is, your master
and origin/master
both point to commit H
, which points back to G
, which points back to F
, and so on. You got all these when you did your initial git clone
or your last git pull --rebase
or whatever: it doesn't really matter how you got them, just that you do have them.
...--F--G--H <-- master, origin/master
Now, normally, you might add your own commits, say I
and J
:
I--J <-- master
/
...--F--G--H <-- origin/master
Meanwhile someone else, using their own Git, adds their own different commits. You don't have them now. Then they publish those commits, so now origin
has them too. You still don't! But now you run git fetch
and now you get them, and here's what happens in your repository. Let's say they made just one commit, which we'll label K
:
I--J <-- master
/
...--F--G--H
\
K <-- origin/master
Note how the label, origin/master
, has "moved forward" from H
to K
. Meanwhile your own master
is already moved forward from H
to J
: yours points to your commit J
, which points back to your commit I
, which points back to H
.
This is where you normally bring git rebase
in. You ask it to copy your commits, I
and J
, to new commits that are "just as good" but are based on commit K
:
I--J <-- master
/
...--F--G--H I'-J'
\ /
K <-- origin/master
Let's say the copy goes well, with no problematic merge conflicts and such. You now have new commits that are just like your originals, except for their new "base" commit K
. That's a rebase!
All your Git has to do now is peel your master
label off the old J
and make it point to the new copied J'
:
I--J [abandoned]
/
...--F--G--H I'-J' <-- master
\ /
K <-- origin/master
and now that the old commits are officially abandoned, we can straighten the whole picture out:
I'-J' <-- master
/
...--F--G--H--K <-- origin/master
There are multiple ways for git rebase
to go awry. Let's ignore some of them and just concentrate on the one that bit you.
Let's draw out more of the stuff that comes before F--G--H
this time. Also, you didn't have your own commits (you said, and I believe you).
A--B--C--D--E--F--G--H <-- master, origin/master
Now, someone else did something upstream to "rebase", i.e., to copy some commits. I don't know exactly what they did, but let's say they discovered that commit D
was Defective, and perhaps even deleted it entirely. They now have:
D--E--F--G--H [abandoned]
/
A--B--C
\
E'-F'-G'-H' <-- master
Now you run your git fetch
. What this does is to pick up their master
and forcibly adjust your origin/master
to match theirs. So you still have A-...-H
as usual, with your master
pointing to that, but now you have origin/master
pointing to H'
:
D--E--F--G--H <-- master
/
A--B--C
\
E'-F'-G'-H' <-- origin/master
That's your git fetch
step. Now your git pull
runs git rebase
. What rebase does, or tries to do, is to copy commits. Which commits should it copy? The answer here can get complicated, but to simplify things a lot, it will think, at this point, that D--E--F--G--H
are your commits. Those are the obvious copy candidates: they're the commits on your master, that are not on origin/master
.
It does matter that E'-F'-G'-H'
are copies themselves. But let's just ignore that. Whether your Git detects them as copies or not, your Git is at least going to restore commit D
, thinking it's your work, rather than something that got stripped out. In any case, because you're seeing 11 commits, your Git has somehow restored or resurrected or copied 11 commits. Let's just draw all of D
-through-H
since our solution will be the same either way.
Your git rebase
copies "your" commits (not really yours, it just thinks they're yours) and moves your master
label, and you now have this:
D--E--F--G--H [abandoned]
/
A--B--C D'-E"-F"-G"-H" <-- master
\ /
E'-F'-G'-H' origin/master
Suddenly you're five commits "ahead of" origin/master
.
In this case, since you're sure you really have no commits of your own, what you need to do is tell your Git to abandon all these new copies entirely.
The command that does all this for you is git reset --hard
. The thing about git reset --hard
is that it does let you throw away a bunch of your own work, so you must be careful about when you use it.
In this particular case, though, you promised you had no commits of your own here. Remember, it's just this particular case: no commits of your own, no uncommitted work in the work-tree, etc. So it's safe to wipe things out.
So, what you want to do in this case is to tell your Git to move your master
to point to the same commit as origin/master
, abandoning all the copies that your git rebase
made:
D--E--F--G--H [abandoned]
/
A--B--C D'-E"-F"-G"-H" [abandoned]
\ /
E'-F'-G'-H' <-- master, origin/master
If we now stop drawing in the abandoned commits, you'll see that your graph is now just:
A--B--C--E'-F'-G'-H' <-- master, origin/master
which is what you want: you're no longer "ahead of" origin/master
.
Suppose, in all that mess, you really did have some commits to keep. In this case, there are a bunch of ways to fix this, but probably the easiest is to use git rebase -i
(interactive rebase: copy selected commits). You can git rebase -i
to get the 11 commits that Git thinks are yours into a set of instructions. Then you delete, from the instruction sheet, all but the ones that really are yours, and your Git copies those yet again, and abandons the un-copied ones.
Since you promised there were no such commits, we were able to use the simpler git reset --hard
. (Depending on your Git version, we might have to use reset
. All versions of Git refuse to let you delete everything from the instruction sheet. Newer versions let you put in a "no-op" instruction so that there's at least one instruction left, even if there are no copy instructions.)
Upvotes: 2
Reputation: 522171
I dont know where they came from
When you rebase your branch on the remote master
, you are taking that remote branch and replaying all your local commits on top of the remote branch. As such, it is a completely normal state for your local branch to be ahead of the remote.
and want to get rid of them
These commits are quite possibly your own work. Don't get rid of them!
Here is a diagram showing how rebasing works, so that you can see what is likely happening in your case. Consider that both your local master
and origin/master
begin at the same commit:
origin: A
master: A
Now, you make say 3 commits while at the same time other people make 3 commits to the remote:
origin: A -- E -- F -- G
master: A -- B -- F -- D
If you now do a git pull --rebase origin master
you will get the following:
origin: A -- E -- F -- G
master: A -- E -- F -- G -- B' -- F' -- D'
^ ^^ ^^ these are your commits
In other words, after rebasing your local branch will appear as if you just cloned the remote and freshly committed all your work on top of it. And in the example, rebasing has left your local branch 3 commits ahead of the remote.
Upvotes: 0
Reputation: 5357
With git pull --rebase you get commits from the remote branch (master) that others pushed. But if you have commits that are not pushed to remote master, then status of git will refer that you local branch (master) is ahead of remote master for those commits.
Upvotes: 0