1.6 El código borrado puede ser un reto

El libro se puede descargar aquí

Cuando se encuentra casos de código borrado, puede ser tentador asumir que solo debemos borrar código y que no hay más nada que pensar al respecto. Desafortunadamente, ese no siempre es el caso. En muchas ocasiones el código no fue realmente borrado sino que se movió.

1.6.1 Ejemplo 6

Listing 1.92:Ejemplo 6
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "blue": "blue sky"} 
8 
9def getPhrase(origColor): 
10    color = origColor.lower() 
11    if color not in colors: 
12        sys.stderr.write("There is no phrase defined for color %s\n" % origColor) 
13        sys.exit(1) 
14    phrase = colors[color] 
15<<<<<<< HEAD 
16||||||| 3c7f401 
17    phrase = "%s: %s" % (origColor, phrase) 
18======= 
19    phrase = "Color: %s\nPhrase: %s" % (origColor, phrase) 
20>>>>>>> example7/branchB 
21    return phrase 
22 
23print(getPhrase(sys.argv[1]))

dMU

La línea donde se modifica la frase para incluir el color original (originalmente en la línea 17) fue removida.

dML

El formato de la frase fue modificado (desde la línea 17 a la 19).

Resolución

Bajo condiciones normales, si la intención de una rama era borrar una línea, entonces probablemente no hay punto en una modificación que se hizo de esa línea en otra rama, cierto? Así que:

Listing 1.93:Ejemplo 6 - resolución
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "blue": "blue sky"} 
8 
9def getPhrase(origColor): 
10    color = origColor.lower() 
11    if color not in colors: 
12        sys.stderr.write("There is no phrase defined for color %s\n" % origColor) 
13        sys.exit(1) 
14    phrase = colors[color] 
15    return phrase 
16 
17print(getPhrase(sys.argv[1]))

En este caso es bastante obvio, pero con bastante frecuencia se necesita un poquito más de uso de ciclos de máquina cerebral para poder hacer una resolución informada. Tomemos este otro ejemplo:

1.6.2 Ejemplo 7

Listing 1.94:Ejemplo 7
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "blue": "blue sky"} 
8 
9def getPhrase(color): 
10<<<<<<< HEAD 
11||||||| f75877d 
12    if color not in colors: 
13        sys.stderr.write("Got no phrase for color %s\n" % color) 
14        sys.exit(1) 
15======= 
16    if color not in colors: 
17        sys.stderr.write("Phrase for color %s is not defined\n" % color) 
18        sys.exit(1) 
19>>>>>>> example8/branchB 
20    phrase = getFormattedPhrase(color) 
21    return phrase 
22 
23def getFormattedPhrase(aColor): 
24    return "%s: %s" % (aColor, colors[aColor]) 
25 
26print(getPhrase(sys.argv[1]))

dMU

El condicional para verificar que el color esté definido (líneas 12-14) fue borrado.

dML

El mensaje de error cuando el color no está definido fue modificado (de la línea 13 a la 17).

Resolución

Exactamente como en el ejemplo anterior, borramos las líneas en conflicto:

Listing 1.95:Ejemplo 7 - resolución
1#!/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 = getFormattedPhrase(color) 
11    return phrase 
12 
13def getFormattedPhrase(aColor): 
14    return "%s: %s" % (aColor, colors[aColor]) 
15 
16print(getPhrase(sys.argv[1]))

Vamos muy bien! Pero no nececitamos mucha potencia cerebral pare resolverlo, cierto? Donde está el truco? Tomemos este otro ejemplo:

1.6.3 Ejemplo 8

