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.
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.
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 :
// numbers is an array of numbers
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) {
Si un des commits ne peut pas être testé (s'il ne build pas par exemple), on a plusieurs solutions :
choisir à la main un autre commit :
git reset HARD~2 # se placer sur 2 commits avant celui choisi par git bisect
laisser git bisect choisir le prochain commit :
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 !
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