1.1 What is a Conflict?

Book can be downloaded from here

A conflict arises when there are two different directions in which the same piece of code was modified. As long as you keep them separate (as in separate branches) there will be no problem, but when you try to merge them, git will stop and ask you for your help so that you can figure out what needs to be done to finish the merge operation.

1.1.1 Example 0 - A non-conflict

Let me introduce you to one of the files that we will be using (with a few variations along the way) as the basis for some of our examples moving forward.

Listing 1.1:Example 0 - common ancestor
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    phrase = colors[color] 
11    return phrase 
12 
13print(getPhrase(sys.argv[1]))

It’s a very simple python script.

Suppose that two developers start working from this file having to add a different color each. Developer A produces this:

Listing 1.2:Example 0 - Developer A
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "red": "red tide", 
8          "blue": "blue sky"} 
9 
10def getPhrase(color): 
11    phrase = colors[color] 
12    return phrase 
13 
14print(getPhrase(sys.argv[1]))

We can see that color red was added on line 7.

And Developer B produced this:

Listing 1.3:Example 0 - Developer B
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "green": "green peas", 
7          "white": "white noise", 
8          "blue": "blue sky"} 
9 
10def getPhrase(color): 
11    phrase = colors[color] 
12    return phrase 
13 
14print(getPhrase(sys.argv[1]))

Developer B added color green on line 6.

History of the branches looks like this:

Listing 1.4:Example 0 - branch history
* d3c8087 (example0/branchB) Adding green peas 
| * 4f281f2 (example0/branchA) Adding red tide 
|/ 
* f44c861 Get a phrase from a color

Given that the lines were added at different locations (separated by the line that defines color white phrase), then git has no problem merging them:

Listing 1.5:Example 0 - git merge output
$ git merge example0/branchB --no-edit 
Auto-merging example.py 
Merge made by the ’recursive’ strategy. 
 example.py | 1 + 
 1 file changed, 1 insertion(+)

Resulting history looks like this: 1

Listing 1.6:Example 0 - final branch history
*   74a049b Merge branch ’example0/branchB’ into example0/branchA 
|\ 
| * d3c8087 (example0/branchB) Adding green peas 
* | 4f281f2 Adding red tide 
|/ 
* f44c861 Get a phrase from a color

The resulting file looks like this:

Listing 1.7:Example 0 - merged code
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "green": "green peas", 
7          "white": "white noise", 
8          "red": "red tide", 
9          "blue": "blue sky"} 
10 
11def getPhrase(color): 
12    phrase = colors[color] 
13    return phrase 
14 
15print(getPhrase(sys.argv[1]))

And you can see how the resulting file has both colors green and red:

Listing 1.8:Example 0 - colors section
5colors = {"black": "black mirror", <-- Preexisting line 
6          "green": "green peas",   <-- Line from Developer B 
7          "white": "white noise",  <-- Preexisting line 
8          "red": "red tide",       <-- Line from Developer A 
9          "blue": "blue sky"}      <-- Preexisting line

This is also visible if you check the annotations of the file:2

Listing 1.9:Example 0 - annotations
^f44c861 (Developer A 1584200950 -0600  5) colors = {"black": "black mirror", 
d3c80878 (Developer B 1584201394 -0600  6)           "green": "green peas", 
^f44c861 (Developer A 1584200950 -0600  7)           "white": "white noise", 
4f281f22 (Developer A 1584201248 -0600  8)           "red": "red tide", 
^f44c861 (Developer A 1584200950 -0600  9)           "blue": "blue sky"}

There we have a successful merge. Git is able to merge the code because they touch different sections of the file. Git can see that the two pieces of code are separated by the line defining the white color phrase and so the merge goes fine.

Let’s go back to our common ancestor and let’s show 2 different conflict examples.

1.1.2 Example 1 - A simple conflict

First (again) the common ancestor, which is the same common ancestor we used for example 0.

Listing 1.10:Example 1 - common ancestor
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    phrase = colors[color] 
11    return phrase 
12 
13print(getPhrase(sys.argv[1]))

Now, just like we did in our previous example, let’s have our two developers add exactly the same colors that were added before but, instead of doing it on separate places, they will do it at exactly the same spot.

Developer A adds color red on line 7:

Listing 1.11:example 1 - Developer A
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "red": "red tide", 
8          "blue": "blue sky"} 
9 
10def getPhrase(color): 
11    phrase = colors[color] 
12    return phrase 
13 
14print(getPhrase(sys.argv[1]))