Listing 1.96:Ejemplo 8
1#!/usr/bin/python 
2 
3import sys 
4 
5colors = {"black": "black mirror", 
6          "white": "white noise", 
7          "blue": "blue sky"} 
8 
9def getPhrase(color): 
10<<<<<<< HEAD 
11||||||| 305fd0a 
12    if color not in colors: 
13        sys.stderr.write("Got no phrase for color %s\n" % color) 
14        sys.exit(1) 
15======= 
16    if color not in colors: 
17        sys.stderr.write("Phrase for color %s is not defined\n" % color) 
18        sys.exit(1) 
19>>>>>>> example9/branchB 
20    phrase = getFormattedPhrase(color) 
21    return phrase 
22 
23def getFormattedPhrase(aColor): 
24    if aColor not in colors: 
25        sys.stderr.write("Got no phrase for color %s\n" % aColor) 
26        sys.exit(1) 
27    return "%s: %s" % (aColor, colors[aColor]) 
28 
29print(getPhrase(sys.argv[1]))

Este se parece mucho al ejemplo 7.

dMU

La verificación de que el color esté definido fue borrada.

dML

El mensaje cuando el color no está definido fue modificado.

Resolución

Deberíamos eliminar las líneas, cierto? Justo como hicimos en el Bueno... no exactamente. Si miramos la definición de getFormattedPhrase(), en las líneas 23-27, veremos que tenemos exactamente el mismo mensaje de error del MB... excepto por el nombre de la variable del color. Miren el MB:

Listing 1.97:Ejemplo 8 - MB
11||||||| 305fd0a 
12    if color not in colors: 
13        sys.stderr.write("Got no phrase for color %s\n" % color) 
14        sys.exit(1) 
15=======

Miren la definición de getFormattedPhrase(), fuera del CB:

Listing 1.98:Ejemplo 8 - getFormattedPhrase()
23def getFormattedPhrase(aColor): 
24    if aColor not in colors: 
25        sys.stderr.write("Got no phrase for color %s\n" % aColor) 
26        sys.exit(1) 
27    return "%s: %s" % (aColor, colors[aColor])

Y eso debería sugerirnos que el bloque if no fue realmente borrado. En realidad fue movido como parte de un esfuerzo de refactoring33 . En este caso lo que debemos hacer es ajustar el código en getFormattedPhrase() como se indica en el dML. Así que la resolución correcta del conflicto se vería así:

Listing 1.99:Ejemplo 8 - resolución
1#!/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 = getFormattedPhrase(color) 
11    return phrase 
12 
13def getFormattedPhrase(aColor): 
14    if aColor not in colors: 
15        sys.stderr.write("Phrase for color %s is not defined\n" % aColor) 
16        sys.exit(1) 
17    return "%s: %s" % (aColor, colors[aColor]) 
18 
19print(getPhrase(sys.argv[1]))

Mucho cuidado con el nombre de la variable!!!

No piensen que un conflicto está restringido a los CBs. Otras partes del código podrían requerir ajustes. Nada está fuera de los límites para resolver conflictos. También consideren que, en este caso, el archivo era muy pequeño así que era fácil ver qué había sucedido con el código. Pero consideren cual sería la situación si el archivo tuviera algunos cientos de líneas y no fuera tan sencillo ver lo que pasó. O incluso un poco más difícil, si la sección fue movida a un módulo/archivo diferente. Entonces sería bastante más difícil ver qué sucedió.... justo como:

1.6.4 Ejemplo 9

Listing 1.100:Ejemplo 9
1#!/usr/bin/python 
2 
3from module import getFormattedPhrase 
4import sys 
5 
6def getPhrase(color): 
7<<<<<<< HEAD 
8||||||| f78479a 
9    if color not in colors: 
10        sys.stderr.write("Got no phrase for color %s\n" % color) 
11        sys.exit(1) 
12======= 
13    if color not in colors: 
14        sys.stderr.write("Color %s has no phrase\n" % color) 
15        sys.exit(1) 
16>>>>>>> example10/branchB 
17    phrase = getFormattedPhrase(color) 
18    return phrase 
19 
20print(getPhrase(sys.argv[1]))

Se parece al ejemplo 7 donde solo debimos borrar la sección de código y ser felices. Pero si miraran dentro de module.py verían la misma sección de código que fue movida en el ejemplo 8:

