3.2 Conflictos de archivo completo

El libro se puede descargar aquí

Así que, han disfrutado de los conflictos previos, cierto? Por supuesto! Bien... si les gustaron los anteriores, este lo van a amar!

3.2.1 Ejemplo 17

Desde el Repo del JDK de OpenJDK, hagan checkout de la revisión 9d3e0870754 y mezclen 2978ffb9f9d 4 .

Verán que habrá un conflicto en el archivo langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml. Pero no es solo un conflicto. Es el conflicto. En vez de tener algunas líneas con conflicto, dentro del archivo, todo el archivo está en conflicto. Aquí hay algunas líneas del archivo (los números de las líneas están a la izquierda):

Listing 3.18:Ejemplo 17 - Archivo en conflicto
     1  <<<<<<< HEAD 
     2  <?xml version=’1.0’ encoding=’utf-8’?> 
     3 
     4  <!-- 
     5   Copyright 2003 Sun Microsystems, Inc.  All Rights Reserved. 



   205      </SerializedForm> 
   206  </Doclet> 
   207  ||||||| 4ae52d7dc1e 
   208  <?xml version=’1.0’ encoding=’utf-8’?> 
   209 



   411      </SerializedForm> 
   412  </Doclet> 
   413  ======= 
   414  <?xml version=’1.0’ encoding=’utf-8’?> 
   415 
   416  <!-- 



   616         <Footer/> 
   617      </SerializedForm> 
   618  </Doclet> 
   619  >>>>>>> 2978ffb9f9d

Antes de entrar en detalles, déjenme mostrarles lo que sucedió en la otra rama en términos de los cambios sobre este archivo:

Listing 3.19:Ejemplo 17 - Cambios sobre la otra rama
$ git diff HEAD...MERGE_HEAD -- langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
diff --git a/langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml b/langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
index 8eaa2d77abc..29c473790e4 100644 
--- a/langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
+++ b/langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
@@ -1,7 +1,7 @@ 
 <?xml version=’1.0’ encoding=’utf-8’?> 
 
 <!-- 
- Copyright 2003 Sun Microsystems, Inc.  All Rights Reserved. 
+ Copyright 2003-2009 Sun Microsystems, Inc.  All Rights Reserved.^M 
  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 
 
  This code is free software; you can redistribute it and/or modify it

Un simple cambio de una sola línea. La línea es diferente a lo que tenemos en HEAD?

Listing 3.20:Ejemplo 17 - archivo como está en HEAD
$ git show HEAD:langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml | head -n 5 
<?xml version=’1.0’ encoding=’utf-8’?> 
 
<!-- 
 Copyright 2003 Sun Microsystems, Inc.  All Rights Reserved. 
 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.

La línea se ve justamente como lo que fue removido en la otra rama... Esto pareciera ser algo muy fácil, cierto? No debería haber ningún conflicto, en realidad. Qué pasó entonces? Hay una respuesta muy rápida pero... Por qué ahorrarnos el placer de una explicación larga?

Los archivos de texto están hechos de líneas. git considera las líneas separadas que conforman un archivo para saber cuales líneas son las mismas y cuales fueron modificadas cuando se va a mezclar código. Ahora bien, alguna vez se han preguntado cómo se define lo que es una línea? Cada línea está separada de la siguiente a través de un marcador que se llama Salto de Línea (EOL, por sus siglas en inglés). Pero hay un pequeño problema. No hay uno ni dos sino tres(!!!) formatos diferentes de marcadores de EOL y cada uno está asociado a un sistema operativo diferente.

Aquí hay un pequeño archivo de texto con 3 formatos diferentes de EOL para que vean como cambian los archivos a nivel binario:

Figure 3.1:Mac EOL
PIC
Figure 3.2:NIX EOL
PIC
Figure 3.3:Windows EOL
PIC

En un archivo nuevo, los editores de texto tienen la tendencia a colocar el formato de EOL del sistema operativo sobre el que se está trabajando. Si el archivo ya existía, los editores de texto5 utilizan el mismo formato del archivo, incluso si es diferente al sistema operativo sobre el que está trabajando el editor. Guarden un archivo de texto en los diferentes formatos y vean como cambian los marcadores entre las líneas en un editor binario.

Podrían estarse preguntando “Cómo terminamos en este desastre de EOL?” Es una pregunta apropiada y, justo como cualquier buena historia, se trata de traiciones, puñaladas traperas de los amigos cercanos y ambición pero es algo que sale bastante del foco de este manual así que les pediré amablemente que lean La Historia de la Nueva Línea si tienen curiosidad por saber como llegamos a este desastre.

