Zurück
Tech 5 Min. Lesezeit - 15. Mai 22 - Mayeul Le Monies de Sagazan

Die Ursache eines Bugs effizient mit git bisect finden

Sie möchten schnell den Commit finden, der einen Bug in Ihrer Codebasis eingeführt hat, aber Sie haben mehrere Hundert oder Tausende von Commits? git bisect ist wahrscheinlich das richtige Tool für Sie.

Die Theorie

Im Englischen bedeutet bisect „Halbieren“, und genau das tut git bisect:
  • man gibt ihm einen „guten“ Commit, das heisst einen Commit, in dem der Bug noch nicht vorhanden war;
  • danach gibt man ihm einen „schlechten“ Commit (oft der aktuelle Commit);
  • schliesslich, solange git bisect mehrere Commits zwischen dem „guten“ und dem „schlechten“ Commit findet, nimmt es den Commit zwischen den beiden und fragt uns, ob dieser Commit in Ordnung ist.
Der Vorteil der Verwendung einer binären Suche ist, dass man weiss, wie viele Schritte maximal erforderlich sind, um den Commit zu finden, der den Bug eingeführt hat, und dass diese Anzahl an Schritten nur um 1 steigt, jedes Mal wenn sich die Anzahl unserer verdächtigen Commits verdoppelt.
Zum Beispiel der Linux-Code enthält über 1 Million Commits, doch bräuchte es maximal nur 20 Schritte (binärer Logarithmus von 1 000 000), um den Commit zu finden, der einen Bug einführt!
Das zur Theorie, nun wollen wir sehen, wie man dieses Tool in der Praxis einsetzt.

Die Praxis

Um die Verwendung von git bisect zu zeigen, werden wir dieses git-Repository (das einen Bug enthält).
Wir werden zuerst den Code herunterladen:
git clone https://github.com/mle-moni/bisect-test
Die Datei test.js enthält diesen 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)}`)
Der folgende Befehl wird uns sagen, ob der Commit den Bug enthält oder nicht:
node test.js 3
(wir werden den Commit als schlecht betrachten, wenn dieser Befehl einen Fehler zurückgibt)
Nun wollen wir sehen, welche Commits gemacht wurden:
# 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
Unser Ziel wird es sein, mit git bisect herauszufinden, dass der fehlerhafte Commit tatsächlich derjenige ist, der den Namen "bug introduction" trägt.
Los geht's!
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
Das Tool verschiebt uns dann auf den Commit zwischen dem guten und dem schlechten:
Bisecting: 13 revisions left to test after this (roughly 4 steps)
[fb045ac20c5972136afd8c10e510c0483f97b1a9] we still don't know that there is a bug
Danach testen wir den Code (hier ist es einfach, da es sich um JavaScript handelt, oft werden wir einen Kompilierungsschritt haben):
node test.js 3
Error: no number for this index
Dieser Commit enthält den Bug, wir werden git bisect also sagen, dass der Commit schlecht ist:
git bisect bad
Bisecting: 6 revisions left to test after this (roughly 3 steps)
[991ef4bb20a5d29cc6a307dd3a289a5fc3159c3d] no bug here too
Danach setzen wir diese Routine fort, bis wir den schlechten Commit gefunden haben!
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
Und schliesslich:

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 
Date:   Sun May 15 14:45:17 2022 +0200
    bug introduction
test.js | 3 +++
1 file changed, 3 insertions(+)
Schliesslich können wir uns ansehen, welche Änderungen das Auftreten des Bugs verursacht haben:
git diff HEAD^
function getNumber(numbers, index) {
+       if (!numbers[index]) {
+               throw new Error('no number for this index')
+       }
        return numbers[index]
 }
Hier war der "Bug" also die Bedingung
if (!numbers[index]) {
da numbers[index] kann 0 und dass !0 ergibt true, es wäre nötig gewesen, präziser zu sein und den Fehler nur anzuzeigen, wenn numbers[index] war undefined :
if (numbers[index] === undefined) { 

Sonderfälle

Wenn einer der Commits nicht getestet werden kann (z.B. wenn er nicht buildet), gibt es mehrere Lösungen:
  • einen anderen Commit manuell auswählen:
git reset HARD~2 # se placer sur 2 commits avant celui choisi par git bisect
  • git bisect den nächsten Commit wählen lassen:
git bisect skip
Wenn wir die binäre Suche beenden möchten, können wir dies einfach tun:
git bisect reset
Das war's zum Tool git bisect!

Bonus

Wenn Sie die Möglichkeit haben möchten, die Diffs mit VS Code anzusehen und dabei weiterhin die Git-Tools (rebase, merge, ...) in der CLI mit vim zu verwenden, ist dies durch Konfigurieren von git difftool.
Hier ist zum Beispiel meine Konfiguration ~/.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
Danach muss man git difftool einfach auf die gleiche Weise verwenden, wie man git diff verwendet:
git difftool HEAD^
Launch 'vscode' [Y/n]? Y
git difftool example

Möchten Sie bei der Lancierung Ihres digitalen Projekts begleitet werden?

Reichen Sie Ihr Projekt jetzt ein