3.1 API conflicts
Book can be downloaded from here
Before explaining the details you have to care for when dealing with
API conflicts, let’s consider what API changes mean for merges without
conflicts.
3.1.1 Example 15
Let me show you the common ancestor:
Listing 3.1:example 15 - common ancestor1#!/usr/bin/python 2 3import sys 4 5colors = {"black": "black mirror", 6 "white": "white noise", 7 "blue": "blue sky"} 8 9def getPhrase(color): 10 color = color.lower() 11 if color not in colors: 12 sys.stderr.write("There is no phrase defined for color %s\n" % color) 13 sys.exit(1) 14 phrase = colors[color] 15 phrase = "%s: %s" % (color, phrase) 16 return phrase 17 18for color in sys.argv[1:]: 19 print(getPhrase(color))
We will be printing the phrases for multiple colors now, not just a single
one.
On branchA the API is changed so that it can be specified whether the original
color will be included in the phrase:
Listing 3.2:example 15 - branchA1#!/usr/bin/python 2 3import sys 4 5colors = {"black": "black mirror", 6 "white": "white noise", 7 "blue": "blue sky"} 8 9def getPhrase(color, includeColor): 10 color = color.lower() 11 if color not in colors: 12 sys.stderr.write("There is no phrase defined for color %s\n" % color) 13 sys.exit(1) 14 phrase = colors[color] 15 if (includeColor): 16 phrase = "%s: %s" % (color, phrase) 17 return phrase 18 19for color in sys.argv[1:]: 20 print(getPhrase(color, True))
In the real world there is probably no point in having this API change,
but it’s ok. Bear with it for the sake of explanation. Let’s take a look at
branchB.
Listing 3.3:example 15 - branchB1#!/usr/bin/python 2 3import sys 4 5colors = {"black": "black mirror", 6 "white": "white noise", 7 "blue": "blue sky"} 8 9def getPhrase(color): 10 color = color.lower() 11 if color not in colors: 12 sys.stderr.write("There is no phrase defined for color %s\n" % color) 13 sys.exit(1) 14 phrase = colors[color] 15 phrase = "%s: %s" % (color, phrase) 16 return phrase 17 18# I love color white 19print(getPhrase("white")) 20for color in sys.argv[1:]: 21 print(getPhrase(color))
Now the phrase for color white will always be printed before all the colors that
are used as parameters. What will happen if we tried to merge? Will we get a conflict
or won’t we? What do you think?
Listing 3.4:example 15 - git merge$ git merge example15/branchB --no-edit Auto-merging example.py Merge made by the ’recursive’ strategy. example.py | 2 ++ 1 file changed, 2 insertions(+)
Nice! And the end result?
Listing 3.5:example 15 - final result1#!/usr/bin/python 2 3import sys 4 5colors = {"black": "black mirror", 6 "white": "white noise", 7 "blue": "blue sky"} 8 9def getPhrase(color, includeColor): 10 color = color.lower() 11 if color not in colors: 12 sys.stderr.write("There is no phrase defined for color %s\n" % color) 13 sys.exit(1) 14 phrase = colors[color] 15 if (includeColor): 16 phrase = "%s: %s" % (color, phrase) 17 return phrase 18 19# I love color white 20print(getPhrase("white")) 21for color in sys.argv[1:]: 22 print(getPhrase(color, True))
You do see the problem, right? Let me show you if you haven’t caught it
yet:
Listing 3.6:example 15 - we have a problem$ ./example.py blue Traceback (most recent call last): File "./example.py", line 20, in <module> print(getPhrase("white")) TypeError: getPhrase() takes exactly 2 arguments (1 given)
Oh, this line, added on branchB, hasn’t been adapted to the new API change
coming from branchA:
Listing 3.7:example 15 - this is the
problem19# I love color white 20print(getPhrase("white"))
And that is because, when the API was changed on branchA, the preexisting
calls on that branch were adjusted.... but the calls that were added on the other
branch won’t magically get those adjustments when merging. Even if there are
no conflicts, API changes can be tricky to deal with when merging
code.
3.1.2 Example 16
Now, what will happen when you have conflicting changes on the API? It’s the
same situation we just saw, but on steroids because you will have to consider
code that is modified from all the branches that are being merged, not just
one.
Let’s use another example based on the example we just used. Here’s the full
conflicted file:
Listing 3.8:example 16 - full conflicted file1#!/usr/bin/python 2 3import sys 4 5RESET=chr(0x1b) + "[m" 6colors = {"black": {"phrase": "black mirror", "fg": chr(0x1b) + "[0;7m"}, # reverse on color 7 "white": {"phrase": "white noise", "fg": chr(0x1b) + "[1m"}, 8 "blue": {"phrase": "blue sky", "fg": chr(0x1b) + "[1;34m"}} 9 10<<<<<<< HEAD 11def getPhrase(color, showColor): 12||||||| 46e6753 13def getPhrase(color): 14======= 15def getPhrase(color, useColor): 16>>>>>>> example16/branchB 17 """ 18 Get the phrase that corresponds to one color 19 20 Parameters 21 ---------- 22 color: color to get the phrase for. It can be used in any combination of uppercase/lowercase letters 23<<<<<<< HEAD 24 showColor: whether to display the original color name before the phrase or not 25||||||| 46e6753 26======= 27 useColor: we can change the color of the phrase when writing on the output 28>>>>>>> example16/branchB 29 """ 30 color = color.lower() 31 if color not in colors: 32 sys.stderr.write("There is no phrase defined for color %s\n" % color) 33 sys.exit(1) 34<<<<<<< HEAD 35 phrase = colors[color] 36 if showColor: 37 phrase = "%s: %s" % (color, phrase) 38||||||| 46e6753 39 phrase = colors[color] 40 phrase = "%s: %s" % (color, phrase) 41======= 42 phrase = colors[color]["phrase"] 43 phrase = "%s: %s" % (color, phrase) 44 if useColor: 45 phrase = colors[color]["fg"] + phrase + RESET 46>>>>>>> example16/branchB 47 return phrase 48 49for color in sys.argv[1:]: 50 print(getPhrase(color, True))
Each branch added a parameter to getPhrase()... and the name can be
confusing, right? Are they the same parameter (same intent) with differing names on
the two branches? That’s yet another layer of difficulty that you might have to deal
with. Luckily for us, there’s docstrings for the function and on the second CB we
can see what the parameter from each branch is about and we can conclude that they
are definitely not the same:
Listing 3.9:example 16 - CB#217 """ 18 Get the phrase that corresponds to one color 19 20 Parameters 21 ---------- 22 color: color to get the phrase for. It can be used in any combination of uppercase/lowercase letters 23<<<<<<< HEAD 24 showColor: whether to display the original color name before the phrase or not 25||||||| 46e6753 26======= 27 useColor: we can change the color of the phrase when writing on the output 28>>>>>>> example16/branchB 29 """
The one in UB, showColor, is used to tell if the original color will be printed
before the phrase. The one in LB, useColor, is used to change text color on
the output for an added visual effect, and as usual, we need to keep both
intents. On the first CB, what parameter will we add first? From my point of
view, either one is fine but you might have rules to follow for that... every
situation is different. I will do this to solve the first two conflicts in a single
shot:
Listing 3.10:example 16 - solution to first 2 CBs10def getPhrase(color, showColor, colorOnOutput): 11 """ 12 Get the phrase that corresponds to one color 13 14 Parameters 15 ---------- 16 color: color to get the phrase for. It can be used in any combination of uppercase/lowercase letters 17 showColor: whether to display the original color name before the phrase or not 18 colorOnOutput: we can change the color of the phrase when writing on the output 19 """
I changed the name of the parameter coming from the other branch so that the
difference between the parameters is more evident. That means that I will
also have to modify the places where useColor (the original name of that
parameter) is in the function. Let’s take a look at the next CB starting on line
24 :
Listing 3.11:example 16 - CB#324<<<<<<< HEAD 25 phrase = colors[color] 26 if showColor: 27 phrase = "%s: %s" % (color, phrase) 28||||||| 46e6753 29 phrase = colors[color] 30 phrase = "%s: %s" % (color, phrase) 31======= 32 phrase = colors[color]["phrase"] 33 phrase = "%s: %s" % (color, phrase) 34 if useColor: 35 phrase = colors[color]["fg"] + phrase + RESET 36>>>>>>> example16/branchB
dMU
Added a conditional in line 26 to control if we will include the original color name on
the phrase.
dML
Modified the way we get the phrase associated to the color because the dictionary
structure has changed (line 32) and added a conditional on line 34 to modify the
phrase so that the output color on the terminal is changed.
Resolution
Given that LB is more different from MB than UB, I will work from LB:
Listing 3.12:example 16 - Step 1 - LB#331======= 32 phrase = colors[color]["phrase"] 33 phrase = "%s: %s" % (color, phrase) 34 if useColor: 35 phrase = colors[color]["fg"] + phrase + RESET 36>>>>>>> example16/branchB
Then that is needed to be done is add the conditional to include the original color
and adjust indentation:
Listing 3.13:example 16 - Step 2 - LB#331======= 32 phrase = colors[color]["phrase"] 33 if showColor: 34 phrase = "%s: %s" % (color, phrase) 35 if useColor: 36 phrase = colors[color]["fg"] + phrase + RESET 37>>>>>>> example16/branchB
And then we are done, right? Gotcha!!! Did you remember to adjust the name of
the parameter that is coming from the other branch? I changed it from useColor
to colorOnOutput while working on solving CBs 1 and 2, so we need to also adjust
that on this CB:
Listing 3.14:example 16 - Step 2 - LB#331======= 32 phrase = colors[color]["phrase"] 33 if showColor: 34 phrase = "%s: %s" % (color, phrase) 35 if colorOnOutput: 36 phrase = colors[color]["fg"] + phrase + RESET 37>>>>>>> example16/branchB
So, the final resolution of the file would be:
Listing 3.15:example 16 - solved conflict1#!/usr/bin/python 2 3import sys 4 5RESET=chr(0x1b) + "[m" 6colors = {"black": {"phrase": "black mirror", "fg": chr(0x1b) + "[0;7m"}, # reverse on color 7 "white": {"phrase": "white noise", "fg": chr(0x1b) + "[1m"}, 8 "blue": {"phrase": "blue sky", "fg": chr(0x1b) + "[1;34m"}} 9 10def getPhrase(color, showColor, colorOnOutput): 11 """ 12 Get the phrase that corresponds to one color 13 14 Parameters 15 ---------- 16 color: color to get the phrase for. It can be used in any combination of uppercase/lowercase letters 17 showColor: whether to display the original color name before the phrase or not 18 colorOnOutput: we can change the color of the phrase when writing on the output 19 """ 20 color = color.lower() 21 if color not in colors: 22 sys.stderr.write("There is no phrase defined for color %s\n" % color) 23 sys.exit(1) 24 phrase = colors[color]["phrase"] 25 if showColor: 26 phrase = "%s: %s" % (color, phrase) 27 if colorOnOutput: 28 phrase = colors[color]["fg"] + phrase + RESET 29 return phrase 30 31for color in sys.argv[1:]: 32 print(getPhrase(color, True))
And now we are ready, right? Gotcha again!!! Did you happen to notice the call
on line 32?
Listing 3.16:example 16 - call that needs to be adusted31for color in sys.argv[1:]: 32 print(getPhrase(color, True))
It has only 2 parameters... but we should have 3, right? Why is that? Oh, it’s one
of those cases where git’s smartness is playing against us. If git notices that both
branches applied the same change, it just takes it in as is from one of the branches
and generates no conflict. In this case both branches added the second parameter and
the value is True for both branches, so it’s the same change coming from both
branches. But we need to adjust it so that both parameters get a True
value and then it will be solved. All in all, the very final resolution of the
conflict
would be:
Listing 3.17:example 16 - solved conflict for real1#!/usr/bin/python 2 3import sys 4 5RESET=chr(0x1b) + "[m" 6colors = {"black": {"phrase": "black mirror", "fg": chr(0x1b) + "[0;7m"}, # reverse on color 7 "white": {"phrase": "white noise", "fg": chr(0x1b) + "[1m"}, 8 "blue": {"phrase": "blue sky", "fg": chr(0x1b) + "[1;34m"}} 9 10def getPhrase(color, showColor, colorOnOutput): 11 """ 12 Get the phrase that corresponds to one color 13 14 Parameters 15 ---------- 16 color: color to get the phrase for. It can be used in any combination of uppercase/lowercase letters 17 showColor: whether to display the original color name before the phrase or not 18 colorOnOutput: we can change the color of the phrase when writing on the output 19 """ 20 color = color.lower() 21 if color not in colors: 22 sys.stderr.write("There is no phrase defined for color %s\n" % color) 23 sys.exit(1) 24 phrase = colors[color]["phrase"] 25 if showColor: 26 phrase = "%s: %s" % (color, phrase) 27 if colorOnOutput: 28 phrase = colors[color]["fg"] + phrase + RESET 29 return phrase 30 31for color in sys.argv[1:]: 32 print(getPhrase(color, True, True))
That was some work, right?
3.1.3 Exercises
Exercise 7
From git repo, checkout revision 4284497396 and merge
cdb5330a9b .
Solve all conflicts. Solution is here.
Copyright 2020 Edmundo Carmona Antoranz