Book can be downloaded from here
Let’s work on our first example from a real project. In this case, git itself.
From git’s repo, checkout revision 80648bb3f2 and merge 20a5fd881a 8. You end up with a conflict in pack-bitmap.c:
I hear you this time. Let’s keep all the lines, you say? Which block should come first? HEAD or the other branch’s? Maybe the call to ewah_iterator_init() should be placed between the conditional and the call to init_type_iterator() on the other branch? Perhaps all the lines should be removed, actually? Maybe we just need to keep only a part of it?
Are all of those scenarios possible? Ah, yes, they are all possible. And are they all correct? Ah, nope. Given that they end up being very different in terms of what the code will end up doing, not all of them are correct. But then, is it a guessing game we play in order to solve conflicts? Nope, no need to guess. You may not be aware of it yet but all you need is a little bit more information... information that, by the way, is not provided to us in the conflict as we see it there. Let me show you some possibilities with a little more information so you can see what’s going on.
For the sake of explanation, assume that the file on the common ancestor 9 is like this, at around those lines:10
If this were the case, that would tip us that, based on the conflict, each one of the branches removed one piece of the original code. HEAD removed the conditional from line 674 and the call to init_type_iterator(). The other branch removed the call to ewah_iterator_init().
Then conflict resolution would be:
Now assume that the common ancestor file looks like this:
In this case, HEAD removed the conditional started on line 674, the other branch removed the call to ewah_iterator_init() and added a call to init_type_iterator(). That points to having this as the conflict resolution:
Finally, assume that the common ancestor file looks like this:
In this scenario, HEAD removed the conditional started on line 673, added a call to ewah_iterator_init(). The other branch kept the conditional but added a later call to init_type_iterator(). This begs for yet another different resolution. We need to remove the conditional and keep the two new calls. Which one should come before? That’s a fairly good question to ask and seeing the code alone won’t be able to tell us. You might need more background knowledge to be able to solve it properly, or check the requirements that brought in each call11 . Let’s assume, for the sake of moving forward with the example, that we figure out that we should place the call to init_type_iterator() before the call to ewah_iterator_init(). Then conflict resolution would be:
So 3 different scenarios for the same conflict. Each scenario meant a different conflict resolution. Now, what I want you to realize is this hard cold truth: the common ancestor 12 is driving conflict resolution. If you do not consider the common ancestor you would be.... what would be the best word to describe it? Oh, yes! Guessing! And I don’t care how educated your guess is (in terms of background knowledge of the code you are working with). You would have to have a memory that beats that provided by git in terms of knowing what code looked like in the past so that I stop calling it so.
Having cleared that up, let’s continue with the current example. What does the common ancestor look like in this case, for real? Finding that out is a process that involves more than one step so let’s start.
First things first. What is the common ancestor?
The common ancestor is revision d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783. On that revision, is the file called the same? Let’s hope so! Did the content change much? Perhaps some lines were added or deleted before the block we are working with? Let’s hope it didn’t change that much! 13 Let’s give it a try:
Oops! We ran out of luck! 14 The file hadn’t been renamed (great!) its previous content did change (bummer!) and so the section we need to look at is not at the same position of the conflict we are dealing with. After surfing the file a little bit, we find the block we care about, some 40 lines before where we were hoping it would be:
And now we could compare what each branch did. On HEAD, we removed the conditional started on line 633.15 . On the other branch, we removed the call to ewah_iterator_init() and we added a call to init_type_iterator(). Which means that in our conflict resolution we would keep the call to init_type_iterator() only because the other parts that were present on the common ancestor are gone:
And by comparing this with 0df82d99da you should find no meaningful differences. 16
I hope this time you really got why it’s so important to be able to see the common ancestor on a conflict. As I said before, if you don’t consider the common ancestor, you are making an educated guess at best, a total disaster at worst. I agree it is a lot of work if we need to go check what the common ancestor code looks like every single time we have a conflict.
What if the file had been renamed? Can we figure out what the original name was easily?
Before you burn the book in frustration17 , let me show you a little git trick. git can actually do the work of showing you what the common ancestor looks like without additional work on your part. By setting merge.conflictStyle to diff3 18, git will kindly add what the common ancestor looked on the section of code that is related to the conflict. Let me show you the current conflict after applying this option for your own amusement:
Wow! We get to see the common ancestor code between the sections of each branch. git is also kind enough to tell us the common ancestor revision19 . Now we can see what each one of the branches did.
In order to solve our conflict, first, start working from HEAD:
Then consider how the other branch changed from the common ancestor:
The call to ewah_iterator_init() on line 679 was removed and a call to init_type_iterator() was added on line 684. So we replicate that on HEAD:
And we can now remove the remaining conflict block pieces and markers:
See? No hassle!
But come on! It is possible to survive solving conflicts without seeing the common ancestor block, right? But of course! Just like it’s possible to develop software without using any unit tests whatsoever, if you know what I mean. It’s not like we need it like oxygen or water. But I already told you the what it is like when you decide not to look at the common ancestor: It’s a guess. Using this option to see the common ancestor code on a conflict is the single most important tip in this handbook.
From git’s repo, checkout revision fe870600fe and merge 1bdca81641 20. Solve both conflicts (there are 2 conflicting files, one conflict section on each one of the files). Solution is here.
Copyright 2020 Edmundo Carmona Antoranz