Developer B adds color green also on line 7:

Listing 1.12:example 1 - Developer B
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "green": "green peas", 
8          "blue": "blue sky"} 
9 
10def getPhrase(color): 
11    phrase = colors[color] 
12    return phrase 
13 
14print(getPhrase(sys.argv[1]))

The new branch history would be something like this:

Listing 1.13:Example 1 - branch history
* 6e7707e (example1/branchB) Adding green peas 
| * 3633ec9 (HEAD -> example1/branchA) Adding red tide 
|/ 
* 9f086a6 Get a phrase from a color

It’s basically the same example only that the colors were added at the same spot. If somebody were to ask git to merge both branches, we would see something very different from example 0:

Listing 1.14:Example 1 - git merge output
$ git merge example1/branchB 
Auto-merging example.py 
CONFLICT (content): Merge conflict in example.py 
Automatic merge failed; fix conflicts and then commit the result

The status of the working tree at the moment:

Listing 1.15:Example 1 - git status
$ git status 
On branch example1/branchA 
You have unmerged paths. 
  (fix conflicts and run "git commit") 
  (use "git merge --abort" to abort the merge) 
 
Unmerged paths: 
  (use "git add <file>..." to mark resolution) 
        both modified:   example.py 
 
no changes added to commit (use "git add" and/or "git commit -a")

It can be seen how example.py is listed in the section called Unmerged paths. It’s telling us it is the kind of conflict where both branches modified a file in different conflicting directions and have produced a Content conflict. 3

The resulting file would look something like this: 4

Listing 1.16:example 1 - conflicting file
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7<<<<<<< HEAD 
8          "red": "red tide", 
9======= 
10          "green": "green peas", 
11>>>>>>> example1/branchB 
12          "blue": "blue sky"} 
13 
14def getPhrase(color): 
15    phrase = colors[color] 
16    return phrase 
17 
18print(getPhrase(sys.argv[1]))

git tried its best to figure out what to do but, given that the two branches do different things at the exact same location in the code, it asks us to give it a hand so that the code can be merged.

The conflicting portion of code is, as you might have guessed:

Listing 1.17:example 1 - conflict section
7<<<<<<< HEAD 
8          "red": "red tide", 
9======= 
10          "green": "green peas", 
11>>>>>>> example1/branchB

This conflict has 2 sections. The first section is how the code looks on HEAD. 5, and we can see it at the conflict starter mark, on line 7.

Listing 1.18:example 1 - HEAD section
7<<<<<<< HEAD <-- conflict section start marker 
8          "red": "red tide", 
9======= <-- separation between HEAD and the other branch

The second section is how the conflicting section of code looks on the other branch that we asked to merge. Git tips us that the name of the branch is example1/branchB on the conflict end marker, on line 11:

Listing 1.19:example 1 - HEAD section
9======= <-- separation between HEAD and the other branch 
10          "green": "green peas", 
11>>>>>>> example1/branchB <-- conflict section end marker

The two sections are separated by a chain of equal signs.

The key question that we should try to answer at this point is: can we solve the conflict?

You are probably thinking: But of course! Just set a line for each one of the new colors and we are done so that we get all colors in the dictionary.

Listing 1.20:example 1 - HEAD solved conflict
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "red": "red tide", 
8          "green": "green peas", 
9          "blue": "blue sky"} 
10 
11def getPhrase(color): 
12    phrase = colors[color] 
13    return phrase 
14 
15print(getPhrase(sys.argv[1]))

Congrats! You have solved your first (right?) conflict. The following steps are required by git in order to consider that the file has been merged to wrap up the merge operation:

1.1.3 How to wrap up a conflict?

If at this point you run git status, you will see that nothing has changed, from git’s point of view:

Listing 1.21:git status after solving the conflict
$ git status 
On branch example1/branchA 
You have unmerged paths. 
  (fix conflicts and run "git commit") 
  (use "git merge --abort" to abort the merge) 
 
Unmerged paths: 
  (use "git add <file>..." to mark resolution) 
        both modified:   example.py 
 
no changes added to commit (use "git add" and/or "git commit -a")

Git wasn’t paying attention to you editing the file. We need to specifically take the file as it is right now:

Listing 1.22:git add; git status
$ git add example.py 
$ git status 
On branch example1/branchA 
All conflicts fixed but you are still merging. 
  (use "git commit" to conclude merge) 
 
Changes to be committed: 
        modified:   example.py

