Aller au contenu
Développement medium

Debug avec git bisect et git blame

14 min de lecture

Votre code régresse et vous ne savez pas quel commit en est la cause ? git bisect trouve le commit fautif par recherche dichotomique (O(log n)), sur 1000 commits, ~10 tests suffisent, et git blame identifie qui a écrit chaque ligne. Ce guide vous apprend à utiliser les deux outils, y compris bisect en mode automatisé et blame avec .git-blame-ignore-revs pour masquer les reformatages.

Prérequis : Consulter l'historique et Sélection de révisions.

  • Utiliser git bisect manuellement pour isoler le commit d'une régression
  • Automatiser bisect avec un script de test pour des audits rapides
  • Lire git blame pour identifier l'auteur et le contexte d'une ligne
  • Combiner bisect et blame dans un workflow de débogage complet

bisect ne cherche pas le bug, il cherche la frontière entre deux états de l'historique. Vous lui donnez ces deux états, il vous place au milieu, et chaque réponse divise l'intervalle par deux. Sur 1000 commits, dix réponses suffisent à isoler le coupable, contre 1000 vérifications dans le pire des cas en parcourant l'historique linéairement.

La contrainte tient en une phrase : votre test doit être déterministe. Un bug qui ne se manifeste qu'une fois sur trois donnera un résultat faux, car une seule mauvaise réponse envoie bisect explorer la moitié d'historique qui ne contient pas la régression.

bisect fonctionne comme une recherche dichotomique :

  1. Vous indiquez un commit bon (sans le bug) et un commit mauvais (avec le bug)
  2. Git sélectionne le commit au milieu
  3. Vous testez et dites si c'est bon ou mauvais
  4. Git réduit de moitié à chaque étape

Pendant toute la session, Git place votre copie de travail en HEAD détaché sur le commit à tester : vous n'êtes plus sur votre branche. C'est normal, et c'est la raison pour laquelle l'étape 7 n'est pas optionnelle, git bisect reset étant le seul moyen propre de revenir sur la branche de départ. Pensez aussi à committer ou remiser vos modifications en cours avant de démarrer, Git refusera de changer de commit sinon.

  1. Démarrez la session bisect :

    Fenêtre de terminal
    git bisect start
  2. Marquez le commit actuel comme mauvais :

    Fenêtre de terminal
    git bisect bad
  3. Marquez un commit connu comme bon (par exemple un tag ou un SHA ancien) :

    Fenêtre de terminal
    git bisect good v1.0.0
    # Ou avec un SHA :
    git bisect good a1b2c3d
  4. Git checkout automatiquement le commit du milieu :

    Bisecting: 15 revisions left to test after this (roughly 4 steps)
    [e4f5a6b...] Refactor module pricing
  5. Testez (lancez vos tests, vérifiez manuellement...) puis marquez :

    Fenêtre de terminal
    git bisect good # Ce commit est OK
    # ou
    git bisect bad # Ce commit a le bug
  6. Répétez l'étape 5 jusqu'à ce que Git identifie le commit fautif :

    d7c8b9a is the first bad commit
    Author: Bob <bob@example.com>
    Date: Thu Mar 20 10:00:00 2026
    feat: modifier le calcul de remise
  7. Terminez la session :

    Fenêtre de terminal
    git bisect reset
    # Retour à la branche d'origine

Tous les commits d'un historique ne sont pas testables : certains ne compilent pas, d'autres sont des états intermédiaires d'un rebase. Les déclarer « bons » ou « mauvais » au hasard fausserait toute la recherche.

Fenêtre de terminal
git bisect skip

Git écarte alors ce commit et propose un voisin. Si la zone entière est intestable, bisect finit par annoncer une plage de commits suspects au lieu d'un commit unique : c'est un résultat valide, à affiner à la main.

Ces deux commandes répondent à des besoins différents. git bisect log produit la trace de vos réponses, rejouable avec git bisect replay : c'est votre filet de sécurité si vous vous trompez de verdict en cours de route. git bisect visualize affiche les commits encore suspects, dans gitk si un environnement graphique est détecté, sinon dans git log.

Fenêtre de terminal
git bisect log # Historique de la session, rejouable avec bisect replay
git bisect visualize # Commits restants (gitk, ou git log sans affichage graphique)

Le vrai pouvoir de bisect : un script teste automatiquement chaque commit.

Fenêtre de terminal
git bisect start HEAD v1.0.0
git bisect run ./test-regression.sh

Le script doit retourner :

  • 0 → le commit est bon
  • 1 à 124, 126 à 127 → le commit est mauvais
  • 125 → le commit est non testable (skip)

Ce script montre les deux réflexes qui rendent un bisect run fiable. D'abord la sortie 125 sur un échec de compilation : sans elle, un commit qui ne construit pas serait déclaré mauvais et enverrait la recherche dans la mauvaise direction. Ensuite le fait de ne lancer qu'un seul test, celui qui reproduit la régression : lancer la suite complète multiplie le temps par le nombre d'étapes et fait échouer le script pour des raisons sans rapport avec le bug recherché.

Attention à une subtilité rarement documentée : un code de sortie supérieur à 127 interrompt la session au lieu de marquer le commit. Un script tué par un signal, ou un exit -1 qui devient 255, arrête donc bisect en cours de route.

test-regression.sh
#!/bin/bash
# Compiler le projet
make build 2>/dev/null || exit 125 # 125 = skip si ça ne compile pas
# Lancer le test spécifique
python -m pytest tests/test_pricing.py::test_remise
# pytest retourne 0 si OK, 1 si échec → parfait pour bisect
Fenêtre de terminal
chmod +x test-regression.sh
git bisect start HEAD v1.0.0
git bisect run ./test-regression.sh

