You want to compare git rebase master
with git rebase origin/master
.
The argument you give to git rebase
is what the git rebase
documentation calls the upstream. This is not a very good name (the documentation is, in this case, not so great) but the answer is in there.
What git rebase
does is to copy some set of commits. Git needs two pieces of information: which commits should it copy? and where should it copy them to? This one argument, this upstream as the documentation calls it, provides the answer to both questions.
To understand this copying process, you need to draw at least some part of the commit graph so that you can see which commits get copied and what happens afterward.
Drawing commit graphs is a little bit tricky, although if you practice it for a while you can get quite good at it. To draw the graph, you need to view the commits with their parent linkages. You can use git log --all --decorate --oneline --graph
, which will do its own graph drawing, or use a graphical viewer like gitk
or some Git GUIs. (You cannot use GitHub's graphs as they contain the wrong information.) If you draw them manually, it's helpful to have a whiteboard or similar.
Let's draw a quick sample graph here. Your own graph may be different.
C4 <-- feature
/
...--C1--C2--C3 <-- master
\
C5--C6 <-- origin/master
These Cn
s represent commits. We say that each commit "points back" to its parent (previous) commit. Note that the parent of a commit always comes earlier, but "earlier" doesn't always mean a lot. Here, probably made your branch feature
from your branch master
, then made one commit on it. Since then, you also ran git fetch
, which has obtained two commits from what Git calls a remote named origin
. These two commits are now on your origin/master
, but not on your master
.
What to copy and where to copy
If at this point you run git checkout feature
and then git rebase master
, Git will tell you that there is nothing to do (Current branch feature is up to date
). If you run git rebase origin/master
, though, it will do something.
The key as to why is, again, in the documentation. Rebase starts out by selecting which commits to copy using the two-dot notation described in the gitrevisions documentation:
master..feature
(the name feature
comes from your current branch, the one you have checked out; the name master
comes from your argument to git rebase
).
This two-dot notation means: On the commit graph, start at feature
and work backwards through its parents, and mark all these commits green. Then, on the same graph, start at master
and work backwards through its parents and mark all these commits red.
When we do this with the graph I've drawn, we mark C4
green, then mark C3
green, then mark C2
green, and so on. Commits C5
and C6
don't get marked at all. Then, we mark C3
red—this overrides its green—and mark C2
red, and so on. What we're left with is just C4
in green.
This means that what git rebase master
will do is copy commit C4
.
Now, the next step is to figure out where to copy. The answer is: right after whichever commit the upstream points to. So this says: Copy commit C4 to where it is right now. Which is pointless, so git rebase
says there is nothing to do.
If you run git rebase origin/master
right now, Git will mark C4
green, and then C3
green, and so on as before; then it will mark C6
red, C5
red, C3
red, and so on. This gives us the list of commits to copy, which once again is just commit C4
. We marked more commits, but the end result for what to copy turned out to be the same. And then the next step is to figure out where to copy, and this time, it's different.
We're supposed to copy all the commits—all one of them—to come right after the one origin/master
points-to, which is C6
. So we must copy commit C4
there.
How Git copies commits
The main command that copies commits is git cherry-pick
. So, internally, git rebase
often uses git cherry-pick
to do the copying. Some kinds of rebase use different commands internally, but the end result is intended to be the same. We can just view this as a cherry-pick, anyway.
To do the copying, Git first checks out the "after" commit, C6
. It does so without checking out any branch—it checks out the raw commit instead, using what Git calls "detached HEAD" mode. If something goes wrong during the rebase, you are left in this "detached HEAD" mode. That's not a big deal, and if everything goes right, Git gets right back out of this mode, but it's worth remembering.
Anyway, so Git "detaches HEAD" and makes it point to commit C6:
C4 <-- feature
/
...--C1--C2--C3 <-- master
\
C5--C6 <-- origin/master, HEAD (detached)
and then cherry-picks C4
to copy it. Read the git cherry-pick
documentation for details, but essentially, this copies the changes you made, by comparing C4
vs C3
, then makes a new commit with a copy of the commit message as well. The new copy comes after the HEAD commit, which is C6
, so it looks like this:
C4 <-- feature
/
...--C1--C2--C3 <-- master
\
C5--C6 <-- origin/master
\
C4A <-- HEAD (detached)
The new commit is a lot like C4
, but has a different parent commit: it points back to existing commit C6
, not to C3
.
After the copying
We've now finished copying all the commits we had to copy, so for its final trick, git rebase
makes the branch name that we were on, i.e., feature
, point to this last commit it copied. In other words, it reattaches your HEAD, but at the new graph location:
C4 [abandoned]
/
...--C1--C2--C3 <-- master
\
C5--C6 <-- origin/master
\
C4A <-- feature (HEAD)
What happens to the original, pre-copy, commit C4
?
Git secretly hangs on to it for a while, but it's "gone invisible". It has no name by which you can find it. It's not on any of your branches any more! Eventually, the things that keep it around—what Git calls reflog entries—expire, and Git removes it entirely through what Git calls garbage collection. For a short while, though, you can "undo" a git rebase
if you need to.
Branch names change—well, move, over time. Commits don't.
Let's go back to the original graph, i.e., back before we ran git rebase origin/master
:
C4 <-- feature
/
...--C1--C2--C3 <-- master
\
C5--C6 <-- origin/master
If, at this point, you run git checkout master
and then git merge origin/master
—or git checkout master
and then git pull
, which runs git merge
for you—Git will move your name master
in a "fast-forward" operation:
C4 <-- feature
/
...--C1--C2--C3
\
C5--C6 <-- master (HEAD), origin/master
There was no need to do anything else, so Git did the least work it could get away with. It made the name master
point to commit C6
, too, just like origin/master
does. (I've marked master
with HEAD
since we did a git checkout
master to get here.)
If you now do the same kind of rebase you were going to do earlier, now both names, master
and origin/master
, point to the same commit. So now, after git checkout feature
to attach your HEAD
to feature
, git rebase master
and git rebase origin/master
will do the same thing.
Remember, the rebase operation uses the upstream argument to find out what to copy and where to put the copies. That's all it uses this argument for, so if two different names point to the same commit, the "what to copy" and "where to copy" answers will be the same for both names.
It's when two different names point to different commits that you get different results. The green-vs-red trick may (or may not—try this with other graphs) mean that you get the same commits copied, but the "where to copy" part is different, by definition.