While experimenting with git, I created two branches without a common commit ancestor. Let's call them "master" and "other". The current branch is "master".
As expected, trying to merge "other" via:
git merge other
produced: fatal: refusing to merge unrelated histories
This is precisely what I expected to happen. Surprisingly to me, running rebase via:
git rebase other
succeeded.
This was a surprise to me as I assumed that rebase requires a common commit ancestor just like git merge. Does git rebase ever require a common ancestor?
CodePudding user response:
I think the way to understand this, as with so much about rebase
, is to understand two things:
rebase
is justcherry-pick
: it creates new commits based on successive diffs, leaving the old ones in place, and appends them to a target. The difference is merely that, afterwards, cherry picking advances the destination branch name, whereas rebasing transfers the source branch name.git rebase xxx
is a shorthand. The results can therefore be surprising.
The full form of git rebase
is git rebase --onto x y z
, which means: "Starting at (but not including) y
, cherry-pick each successive commit onto x
until you have cherry-picked z
."
When you use the shorthand form, x
is usually the commit that you specify, z
is the current branch, and y
is the common ancestor of the two.
But there are circumstances where the shorthand doesn't work that way. In this case, there is no common ancestor. So for y
, Git chooses the "root", i.e. nothingness — just as if you had used the full form with the --root
option.
To illustrate, let us suppose branch one
consists of commits a
and then b
, and branch two
consists of commits c
and then d
:
a <-- b (one)
c <-- d (two)
Then if you are on two
and you say git rebase one
, one
is b
, so Git walks backwards from two
(d
) to c
and says to itself: can I cherry-pick the diff "nothingness-to-c
" onto b
? If so (because there's no conflict), it does. Then it says: can I cherry-pick the diff "c
-to-d
" onto the commit I just created? If so, it does. And that's the end — we've reached the current branch commit — so it stops, and shifts the current branch pointer (HEAD
) to the last new commit that it created:
a <-- b <-- c' <-- d' (two)
^
(one)
Note that c'
and d'
are copies (ie new commits created by Git). The original c
and d
still exist, but no branch name now points at them, and eventually they will be deleted through garbage collection.