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 61#!/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ón1#!/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 71#!/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ón1#!/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 81#!/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 - MB11||||||| 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
refactoring .
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ón1#!/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 91#!/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.py1import 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 final1import 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 final1#!/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 .
Hay un CB en builtin/grep.c.
Listing 1.104:Ejemplo 10 - CB en builtin/grep.c1123<<<<<<< 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
revisiones
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.c1054 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.c1054 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.c1120 } 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
.
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 diff1054$ 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.c426static 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.c1679/* 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.c1763 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
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