1.2 Los conflictos son una moneda de 3 caras

El libro se puede descargar aquí

Hagamos nuestro primer ejemplo de un proyeco real. En este caso, del propio git.

1.2.1 Ejemplo 3 - un conflicto de git

Del repo de git, hagan checkout de la revision 80648bb3f2 and mezclen 20a5fd881a 8. Deben quedar con un conflicto en pack-bitmap.c:

Listing 1.34:Ejemplo 3 - Conflicto en pack-bitmap.c
671        struct bitmap *objects = bitmap_git->result; 
672 
673<<<<<<< HEAD 
674        ewah_iterator_init(&it, type_filter); 
675======= 
676        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
677                return; 
678 
679        init_type_iterator(&it, bitmap_git, object_type); 
680>>>>>>> 20a5fd881a 
681 
682        for (i = 0; i < objects->word_alloc &&

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.

Ejemplo 3 - Escenario 1

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

Listing 1.35:Ejemplo 3 - Escenario 1 - código en el ancestro común
671        struct bitmap *objects = bitmap_git->result; 
672 
673        ewah_iterator_init(&it, type_filter); 
674        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
675                return; 
676 
677        init_type_iterator(&it, bitmap_git, object_type); 
678 
679        for (i = 0; i < objects->word_alloc &&

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::

Listing 1.36:Ejemplo 3 - Escenario 1 - resolución del conflicto
671        struct bitmap *objects = bitmap_git->result; 
672 
673        for (i = 0; i < objects->word_alloc &&
Ejemplo 3 - Escenario 2

Ahora asuman que el ancestro común se ve así:

Listing 1.37:Ejemplo 3 - Escenario 2 - código en el ancestro común
671        struct bitmap *objects = bitmap_git->result; 
672 
673        ewah_iterator_init(&it, type_filter); 
674        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
675                return; 
676 
677        for (i = 0; i < objects->word_alloc &&

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:

Listing 1.38:Ejemplo 3 - Escenario 2 - Resolución del conflicto
671        struct bitmap *objects = bitmap_git->result; 
672 
673        init_type_iterator(&it, bitmap_git, object_type); 
674 
675        for (i = 0; i < objects->word_alloc &&

Ejemplo 3 - Escenario 3

Finalmente, asuman que el ancestro común se ve así:

Listing 1.39:Ejemplo 3 - Escenario 3 - código en el ancestro común
671        struct bitmap *objects = bitmap_git->result; 
672 
673        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
674                return; 
675 
676        for (i = 0; i < objects->word_alloc &&

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:

Listing 1.40:Ejemplo 3 - Escenario 3 - Resolución del conflicto
671        struct bitmap *objects = bitmap_git->result; 
672 
673        init_type_iterator(&it, bitmap_git, object_type); 
674 
675        ewah_iterator_init(&it, type_filter); 
676 
677        for (i = 0; i < objects->word_alloc &&

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:

Ejemplo 3 - Resolviendo el conflicto de verdad

Primero lo primero. Cual es el ancestro común?

Listing 1.41:Ejemplo 3 - Averiguando el ancestro común
$ git merge-base HEAD MERGE_HEAD 
d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783

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:

Listing 1.42:Ejemplo 3 - verificando el ancestro común
$ git blame -s -L 671,681 d0654dc30 -- pack-bitmap.c 
fff42755efc 671)        while (roots) { 
fff42755efc 672)                struct object *object = roots->item; 
fff42755efc 673)                roots = roots->next; 
fff42755efc 674) 
3ae5fa0768f 675)                if (find_pack_entry_one(object->oid.hash, bitmap_git->pack) > 0) 
fff42755efc 676)                        return 1; 
fff42755efc 677)        } 
fff42755efc 678) 
fff42755efc 679)        return 0; 
fff42755efc 680) } 
fff42755efc 681)

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:

Listing 1.43:Ejemplo 3 - verificando el ancestro común... de nuevo
$ git blame -s -L 631,638 d0654dc30 -- pack-bitmap.c 
3ae5fa0768f 631)        struct bitmap *objects = bitmap_git->result; 
3ae5fa0768f 632) 
3ae5fa0768f 633)        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
fff42755efc 634)                return; 
fff42755efc 635) 
fff42755efc 636)        ewah_iterator_init(&it, type_filter); 
fff42755efc 637) 
fff42755efc 638)        while (i < objects->word_alloc && ewah_iterator_next(&filter, &it)) {

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:

Listing 1.44:Ejemplo 3 - resolucin final
671        struct bitmap *objects = bitmap_git->result; 
672 
673        init_type_iterator(&it, bitmap_git, object_type); 
674 
675        for (i = 0; i < objects->word_alloc &&

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:

Example 3 - Solving the conflict for real with diff3

Listing 1.45:Ejemplo 3 - conflicto con diff3 aplicado
671        struct bitmap *objects = bitmap_git->result; 
672 
673<<<<<<< HEAD 
674        ewah_iterator_init(&it, type_filter); 
675||||||| d0654dc308 
676        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
677                return; 
678 
679        ewah_iterator_init(&it, type_filter); 
680======= 
681        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
682                return; 
683 
684        init_type_iterator(&it, bitmap_git, object_type); 
685>>>>>>> 20a5fd881a 
686 
687        for (i = 0; i < objects->word_alloc &&

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:

Listing 1.46:Ejemplo 3 - paso 1
673<<<<<<< HEAD 
674        ewah_iterator_init(&it, type_filter); 
675||||||| d0654dc308

Consideren como cambió el código desde el ancestro común hacia la otra rama:

Listing 1.47:Ejemplo 3 - Paso 2
675||||||| d0654dc308 
676        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
677                return; 
678 
679        ewah_iterator_init(&it, type_filter); 
680======= 
681        if (bitmap_git->reuse_objects == bitmap_git->pack->num_objects) 
682                return; 
683 
684        init_type_iterator(&it, bitmap_git, object_type); 
685>>>>>>> 20a5fd881a

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:

Listing 1.48:Ejemplo 3 - Paso 3
673<<<<<<< HEAD 
674        init_type_iterator(&it, bitmap_git, object_type); 
675||||||| d0654dc308

Y ya podemos remover el resto del conflicto y los marcadores:

Listing 1.49:example 3 - Done!
671        struct bitmap *objects = bitmap_git->result; 
672 
673        init_type_iterator(&it, bitmap_git, object_type); 
674 
675        for (i = 0; i < objects->word_alloc &&

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.

1.2.2 Tips

1.2.3 Ejercicios

Ejercicio 3

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