Git exécute le script sur chaque commit testé et trouve le fautif sans intervention manuelle.

Quand la commande de test tient sur une ligne, sh -c évite de créer un fichier. Le point à ne pas manquer est la reconstruction des dépendances à chaque étape : make clean et npm ci garantissent que vous testez bien le code du commit courant, et non des artefacts laissés par l'étape précédente. C'est la cause la plus fréquente d'un bisect qui désigne un commit manifestement innocent.

Fenêtre de terminal
# Projet C/C++
git bisect run sh -c 'make clean && make && ./run_tests'
# Projet Node.js
git bisect run sh -c 'npm ci && npm test'

git blame annote chaque ligne d'un fichier avec le commit, l'auteur et la date :

Fenêtre de terminal
git blame src/pricing.py
a1b2c3d4 (Alice 2026-03-15 14:30:00 +0100 1) def calculate_price(qty, price):
a1b2c3d4 (Alice 2026-03-15 14:30:00 +0100 2) total = qty * price
d7c8b9a0 (Bob 2026-03-20 10:00:00 +0100 3) if qty > 100:
d7c8b9a0 (Bob 2026-03-20 10:00:00 +0100 4) total *= 0.9
a1b2c3d4 (Alice 2026-03-15 14:30:00 +0100 5) return total

Les lignes 3-4 ont été ajoutées par Bob le 20 mars.

Un détail de lecture qui déroute au premier abord : Git préfixe d'un accent circonflexe (^) les empreintes des commits dits limites, c'est-à-dire les plus anciens de la plage examinée. Ce marqueur signifie « la ligne existait déjà avant le début de l'intervalle observé », pas « ce commit est spécial ».

Trois de ces options changent radicalement le résultat et méritent d'être utilisées par défaut. -w neutralise les réindentations, -M suit les lignes déplacées à l'intérieur du fichier, -C les lignes reprises d'un autre fichier modifié dans le même commit. Sans elles, blame attribue un déplacement de code à celui qui l'a déplacé, et non à celui qui l'a écrit. Les répéter (-C -C, -C -C -C) élargit progressivement la recherche, au prix d'un temps d'exécution nettement plus long sur un gros fichier.

OptionEffetExemple
-L 10,25Limiter aux lignes 10-25git blame -L 10,25 file.py
-L :funcLimiter à une fonctiongit blame -L :calculate_price file.py
-CDétecter les lignes copiées depuis un autre fichiergit blame -C file.py
-C -CDétecter aussi dans les commits de créationgit blame -C -C file.py
-wIgnorer les changements de whitespacegit blame -w file.py
-MDétecter les lignes déplacées dans le même fichiergit blame -M file.py
--sinceDepuis une dategit blame --since=2.weeks file.py

Les reformatages massifs (prettier, black) polluent le blame. Créez un fichier d'exclusion :

.git-blame-ignore-revs
# Reformatage avec black
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
# Migration vers prettier
e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3
Fenêtre de terminal
# Configurer Git pour ignorer ces commits
git config blame.ignoreRevsFile .git-blame-ignore-revs

Ajoutez .git-blame-ignore-revs au repo pour que toute l'équipe en bénéficie. GitHub reconnaît aussi ce fichier automatiquement.

git annotate est un alias de blame avec un format de sortie légèrement différent. En pratique, utilisez blame, c'est le standard.

Cet enchaînement répond à quatre questions successives : quel commit, quoi dedans, quelles lignes, et pourquoi. Chacune se pose seulement quand la précédente a une réponse, ce qui évite l'erreur classique consistant à lire un blame avant de savoir quel commit chercher. La dernière étape mérite une nuance : git revert crée un nouveau commit qui annule le fautif, sans réécrire l'historique. C'est l'option à privilégier sur une branche partagée, où un reset obligerait toute l'équipe à réaligner sa copie locale.

Fenêtre de terminal
# 1. Un bug est signalé. Trouver le commit fautif :
git bisect start HEAD v1.0.0
git bisect run npm test
# 2. bisect trouve le commit d7c8b9a. Voir les détails :
git show d7c8b9a
# 3. Identifier les lignes modifiées :
git blame -L 3,4 src/pricing.py
# 4. Comprendre le contexte avec le log de la fonction :
git log -L :calculate_price:src/pricing.py
# 5. Corriger et committer :
git revert d7c8b9a # ou fix manuel

Les deux premières lignes de ce tableau désignent la même cause profonde : un script de test qui ne dit pas la vérité. Avant de lancer une session complète, prenez trente secondes pour exécuter votre script sur un commit que vous savez bon puis sur un commit que vous savez mauvais, et vérifiez le code de retour avec echo $?. Un bisect lancé sur un test non fiable produit un résultat faux avec la même assurance qu'un résultat juste.

SymptômeCause probableSolution
bisect retourne un mauvais commitScript de test non fiableTestez le script sur un commit bon et un mauvais manuellement
bisect tourne en boucleCommits non testables (ne compilent pas)Retournez 125 dans le script pour skipper
blame montre un commit de reformatageReformatage massifCréez .git-blame-ignore-revs
-L :func ne fonctionne pasLangage non reconnuConfigurez *.ext diff=lang dans .gitattributes
bisect corrompuSession interrompuegit bisect reset pour tout nettoyer
  • git bisect : recherche dichotomique, O(log n) commits à tester
  • git bisect run script : automatisation complète, le script retourne 0 (bon) ou 1 (mauvais)
  • git blame : identifie l'auteur et le commit de chaque ligne
  • -C détecte les copier-coller, -w ignore le whitespace
  • .git-blame-ignore-revs pour exclure les reformatages du blame
  • Workflow : bisectshowblamelog -L → fix

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn