git bisect : trouver le commit qui a introduit un bug
git bisect trouve le commit qui a introduit un bug grâce à une recherche dichotomique dans l'historique. Lancez git bisect start, marquez le commit défectueux avec git bisect bad et un commit sain avec git bisect good <hash>, puis testez les commits proposés jusqu'à ce que Git désigne le coupable ; terminez par git bisect reset.
git bisect recherche le commit qui a introduit un bug par dichotomie. Vous indiquez un commit où le bug est présent et un commit où il ne l'était pas : Git vous place au milieu de l'intervalle et vous demande « ici, ça marche ou pas ? ». Chaque réponse élimine la moitié des candidats, si bien que parmi 1000 commits, le coupable est trouvé en une dizaine de vérifications environ.
C'est particulièrement précieux quand le bug n'a rien d'évident : un test échoue, les performances chutent, la mise en page est cassée, et les diffs ne disent rien. Au lieu de lire des centaines de modifications, vous posez plusieurs fois la même question simple.
Comment fonctionne la recherche dichotomique dans l'historique
Supposons qu'il y ait 200 commits entre la version fonctionnelle v2.3 et le main actuel. Git prend le commit du milieu et le teste. S'il est déjà cassé, le bug est apparu dans la première moitié ; sinon, dans la seconde. Le nombre de candidats est divisé par deux à chaque étape : 200, 100, 50, 25, 13, 7, 4, 2, 1. Huit vérifications au lieu de deux cents.
D'où l'exigence principale : la propriété « cassé » doit être monotone. Si le bug est apparu, a disparu puis est réapparu, bisect trouvera l'une des transitions, mais pas forcément celle qui vous intéresse.
git bisect pas à pas : le mode manuel
1. Identifiez un bon et un mauvais commit
Le mauvais est généralement le HEAD actuel. Le bon est n'importe quel commit où tout fonctionnait à coup sûr : le tag de la release précédente, un commit d'il y a une semaine. Pour trouver un candidat, utilisez git log --oneline, git tag ou git log --since="2 weeks ago".
2. Démarrez la session
git bisect start
git bisect bad # le commit actuel est cassé
git bisect good v2.3 # dans la release v2.3, tout fonctionnait
Bisecting: 99 revisions left to test after this (roughly 7 steps)
[4f5e6d7c...] Mettre à jour les dépendances du frontend
Vous pouvez aussi l'écrire en une ligne : git bisect start HEAD v2.3 — d'abord le mauvais, puis le bon. Git bascule immédiatement le répertoire de travail sur le commit du milieu (en état detached HEAD).
3. Testez et marquez
Compilez le projet, lancez le test ou reproduisez le bug à la main. Puis indiquez le résultat :
git bisect good # ici, ça fonctionne
Bisecting: 49 revisions left to test after this (roughly 6 steps)
git bisect bad # ici, c'est cassé
Bisecting: 24 revisions left to test after this (roughly 5 steps)
4. Obtenez le résultat
Quand il ne reste qu'un candidat, Git l'affiche :
a1b2c3d4e5f6... is the first bad commit
commit a1b2c3d4e5f6...
Author: Jean Dupont <jean@example.com>
Date: Tue Sep 29 11:40:12 2026 +0200
Mettre en cache le calcul du panier
src/cart.js | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
5. Terminez la session
git bisect reset
Cette commande vous ramène sur la branche de départ. N'oubliez pas cette étape : tant que la session n'est pas fermée, vous restez en detached HEAD, et de nouveaux commits peuvent facilement se perdre. La variante git bisect reset HEAD vous laisse sur le commit trouvé, et git bisect reset <commit> sur n'importe quel commit indiqué.
Recherche automatique : git bisect run
Si la vérification peut s'exprimer sous forme de commande, laissez Git faire tout le travail. git bisect run lance un script à chaque étape et marque lui-même le commit selon le code de retour :
| Code de retour | Signification |
|---|---|
| 0 | good — le commit est bon |
| 1–127, sauf 125 | bad — le commit est mauvais |
| 125 | skip — impossible de tester ce commit, on le saute |
| supérieur à 127 (ou <0) | interrompre bisect (par exemple, le script a été tué par un signal) |
Exemples d'utilisation :
git bisect start HEAD v2.3
git bisect run npm test
git bisect run pytest tests/test_cart.py -x
git bisect run make check
Pour des vérifications plus complexes, écrivez un petit script. Il est essentiel de distinguer « la compilation a échoué » de « le bug est reproduit », sinon bisect prendra un build cassé pour le bug recherché :
#!/bin/sh
# check.sh
npm ci --silent || exit 125 # impossible d'installer les dépendances — on saute
npm run build || exit 125 # le build a échoué — on saute
node scripts/check-cart-total.js # 0 — ok, 1 — bug
chmod +x check.sh
git bisect run ./check.sh
Gardez le script hors des fichiers suivis, ou dans un dossier présent dans tous les commits testés : bisect change le contenu du répertoire de travail, et un fichier ajouté récemment disparaîtra sur les anciens commits. Le plus sûr est de le placer dans /tmp ou dans n'importe quel dossier hors du dépôt.
Un test qui n'existait pas encore
Situation fréquente : vous avez écrit un test qui reproduit le bug, mais ce fichier n'existe pas dans les anciens commits. Solution : là encore, une copie hors du dépôt.
cp tests/test_regression.py /tmp/
git bisect run pytest /tmp/test_regression.py
git bisect skip : quand un commit ne peut pas être testé
Parfois, le commit du milieu ne compile pas ou échoue pour une autre raison. Le marquer good ou bad fausserait le résultat. Sautez-le :
git bisect skip
git bisect skip v2.4..v2.4.3 # sauter un intervalle
Git choisira un commit voisin. Si les commits sautés se trouvent pile à la frontière, vous obtiendrez à la fin une liste de « coupables possibles » au lieu d'un seul — examinez alors leurs diffs à la main.
Vos propres termes à la place de good et bad
Bisect ne sert pas qu'à traquer des bugs. Par exemple, vous voulez trouver le commit après lequel la page s'est mise à charger plus vite. Qualifier de « mauvais » les commits rapides prête à confusion, d'où des termes alternatifs :
git bisect start --term-old=slow --term-new=fast
git bisect fast HEAD
git bisect slow v2.3
Les synonymes intégrés sont old et new : git bisect new, git bisect old. Une seule règle : l'état « ancien » doit correspondre au commit le plus ancien.
Le journal de session : log, replay, visualize
git bisect log— affiche toutes vos marques. Sauvegardez-le dans un fichier si la session est longue.git bisect replay fichier— rejoue la session à partir du journal. Vous vous êtes trompé à la sixième étape ? Sauvegardez le log, supprimez les lignes erronées, lancezgit bisect resetpuisreplay.git bisect visualize(ougit bisect view) — montre les candidats restants ; avec--oneline, les affiche via git log.
git bisect log > /tmp/bisect.log
# on corrige le log
git bisect reset
git bisect replay /tmp/bisect.log
Bisect et commits de merge
Dans un historique riche en merges, bisect peut vous emmener à l'intérieur d'une branche fusionnée — dans des commits créés avant que la branche soit synchronisée avec main, donc construits sur une autre base. Si vous voulez d'abord savoir quel merge a apporté le bug, utilisez :
git bisect start --first-parent
Git ne suit alors que les premiers parents des commits de merge, c'est-à-dire l'historique de la branche principale. Une fois le merge fautif trouvé, vous pouvez lancer un second bisect à l'intérieur de la branche concernée.
Erreurs courantes
- Inverser good et bad. Le résultat n'aura aucun sens. Restaurez la session avec
git bisect logetreplay, sans tout recommencer. - Un répertoire de travail sale. Les modifications non committées gênent le changement de commit et faussent la vérification. Avant de démarrer, committez ou faites un
git stash. - Des artefacts de build non nettoyés. Un cache obsolète ou un
node_modulesissu d'un autre commit donnent de faux résultats. Faites un build propre dans le script. - Un test instable (flaky). Si le test échoue parfois de lui-même, bisect marquera un commit au hasard. Lancez la vérification plusieurs fois ou corrigez le test avant la recherche.
- Oublier le reset. Vous êtes resté en detached HEAD et avez commencé à travailler. Le reflog vous aidera à retrouver les commits perdus.
Exemple : traquer une régression de bout en bout
Des utilisateurs signalent que le total du panier est mal calculé quand une remise s'applique. Dans la release v2.3, tout était correct ; dans main, non. On écrit une vérification minimale et on lance la recherche :
cat > /tmp/check.sh <<'SH'
#!/bin/sh
npm ci --silent || exit 125
node -e "const c=require('./src/cart'); process.exit(c.total([{price:100}], 0.1) === 90 ? 0 : 1)"
SH
chmod +x /tmp/check.sh
git stash # mettre de côté les modifications non committées
git bisect start HEAD v2.3
git bisect run /tmp/check.sh
...
a1b2c3d4e5f6... is the first bad commit
git bisect log > /tmp/cart-bisect.log
git bisect reset
git stash pop
Toute la recherche sur une centaine de commits se déroule sans vous, pendant que vous faites autre chose. Le log sauvegardé sera utile si, dans le ticket, vous devez montrer à vos collègues comment le coupable a été trouvé.
Que faire du commit trouvé
Une fois le coupable identifié, examinez-le en entier : git show a1b2c3d. Un petit commit avec un message clair révèle immédiatement la cause — c'est l'une des raisons pour lesquelles les équipes apprécient les commits atomiques : bisect sur un historique fait d'énormes « fix stuff » est presque inutile. Ensuite, deux options : corriger le bug avec un nouveau commit ou, si la modification est entièrement ratée et déjà publiée, l'annuler avec git revert a1b2c3d.
En résumé
git bisect transforme la traque d'une régression — lire des centaines de diffs — en quelques réponses « ça marche / ça ne marche pas », et avec git bisect run, en une seule commande. Il ne faut que deux repères — un bon et un mauvais commit — et une vérification reproductible. La lecture de l'historique, la navigation entre les commits et la recherche de bugs sont mises en pratique dans le cours Git.
Questions fréquentes
Combien d'étapes prend git bisect ?
Environ log2 du nombre de commits dans l'intervalle : à peu près 7 étapes pour 100 commits et 10 pour 1000. Git affiche lui-même l'estimation roughly N steps.
Peut-on lancer bisect sans connaître de bon commit ?
Il en faut au moins un. Prenez un commit manifestement ancien ou un tag de release et testez-le à la main ; les étapes supplémentaires dues à un intervalle large ne coûtent presque rien.
git bisect modifie-t-il l'historique du dépôt ?
Non, il se contente de déplacer le répertoire de travail entre les commits et stocke les marques dans des références internes refs/bisect, supprimées par git bisect reset.
Peut-on indiquer plusieurs bons commits ?
Oui, la commande git bisect good accepte plusieurs commits, ce qui réduit l'intervalle, surtout dans un historique avec des branches.
Bisect ne sert-il qu'à trouver des bugs ?
Non, il permet de trouver n'importe quel moment où une propriété a changé : apparition d'un fichier, accélération du build, changement de comportement. Les termes old/new ou vos propres termes via --term-old et --term-new sont pratiques pour cela.
Ce sujet est traité en détail avec de la pratique dans le cours « Git de zéro à développeur confirmé ».
Voir le cours