A3.2 Quién, cuándo y dónde: blame, bisect y grep¶
Esta página usa la misma tienda de la anterior:
mkdir tienda && cd tienda
git init
echo '# Tienda' > README.md && git add README.md && git commit -m 'Crea el proyecto'
printf 'precio=10\nmoneda=EUR\n' > config.txt && git add config.txt && git commit -m 'Añade la configuración'
printf 'pan\nleche\nhuevos\n' > productos.txt && git add productos.txt && git commit -m 'Añade la lista de productos'
echo 'descuento=5' > descuentos.txt && git add descuentos.txt && git commit --author='Luis Prueba <luis@ejemplo.com>' -m 'Añade descuentos'
sed -i 's/EUR/USD/' config.txt && git commit -a --author='Luis Prueba <luis@ejemplo.com>' -m 'Cambia la moneda'
sed -i 's/precio=10/precio=100/' config.txt && git commit -am 'Ajusta el precio'
echo '--- fin ---' > pie.txt && git add pie.txt && git commit -m 'Añade el pie'
echo 'Tienda de ejemplo' >> README.md && git commit -am 'Actualiza el README'
blame: quién escribió cada línea¶
git blame archivo muestra el archivo línea a línea, y delante de cada una, el commit, la persona y la fecha del último cambio que la tocó. Se limita a unas líneas con -L (-L 1,2 = de la 1 a la 2):
$ git blame -L 1,2 config.txt
73be57a (Ana Ejemplo 2025-03-01 10:06:00 +0000 1) precio=100
d293cb8 (Luis Prueba 2025-03-01 10:05:00 +0000 2) moneda=USD
Se lee así: la línea 1 (precio=100) es del commit más reciente que la tocó (el del precio) y no de quien la creó, porque el blame enseña el último cambio; la línea 2 (moneda=USD) es de Luis. Para ver la historia completa de una línea y no solo el último cambio, está log -L:
$ git log -L1,1:config.txt --oneline
73be57a Ajusta el precio
diff --git a/config.txt b/config.txt
index 2a31dbd..edb7a43 100644
--- a/config.txt
+++ b/config.txt
@@ -1,1 +1,1 @@
-precio=10
+precio=100
7b9750a Añade la configuración
diff --git a/config.txt b/config.txt
new file mode 100644
index 0000000..c5dc659
--- /dev/null
+++ b/config.txt
@@ -0,0 +1,1 @@
+precio=10
Muestra cada cambio que ha sufrido la línea 1, con su diff: primero se creó (precio=10) y luego se cambió a precio=100.
Blame no es para culpar
El nombre viene de «culpar», pero su uso útil es entender por qué una línea está como está: el commit que la cambió trae un mensaje que lo explica. Y a veces quien lo cambió ya no recuerda por qué... pero el mensaje, sí.
bisect: encontrar el commit que rompió algo¶
El precio de la tienda era 10 y ahora es 100. ¿En qué commit se rompió? Se podría mirar los ocho uno a uno, pero con mil commits no. git bisect hace una búsqueda binaria: coge el punto del medio entre uno bueno y uno malo, te pregunta si ahí funciona, y descarta la mitad cada vez. Con 1000 commits, en unos 10 pasos lo encuentra.
Hay que decirle un commit malo (el actual) y uno bueno (por ejemplo, el primero que sabes que funcionaba). En cada paso, git se coloca en un commit intermedio y tú lo pruebas:
$ git bisect start
status: waiting for both 'good' and 'bad' commits
$ git bisect bad
status: waiting for 'good' commit(s), 'bad' commit known
$ git bisect good HEAD~7
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[25219e7d9bad0ebb28b93cbe4b7c9058f67793ea] Añade descuentos
$ grep -q 'precio=10$' config.txt && git bisect good || git bisect bad
Bisecting: 1 revision left to test after this (roughly 1 step)
[73be57a998627c60efe696c063201fce8563e7ec] Ajusta el precio
$ grep -q 'precio=10$' config.txt && git bisect good || git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[d293cb8723fc0105d9ef7f254ed7568e26751f80] Cambia la moneda
$ grep -q 'precio=10$' config.txt && git bisect good || git bisect bad
73be57a998627c60efe696c063201fce8563e7ec is the first 'bad' commit
commit 73be57a998627c60efe696c063201fce8563e7ec
Author: Ana Ejemplo <ana@ejemplo.com>
Date: Sat Mar 1 10:06:00 2025 +0000
Ajusta el precio
config.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git bisect reset
Previous HEAD position was d293cb8 Cambia la moneda
Switched to branch 'main'
En cada paso, Bisecting: N revisions left to test dice cuántos commits quedan por revisar. La «prueba» aquí es un grep que comprueba si config.txt tiene precio=10; si lo tiene, se marca good, y si no, bad. Al final, git dice «git bisect reset te devuelve a donde estabas.
Automático: bisect run¶
Si la prueba se puede escribir como un comando que devuelve éxito o fracaso (como un grep o un script de pruebas), git bisect run hace todos los pasos solo:
$ git bisect start HEAD HEAD~7
Bisecting: 3 revisions left to test after this (roughly 2 steps)
[25219e7d9bad0ebb28b93cbe4b7c9058f67793ea] Añade descuentos
$ git bisect run sh -c "grep -q 'precio=10$' config.txt"
running 'sh' '-c' 'grep -q '\''precio=10$'\'' config.txt'
Bisecting: 1 revision left to test after this (roughly 1 step)
[73be57a998627c60efe696c063201fce8563e7ec] Ajusta el precio
running 'sh' '-c' 'grep -q '\''precio=10$'\'' config.txt'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[d293cb8723fc0105d9ef7f254ed7568e26751f80] Cambia la moneda
running 'sh' '-c' 'grep -q '\''precio=10$'\'' config.txt'
73be57a998627c60efe696c063201fce8563e7ec is the first 'bad' commit
commit 73be57a998627c60efe696c063201fce8563e7ec
Author: Ana Ejemplo <ana@ejemplo.com>
Date: Sat Mar 1 10:06:00 2025 +0000
Ajusta el precio
config.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
bisect found first 'bad' commit
$ git bisect reset
Previous HEAD position was d293cb8 Cambia la moneda
Switched to branch 'main'
Un único comando recorre toda la búsqueda y señala el commit. En un proyecto real, ese comando sería tu conjunto de pruebas (./gradlew test, pytest, dart test...): una prueba que falla hoy encuentra sola el commit que la rompió.
Qué comando devuelve qué
Un comando bueno devuelve el código 0; malo, de 1 a 127 (menos el 125, que significa «no se puede probar este commit, sáltalo»). grep -q devuelve 0 si encuentra el texto y 1 si no, y por eso sirve.
grep: buscar dentro del repositorio¶
git grep texto busca en los archivos del proyecto, mucho más rápido que grep -r porque solo mira lo que git controla (se salta lo que está en .gitignore). Con -n enseña el número de línea:
$ git grep -n precio
config.txt:1:precio=100
Y se puede buscar en una versión antigua, sin cambiar de commit, poniendo el commit detrás:
$ git grep -n precio HEAD~3
HEAD~3:config.txt:1:precio=10
En HEAD~3 (tres commits atrás, antes de «romper» el precio) la línea era precio=10.
Resumen: ¿qué herramienta uso?¶
| Pregunta | Herramienta |
|---|---|
| ¿Qué se hizo y cuándo? | git log con filtros |
| ¿Cuándo apareció o desapareció este texto? | git log -S'texto' |
| ¿Quién escribió esta línea y por qué? | git blame -L y git log -L |
| ¿Qué commit rompió algo? | git bisect (o bisect run) |
| ¿Dónde se usa este texto? | git grep |
| ¿Qué aporta una rama? | git diff main...rama |
Errores frecuentes¶
| Error | Cómo evitarlo |
|---|---|
Hacer cambios en medio de un bisect |
Mientras dura, no toques nada; al acabar, git bisect reset |
Marcar mal un paso del bisect |
Si dudas, git bisect log guarda lo hecho y git bisect reset empieza de nuevo |
Creer que blame da quién creó la línea |
Da quién la cambió por última vez; para el origen, log -L |
Una «prueba» de bisect run que depende de algo fuera del commit |
Debe depender solo del código de ese commit |
Olvidar git bisect reset |
Te deja en un commit antiguo, con HEAD suelto |
Para practicar¶
Los ejercicios GA3.3 y GA3.4 de GA3 · Ejercicios practican blame y bisect run.