3.1 Conflictos de API

El libro se puede descargar aquí

Antes de explicar los detalles de los que se tienen que cuidar cuando trabajen con conflictos de API, consideremos lo que los Cambios de API significan al hacer merges sin conflictos.

3.1.1 Ejemplo 15

Déjanme mostrarles el ancestro común:

Listing 3.1:Ejemplo 15 - ancestro común
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    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))

Vamos a imprimir las frases para múltiples colores, no solo uno.

Sobre branchA la API es cambiaba para que el color original se incluya opcionalmente en la frase:

Listing 3.2:Ejemplo 15 - branchA
1#!/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))

En el mundo real, este cambio probablemente no tenga ninún sentido pero está bien. Tengan paciencia y dejen que siga con el problema para los efectos de explicación. Miremos la rama branchB:

Listing 3.3:Ejemplo 15 - branchB
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    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))

Ahora la frase del color blanco siempre se va a imprimir antes que todos los colores usados como parámetros. Qué sucederá si tratamos de hacer merge? Qué piensan?

Listing 3.4:Ejemplo 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(+)

Excelente! Y el resultado final?

Listing 3.5:Ejemplo 15 - resultado 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, 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))

Ustedes pueden ver el problema, cierto? Dejen que les muestra si es que no lo han visto aún:

Listing 3.6:Ejemplo 15 - tenemos un problema
$ ./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)

Ah, esta línea, agregada en la branchB, no ha sido actualizada a la nueva API que viene de branchA:

Listing 3.7:Ejemplo 15 - este es el problema
19# I love color white 
20print(getPhrase("white"))

Y eso es porque, cuando la API fue cambiada en la rama branchA, las llamadas preexistentes sobre la rama fueron ajustadas.... pero las llamadas que fueron agregadas added en la otra rama no van a recibir un ajuste mágicamente al hacer el merge. Incluso si no hay conflictos, los cambios de API pueden ser engañosos al hacer merges.

3.1.2 Ejemplo 16

Ahora bien, que pasará cuando hay cambios conflictivos en la API? Es la misma situación que acabamos de ver, pero en esterides porque se tiene que considerar las modificaciones de todas las ramas involucradas, no de una sola.

Miremos otro ejemplo basado en el ejemplo que acabamos de usar. Aquí está el archivo completo:

Listing 3.8:Ejemplo 16 - todo el archivo
1#!/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))

Cada rama agregó un método a getPhrase()... y el nombre puede prestarse a confusión, cierto? Son el mismo parámetro (misma intención) con nombres diferentes? Ese sería otro nivel de dificultad adicional que habría que manejar. Afortuadamente, tenemos docstrings para la función y en el segundo CB podemos ver qué hace el parámetro de cada rama. Podemos concluir que no son lo mismo:

Listing 3.9:Ejemplo 16 - CB#2
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    """

El parámetro de UB, showColor, se usa para indicar si el color original va a ser antepuesto a la frase a ser impresa. El parámetro de LB, useColor, se usa para cambiar el color del texto en el terminal como un efecto visual. Por lo tanto, como es habitual, tenemos que mantener la intención de ambas ramas. En el primer CB, cual parámetro irá primero? Desde mi punto de vista, cualquiera de los dos estará bien pero podría haber ciertas reglas que seguir.... cada situación es diferente. Esta será mi resolución para los dos primeros conflictos de un solo golpe:

Listing 3.10:Ejemplo 16 - solución a los primeros 2 CBs
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    """

Cambié el nombre del parámetro que viene de la otra rama para que la diferencia entre ellos sea más avidente. Eso significa que también tendré que modificar los lugares donde se use useColor (el nombre original del prámetro) en la función. Veamos el siguiente CB comenzando en la línea 241 :

Listing 3.11:Ejemplo 16 - CB#3
24<<<<<<< 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

Se agregó un condicional en la línea 26 para controlar si de usará el color original en la frase.

dML

Se modificó la forma en la que se obtiene la frase asociada al color porque la estructura de diccionario cambió (línea 32) y se agregó un condicional en la línea 34 para modificar la frase para que el color del texto en el terminal cambie.

Resolution

Dado que el LB es más diferente al MB que el UB, trabajaremos desde el LB.

Listing 3.12:Ejemplo 16 - Paso 1 - LB#3
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

Luego todo lo que tenemos que hacer es agregar el condicional para incluir el color original y ajustar indentación:

Listing 3.13:Ejemplo 16 - Paso 2 - LB#3
31======= 
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

Y listo, cierto? Los pillé!!! Recuerdan que hay que ajustar el nombre del parámetro Did que viene de la otra rama? Lo cambié de useColor a colorOnOutput mientras se resolvían los CBs 1 y 2, así que debemos ajustar eso en este CB:

Listing 3.14:Ejemplo 16 - Paso 3 - LB#3
31======= 
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

Y así, el resultado final sería este:

Listing 3.15:Ejemplo 16 - conflictos resueltos
1#!/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))

Y ya estamos listos, cierto? Pillados de nuevo!!! Se fijaron en la llamada de la línea 32?

Listing 3.16:Ejemplo 16 - llamada que debe ser ajustada
31for color in sys.argv[1:]: 
32    print(getPhrase(color, True))

Solo tiene 2 parámetros.... pero deberían ser 3, cierto? Por qué pasó esto? Ah, este es uno de esos casos en los que la inteligencia de git se pone en nuestra contra. Si git nota que 2 ramas aplican el mismo cambio, simplemente lo toma de una de las ramas tal cual y no genera conflicto. En este caso ambas ramas agregaron el segundo paámetro con el valor True en ambas, así que es el mismo cambio viniendo desde ambas ramas. Pero debemos ajustar la llamada para que ambos parámetros nuevos sean True y entonces quedará resuelto. Este es el resultado recontrafinal del conflicto 2:

Listing 3.17:Ejemplo 16 - Conflicto resuelto de verdad
1#!/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))

Eso fue algo de trabajo, cierto?

3.1.3 Ejercicios

Exercise 7

De el git repo, hagen checkout de la revisión 4284497396 and hagan merge de cdb5330a9b3 . Resuelvan todos los conflictos. La solución está aquí.

Copyright 2020 Edmundo Carmona Antoranz