1.1 Qué es un conflicto?

El libro se puede descargar aquí

Un conflicto se presenta cundo hay dos direcciones diferentes en las que una sección de código ha sido modificado. Mientras las modificaciones se mantengan separadas (digamos, en ramas diferentes) no habrá problema, pero al tratar de mezclarlas, git se detendrá y pedirá ayuda para saber qué se debe hacer y terminar la operación.

1.1.1 Ejemplo 0 - Un no-conflicto

Permítanme presentarles uno de los scripts que vamos a utilizar (con algunas variaciones por el camino) como la base para algunos de nuestros ejemplos.

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

Es un script python muy sencillo

Supongamos que dos desarrolladores comienzan a trabajar a partir de él para agregar un color diferente. El Desarrollador A produce esto:

Listing 1.2:Ejemplo 0 - Desarrollador 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]))

Se puede ver que se agregó el color rojo en la línea 7.

Y el Desarrollador B produjo esto:

Listing 1.3:Ejemplo 0 - Desarrollador 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.

Se puede que se agregó el color verde en a línea 6.

La historia de las ramas en este punto se ve así::

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

Dado que las líneas fueron agregadas en posiciones diferentes (separadas por la línea que define el color blanco), entonces git no tiene ningún problema en mezclarlas:

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

La historia resultante se ve así: 1

Listing 1.6:Ejemplo 0 - historia final de las ramas
*   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

El archivo resultante se ve así:

Listing 1.7:Ejemplo 0 - código mezclado
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]))

Y se puede ver que el archivo resultante tiene ambos colores rojo y verde:

Listing 1.8:Ejemplo 0 - sección de los colores
5colors = {"black": "black mirror", <-- Linea preexistente 
6          "green": "green peas",   <-- Linea del Desarrollador B 
7          "white": "white noise",  <-- Linea preexistente 
8          "red": "red tide",       <-- Linea del Desarrollador A 
9          "blue": "blue sky"}      <-- Linea preexistente

Esto también se puede ver se se ven las anotaciones del archivo:2

Listing 1.9:Ejemplo 0 - anotaciones
^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"}

Y ahí tenemos una mezcla exitosa. Git fue capaz de hacer la mezcla porque se modifican secciones diferentes del archivo. Git puede ver que las dos secciones están separadas por una línea que define la frase del color blanco y por eso la mezcla se puede hacer.

Vamos de nuevo a nuestro ancestro común y mostremos 2 ejemplos de conflictos.

1.1.2 Ejemplo 1 - Un conflicto sencillo

Primero (de nuevo), el ancestro común, que es el ancestro común que utilizamos en el ejemplo 0.

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

Ahora, como hicimos anteriormente, hagamos que nuestros 2 desarrolladores agreguen exactamente los mismos colores del ejemplo anterior pero, en vez de hacerlo sobre sitios diferentes, que lo hagan exactamente en la misma posición.

El Desarrollador A arega el color rojo en la línea 7:

Listing 1.11:Ejemplo 1 - Desarrollador 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]))

El Desarrollador B agrega el color verde también en la línea 7:

Listing 1.12:Ejemplo 1 - Desarrollador 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]))

La nueva historia de las ramas se ve así:

Listing 1.13:Ejemplo 1 - historia de las ramas
* 6e7707e (example1/branchB) Adding green peas 
| * 3633ec9 (HEAD -> example1/branchA) Adding red tide 
|/ 
* 9f086a6 Get a phrase from a color

Es básicamente el mismo ejemplo solo que los colores fueron agregados en la misma línea.Si alguien le pidiera a git que mezcle ambas ramas, se debería ver algo bastante diferente de lo que se vio en el ejemplo 0:

Listing 1.14:Ejemplo 1 - salida de git merge
$ 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

El estado del árbol de trabajo en este momento:

Listing 1.15:Ejemplo 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")

Se puede ver que example.py está listado en la sección llamada Unmerged paths (rutas no mezcladas). Eso nos dice que es el tipo de conflicto donde las dos ramas modificaron un archivo en direcciones en conflicto y que han producido un Conflicto de Contenido.3

El archivo resultante se vería así: 4

Listing 1.16:Ejemplo 1 - archivo en conflicto
1||||||| 30e1283 
2The resulting file would look something like this: 
3\begin{lstlisting}[style=python_style, caption={\bf example 1} - conflicting file] 
4======= 
5The resulting file would look something like this: 
6\footnote{Do not start complaining because we are not using \hyperref[diff3]{diff3}. Take it easy, it is coming {\bf very} soon.} 
7\begin{lstlisting}[style=python_style, caption={\bf example 1} - conflicting file] 
8>>>>>>> main 
9#!/usr/bin/python 
10 
11import sys 
12 
13colors = {"black": "black mirror", 
14          "white": "white noise", 
15<<<<<<< HEAD 
16          "red": "red tide", 
17======= 
18          "green": "green peas", 
19>>>>>>> example1/branchB 
20          "blue": "blue sky"} 
21 
22def getPhrase(color): 
23    phrase = colors[color] 
24    return phrase 
25 
26print(getPhrase(sys.argv[1]))

Git trató lo mejor que pudo de resolver qué había que hacer y, dado que las dos ramas hacen cosas diferentes en el mismopunto, git nos pide que le echemos una mano para que el código sea mezclado.

La sección de conflicto, como se pueden imaginar, es:

Listing 1.17:Ejemplo 1 - Sección en conflicto
7<<<<<<< HEAD 
8          "red": "red tide", 
9======= 
10          "green": "green peas", 
11>>>>>>> example1/branchB

El conflicto tiene 2 secciones. La primera seccion es como se ve el código en HEAD. 5, y se puede ver HEAD en la marca de comienzo de conflicto, en la línea 7.