Volviendo a nuestro problema particular, desde el punto de vusta de git, un cambio en el formato de EOL efectivamente cambiará el contenido de todas y cada una de las líneas que conforman el archivo. Abren un arhivo, modifican una sola línea, guardan cambiando el formato de EOL y, para git, el archivo fue completamente limpiado y reescrito por completo.... en Esperanto. Es un archivo completamente nuevo de cabo a rabo. Déjenme repetirlo para que el concepto cale: Archivo (pausa dramática) Completamente (pausa dramática) Diferente. Ninguna de las lineas preexistentes del archivo sobrevivió esa revisión.

Es este el caso de lo que pasó en este ejercicio? Pues veamos qué nos puede decir unix2dos acerca del formato de EOL del archivo en las diferentes versiones involucradas:

Listing 3.21:Ejemplo 17 - tipos de EOL
$ git show HEAD:langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml | unix2dos -imud 
       0     205       0 
$ git show MERGE_HEAD:langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml | unix2dos -imud 
     205       0       0 
$ git show 4ae52d7dc1ef:langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml | unix2dos -imud 
     205       0       0

Y podemos ver cómo en HEAD los saltos de línea están en una columna diferente, lo que indica un cambio de formato de EOL.

Antes de entrar a considerar qué podemos hacer al respecto, preguntémonos “Como fue que pasó esto en primer lugar?” Para nuestro asombro, hay algunas posibilidades más o menos sencillas para que esto ocurra:

Ok, ok... suficiente teoría. Consideremos los diferentes enfoques que podrían seguir para salir de este enredo.

Quedarnos en HEAD, traer los cambios de la otra rama

Lo primero que hay que notar es que esto está rompiendo bastante el objetivo de hacer un merge, cierto? Lo que quieren es que todos los cambios de la otra rama sean copiados automágicamente a su código y obtener conflictos en las secciones que efectivamente lo sean. Ahora estamos en una situación donde tendremos que hacer todo a mano. Afortunadamente, en este caso, ya sabemos lo que se requiere que traigamos de la otra rama. Tenemos que ajustar los años cubiertos por el copyright. Por lo tanto, este sería el archivo resultante, las primeras líneas:

Listing 3.22:Ejemplo 17 - HEAD con los cambios de la otra rama
1<<<<<<< HEAD 
2<?xml version=’1.0’ encoding=’utf-8’?> 
3 
4<!-- 
5 Copyright 2003-2009 Sun Microsystems, Inc.  All Rights Reserved. 
6 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.

Removemos las otras partes del conflicto y los marcadores. Luego completamos el merge:

Listing 3.23:Ejemplo 17 - completar el merge
$ git add langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
$ git merge --continue 
[detached HEAD b4a035c194c] Merge commit ’2978ffb9f9d’ into HEAD

Eso está bien. Como un arreglo rápido, esto hace el trabajo... pero no hemos resuelto la verdadera causa del problema. Todavía hay una discrepancia en el formato de EOL entre las ramas. Como un pequeño experimento, hagamos checkout de la rama que acabamos de mezclar, 2978ffb9f9d, escribamos un pequeño cambio sobre el archivo, regresemos a la revisión que acabamos de crear de mezcla y pidamos mezclar de nuevo:

Listing 3.24:Ejemplo 17 - checkout 2978ffb9f9d
$ git checkout 2978ffb9f9d 
Warning: you are leaving 1 commit behind, not connected to 
any of your branches: 
 
  b4a035c194c Merge commit ’2978ffb9f9d’ into HEAD 
 
If you want to keep it by creating a new branch, this may be a good time 
to do so with: 
 
 git branch <new-branch-name> b4a035c194c 
 
HEAD is now at 2978ffb9f9d Merge

Coloquemos que el copyright es 2003-2020, ahora, para los efectos de ejemplo8 :

Listing 3.25:Ejemplo 17 - Modificación sobre 2978ffb9f9d
1<?xml version=’1.0’ encoding=’utf-8’?> 
2 
3<!-- 
4 Copyright 2003-2020 Sun Microsystems, Inc.  All Rights Reserved. 
5 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.

Completamos la revisión:

Listing 3.26:Ejemplo 17 - creando una nueva revisión
$ git add langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
$ git commit -m "A tiny change" 
[detached HEAD 0d57690bf36] A tiny change 
 1 file changed, 1 insertion(+), 1 deletion(-)

En este punto, las dos revisiones que queremos mezclar se deben ver así (miren las dos revisiones superiores):