Listing 1.101:Ejemplo 9 - module.py
1import sys 
2 
3colors = {"black": "black mirror", 
4          "white": "white noise", 
5          "blue": "blue sky"} 
6 
7def getFormattedPhrase(aColor): 
8    if aColor not in colors: 
9        sys.stderr.write("Got no phrase for color %s\n" % aColor) 
10        sys.exit(1) 
11    return "%s: %s" % (aColor, colors[aColor])

En este caso resolver el conflicto requeriría ajustar el mensaje de error en module.py siguiendo la nueva frase del LB de nuestro CB en ejemplo.py, ajustando el nombre de la variable, justo como hicimos en el Ejemplo 8:

Listing 1.102:Ejemplo 9 - module.py final
1import sys 
2 
3colors = {"black": "black mirror", 
4          "white": "white noise", 
5          "blue": "blue sky"} 
6 
7def getFormattedPhrase(aColor): 
8    if aColor not in colors: 
9        sys.stderr.write("Color %s has no phrase\n" % aColor) 
10        sys.exit(1) 
11    return "%s: %s" % (aColor, colors[aColor])

Y removemos elCB de example.py:

Listing 1.103:Ejemplo 9 - example.py final
1#!/usr/bin/python 
2 
3from module import getFormattedPhrase 
4import sys 
5 
6def getPhrase(color): 
7    phrase = getFormattedPhrase(color) 
8    return phrase 
9 
10print(getPhrase(sys.argv[1]))

1.6.5 Cómo podemos evitar... adivinar?

Primero, no asuman que es un proceso de un solo paso. Cazar cuando sucedió que se borró o movió código implica subirse las mangas y ensuciarse de historia. Así que, sin más preambulo, intentemos con un ejemplo real:

1.6.6 Ejemplo 10 - un ejemplo de git sobre código borrado

Del repo de git, hagan un checkout de la revisión 0da63da794 y mezclen la revisión f1928f04b2 34 . Hay un CB en builtin/grep.c.

Listing 1.104:Ejemplo 10 - CB en builtin/grep.c
1123<<<<<<< HEAD 
1124        if (recurse_submodules && untracked) 
1125                die(_("--untracked not supported with --recurse-submodules")); 
1126 
1127||||||| d0654dc308 
1128        if (recurse_submodules && (!use_index || untracked)) 
1129                die(_("option not supported with --recurse-submodules")); 
1130 
1131======= 
1132>>>>>>> f1928f04b2 
1133        if (!show_in_pager && !opt.status_only) 
1134                setup_pager();

y se puede ver fácilmente que se trata de código borrado.

dMU

Se modificó el condidional y se modificó el mensaje que se escribe en la llamada a die() en la línea 1125.

dML

La sección completa fue borrada.

Resolution

Deberíamos remover la sección completa así como así? Tratemos de averiguar cuando el bloque fue borrado en la otra rama.

Listing 1.105:Ejemplo 10 - git blame –reverse
$ git blame -s --reverse d0654dc308..f1928f04b2 -- builtin/grep.c 



d7992421e1a 1118)       if (recurse_submodules && (!use_index || untracked)) 
d7992421e1a 1119)               die(_("option not supported with --recurse-submodules")); 
d7992421e1a 1120) 
f1928f04b25 1121)       if (!show_in_pager && !opt.status_only) 
f1928f04b25 1122)               setup_pager();

git blame –reverse nos permite ver cual fue la última revisión en la que una línea de código estuvo presente en la historia. La dos revisiones que utilicé fueron el ancestro común (provisto en el CB), donde sabemos que la línea estaba presente, y la rama donde se borró el código que en este caso es la otra rama.

Y podemos ver que la última revisión donde las dos líneas a las que les estamos siguiendo la pista están presentes es en la d7992421e1a, en la dirección hacia la otra rama. El siguiente paso sería ver en cual de las revisiones que le siguen se removieron las líneas:

