Vous voulez trouver rapidement le commit qui a introduit un bug dans votre base de code mais vous avez plusieurs centaines, ou milliers de commits ?
git bisect est probablement l'outil qu'il vous faut.
La théorie
En anglais bisect veut dire "Couper en deux", et c'est exactement ce que fait git bisect :
on lui donne un commit "bon", c'est-à-dire un commit où le bug n'était pas encore présent ;
ensuite, on lui donne un commit "mauvais" (souvent le commit actuel) ;
enfin, tant que git bisect trouve plusieurs commits entre le "bon" et le "mauvais" commit, il prend le commit entre les 2 et nous demande si ce commit est ok.
L'avantage d'utiliser une recherche binaire est que l'on sait combien d'étapes il faudra au maximum pour trouver le commit qui a introduit le bug, et que ce nombre d'étapes n'augmente que de 1 à chaque fois que notre nombre de commits suspects double.
Par exemple le
code de Linux contient plus de 1 million de commits, pourtant il ne faudrait au maximum que 20 étapes (logarithme binaire de 1 000 000) pour trouver le commit qui introduit un bug !
Voila pour la théorie, maintenant on va voir comment utiliser cet outil en pratique.
La pratique
Pour montrer l'utilisation de git bisect, nous allons utiliser ce
dépôt git (qui contient un bug).
Nous allons commencer par télécharger le code :
git clone https://github.com/mle-moni/bisect-test
Le fichier test.js contient ce code :
function getNumber(numbers, index) { if (!numbers[index]) { throw new Error('no number for this index') } return numbers[index] } const numbers = [ 42, -121, 4235, 0 ] const index = process.argv[2] console.log(number is ${getNumber(numbers,index)})
La commande suivante nous permettra de savoir si le commit contient le bug ou non :
node test.js 3
(on considérera le commit comme mauvais si cette commande renvoie une erreur)
Maintenant voyons quels commits ont été faits :
# montre la liste des commits avec leur auteur du plus ancien au plus récent git shortlog
LE MONIES DE SAGAZAN Mayeul (28): no bug here no bug here too no bug here too no bug here too no bug here too no bug here too no bug here too no bug here too no bug here too no bug here too bug introduction we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we still don't know that there is a bug we just discovered that there is a bug Create README.md
Notre but sera, avec git bisect, de trouver que le commit fautif est bien celui qui porte le nom "bug introduction".
C'est parti !
git bisect start
# on choisit un commit où il n'y avait pas le bug (ici, c'est le premier commit du dépôt git) git bisect good 0f436453aac33b7d39f04be33b909097b34def10
# on précise que le commit actuel est mauvais git bisect bad
L'outil nous déplace ensuite sur le commit entre le bon et le mauvais :
Bisecting: 13 revisions left to test after this (roughly 4 steps) [fb045ac20c5972136afd8c10e510c0483f97b1a9] we still don't know that there is a bug
Ensuite on teste le code (ici c'est facile car c'est du JavaScript, souvent on aura une étape de compilation) :
node test.js 3 Error: no number for this index
Ce commit contient le bug, on va donc dire à git bisect que le commit est mauvais :
git bisect bad
Bisecting: 6 revisions left to test after this (roughly 3 steps) [991ef4bb20a5d29cc6a307dd3a289a5fc3159c3d] no bug here too
Ensuite on continue cette routine jusqu'à trouver le mauvais commit !
node test.js 3 number is 0 git bisect good Bisecting: 3 revisions left to test after this (roughly 2 steps) [bc4dd976f1b8e7e79a7109ac074b610dddcf6dd5] no bug here too
node test.js 3 number is 0 git bisect good Bisecting: 1 revision left to test after this (roughly 1 step) [f1a089670548b09e2b52737aa05aa25921ee463f] we still don't know that there is a bug
node test.js 3 Error: no number for this index git bisect bad Bisecting: 0 revisions left to test after this (roughly 0 steps) [9d7a3917e0fdfc71be8426f19ccf26b217d0f546] bug introduction
Et enfin :
node test.js 3 Error: no number for this index git bisect bad 9d7a3917e0fdfc71be8426f19ccf26b217d0f546 is the first bad commit commit 9d7a3917e0fdfc71be8426f19ccf26b217d0f546 Author: LE MONIES DE SAGAZAN Mayeul <mail@example.com> Date: Sun May 15 14:45:17 2022 +0200 bug introduction test.js | 3 +++ 1 file changed, 3 insertions(+)
Enfin on peut regarder quels changements ont provoqué l'apparition du bug :
git diff HEAD^ function getNumber(numbers, index) { + if (!numbers[index]) { + throw new Error('no number for this index') + } return numbers[index] }
Ici le "bug" était donc la condition
if (!numbers[index]) {
puisque numbers[index] peut être 0 et que !0 donne true, il aurait fallu être plus précis et afficher l'erreur uniquement si numbers[index] était undefined :
if (numbers[index] === undefined) {
Cas particuliers
Si un des commits ne peut pas être testé (s'il ne build pas par exemple), on a plusieurs solutions :
git reset HARD~2 # se placer sur 2 commits avant celui choisi par git bisect
git bisect skip
Si on souhaite arrêter la recherche binaire on peut le faire simplement :
git bisect reset
Voilà, c'est tout pour l'outil git bisect !
Bonus
Si vous voulez avoir la possibilité de regarder les diff avec VS Code tout en continuant d'avoir les outils de git (rebase, merge, ...) en CLI avec vim, c'est possible en configurant
git difftool.
Voici par exemple ma configuration ~/.gitconfig
[core] editor = vim [user] name = LE MONIES DE SAGAZAN Mayeul email = mail@example.com [diff] tool = vscode [difftool "vscode"] cmd = code --wait --diff $LOCAL $REMOTE
Ensuite il suffit d'utiliser git difftool de la même façon qu'on utilise git diff :
git difftool HEAD^ Launch 'vscode' [Y/n]? Y