1.4 Consider intents against the common ancestor

Book can be downloaded from here

No need to be showing full ancestor and branches code separately anymore. Only showing the conflicted file or CB should be enough. Take this example of a conflict:

1.4.1 Example 4 - conflict on our beloved script

Listing 1.68:example 4
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "blue": "blue sky"} 
8 
9def getPhrase(color): 
10<<<<<<< HEAD 
11    phrase = "%s: %s" % (color, colors[color]) 
12    return phrase 
13||||||| c8bf13a 
14    phrase = colors[color] 
15    return phrase 
16======= 
17    if color in colors: 
18        phrase = colors[color].upper() 
19        return phrase 
20    else: 
21        sys.stderr.write("%s is not a known color\n" % color) 
22        sys.exit(1) 
23>>>>>>> example4/branchB 
24 
25print(getPhrase(sys.argv[1]))

If you jump right in, you could solve it as we did before. Start working on UB21 :

Listing 1.69:example 4 - Step 1 - UB
10<<<<<<< HEAD 
11    phrase = "%s: %s" % (color, colors[color]) 
12    return phrase 
13||||||| c8bf13a

Now we consider dML:

Listing 1.70:example 4 - Step 2 - dML
13||||||| c8bf13a 
14    phrase = colors[color] 
15    return phrase 
16======= 
17    if color in colors: 
18        phrase = colors[color].upper() 
19        return phrase 
20    else: 
21        sys.stderr.write("%s is not a known color\n" % color) 
22        sys.exit(1) 
23>>>>>>> example4/branchB

dML: There is a new condition that first checks if the color exists (line 17). If it doesn’t, it fails on the else block (lines 20-22). Preexisting lines were put inside the if block and got their indentation adjusted for that reason (lines 18-19). Let’s first copy over those lines from LB to UB.

Listing 1.71:example 4 - Step 3 - Copy from LB
10<<<<<<< HEAD 
11    if color in colors: 
12        phrase = colors[color].upper() 
13        return phrase 
14    else: 
15        sys.stderr.write("%s is not a known color\n" % color) 
16        sys.exit(1) 
17||||||| c8bf13a

We almost got it. But there’s a problem: Because we copied over content verbatim, we lost the rephrased line as we had it on th UB, remember? Look at UB before we started solving the conflict section again (line 11):

Listing 1.72:example 4 - Step 1 - UB
10<<<<<<< HEAD 
11    phrase = "%s: %s" % (color, colors[color]) 
12    return phrase 
13||||||| c8bf13a

Ok... then we could replace the line where we get the value from the array and we should be fine: 22

Listing 1.73:example 4 - Step 4 - Edit UB
10<<<<<<< HEAD 
11    if color in colors: 
12        phrase = "%s: %s" % (color, colors[color]) 
13        return phrase 
14    else: 
15        sys.stderr.write("%s is not a known color\n" % color) 
16        sys.exit(1) 
17||||||| c8bf13a

This is not correct either. Now we lost the upper() call coming from LB. So, we can’t just copy changes verbatim from one block to the other? It really depends on what the lines are about. In this case we have a line that is getting changes from both branches, so we need to consider the intent of both branches, not the literal changes.

Let’s start over solving the conflict from scratch from UB:

Listing 1.74:example 4 - Step 1 - UB
10<<<<<<< HEAD 
11    phrase = "%s: %s" % (color, colors[color]) 
12    return phrase 
13||||||| c8bf13a

A new condition is added and we copy it over:23

Listing 1.75:example 4 - Step 3 - Conditional
10<<<<<<< HEAD 
11    if color in colors: 
12    phrase = "%s: %s" % (color, colors[color]) 
13    return phrase 
14||||||| c8bf13a

We need to adjust indentation for those lines just like it was done on the other branch:

Listing 1.76:example 4 - Step 4 - Adjust indentation
10<<<<<<< HEAD 
11    if color in colors: 
12        phrase = "%s: %s" % (color, colors[color]) 
13        return phrase 
14||||||| c8bf13a

We add the upper() call:

Listing 1.77:example 4 - Step 5 - upper() call
10<<<<<<< HEAD 
11    if color in colors: 
12        phrase = "%s: %s" % (color, colors[color].upper()) 
13        return phrase 
14||||||| c8bf13a

Add the else block:

Listing 1.78:example 4 - Step 6 - else block
10<<<<<<< HEAD 
11    if color in colors: 
12        phrase = "%s: %s" % (color, colors[color].upper()) 
13        return phrase 
14    else: 
15        sys.stderr.write("%s is not a known color\n" % color) 
16        sys.exit(1) 
17||||||| c8bf13a

And now we have reached the solution to the conflict. Remove the other sections of the conflict and the conflict markers:

Listing 1.79:example 4 - final
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "blue": "blue sky"} 
8 
9def getPhrase(color): 
10    if color in colors: 
11        phrase = "%s: %s" % (color, colors[color].upper()) 
12        return phrase 
13    else: 
14        sys.stderr.write("%s is not a known color\n" % color) 
15        sys.exit(1) 
16 
17print(getPhrase(sys.argv[1]))

But then, if we can’t copy over code from any of the two branches, how do we work around it? The trick is to replicate changes that were introduced from dML and put them on UB24 , not actually copying stuff verbatim.

This task where you carefully analyze what has changed (word by word, space by space, tab by tab, parenthesis by parenthesis, conditional by conditional, etc etc) has to be done every single time you get a conflict. I would love to be able to provide you with a trick that makes this process simpler... but there isn’t any. One helpful tool you could consider under certain circumstances is to use git diff –color-words to compare the common ancestor with the two branches or even the branches themselves: 25

Figure 1.1:Differences introduced by branchA
PIC
Figure 1.2:Differences introduced by branchB
PIC

When people decide to cut corners and not do a thorough check for the differences they easily end up with changes that are not copied over from the branches involved (or copying things verbatim deleting other changes) and that is what will eventually end up exploding later on, sometimes in a BIG... SPECTACULAR... FLASHY WAY.... in production. Certainly not the best of all places to find out that there was some change that was not carried over when a merge was done a few months or weeks before.

If code is added on one branch, it should probably end up on the resulting code as well, just like we did with the else section of code 26. And deleted code is a special case that has to be analyzed carefully and I will take care of it later on.

1.4.2 Exercises

Exercise 4 - a git conflict

From git’s repo, checkout d9d65e9f6a and merge b57e8119e6. 27. Solution is here.

Exercise 5 - another git conflict

From git’s repo, checkout cf054f817a and merge caf388caa1. 28. Solution is here.

Copyright 2020 Edmundo Carmona Antoranz