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ún1#!/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 A1#!/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 B1#!/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í:
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 mezclado1#!/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 colores5colors = {"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:
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ún1#!/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 A1#!/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 B1#!/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.
El archivo resultante se vería así:
Listing 1.16:Ejemplo 1 - archivo en conflicto1||||||| 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 conflicto7<<<<<<< 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.
, 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 HEAD7<<<<<<< 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 rama9======= <-- 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 resuelto1#!/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 A1#!/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 B1#!/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 conflicto1#!/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 resuelto1#!/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.
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 invertido11<<<<<<< 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í.
Ejercicio 2
Del repositorio de ejercicios, mezcle las ramas exercise2/branchA y
exercise2/branchB. La solución está aquí. Copyright 2020 Edmundo Carmona
Antoranz