¿Cómo usar git bisect?

438

He leído algunos artículos que dicen que git bisectes increíble. Sin embargo, no soy un hablante nativo y no puedo entender por qué es increíble.

¿Podría alguien demostrar con algún ejemplo de código:

  1. ¿Cómo usarlo?
  2. ¿Es como svn blame?
Adaptador IA
fuente
@ 01: Como dice el libro de git: realiza una búsqueda de fuerza bruta a través de la historia del proyecto .
Eckes
77
No es así /// bruto :-), utiliza la búsqueda binaria.
cojocar
"git blame" es similar a "svn blame". "git bisect" es algo completamente diferente
William Pursell
Por lo que vale, también hay una buena descripción de bisect en Pro Git . La respuesta de Sylvain es otra buena oportunidad. Si después de mirar todo eso aún no lo entiende, le sugiero que haga una pregunta más específica. Las preguntas generales engendran respuestas generales.
Cascabel
44
enlace actualizado del libro: git-scm.com/book/en/Git-Tools-Debugging-with-Git#Binary-Search
gdelfino

Respuestas:

655

La idea detrás git bisectes realizar una búsqueda binaria en el historial para encontrar una regresión particular. Imagine que tiene el siguiente historial de desarrollo:

... --- 0 --- 1 --- 2 --- 3 --- 4* --- 5 --- current

Usted sabe que su programa no funciona correctamente en la currentrevisión y que estaba funcionando en la revisión 0. Por lo que la regresión fue probablemente introducido en una de las confirmaciones 1, 2, 3, 4, 5, current.

Puede intentar verificar cada confirmación, compilarla, verificar si la regresión está presente o no. Si hay una gran cantidad de confirmaciones, esto puede llevar mucho tiempo. Esta es una búsqueda lineal. Podemos hacerlo mejor haciendo una búsqueda binaria. Esto es lo que hace el git bisectcomando. En cada paso, trata de reducir a la mitad el número de revisiones que son potencialmente malas.

Usarás el comando así:

$ git stash save
$ git bisect start
$ git bisect bad
$ git bisect good 0
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[< ... sha ... >] 3

Después de este comando, gitpagará un commit. En nuestro caso, será commit 3. Necesita construir su programa y verificar si la regresión está presente o no. También deberá indicar gitel estado de esta revisión, ya sea git bisect badsi la regresión está presente o git bisect goodno.

Supongamos que la regresión se introdujo en commit 4. Entonces la regresión no está presente en esta revisión, y le decimos que lo haga git.

$ make
$ make test
... ... ...
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[< ... sha ... >] 5

Luego verificará otro commit. Cualquiera 4o 5(ya que solo hay dos confirmaciones). Supongamos que escogió 5. Después de una compilación, probamos el programa y vemos que la regresión está presente. Luego le decimos que git:

$ make
$ make test
... ... ...
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[< ... sha ... >] 4

Probamos la última revisión, 4. Y como es el que introdujo la regresión, le decimos que git:

$ make
$ make test
... ... ...
$ git bisect bad
< ... sha ... > is the first bad commit
< ... commit message ... >

En esta situación simple, sólo tuvimos que prueba 3 versiones ( 3, 4, 5) en lugar de 4 ( 1, 2, 3, 4). Esta es una pequeña victoria, pero esto se debe a que nuestra historia es muy pequeña. Si el rango de búsqueda es de N commits, deberíamos esperar probar 1 + log2 N commits en git bisectlugar de aproximadamente N / 2 commits con una búsqueda lineal.

Una vez que haya encontrado el commit que introdujo la regresión, puede estudiarlo para encontrar el problema. Una vez hecho esto, puede git bisect resetvolver a poner todo en el estado original antes de usar el git bisectcomando.