Listing 3.27:Ejemplo 17 - historia de las revisiones
* 0d57690bf36 A tiny change 
| *   b4a035c194c Merge commit ’2978ffb9f9d’ into HEAD 
| |\ 
| |/ 
|/| 
* | 2978ffb9f9d Merge 
| * 9d3e0870754 Merge 
|/ 
* 4ae52d7dc1e Added tag jdk7-b50 for changeset 7faffd237305

Intentemos mezclar de nuevo:

Listing 3.28:Ejemplo 17 - nueva mezcla
$ git checkout b4a035c194c 
Warning: you are leaving 1 commit behind, not connected to 
any of your branches: 
 
  0d57690bf36 A tiny change 
 
If you want to keep it by creating a new branch, this may be a good time 
to do so with: 
 
 git branch <new-branch-name> 0d57690bf36 
 
HEAD is now at b4a035c194c Merge commit ’2978ffb9f9d’ into HEAD 
$ git merge 0d57690bf36 
Auto-merging langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
CONFLICT (content): Merge conflict in langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
Automatic merge failed; fix conflicts and then commit the result.

Pues, tenemos un conflicto. Y al mirar dentro, será un conflicto de archivo completo de nuevo:

Listing 3.29:Ejemplo 17 - archivo con conflicto... de nuevo
     1  <<<<<<< HEAD 
     2  <?xml version=’1.0’ encoding=’utf-8’?> 
     3 
     4  <!-- 
     5   Copyright 2003-2009 Sun Microsystems, Inc.  All Rights Reserved. 
     6   DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 



   205      </SerializedForm> 
   206  </Doclet> 
   207  ||||||| 2978ffb9f9d 
   208  <?xml version=’1.0’ encoding=’utf-8’?> 
   209 
   210  <!-- 
   211   Copyright 2003-2009 Sun Microsystems, Inc.  All Rights Reserved. 
   212   DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 



   411      </SerializedForm> 
   412  </Doclet> 
   413  ======= 
   414  <?xml version=’1.0’ encoding=’utf-8’?> 
   415 
   416  <!-- 
   417   Copyright 2003-2020 Sun Microsystems, Inc.  All Rights Reserved. 
   418   DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 



   617      </SerializedForm> 
   618  </Doclet> 
   619  >>>>>>> 0d57690bf36

No les estoy tomando el pelo. Mientras haya diferentes formatos de EOL entre las ramas involucradas en un merge... o rebase... o cherry-pick... o revert, van a ver estos conflictos totalmente inútiles... todas... y cada una... de las veces.

Consideren que, en este caso, era un cambio un pequeño que tuvo que ser movido desde la otra rama. Pero y si fueran cambios mas grandes? Multiples cambios? Unos cambios que no deberían dar conflicto y algunos que si deberían generar conflicto? Están dispuestos a pasar copiando cosas de un lado al otro todo el día? No es la mejor forma de gastar ciclos de CPU de nuestro cerebro duramente entrenado para hacer mezclas, cierto? Exacto, lo mismo pensé.

Dado que, en nuestro ejemplo, el cambio de formato de EOL sucedió en nuestra rama, deberíamos cambiar el formato de EOL de vuelta al original antes de intentar hacer el merge? Eso podría funcionar. Veamos. Primero, comencemos de nuevo:

3.2.2 Ejemplo 17 - de nuevo... con un giro

Desde el repositorio JDK de OpenJDK, hagan checkout de la revisión 9d3e0870754. Abran el archivo y cambien el formato de EOL a Windows9 . Luego acometan. Luego mezclen 2978ffb9f9d.

Listing 3.30:Ejemplo 17 - intentando el merge de nuevo
$ git checkout 9d3e0870754 
HEAD is now at 9d3e0870754 Merge 
$ unix2dos langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
unix2dos: converting file langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml to DOS format... 
$ git add langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
$ git commit -m "Changing EOL-format" 
[detached HEAD d5bb2068164] Changing EOL-format 
 1 file changed, 205 insertions(+), 205 deletions(-) 
$ git merge 2978ffb9f9d 
Auto-merging langtools/src/share/classes/com/sun/tools/javac/util/RawDiagnosticFormatter.java 
Auto-merging langtools/src/share/classes/com/sun/tools/javac/util/BasicDiagnosticFormatter.java 
Auto-merging langtools/src/share/classes/com/sun/tools/javac/util/AbstractDiagnosticFormatter.java 
Auto-merging langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties 



 langtools/test/tools/javac/processing/model/testgetallmembers/Main.java                               | 2 +- 
 langtools/test/tools/javadoc/6176978/T6176978.java                                                    | 2 +- 
 langtools/test/tools/javadoc/6176978/X.java                                                           | 2 +- 
 83 files changed, 83 insertions(+), 83 deletions(-) 