When all files have been merged/added to index, a revision is finally created by running git merge –continue. First you should get an editor with some default content for what the revision comment will be. Edit the content to what you find appropriate. Save and exit. When you exit the editor, the merge revision will be created. Here it is how it looks after going out of the editor:

Listing 1.23:git merge –continue
$ git merge --continue 
[example1/branchA 03938ed] Merge branch ’example1/branchB’ into example1/branchA

Then, we get our merge revision:

Listing 1.24:example 1 - final branch history
*   03938ed (example1/branchA) Merge branch ’example1/branchB’ into example1/branchA 
|\ 
| * 6e7707e (example1/branchB) Adding green peas 
* | 3633ec9 Adding red tide 
|/ 
* 9f086a6 Get a phrase from a color

1.1.4 Example 2 - Another simple example

Let’s try another simple example. Using the same common ancestor as before (we were using only 3 colors, remember?), now Developer A ends up with this:

Listing 1.25:example 2 - Developer A
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    phrase = colors[color] 
11    phrase = "%s: %s" % (color, phrase) 
12    return phrase 
13 
14print(getPhrase(sys.argv[1]))

This is something different. The output phrase now contains the color and then the associated phrase, separated by a colon (line 11). If we run it with blue, we get:

Listing 1.26:example 2 - running script from Developer A
$ ./example.py blue 
blue: blue sky

Developer B ended up with this:

Listing 1.27:example 2 - Developer B
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    phrase = colors[color] 
11    phrase = phrase.upper() 
12    return phrase 
13 
14print(getPhrase(sys.argv[1]))

Developer B is adding an upper() call to set the phrase in uppercase (line 11). If we run the script like we did before:

Listing 1.28:example 2 - running script from Developer B
$ ./example.py blue 
BLUE SKY

And history looks like this:

Listing 1.29:example 2 - branch history
* 9b9d7b2 (example2/branchB) Using upper on the phrase 
| * c944ee5 (example2/branchA) Showing color and phrase 
|/ 
* 8c9d315 Get a phrase from a color

And we can foresee the conflict if we tried to merge this time, don’t we?

Listing 1.30:example 2 - Conflicted file
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    phrase = colors[color] 
11<<<<<<< HEAD 
12    phrase = "%s: %s" % (color, phrase) 
13======= 
14    phrase = phrase.upper() 
15>>>>>>> example2/branchB 
16    return phrase 
17 
18print(getPhrase(sys.argv[1]))

In this case, let’s keep the line where we set the phrase to have the original color, then we do the upper() call and it should be good:

Listing 1.31:example 2 - Resolved conflict
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    phrase = colors[color] 
11    phrase = "%s: %s" % (color, phrase) 
12    phrase = phrase.upper() 
13    return phrase 
14 
15print(getPhrase(sys.argv[1]))

And if we run the resulting script:

Listing 1.32:example 2 - running resulting script
$ ./example.py blue 
BLUE: BLUE SKY

1.1.5 Questions

Here are some things to think about:

Q: Could the lines be set in reverse order?

Sure! It all depends on what the requirements are from each branch and how they should behave in the current revision. On example 1, it was just adding elements to a dictionary so there was no meaningful difference. If they were being ordered alphabetically then order would matter. On example 2 it’s very important the order in which you put them. If the upper() call had been placed before re-setting the phrase to include the original color, then the original color would end up not being in uppercase. Is that correct? Perhaps! The requirements (not just the conflicting code) might need to be considered to know what to do in this case.

Q: Could it be written in a completely different way?

Sure! As long as the intent of what the different branches meant to do is included in the conflict resolution, it will be OK. 6

Q: What would the conflict look like if the branches are reversed when merging?

In example 2, working on example2/branchB, I try to merge example2/branchA. What would be the result? It would look exactly the same only that the conflict sections for each branch would be reversed and the name of the branch would change on the conflict closing mark:

Listing 1.33:example 2 - inverted conflict
11<<<<<<< HEAD 
12    phrase = phrase.upper() 
13======= 
14    phrase = "%s: %s" % (color, phrase) 
15>>>>>>> example2/branchA

1.1.6 Exercises

Exercise 1

Take branch exercise1/branchA from the exercises repo. There is a short list of irregular verbs inside irregular.txt in alphabetical order. Add these two verbs: drink and know. Then take branch example1/branchB and add these other 2 verbs: lose and keep . Merge the two branches. Solution is here. 7

Exercise 2

From the exercises repo, merge branches exercise2/branchA and exercise2/branchB. Solution is here.

Copyright 2020 Edmundo Carmona Antoranz