Sylvain Defresne
fuente
55
Voy a contrarrestar aquí, esta es una buena explicación de bisect pero realmente no me ayuda a usarla. Especialmente desde que logré encontrar un buen compromiso y ahora estoy en esa rama. Desde esta posición, esta explicación no sirve de nada. ¿Cómo puedo especificar la rama defectuosa sin verificarla, por ejemplo
PandaWood
44
Puede usar git bisect bad <rev> [<rev>...]para marcar revisiones específicas como malas (o buenas con git bisect good <rev> [<rev>...]). revpuede ser cualquier identificador de revisión como un nombre de rama, una etiqueta, un hash de confirmación (o un prefijo único de hash de confirmación), ...
Sylvain Defresne
39
... y una vez que haya terminado, escribe git bisect resetpara volver a poner todo en la confirmación reciente
peetonn
17
Una de las respuestas más impresionantes en la pila. Muy bien articulado. He estado haciendo este proceso manualmente durante años, simplemente seleccionando un punto medio arbitrario entre un compromiso bueno y uno malo, luego nuevamente entre ese y el bueno / malo dependiendo de si fue bueno / malo en sí mismo. Siempre ha sido un gran dolor de cabeza y nunca había oído hablar de este subcomando git hasta hoy ... jajaja
Chev
3
@Nemoden, sí, puede, básicamente puede ser útil para cualquier tipo de proyecto. Solo necesita reemplazar el paso "hacer prueba" con "implementar el sitio web y reproducir el problema"
alex.b
159

git bisect run bisección automática

Si tiene un ./testscript automatizado que tiene el estado de salida 0 si la prueba es correcta, puede encontrar automáticamente el error con bisect run:

git checkout KNOWN_BAD_COMMIT
git bisect start

# Confirm that our test script is correct, and fails on the bad commit.
./test
# Should output != 0.
echo $?
# Tell Git that the current commit is bad.
git bisect bad

# Same for a known good commit in the past.
git checkout KNOWN_GOOD_COMMIT
./test
# Should output 0.
echo $?
# After this, git automatically checks out to the commit
# in the middle of KNOWN_BAD_COMMIT and KNOWN_GOOD_COMMIT.
git bisect good

# Bisect automatically all the way to the first bad or last good rev.
git bisect run ./test

# End the bisect operation and checkout to master again.
git bisect reset

Esto supone, por supuesto, que si el script de prueba ./testes rastreado, no desaparece en algún commit anterior durante la bisección.

He descubierto que muy a menudo puedes escapar simplemente copiando el script del árbol fuera del árbol, y posiblemente jugando con PATHvariables similares, y ejecutándolo desde allí.

Por supuesto, si la infraestructura de prueba de la que testdepende se rompe con los commits más antiguos, entonces no hay solución, y tendrá que hacer las cosas manualmente, decidiendo cómo probar los commits uno por uno.

Sin embargo, he descubierto que el uso de esta automatización a menudo funciona, y puede ser un gran ahorro de tiempo para las pruebas más lentas que se encuentran en su cartera de tareas, donde puede dejar que se ejecute durante la noche y posiblemente identificar su error a la mañana siguiente, vale la pena. el intento

Mas consejos

Manténgase en la primera confirmación fallida después de la bisección en lugar de volver a master:

git bisect reset HEAD

start+ inicial bady goodde una vez:

git bisect start KNOWN_BAD_COMMIT KNOWN_GOOD_COMMIT~

es lo mismo que:

git checkout KNOWN_BAD_COMMIT
git bisect start
git bisect bad
git bisect good KNOWN_GOOD_COMMIT

Vea lo que se ha probado hasta ahora (por manual goody / bado run):

git bisect log

Salida de muestra:

git bisect log
git bisect start
# bad: [00b9fcdbe7e7d2579f212b51342f4d605e53253d] 9
git bisect bad 00b9fcdbe7e7d2579f212b51342f4d605e53253d
# good: [db7ec3d602db2d994fe981c0da55b7b85ca62566] 0
git bisect good db7ec3d602db2d994fe981c0da55b7b85ca62566
# good: [2461cd8ce8d3d1367ddb036c8f715c7b896397a5] 4
git bisect good 2461cd8ce8d3d1367ddb036c8f715c7b896397a5
# good: [8fbab5a3b44fd469a2da3830dac5c4c1358a87a0] 6
git bisect good 8fbab5a3b44fd469a2da3830dac5c4c1358a87a0
# bad: [dd2c05e71c246f9bcbd2fbe81deabf826c54be23] 8
git bisect bad dd2c05e71c246f9bcbd2fbe81deabf826c54be23
# bad: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05] 7
git bisect bad c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05
# first bad commit: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c0