Listing 1.106:Ejemplo 10 - git log –oneline
$ git log --graph --oneline d7992421e1a..f1928f04b2 -- builtin/grep.c 
* f1928f04b2 grep: use no. of cores as the default no. of threads 
* 70a9fef240 grep: move driver pre-load out of critical section 
* 1184a95ea2 grep: re-enable threads in non-worktree case 
* 6c307626f1 grep: protect packed_git [re-]initialization 
* c441ea4edc grep: allow submodule functions to run in parallel

Y al mirar este puñado de revisiones35 nos damos cuenta de que las líneas fueron movidas, no no borradas, en c441ea4edc:

Listing 1.107:ejemplo 10 - git show c441ea4edc
$ git show c441ea4edc 



diff --git a/builtin/grep.c b/builtin/grep.c 
index d3ed05c1da..ac3d86c2e5 100644 
--- a/builtin/grep.c 
+++ b/builtin/grep.c 



@@ -1052,6 +1050,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 
        pathspec.recursive = 1; 
        pathspec.recurse_submodules = !!recurse_submodules; 
 
+       if (recurse_submodules && (!use_index || untracked)) 
+               die(_("option not supported with --recurse-submodules")); 

        if (list.nr || cached || show_in_pager) { 
                if (num_threads > 1) 
                        warning(_("invalid option combination, ignoring --threads")); 



@@ -1105,9 +1114,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 
                } 
        } 
 
-       if (recurse_submodules && (!use_index || untracked)) 
-               die(_("option not supported with --recurse-submodules")); 

        if (!show_in_pager && !opt.status_only) 
                setup_pager();

Esas líneas todavía están en el arbol de trabajo? Sí están, aunque un poco desplazadas:

Listing 1.108:ejemplo 10 - otra sección de builtin/grep.c
1054        pathspec.recurse_submodules = !!recurse_submodules; 
1055 
1056        if (recurse_submodules && (!use_index || untracked)) 
1057                die(_("option not supported with --recurse-submodules")); 
1058 
1059        if (show_in_pager) {

Traigamos los cambios como fueron introducidos en el UB y estaremos bien:

Listing 1.109:example 10 - adjust section of builtin/grep.c
1054        pathspec.recurse_submodules = !!recurse_submodules; 
1055 
1056        if (recurse_submodules && untracked) 
1057                die(_("--untracked not supported with --recurse-submodules")); 
1058 
1059        if (show_in_pager) {

Borramos el CB por completo:

Listing 1.110:example 10 - remover el CB en builtin/grep.c
1120                } 
1121        } 
1122 
1123        if (!show_in_pager && !opt.status_only) 
1124                setup_pager();

Y si comparamos con la revisión 56ceb64eb0, no debería haber diferencias significativas.

1.6.7 No se impresionen por conflictos grandes

El código borrado puede producir conflictos muy grades que, al mirar más detenidamente, en realidad no lo son tanto. Cuando se borra código en una rama y se modifica por lo menos una de esas líneas borradas en otra rama, git no puede hacer mucho más que mostrar el código original y como se ve el código en las dos puntas que se mezclan, usando diff3 como siempre debemos hacer, cierto? Si el bloque era originalmente de 5 líneas al comienzo y termina de 6 al final y en la otra rama se borran las 5 líneas originales, eso sería 11 líneas más marcadores.... no suena muy grande. Pero si el código original era de 300 líneas y el código final también es de 300 líneas con una línea modificada y en la otra rama se borran las 300 líneas originales, ahora estamos hablando de 300 * 2 + marcadores. Y todo ello por una línea de código que fue modificada. Los ejemplos que intentamos previamente sobre código borrado no han sido grandes... así que utilicemos u ejemplo con algunas líneas más.

1.6.8 Ejemplo 11

