Hoy he aprendido a usar git-bisect y esto es lo que he sacado en claro.
Escenario inicial
El típico caso en el que git bisect nos puede ayudar y mucho es el siguiente:
Supongamos que estamos trabajando en el desarrollo de nuestro proyecto y llegamos a una versión que consideramos estable, llamémosle versión v1.0.0. Nuestro trabajo en el proyecto continúa y vamos añadiendo nuevas features. Llegamos a un punto en el que consideramos que debemos liberar una nueva versión, sea la versión v1.1.0-rc. Durante el proceso de preparación para la liberación de esta nueva versión nos damos cuenta de que hay una funcionalidad que ha dejado de comportarse como debía (desafortunadamente nuestros tests no cubrían el caso concreto en el que hemos detectado el fallo1). Sabemos que en la versión v1.0.0 esto estaba funcionando correctamente, por lo que tenemos dos puntos de nuestra historia identificados con estado válido (good) e inválido (bad) separados por un número de commits no despreciable. Sin duda, saber el commit en el que las cosas empiezan a torcerse es muy útil de cara a corregir el problema. Por ello, podríamos ir haciendo comprobaciones commit a commit para conseguir identificar el punto exacto en el que las cosas empiezan a ir mal… ¡Qué pereza!

Aquí es donde entra en juego git bisect para alegrarnos el día.
¿Para qué sirve git bisect?
Bisect es una funcionalidad de git que permite recorrer un grupo de commits y hacer comprobaciones en ellos de manera interactiva. Es especialmente útil para poder localizar el punto en el que se cuela un bug en una serie de commits, aunque no es su única aplicación.
Los commits se recorren de manera no lineal, utilizando un algoritmo de búsqueda binario. Esta es, sin duda, una buena aplicación para recorrer un listado ordenado y encontrar un determinado elemento que cumple una condición, reduciendo de manera significativa el número de pasos necesarios.
Inicialmente lo único que sabemos es que en un punto de la historia de nuestro proyecto todo funcionaba correctamente (en el ejemplo, la versión v1.0.0) y que en otro punto ha dejado de funcionar como se esperaba (v1.1.0-rc). El estado de todos los commits intermedios es una incógnita para nosotros.

Iniciando el análisis
Para empezar a trabajar hay que determinar el punto de inicio y el punto de fin entre los que se encuentra el bug que estamos buscando. Podemos especificarlo de manera directa al inicio como sigue:
$ git bisect start <start-commit> <end-commit>
Otra opción es iniciar el análisis y especificar por separado el último commit good que tenemos identificado y el primer commit bad conocido, típicamente HEAD. En nuestro ejemplo sería algo así:
$ git bisect start
$ git bisect bad HEAD
$ git bisect good v1.0.0
Tras iniciar el proceso, iremos “saltando” de commit en commit, lo que nos permitirá realizar las pruebas que consideremos necesarias en cada uno de ellos para determinar si el bug está o no presente en el commit bajo análisis.
Podremos abandonar el proceso de bisect en cualquier momento, volviendo al punto en el que nos encontrábamos antes de iniciar el análisis.
$ git bisect reset
Como ya hemos avanzado antes, los commits no se recorren de manera lineal, sino que se aplica un algoritmo de búsqueda binario. En la imagen siguiente, el commit bajo análisis es el representado en amarillo (el punto medio entre el último commit good y el primer commit bad conocidos).

Para pasar al siguiente commit sólo tenemos que “marcar” el punto actual como bueno (good) o malo (bad).
$ git bisect good
De este modo, podemos saber que todos los commits entre el etiquetado como la versión v1.0.0 y el commit que acabamos de marcar como good son también correctos y ahí no está nuestro bug.

Tras establecer la etiqueta en el commit, git-bisect se encarga de “llevarnos” al siguiente commit a analizar. Tendremos que repetir el proceso para determinar si se trata de un commit good o bad.

Después de completar unas cuantas iteraciones, llegará un punto en el que git será capaz de determinar el primer commit que contiene el bug. Work Done!

Posibles problemas
Durante el proceso de análisis y comprobación de cada commit es posible que se marque alguno de manera incorrecta. ¿Cómo tenemos que proceder para revertir esta situación?
- Podemos reiniciar el proceso y en consecuencia perder el estado actual de las etiquetas que hemos ido introduciendo.
$ git bisect reset
$ git bisect start
- Cuando reiniciar el proceso no es una opción, podemos utilizar el subcomando log para almacenar los cambios que hemos hecho en un fichero. Tras esto podremos modificar el estado de los commits en el fichero en el que hemos volcado el output del subcomando log y cargarlo de nuevo mediante el subcomando replay.
$ git bisect log > bisect_log_file
$ vim bisect_log_file
$ git bisect replay bisect_log_file
Et voilà!
Es posible también que nos encontremos con commits en los que no podemos aplicar las pruebas para determinar si el bug está presente o no, o en su defecto, que tenemos la total certeza de que ese commit no lo ha introducido. En ese caso es posible omitir el análisis del commit y ahorrarnos ese valioso tiempo.
$ git bisect skip
Bisecting like a pro
Este proceso es claramente repetitivo… y por ello es posible automatizarlo si podemos incluir las pruebas a realizar en cada commit en un script. Este script debe devolver un código de salida entre 1 y 127 (excepto 1252) en caso de que el commit bajo análisis se quiera etiquetar como bad o un 0 si se quiere etiquetar como good. Cualquier otro código de salida abortará el proceso.
Para realizar el análisis mediante un script usaremos lo siguiente:
$ git bisect run <script> <script_arguments>
Conclusiones
Esto es todo lo que he aprendido sobre git-bisect. Creo que es una herramienta muy útil a la que se le puede sacar mucho partido. Recuerda que git es tu amigo.
Como siempre, puedes encontrar información más detallada en git-scm.