Rebase without tears¶
Why I wrote this page¶
I’m looking at the git-rebase man page now. I may not be alone in finding it hard to understand, and easy to forget. I have twice or three times worked out how it worked, and then forgotten, and wished I had written something down to explain it to myself. Here is that explanation.
What is this “rebase” of which you speak?¶
Actually it’s a little difficult to explain. Rebasing is the process of taking a fragment of git change history, and rewriting that history as if it had begun at a different commit. It’s easiest to explain by example, and there are some examples later in this page. See also the Pro-Git chapter on rebasing
Your actual rebase¶
I like to think of rebase in its full form, because the full form helps to remind me of what it is doing. Here’s the full form of most rebase commands 1:
git rebase --onto <graft-point> <exclude-from> <include-from>
I’m using different names from the git-rebase
man page - see
2.
The shorter forms use defaults for things you don’t specify:
If you don’t specify
--onto
,<graft-point>
defaults to<exclude-from>
If you don’t specify an
<include-from>
,<include-from>
defaults to the current branch.
Which commits will rebase apply?¶
Rebase will apply all the commits found by:
git log <exclude-from>..<include-from>
These are the commits that are reachable from <include-from>
that are not
reachable from <exclude-from>
. See Logging with two dots.
Which branch does rebase modify?¶
rebase modifies the <include-from>
branch. If you don’t specify
<include-from>
it will modify the default for <include-from>
, that is, the
current branch.
Basic example¶
Let’s go through the man page examples. Here’s a history tree:
A---B---C topic
/
D---E---F---G master
We want to take the novel contents of the topic
branch (A, B, C
) and
regraft it so that it starts at the master
branch, like this:
A'--B'--C' topic
/
D---E---F---G master
Let’s do something to ease the explanation, and tag the divergence point E
thus:
git tag divergence-point topic~3 # E
Obviously that gives us:
A---B---C topic
/
| F---G master
|/
D---E (tag) divergence-point
Reading the Your actual rebase command, we suspect the command we want is:
git rebase --onto master divergence-point topic
And indeed, that does give us what we want. However we had a to make a tag for the divergence point, and that was a bit annoying. Can we get away without that?
Yes, because because the meaning of <exclude-from> <include-from>
above is
to collect the commits that you are going to apply. See Which commits will rebase apply?
for an explanation. The commits wil be those shown by git log
<exclude-from>..<include-from>
. I took the liberty of making a repository to
match the history above. Here is the result of git log --oneline
master..topic
, before the rebase:
8de3e90 C
9dcbae2 B
cc3741a A
And of course that is the same as git log --oneline divergence-point..topic
.
So we could also do the rebase command with:
git rebase --onto master master topic
And, in fact, if you don’t specify the --onto
option, then rebase assumes
you want to graft onto the <exclude-from>
position, so you could also do:
git rebase master topic
and in fact, if you don’t specify the <include-from>
position, rebase assumes
that you want <include-from>
to be the state of the current branch, so you
could also do:
git checkout topic # unless you are on ``topic`` already
git rebase master
Other examples¶
Here is another example from the git-rebase
man page. We want to go from
this:
o---o---o---o---o master
\
o---o---o---o---o next
\
o---o---o topic
to this:
o---o---o---o---o master
| \
| o'--o'--o' topic
\
o---o---o---o---o next
How? Let’s check the Your actual rebase command. Maybe it is this:
git rebase --onto master next topic
Yup, that’s it! You understand rebase!
Now we want to go from this:
H---I---J topicB
/
E---F---G topicA
/
A---B---C---D master
to this:
H'--I'--J' topicB
/
| E---F---G topicA
|/
A---B---C---D master
We check the Your actual rebase command. Could it be this?:
git rebase --onto master topicA topicB
Could it be anything else? Congratulations, you are now a rebase master.
Safety¶
Summary: always make a new branch before doing a rebase
You’ll see from Which branch does rebase modify? that when you a rebase you will change the branch you are rebasing. As you remember from the Basic example, if you do:
git rebase --onto master master topic
on this history graph:
A---B---C topic
/
D---E---F---G master
then you’ll get this:
A'--B'--C' topic
/
D---E---F---G master
If you only have branches master
and topic
then you have just orphaned
commits A, B, C
which will in due course get cleaned out of your repository.
You might want to keep track of where you were before. You might want to do
that in case you made a mistake and need to retrace your steps. The easiest way
to do this, is to make a copy of the branch before doing the rebase, like this:
git branch topic-rebased topic
git rebase --onto master master topic-rebased
or, if you are already on topic
and you want to use the shorthand form of
rebase:
git branch topic-rebased topic
git checkout topic-rebased
git rebase master
Now you have left topic
to be a branch that records where you were before:
A'--B'--C' topic-rebased
/
D---E---F---G master
\
A---B---C topic
If you forget to make a new branch before rebase, and you do need to go back,
then you can use git reflog
.
Footnotes
- 1
I’ve missed out the
--interactive
option, but that doesn’t change the logic. There is one more substantial variation of the Your actual rebase command, using--root
. This goes:git rebase --onto <graft-point> --root <include-from>
I’ve put a couple of extra spaces between
--root
and<include-from>
to emphasise that--root
is a flag, and<include-from>
is an argument with the same meaning as for the normal rebase command.If you do
git checkout <include-from>
and thengit log
, you’ll see all the commits down to and including the first (root) commit of that branch. The root commit is a commit without a parent. The--root
version of the rebase commands then takes all the commits, from the root commit up until<include-from>
, including the root commit, and grafts them onto<graft-point>
.Let’s say you somehow have two detached histories in your repository:
A--B--C--D master X--Y--Z other-branch
The root of
master
is A, and the root ofother-branch
is X. To attach these histories you could do:git rebase --onto master --root other-branch
resulting in:
A--B--C--D master \ X'--Y'--Z' other-branch
It would be annoying to have to do the same operation without the
--root
option, because you’d first have to find the root commit, apply the root commit, and then rebase the rest of the X-Y-Z history on top of that, rather like:git tag root-of-other-branch other-branch~2 # tags X commit git branch tmp-branch master # start rebase at master git checkout tmp-branch git cherry-pick root-of-other-branch # apply root commit git rebase --onto tmp-branch root-of-other-branch other-branch # You are now on the rebased other-branch git branch -D tmp-branch
- 2
I’m using different names for the command options, compared to the
git-rebase
man page. The man page uses:<newbase>
for my<graft-point>
<upstream>
for my<exclude-from>
<branch>
for my<include-from>