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):
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.
Mac: CR (Carriage Return, caracter 0x0d, lo que normalmente
tratamos como ∖r en lenguages de programación)
NIX: LF (Line Feed, caracter 0x0a, lo que normalmente tratamos como
∖n en lenguages de programación)
Windows: CRLF (Carriage Return seguido de Line Feed, caracteres
0x0d0a)
Aquí hay un pequeño archivo de texto con 3 formatos diferentes de EOL para
que vean como cambian los archivos a nivel binario:
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:
El desarrollador cambió el formato a propósito. Podría haber
una razón técnica para ello (Me es difícil pensar en una pero...) Pero
si el cambio fue solo por hacerlo, esto merece un llamado de atención por
toda la carga que este cambio genera más adelante (ya verán).
El editor (IDE, editor de texto) lo cambió sin que el desarrollador
se de cuenta. Son cosas que pasan! Alguna vez han abierto un archivo NIX
en el Notepad de Windows? Guardaron el archivo sin si quiera pensarlo?
Ahí tienen! 6
Y apuesto a que hay otros editores que no mantienen el formato original de
EOL. De cualquier forma, este cambio de formato debió ser notado por el
desarrollador antes de publicar los cambios para que otros desarrolladores
los halen dado que si otro desarrollador cambia dicho archivo y entonces
hala (mezcla, hace rebase o cherry-pick) el cambio donde se cambia el
formato de EOL, van a terminar con un horrendo conflicto de archivo
completo como los que estamos estudiando... incluso si el cambio es de
una sola línea. Si usan un front-end decente para ver los cambios de las
revisiones, deberían ver que incluso si lo que quisieron modificar fue una
sola línea, todo el archivo se verá como que ha sido limpiado y vuelto
a agregar de cabo a rabo. Ese es el síntoma que debería hacer que
se den cuenta de que hay un problema y que deben corregirlo antes de
publicar.
el propio git se mete en el medio. git tiene algunos trucos que pueden ser
utilizados para configurar el formato de EOL de los archivos. Personalmente,
considero que son bastante difíciles de configurar apropiadamente considerando
los diferentes sistema operativos, IDEs, etc. Siempre recomiendo configurar
git para que no modifique los saltos de línea y dejar que los desarrolladores
se hagan cargo. Y esto se puede lograr con un pequeño ajuste en el archivo
.gitattributes de tal forma que puede ser compartido por cualquiera que
trabaje en un proyecto. Y si están totalmente seguros de que quieren dejar
que git se encargue del formato de EOL, asegúrense de leer los detalles
al respecto comenzando con git help attributes, específicamente la
sección relacionada a checking-out y checking-in. Esa es una muy
buena lectura. Por último, pero no menos importante, no caigan en la
trampa de usar core.autocrlf. Lean de lo que se trata antes de decidir
qué valor utilizar allí7
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
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.
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
Asegúrense de que los archivos nunca cambian su formato de EOL... a
menos que sea verdaderamente requerido.
Si un archivo tuvo un cambio de formato deEOL (sin una razón
legítima), no acepten ese cambio.
Usen el script descrito aquí para corregir la historia de la rama si notan
que hubo un cambio de formato de EOL antes de mezclarlo en otras
ramas.
Asegúrense de que sus herramientas estén configuradas para no ocultar
cambios de EOL.
Copyright 2020 Edmundo Carmona Antoranz
Mucho más contenido viene en camino.