Muestre referencias buenas y malas en git log para obtener una mejor noción del tiempo:

git log --decorate --pretty=fuller --simplify-by-decoration master

Esto solo muestra confirmaciones con una referencia correspondiente, lo que reduce el ruido real, pero incluye referencias de tipo autogeneradas:

refs/bisect/good*
refs/bisect/bad*

que nos dicen qué compromisos hemos marcado como buenos o malos.

Considera este repositorio de prueba si quieres jugar con el comando.

El fracaso es rápido, el éxito es lento

Algunas veces:

  • la falla ocurre rápido, por ejemplo, una de las primeras pruebas se rompe
  • el éxito lleva un tiempo, p. ej., la prueba rota pasa, y todas las demás pruebas que no nos interesan siguen

Para esos casos, por ejemplo, suponiendo que la falla siempre ocurre dentro de 5 segundos, y si somos flojos para hacer la prueba más específica como realmente deberíamos, podemos usarla timeoutcomo en:

#!/usr/bin/env bash
timeout 5 test-command
if [ $? -eq 1 ]; then
  exit 1
fi

Esto funciona desde las timeoutsalidas 124mientras que la falla de las test-commandsalidas 1.

Estados de salida mágica

git bisect run es un poco exigente con los estados de salida:

  • cualquier cosa por encima de 127 hace que la bisección falle con algo como:

    git bisect run failed:
    exit code 134 from '../test -aa' is < 0 or >= 128
    

    En particular, una C assert(0)conduce a SIGABRTay sale con el estado 134, muy molesto.

  • 125 es mágico y hace que se salte la carrera git bisect skip.

    La intención de esto es ayudar a omitir compilaciones rotas debido a razones no relacionadas.

Ver man git-bisectpara los detalles.

Por lo tanto, es posible que desee utilizar algo como:

#!/usr/bin/env bash
set -eu
./build
status=0
./actual-test-command || status=$?
if [ "$status" -eq 125 ] || [ "$status" -gt 127 ]; then
  status=1
fi
exit "$status"

Probado en git 2.16.1.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
77
¿Cómo sabe git para evitar que su nueva prueba desaparezca al revertir / biseccionar a una revisión anterior / incorrecta (que no tenía su prueba recién escrita)?
thebjorn
8
@thebjorn tiene un punto: hasta donde yo sé, la prueba debe estar en un ejecutable externo en PATH o en un archivo sin seguimiento en el repositorio. En muchos casos esto es posible: coloque la prueba en un archivo separado, incluya la placa de prueba necesaria con un test_scriptconjunto de pruebas bien diseñado y modular, y ejecútelo desde el archivo separado mientras biseca. Cuando corrija, combine la prueba en el conjunto de pruebas principal.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视 Lo siento, revertí tu edición a continuación, porque siento que es más explicativo para los novatos y solo agrega un punto adicional a tu respuesta (no es una respuesta exacta como la tuya, así que mencionar eso, es para agregar un punto)
Nicks
1
Hay muchas maneras de equivocarse con 'git bisect run', por ejemplo, ver cómo una buena fusión revirtió un buen commit. Entra, sale, vuelve a entrar y sale de nuevo y solo el último "salir" es malo. Sin embargo, siempre puedes hacer un manual 'git bisect'. Debido a que es una bisección, solo toma unos pocos pasos, por ejemplo, 1024 confirmaciones en 10 pasos.
combinatorista
1
@combinatorist tienes razón en que podría fallar. Me resulta bisect runespecialmente útil cuando la prueba tarda mucho en terminar, y estoy bastante seguro de que el sistema de prueba no se romperá. De esta manera, puedo dejarlo ejecutándose en segundo plano, o de la noche a la mañana si consume demasiados recursos, sin perder el tiempo de cambio de contexto cerebral.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
124

TL; DR

Comienzo:

$ git bisect start
$ git bisect bad
$ git bisect good <goodcommit>

Bisecting: X revisions left to test after this (roughly Y steps)

Repetir:

El problema aún existe?

  • Si: $ git bisect bad
  • No: $ git bisect good

Resultado:

<abcdef> is the first bad commit

Cuando termine:

