2019-09-23 A comparison between merging and rebasing

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. ;-)

CategoryEnglish, CategoryBlog, CategoryGit