Del repo de git, hagan checkout de la revisión d88949d86e y mezclen d9f62dfa0d 36. Miren el CB en ref-filter.c. El CB comienza en la línea 1680 y termina en la línea 1959 así que no voy a escribir el contenido del CB en el artículo debido al tamaño. Pero igualmente vamos a hacer el análisis y la resolución.

dMU

Toda ls sección fue borrada.

dML

Les voy a dar 5 minutos para que ustedes lo descubran por ustedes mismos. Luego les diré cual es la diferencia así que, déjen de leer, miren el códogo por y minutos y vuelvan! Sin hacer trampas!

[Pasan 5 minutos]

Entonces... lo pudieron ver? Se cambia !oidcpm() por oideq(), cierto? Fue difícil verlo? Seguro que lo fue.... en realidad no puedo asegurarlo ya que yo no lo hice visualmente. Dejé que git me ayudara:

Listing 1.111:Ejemplo 11 - git diff
1054$ git diff HEAD...MERGE_HEAD -- ref-filter.c 
1055diff --git a/ref-filter.c b/ref-filter.c 
1056index 0bccfceff2..ccca317ce1 100644 
1057--- a/ref-filter.c 
1058+++ b/ref-filter.c 
1059@@ -1710,7 +1710,7 @@ struct contains_stack { 
1060 static int in_commit_list(const struct commit_list *want, struct commit *c) 
1061 { 
1062        for (; want; want = want->next) 
1063-               if (!oidcmp(&want->item->object.oid, &c->object.oid)) 
1064+               if (oideq(&want->item->object.oid, &c->object.oid)) 
1065                        return 1; 
1066        return 0; 
1067 }

git es una herramienta de analisis fenomenal. Traten de aprender como extraer lo más posible:

De vuelta al conflicto... vieron? Todas esas 279 líneas de conflicto horrible se deben a este pequeño cambio (y el borrado en el dMU, por supuesto). Ahora que sabemos de qué se trataba, pueden resolver este conflicto por sí solos, cierto? Inténtelo. Aquí está la lista completa de los cambios para que verifiquen al final:

Luego de averiguar que el código en cuestión fue movido:

Listing 1.112:Ejemplo 11 - commit-reach.c
426static int in_commit_list(const struct commit_list *want, struct commit *c) 
427
428        for (; want; want = want->next) 
429                if (oideq(&want->item->object.oid, &c->object.oid)) 
430                        return 1; 
431        return 0; 
432}

Removiendo el CB de ref-filter.c:

Listing 1.113:Ejemplo 11 - ref-filter.c
1679/* 
1680 * Return 1 if the refname matches one of the patterns, otherwise 0. 
1681 * A pattern can be a literal prefix (e.g. a refname "refs/heads/master" 
1682 * matches a pattern "refs/heads/mas") or a wildcard (e.g. the same ref 
1683 * matches "refs/heads/mas*", too). 
1684 */ 
1685static int match_pattern(const struct ref_filter *filter, const char *refname) 
1686{

También hay un conflicto en este archivo:

Listing 1.114:Ejemplo 11 - builtin/log.c
1763        if (ignore_if_in_upstream) { 
1764                /* Don’t say anything if head and upstream are the same. */ 
1765                if (rev.pending.nr == 2) { 
1766                        struct object_array_entry *o = rev.pending.objects; 
1767                        if (oideq(&o[0].item->oid, &o[1].item->oid)) 
1768                                goto done; 
1769                } 
1770                get_patch_ids(&rev, &ids); 
1771        }

1.6.9 Moraleja

Piensen en los conflictos relacionados a código borrado como si se tratara de un iceberg. Solo podemos ver la punta del conflicto, pero podría tratarse de una montaña invertida más grande que el Olimpo 37

1.6.10 Ejercicios

Ejercicio 6

Del repo de git, hagan checkout de la revisión 0194c9ad72 y mezclen la revisión aa46a0da30. La solución está aquí. Copyright 2020 Edmundo Carmona Antoranz