git bisect reset
Geoffrey Hale
fuente
3
Asegúrese de estar en la raíz de su repositorio de git, o obtendrá un extraño "Necesita ejecutar este comando desde el nivel superior del árbol de trabajo". error.
PR Whitehead
Así que me git mal en mi HEAD y git bueno en el primer commit, cuando el error no estaba presente. Que hacemos después? cuando el error no está presente git bisect bueno para pasar al siguiente commit?
Gobliins
@Gobliins Cuando el error no está presente, corríjalo git bisect goodpara pasar al siguiente commit.
Geoffrey Hale
40

Solo para agregar un punto más:

Podemos especificar un nombre de archivo o ruta git bisect starten caso de que sepamos que el error proviene de archivos particulares. Por ejemplo, supongamos que sabíamos que los cambios que causaron la regresión estaban en el directorio com / workingDir, entonces podemos ejecutar git bisect start com/workingDirEsto significa que solo se verificarán las confirmaciones que cambiaron el contenido de este directorio, y esto hace que las cosas sean aún más rápidas.

Además, si es difícil saber si un commit en particular es bueno o malo, puede ejecutarlo git bisect skip, lo que lo ignorará. Dado que hay suficientes otras confirmaciones, git bisect usará otra para limitar la búsqueda.

Mellas
fuente
15

$ git bisect ..Básicamente una herramienta Git para depurar . 'Git Bisect' depura pasando por los commits anteriores desde su último commit de trabajo (conocido). Utiliza la búsqueda binaria para pasar por todas esas confirmaciones, para llegar a la que introdujo la regresión / error.

$ git bisect start # Bisecta de inicio

$ git bisect bad # indicando que el commit actual (v1.5) tiene el punto 'malo' de regresión / configuración

$ git bisect good v1.0 # mencionándolo como el último buen compromiso de trabajo (sin regresión)

Esta mención de los puntos 'malo' y 'bueno' ayudará a git bisect (búsqueda binaria) a elegir el elemento medio (commit v1.3). Si la regresión está allí en commit v1.3, la configurará como el nuevo punto 'malo', es decir ( Bueno -> v1.0 y Malo -> v1.3 )

$ git bisect bad

o de manera similar si el commit v1.3 está libre de errores, lo configurará como el nuevo 'Punto bueno', es decir (* Bueno -> v1.3 y Malo -> v1.6).

$ git bisect good
Nabeel Ahmed
fuente
2

Nota: los términos goody badno son los únicos que puede usar para marcar una confirmación con o sin una determinada propiedad.

Git 2.7 (cuarto trimestre de 2015) introdujo nuevas git bisectopciones.

 git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
                  [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]

Con documentación agregando:

A veces no está buscando el compromiso que introdujo una ruptura, sino más bien un compromiso que causó un cambio entre algún otro estado "antiguo" y "nuevo" .

Por ejemplo, podría estar buscando la confirmación que introdujo una solución particular.
O podría estar buscando la primera confirmación en la que los nombres de los archivos de código fuente finalmente se convirtieron al estándar de nomenclatura de su empresa. O lo que sea.

En tales casos, puede ser muy confuso usar los términos "bueno" y "malo" para referirse a "el estado antes del cambio" y "el estado después del cambio".

Por lo tanto, puede usar los términos " old" y " new", respectivamente, en lugar de " good" y " bad".
(Pero tenga en cuenta que no puede mezclar " good" y " bad" con " old" y " new" en una sola sesión).

En este uso más general, proporciona git bisectun " new" commit tiene alguna propiedad y un " old" commit que no tiene esa propiedad.

Cada vez que git bisectcomprueba una confirmación, prueba si esa confirmación tiene la propiedad:
si la tiene, marque la confirmación como " new"; de lo contrario, márquelo como " old".

Cuando se realiza la bisección, git bisectinformará qué confirmación introdujo la propiedad.


Ver commit 06e6a74 , commit 21b55e3 , commit fe67687 (29 de junio de 2015) por Matthieu Moy ( moy) .
Ver commit 21e5cfd (29 de junio de 2015) por Antoine Delaite ( CanardChouChinois) .
(Fusionada por Junio ​​C Hamano - gitster- en commit 22dd6eb , 05 oct 2015)

VonC
fuente