Listing 1.18:Ejemplo 1 - sección de HEAD
7<<<<<<< HEAD <-- marca de inicio de conflicto 
8          "red": "red tide", 
9======= <-- separador entre HEAD y la otra rama

La segunda sección es como se ve el código de la sección del conflicto en la otra rama que pedimos mezclar. Git nos indica que el nombre de la otra rama es example1/branchA en la marca de cierre el conflicto, en la línea 11:

Listing 1.19:Ejemplo 1 - sección de la otra rama
9======= <-- separador entre HEAD y la otra rama 
10          "green": "green peas", 
11>>>>>>> example1/branchB <-- marca de final de conflicto

Las 2 secciones están separadas por una serie de signos de igual.

La pregunta clave que tendríamos que responder en este punto es: podemos resolver este conflicto? Probalemente estén pensado pero por supuesto! Solo agregamos la línea del color nuev de cada rama para que todos los colores queden en el diccionario.

Listing 1.20:Ejemplo 1 - conflicto resuelto
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]))

Felicitaciones! Ya resolveron su primer (cierto?) conflicto. Los siguientes pasos son requeridos por git para poder terminar con la operación de mezcla.

1.1.3 Como finalizar un conflicto?

Si en este punto corriéramos git status, verían que nada ha cambiado aún, desde el punto de vista de git:

Listing 1.21:git status luego de editar el archivo
$ 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 no le estuvo prestando atención al archivo. Necesitamos decirle a git que tome el archivo como está en este momento y lo coloque en el índice

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

Cuando todos los archivos estén mezclados/agregados al índice, una revisión se puede crear corriendo git merge –continue. Esto hará que continúe el flujo de creación de la revisión como si hiciéramos un git commit con un editor de texto donde colocaremos el comentario de la revisión, y al guardar y salir se creará la revisión finalmente. Aquí está como se ve al correr el git merge –continue y saliendo del editor de texto:

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

Finalmente obtenemos la nueva revisión:

Listing 1.24:Ejemplo 1 - historia final de las ramas
*   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 Ejemplo 2 - Otro ejemplo sencillo

Intentemos otro ejemplo sencillo. Usando el mismo ancestro común de antes (teníamos 3 colores, cierto?), ahora el Desarrollador A nos presenta esto:

Listing 1.25:Ejemplo 2 - Desarrollador 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]))

Esto es algo diferente. La frase de salida ahora contiene el color y la frase asociada, separados por : (línea 11). Si corremos el script con blue, obtenemos:

Listing 1.26:Ejemplo 2 - Ejecutando el script del Desarrollador A
$ ./example.py blue 
blue: blue sky

El Desarrollador B termina con esto:

Listing 1.27:Ejemplo 2 - Desarrollador 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]))

El Desarrollador B está agregando una llamada a upper() para colocar la frase en mayúsculas (linea 11). Si ejecutamos el script como hicimos antes:

Listing 1.28:Ejemplo 2 - ejecutando el script del Desarrollador B
$ ./example.py blue 
BLUE SKY

Y la historia se ve así:

Listing 1.29:Ejemplo 2 - historia de las ramas
* 9b9d7b2 (example2/branchB) Using upper on the phrase 
| * c944ee5 (example2/branchA) Showing color and phrase 
|/ 
* 8c9d315 Get a phrase from a color

Y esta vez podemos predecir el conflicto que vamos a tener al intentar mezclar, cierto?

Listing 1.30:Ejemplo 2 - Archivo en conflicto
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]))

En este caso retengamos la línea donde colocamos el color original en la frase, luego hacemos una llamada a upper() y eso sería todo:

Listing 1.31:Ejemplo 2 - Conflicto resuelto
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]))

Y si ejecutamos el script final:

Listing 1.32:Ejemplo 2 - ejecutando el script final
$ ./example.py blue 
BLUE: BLUE SKY

1.1.5 Preguntas

Estas son algunas preguntas sobre las cuales pensar:

P: Se podría colocar las líneas de forma inversa?

Claro! Todo depende de lo que indiquen los requerimientos de cada rama y como se deben comportar en la revisión actual. En el ejemplo 1, solo se trataba de agregar elementos a un diccionario así que no había diferencia significativa si se colocaban las líneas de forma inversa. Si en ese ejemplo se colocaran los colores de forma alfabética, entonces el orden si sería importante. En este ejemplo, si la llamada a upper() se colocara antes de colocar el color original en la frase, entonces las mayúsculas solo afectarían a la frase, no al color original. Eso es correcto? Quizás! Considerar los requerimientos (no solo el código en conflicto) podrían ser necesarios para saber qué hacer en este caso.

P: Se podría escribir de una forma completamente diferente?

Por supuesto! Mientras las intenciones de lo que quería hacer cada rama se incluyan en la resolución del conflicto, estará bien.6

P: Cómo se vería el conflicto si las ramas hubieran estado invertidas al mezclar?

En el ejemplo 2, trabajando sobre example2/branchB, trato de mezclar example2/branchA. Cual sería el resultado? Se vería exactamente igual solo que en la sección en conflicto, las secciones de cada rama estarían invertidas y el nombre de la rama en la marca de cierre de conflicto sería otra:

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

1.1.6 Ejercicios

Ejercicio 1

Tome la rama exercise1/branchA del repositorio de ejercicios. Hay una lista de verbos irregulares en inglés dentro de irregular.txt en orden alfabético. Agregue estos dos verbos: drink y know. Luego tome example1/branchB y agregue estos dos verbos: lose y keep . Mezcle ambas ramas. La solución está aquí. 7

Ejercicio 2

Del repositorio de ejercicios, mezcle las ramas exercise2/branchA y exercise2/branchB. La solución está aquí. Copyright 2020 Edmundo Carmona Antoranz