El libro se puede descargar aquí
Hagamos nuestro primer ejemplo de un proyeco real. En este caso, del propio git.
Del repo de git, hagan checkout de la revision 80648bb3f2 and mezclen 20a5fd881a 8. Deben quedar con un conflicto en pack-bitmap.c:
Los escucho. Debemos mantener todas las líneas? Cual bloque debe venir primero? HEAD o la otra rama? Quizás la llamada a ewah_iterator_init() debería ser colocada entre el condicional y la llamada a init_type_iterator() de la otra rama? Quizás todas las líneas deberían ser borradas? Quizás solo nos debamos quedar con solo una parte?
Todos esos escenarios son posibles? Si, todos son posibles. Y todos son correctos? Ah, no. Dado que ellos terminan siendo diferentes con relación a lo que el código hace, no todos pueden ser correctos. Pero entonces, esto de resolver conflictos se trata de un juego de adivinanzas? Una lotería? No, no hay necesidad de adivinar. Quizás en este punto todavía no estén al tanto de ello pero lo que se necesita es un poco más de información. Información que, por cierto, no está disponible en el conflicto como se muestra allí. Déjenme mostrarles algunos escenarios para que vean de lo que estoy hablando.
Para los efectos de poder hacer la explicación, assuman que el archivo en en el ancestro común 9 se ves así, alrededor de esas líneas: 10
Si eso fuera así, eso quiere decir que, basados en el conflicto, cada una de las ramas removió una sección del código original. HEAD removió el condicional de la línea 674 y la llamada a init_type_iterator(). La otra rama removió la llamada a ewah_iterator_init().
Entonces la resolución del conflicto sería esta::
Ahora asuman que el ancestro común se ve así:
En este caso, , HEAD removió el condicional que arranca en la linea 674, la otra rama reemplaza la llamada a ewah_iterator_init() por una llamada a init_type_iterator(). Eso apunta a tener esto como resolución de conflicto:
Finalmente, asuman que el ancestro común se ve así:
En este escenario, HEAD removió el condicional que arranca en la línea 673 y agrega una llamada a ewah_iterator_init(). En la otra rama se mantiene el condicional pero se agrega una llamada a init_type_iterator(). Esto significa otra resolución. Debemos remover el condicional y mantener las dos llamadas.... pero cual debería venir primero? Esa es una muy buena pregunta y es algo que el código solo no nos puede responder. Tendríamos que requerir más conocimiento de fondo del código en cuestión para poderlo resolver de forma apropiada, o incluso tener que revisar los requerimientos que se utilizaron para incluir cada llamada.11 Asumamos, solo para poder avanzar con el escenario que ya sabemos que la llamada a init_type_iterator() se debe hacer antes de la llamada a ewah_iterator_init(). Entonces la resolución del conflicto sería esta:
Así que tenemos 3 escenarios diferentes para el mismo conflicto. Y cada escenario requirió una resolución diferente. En este punto lo que quiero que descubran es la cruda realidad: el ancestro común 12 dirige la resolución del conflicto. Si no consideran el ancestro común, estarían.... cual sería la mejor palabra para describirlo? Ah, si! Adivinando! Y no me importa cuan educada su adivinanza sea (en términos del conocimiento del código involucrado). Tendrían que tener una memoria que supere a la provista por git para recordar como era el código en el pasado para que deje de llamarlo de esa forma.
Habiendo aclarado ese punto, continuemos con el ejemplo actual. Como se veía el ancestro común en este caso? Descubrirlo es un proceso que requiere de más de u paso así que comencemos:
Primero lo primero. Cual es el ancestro común?
El ancestro común es la revisión d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783. En esa revisión, el archivo se llama igual? Esperemos que sí!. El contenido cambió mucho? Quiás se agregaron o eliminaron algunas líneas antes del código relativo al bloque en conflicto? Esperemos que no sea el caso!13 . Hagamos un intento:
Ups! Se nos acabó la suerte.14 El archivo no ha sido renombrado (que bien!) pero su contenido sí cambió (que mal!) y por lo tanto la sección que tenemos que mirar no está en la misma posición. Luego de mirar el archivo un rato, nos damos cuenta de que lo que nos interesa está unas 40 líneas más arriba en el archivo:
Y ahora podemos comparar lo que hizo cada rama. En HEAD, se removió el condicional que comienza en la línea 633.15 . En la otra rama, se removió la llamada a ewah_iterator_init() y se agregó la llamada a init_type_iterator(). Lo que significa que en nuestra resolución del conflicto solo debemos mantener la llamada a init_type_iterator() ya que las otras partes del código original pasaron a mejor vida:
Y al comparar esto con la revisión 0df82d99da, no se deberían ver diferencias significativas. 16
En este punto espero que hayan comprendido por qué es tan importante considerer el ancestro común en un conflicto. Como dije antes, al no considerar el ancestro común están, en el mejor de los casos, adivinando... en el peor de los casos, cometiendo un desastre. Estoy de acuerdo en que es bastante trabajo si vamos a hacer este proceso cada vez que tenemos un conflicto.
Qué sucede si el archivo fue renombrado? Podemos averiguar el nombre original fácilmente?
Antes de que quemen (o borren) el archivo en medio de la frustración, déjenme mostrarles un pequeño truco que git tiene debajo de la manga. Git les puede mostrar el contenido del ancestro común sin trabajo adicional de su parte. Al configurar.merge.conflictStyle para que tenga el valor diff3 17, git les mostrará el código de el ancestro común como una sección más del conflicto. Déjenme mostrarles como se vería el conflicto que acabamos de analizar con esta opción:
Ajá! Ahora podemos ver el ancestro común entre las secciones de cada rama. git también tiene la amabilidad de indicarnos cual es la revisión del ancestro común18 . Y ahora podemos ver qué hizo cada rama.
Para resolver nuestro conflicto, comiencen a trabajar en la sección de HEAD:
Consideren como cambió el código desde el ancestro común hacia la otra rama:
La llamada a ewah_iterator_init() en la línea 679 se reemplazó por una llamada a init_type_iterator() en la línea 684. Replicamos este cambio en HEAD:
Y ya podemos remover el resto del conflicto y los marcadores:
Vieron? Sin complicaciones.
Pero, en serio! Es posible sobrevivir resolver conflictos sin mirar el ancestro común, cierto? Pero por supuesto! Igual que es posible desarrollar código sin hacer ningún tipo de pruebas unitarias, me explico? No es que sea como el oxígeno o el agua. Pero ya les dije lo que es hacerlo sin mirar el ancestro común: Es adivinar! Usar esta opción para mirar el ancestro común es el tip más importante de este manual.
Del repo de git, hacer checkout de la revisión fe870600fe y mezclar 1bdca81641 19. Resuelvan ambos conflictos (hay 2 archivos con conflctos, un conflicto en cada archivo). La solución está aquí.
Copyright 2020 Edmundo Carmona Antoranz