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?
I think the way to understand this, as with so much about
rebase, is to understand two things:rebaseis 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 xxxis a shorthand. The results can therefore be surprising.The full form of
git rebaseisgit rebase --onto x y z, which means: "Starting at (but not including)y, cherry-pick each successive commit ontoxuntil you have cherry-pickedz."When you use the shorthand form,
xis usually the commit that you specify,zis the current branch, andyis 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--rootoption.To illustrate, let us suppose branch
oneconsists of commitsaand thenb, and branchtwoconsists of commitscand thend:Then if you are on
twoand you saygit rebase one,oneisb, so Git walks backwards fromtwo(d) tocand says to itself: can I cherry-pick the diff "nothingness-to-c" ontob? 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:Note that
c'andd'are copies (ie new commits created by Git). The originalcanddstill exist, but no branch name now points at them, and eventually they will be deleted through garbage collection.