Aujourd'hui je suis retombé sur un cas où on a vu un bug sur l'application sur laquelle je travaille. On sait que quelques jours plus tôt ce bug n'était pas là. On utilise git et on commit à minima tous les soirs sur une branche. Donc normalement on devrait pouvoir retrouver la source du bug relativement facilement, en tout cas c'est ce qu'on attend d'un gestionnaire de version.

Justement git fourni une commande bisect qui permet de très vite retrouver le commit qui introduit un bug sans avoir à chercher pendant des heures ou tester les commits un par un. Je vous explique ça tout de suite !

Git bisect dans la théorie

Dans l'idée git bisect ne fait pas de magie et il ne va pas trouver le bug de lui même. En fait c'est plutôt un outils qui va vous aider à naviguer au plus vite dans les commits pour trouver le premier commit où le bug est présent.

Un bisect va nous faire sauter de commit en commit. À chaque saut on lui indique si le bug est présent ou non (en lançant puis testant le cas d'erreur par exemple). Ensuite git va nous faire sauter à un autre commit en fonction de l'information qu'on lui a donnée. Quand je dis "sauter", c'est un peu comme si on faisait un git reset sur un hash de commit : on retrouve l'état qu'on avait à une autre version du code.

Quand on y réfléchit c'est assez logique qu'on peut aller vite pour trouver le commit qui introduit un bug. Imaginons qu'on prenne un projet avec quelques commits depuis la dernière version releasée. On teste la dernière release, le bug n'y est pas. On a donc deux repère tout simple : on a un point dans l'historique où le bug est présent, et un où il n'y est pas. On peut donc appliquer un bisect.

Si on applique la méthode commit par commit sans réfléchir on obtient une recherche dans ce genre-là :

Là bêtement on remonte commit par commit et on teste chaque version jusqu'à trouver un commit où le bug n'est pas présent.

Si on fait une recherche via le bisect, on peut obtenir un parcours dans ce genre-là :

Ici on essaie d'être plus malin :

  • on saute à un commit à peu près à la moitié de l'historique entre la version releasé et la version courante (HEAD)
  • si le bug est présent : on saute à un commit à peu près à la moitié de l'historique entre le commit courant et la version releasée
  • si le bug n'est pas présent : on saute à un commit à peu près à la moitié de l'historique entre le commit courant et la HEAD
  • on repète l'opération jusqu'à tomber sur deux commits consécutifs dont l'un contient le bug et l'autre non

Là mon exemple est un peu court. Mais imaginez qu'on fasse ça sur un historique de plusieurs centaines de commits.

Dans la pratique

Si on le fait réellement sur un projet on va fonctionner comme suit.

Première étape trouver un commit où le bug n'est pas présent. En théorie vous pouvez prendre l'un des premiers commit du projet, mais là on perd du temps inutilement. Personnellement je pars souvent la dernière version en production (si elle ne contient pas le bug évidemment) ou un commit au hasard environ une semaine en arrière (si je sais que le bug est apparu entre temps). À vous de voir votre stratégie pour choisir ce commit.

Maintenant les commandes sont assez simples. On indique à git qu'on cherche à faire un bisect sur notre projet, et on indique que le commit courant contient une erreur (c'est un commit "bad") :

git bisect start
git bisect bad

Ici git ne vous dira rien. Il n'a pas assez d'information.

Puis on indique à git notre commit qu'on sait ne pas contenir le bug :

git bisect good v1.2.1
# ou avec un hash de commit
git bisect good ead0e80bf422d7a304407fdc73e7414018f5ddb3
# ou avec une partie du début du hash
git bisect good ead0e80

Ici vous allez voir que git va commencer à vous indiquer un saut avec un message dans ce genre-là :

Bissection : 0 révision à tester après ceci (à peu près 0 étape)
[b6123bca52fd9568456a4b1bb766ba09609dca3d] feature: automatic schedule republish action

Là vous pouvez lancer votre application, un script de test, des tests unitaires, etc. tout ce qui vous permet de détecter la présence du bug. Une fois fait faite simplement :

git bisect good # si le bug n'est pas présent
git bisect bad # si le bug est présent

Git va de nouveau sauter à un autre commit. Il vous suffit de continuer à vous laisser guider jusqu'à trouver votre commit. À ce moment-là faite vous pouvez inspecter le commit.

Vous pouvez continuer à naviguer dans les commits avec des git reset ou git checkout par exemple. Une référence vers le commit en erreur est automatiquement créé par git avec pour nom refs/bisect/bad. Une fois que vous avez fini de traiter le commit en erreur faite un ̀git bisect reset pour nettoyer les éléments laissés par git pour suivre votre bisect. À noter que si vous voulez arrêter le bisect avant la fin vous pouvez aussi faire un reset.

Vous pouvez aussi utiliser la commande git bisect skip si vous arrivez sur un commit que vous ne pouvez pas tester. Git sautera sur un autre commit proche.

Pourquoi utiliser un bisect et pas juste corriger le bug ?

Vous me direz : pas mal de cas on va plus vite à corriger le bug et faire un commit-push.

Déjà on arrive parfois à des cas où trop de portion de code peuvent être incriminée et c'est difficile de savoir où chercher. Là un bisect permet de trouver ce qui crée le bug et comprendre.

Mais il ne faut pas négliger un autre cas : si on corrige le bug, est-ce qu'on arriverait pas dans un cas où on casse une autre fonctionnalité ? Si nous ou un collègue a fait cette modification c'est sans doute pour une bonne raison, donc il vaut mieux savoir pourquoi la modification a été faite pour comprendre comment régler le bug sans en créer un autre.

Conclusion

Pour résumé, le bisect c'est une fonctionnalité de git que je trouve très pratique. Pas à utiliser tous les jours, mais elle me sauve ou au moins me simplifie la vie régulièrement.

Dans cet article je ne fais que survoler rapidement un usage basique du bisect. Mais il existe beaucoup d'autres fonctionnalités entre autres pour visualiser un peu plus ce qui se passe pendant le bisect ou même pour que l'exécution du test pour déterminer si le commit est good ou bad soit automatique. Je vous laisse regarder la documentation ou d'autres articles si ça vous intéresse 😉

Resources: https://git-scm.com/docs/git-bisect

Crédit photo : https://pixabay.com/fr/photos/insectes-nature-bug-des-animaux-2381427/