Many Git tutorials and howtos discuss the question whether you should merge or rebase your branches on the master
branch when ready to include some feature in your code. What they usually do not mention is that rebase may be trickier to perform than merge. Why is that so? Let’s dive in and see.
cd /mem rm -rf git-rebase-vs-merge git init git-rebase-vs-merge cd git-rebase-vs-merge echo -e 'a1\na2' > a.txt git add a.txt git commit -m "Add a1 and a2" echo aa >> a.txt git commit -am "Add aa" echo aaa >> a.txt git commit -am "Add aaa" git checkout -b branch HEAD^^ echo -e 'a1\na2\nb' > a.txt git commit -am "Add b" echo -e 'a1\na2\nb\nbb' > a.txt git commit -am "Add bb"
We have prepared a repo with three commits on the master
branch. Then we created another branch
, starting two commits behind master, and added two commits on top of that one. They are prepared in such a way a conflict is guaranteed.
Let us now try to merge them.
cd /mem/git-rebase-vs-merge git checkout branch git merge master git diff
Auto-merging a.txt CONFLICT (content): Merge conflict in a.txt Automatic merge failed; fix conflicts and then commit the result. diff --cc a.txt index 475899f,3e7ace2..0000000 --- a/a.txt +++ b/a.txt @@@ -1,4 -1,4 +1,9 @@@ a1 a2 ++<<<<<<< HEAD +b +bb ++======= + aa + aaa ++>>>>>>> master
As we expected, the merge resulted in a conflict. We can of course resolve it and proceed with committing the result, and we are done. Assuming that we want to keep all the lines:
cd /mem/git-rebase-vs-merge sed '/<<<<<<<\|>>>>>>>\|=======/d' -i a.txt git add a.txt git commit -m 'Merge master into branch'
Let us now see what would happen if we proceeded with rebasing instead. We start exactly the same (see the first snippet above). Then, instead of merging, we perform rebase:
cd /mem/git-rebase-vs-merge git checkout branch git rebase master git diff
First, rewinding head to replay your work on top of it... Applying: Add b Using index info to reconstruct a base tree... M a.txt Falling back to patching base and 3-way merge... Auto-merging a.txt CONFLICT (content): Merge conflict in a.txt Patch failed at 0001 Add b Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue". You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort". diff --cc a.txt index 3e7ace2,1a2124d..0000000 --- a/a.txt +++ b/a.txt @@@ -1,4 -1,3 +1,8 @@@ a1 a2 ++<<<<<<< HEAD +aa +aaa ++======= + b ++>>>>>>> Add b
Assume now that we want all the lines again, but in a different order. Instead of playing around with sed
(I am convinced one could do it, but I don’t know enough sed trickery to even attempt it), let us just generate the “solved” state from scratch. (Of course, in Real Life™ you would just normally solve the conflict with the editor.)
cd /mem/git-rebase-vs-merge echo a1 > a.txt echo a2 >> a.txt echo b >> a.txt echo aa >> a.txt echo aaa >> a.txt git add a.txt git rebase --continue 2>&1 exit 0
Applying: Add b Applying: Add bb Using index info to reconstruct a base tree... M a.txt Falling back to patching base and 3-way merge... Auto-merging a.txt CONFLICT (content): Merge conflict in a.txt error: Failed to merge in the changes. hint: Use 'git am --show-current-patch' to see the failed patch Patch failed at 0002 Add bb Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue". You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort".
See what happened?
This is what rebasing is, according to the manual:
The commits […] are then reapplied to the current branch, one by one, in order.
This means that if there is more than one commit on the branch being rebased (branch
in our case), it may happen that more than one of them introduces a conflict. This may make rebasing much more tedious than merging.
Is it a bad thing? Not necessarily. If we have small, atomic commits, they may as well not result in conflict, and even if they do, these conflicts may be easier to solve (since they are smaller). On the other hand, this may be more time-consuming than merging.
(In the case that led me to write this post, resolving conflicts was a real pain, so I decided to stick with merge anyway.)
And by the way, we are left with unresolved rebase in the example above. Feel free to finish it by yourself – my point is already proven.