You’ve heard of merge conflicts, but what about merge bugs? Like conflicts, these are caused when multiple developers work on the same code and then merge changes. Conflicts (when the same line is changed by more than one developer) are easy to detect, but bugs outside of those conflicts are much more complex. They won’t be obvious in merge tools, and they may be difficult to catch via code review.
Professional developers should have a process in place to help identify those merge issues that go beyond merge conflicts. Part one of this series will help to identify the problem and lay out a process. Part two will provide a tool to help make the process manageable.
To illustrate how easy it is for concurrent changes to introduce merge bugs, we will create a very simple Git repository and add a couple of feature branches that modify one source file (see Figure 1 below).
Figure 1. AddUserSelectableDisplayUnits is merged into master but AllValuesInMksUnits has not been merged into master yet.
The initial source code committed to master is shown in CodeSample 1 below.
CodeSample 1. Initial commit on master (e8e702b7).
To prevent another Mars Surveyor type incident your task is to ensure that all data is stored and accessed in MKS units (meter, kilogram, and seconds aka System International Units). You create a new feature branch called AllValuesInMksUnits by branching from master. Before you complete your changes another developer creates a branch called AddUserSelectableDisplayUnits from master and makes the changes displayed in CodeSample 2 below.
CodeSample 2. master (71ffe949) after merging in the AddUserSelectableDisplayUnits branch (changes highlighted in yellow).
The developer added the GetSpeedForDisplay method, lines 9 – 21. At this point the Speed member is in miles/hour. The AddUserSelectableDisplayUnits branch gets merged back into master before you complete your work on the AllValuesInMksUnits branch. When you are ready to commit your changes the code is shown in CodeSample 3 below.
CodeSample 3. The AllValuesInMksUnits (e20b918a) branch that needs to be merged into master
(changes highlighted in yellow).
Lines 13, 20, 21 and 24 have been changed. If you merge the AllValuesInMksUnits branch into master you will not encounter any conflicts—but there would be a bug. The result is displayed in CodeSample 4 below.
CodeSample 4. AllValuesInMksUnits merges into master with no conflicts but introduces errors
(highlighted in red).
The bug can be seen in lines 14 and 18 where the Speed is assumed to be in miles/hour. Since the merge will not cause a conflict, the only way to catch this bug is by careful review of the code. In this unrealistically simple example, with one source file and only two feature branches, you most likely would catch this issue by doing a diff between the AllValuesInMksUnits and master.
Now let us consider a more realistic case where there are dozens of modified files, many feature branches and maybe even a release in between creation of branch AllValuesInMksUnits and the merge into master. In addition, the scope of the changes that you make on the feature branch often results in substantial restructuring of some of the files, which makes it laborious to understand how your changes will interact with changes from other commits on the merge target branch.
There is no simple process that can guarantee that the results of a merge don’t result in errors, but we can describe a straightforward process that can highlight the files where issues are most likely and isolate the changes that other developers made so that we can avoid disrupting their changes. What we are most interested in seeing is the files that we changed in our feature branch that were also changed in the merge target branch. Then we want to see the changes that others made so we can preserve the intent of their changes.
Formally the steps are:
If we apply this process to the previous example, we find that the common commit is e8e702b7. Only the Sample file was changed on the feature branch. Also the only file changed on master is the Sample file. Now the differences are presented in Meld with three tabs (see Figures 2, 3, & 4 below).
Figure 2. Difference between the common commit and tip of master.
Figure 3. The difference between the common commit and the tip of the AllValuesInMksUnits.
Figure 4. Three-way difference between the common commit and the two branches that we want to merge.
Hopefully at this point it has become clear how this process can aid in the identification and resolution of issues while merging branches. Part two of this series will present a tool that uses the features of Git and Meld to make presenting these views a single command.