$

Y esta vez el merge no tuvo problemas. Somos tan buenos. No está especificado en esa salida de la consola pero la revisión del merge es cddf9316c72. Así que esto es lo que tuvimos que hacer desde el principio, cierto? Hacer que ambas ramas tengan el mismo formato de EOL de el ancestro común y entonces mezclar. Bueno, sí, eso funciona. Pudieron mezclar (incluso mejor, no hubo conflictos!). Pero, como un giro adicional, hagamos un checkout de la revisión original desde la que comenzamos a trabajar, 9d3e0870754, modifiquemos una línea del archivo, un cambio inocente, creemos una revisión, regresemos a la revisión de merge que acabamos de crear e intentemos mezclar de nuevo, bien? Qué creen que va a pasar?

Listing 3.31:Ejemplo 17 - checkout de 9d3e0870754
$ git checkout 9d3e0870754 
HEAD is now at 9d3e0870754 Merge

Listing 3.32:Ejemplo 17 - Modificando el archivo sobre 9d3e0870754
1<?xml version=’1.0’ encoding=’utf-8’?> 
2 
3 
4<!-- 
5 Copyright 2003 Sun Microsystems, Inc.  All Rights Reserved. 
6 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.

Agregué una segunda línea vacía antes del comentario XML.

Listing 3.33:Ejemplo 17 - completando la revisión
$ git add langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
$ git commit -m "Adding empty line" 
[detached HEAD 91df2b0b521] Adding empty line 
 1 file changed, 1 insertion(+)

Como se ve la historia en este momento?

Listing 3.34:Ejemplo 17 - historia actual
* 91df2b0b521 Adding empty line 
| *   cddf9316c72 Merge commit ’2978ffb9f9d’ into HEAD 
| |\ 
| | *   2978ffb9f9d Merge 
| | |\ 
| | * | 56fcf6c0524 6814575: Update copyright year 
| * | | d5bb2068164 Changing EOL-format 
|/ / / 
* | |   9d3e0870754 Merge

Ahora hagamos checkout de cddf9316c72 y tratemos de mezclar la revisión que acabamos de crear.

Listing 3.35:Ejemplo 17 - Mezclando de nuevo
$ git checkout cddf9316c72 
Warning: you are leaving 1 commit behind, not connected to 
any of your branches: 
 
  91df2b0b521 Adding empty line 
 
If you want to keep it by creating a new branch, this may be a good time 
to do so with: 
 
 git branch <new-branch-name> 91df2b0b521 
 
HEAD is now at cddf9316c72 Merge commit ’2978ffb9f9d’ into HEAD 
$ git merge 91df2b0b521 
Auto-merging langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
CONFLICT (content): Merge conflict in langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclet.xml 
Automatic merge failed; fix conflicts and then commit the result.

Y apuesto a que esta vez tienen una idea del tamaño de este conflicto, me equivoco? Y si no lo saben, aquí les doy una pista: Comienza con conflicto y termina en de archivo completo. Y pueden ver como al mezclar una rama que fue arrancada antes de corregir el formato de EOL a lo que era originalmente, nos volvemos a conseguir con nuestro desastre.

Pero entonces como se sale de este desastre? El mejor enfoque que podrían tomar es reescribir la historia para que el cambio de formato de EOL no ocurra. Yo se, yo se.... no es recomendado como principio general, pero hay situaciones que lo ameritan. Yo diría que esta es una de esas situaciones. En la sección de Recetas, incluí una forma más o menos sencilla de reescribir la historia de una rama siempre y cuando sea recta, es decir, no tenga merges.

Si reescribir la historia no es una opción, entonces ajusten el formato de EOL al original en la rama en la que fue cambiado y carguen con las consecuencias. Desafortunadamente están manejando una situación que no debió suceder en primer lugar. Si un desarrollador cambia el formato de EOL de un archivo en un PR, eso nunca debió ser aceptado. Debió ser rechazado, el desarrollador debió coregir la historia de la rama tal que el cambio de formato nunca sucede (la rama puede ser corregida con muy poco esfuerzo usando la receta, ok?).

3.2.3 Ejercicios

Ejercicio 8 - usando el script

Del repo de ejercicios, hagan checkout de la rama exercise8/branchA y hagan merge de exercise8/branchB. Deberíamos ver un conflicto de archivo completo en primes.py.

Traten de hacer el merge corrigiendo la historia de la rama usando el script dado en esta receta. La solución está aquí.

3.2.4 Tips

Copyright 2020 Edmundo Carmona Antoranz Mucho más contenido viene en camino.