Wer häufiger in Projekten mit mehreren Entwicklern auf Jagd nach Bugs gehen muss, hat sicher häufig das Problem dass einige Fehler als Nebeneffekte anderer Patches oder Änderungen auftreten können.

Als ich diese Woche dabei war ein einen Bug in der Browser-History in Icingaweb2 zu fixen hatte ich ein vergleichbares Problem: Ich wusste zwar dass ein Fehler früher einmal auftrat der im aktuellen Master nicht mehr auftritt, aber nicht durch welche Änderung der Fehler dann letztendlich beseitigt wurde. Mein bisherigen Ansatz war in diesem Fall einfach zu einem beliebigen verdächtigen Commit in der Mitte auszuchecken und dann einfach zu testen. Wenn der Fehler dort noch auftritt, dann wusste ich zumindest dass der Commit, der den Fehler dann tatsächlich gefixt hat in einem späteren Commit auftritt. Nachteil dieser Technik ist natürlich dass man mühsam die Commit-History durchsuchen muss um manuell einen geeigneten Commit auszuwählen.

Ein Kollege hat mich dabei beobachtet und mich dann freundlicherweise darauf hingewiesen dass es auch wesentlich einfacher geht. Git hat nämlich für diesen Fall tatsächlich eine eingebaute Funktion namens Git-Bisect. Laut Dokumentation führt Git-Bisect eine Binärsuche durch um eine Änderung zu finden, die einen bestimmten Bug einführt, also vergleichbar mit meinem bisherigen Ansatz, aber mit dem kleinen Unterschied dass GIT die Commits automatisch auswählt und auscheckt.

Beispiel

Ich beginne damit an einen Punkt auszuchecken, an dem der Fehler auftritt. Wie man es noch aus der Schulzeit kennt braucht man an dieser Stelle jetzt allerdings ein bisschen Transferleistung um auf die Lösung zu kommen: Git-Bisect sucht nämlich einen Patch der einen Fehler einführt, ich suche aber nach etwas das den Fehler behebt. Deshalb muss ich im Folgenden gute und schlechte Commits verdrehen um das Feature für meinen Zweck nutzen zu können.

Ich starte den Vorgang und markiere den kaputten Commit also demnach mit good.

   git bisect start
   git bisect good

Anschließend markiere ich den aktuellen Master als bad, da ich ja weiß dass der Fehler dort schon gefixt wurde.

   git bisect bad master

Jetzt ist Git bereits zum Commit genau in der Mitte gesprungen und gibt sogar aus wie viele Schritte noch zu tun sind um den Fehler zu finden.

start

An dieser Stelle starte ich jetzt Icinga Web 2 und überprüfe ob der Fehler noch vorhanden ist. Da der Fehler an dieser Stelle nicht mehr auftritt markiere ich den Commit als bad.

    git bisect bad

Git springt wieder an einen neuen Commit und ich wiederhole den selben Schritt. Wenn der Fehler auftreten sollte markiere ich den Commit als gut und fahre ansonsten wie gehabt fort.

    git bisect good

Am Ende des ganzen Prozesses spuckt einem Git den ersten bad Commit aus, also den ersten Commit an dem der Fehler auftritt (oder in unserem Fall eben nicht mehr). Allerdings haben wir jetzt das Problem dass der Commit auf einen größeren Merge-Commit zeigt, was leider noch relativ wenig hilfreich ist. Um dieses Problem zu lösen starte ich den ganzen Prozess noch mal und nehmen diesmal den ersten und letzten Commit des Merge-Commits als Start und Ende.

    git bisect reset; git bisect start
    git bisect bad LETZTER_COMMIT
    git bisect good ERSTER_COMMIT

schluss

Endlich spuckt uns Git den Commit aus der den Fehler dann tatsächlich behoben hat, eine Änderung am Caching unserer Browser-History.

Fazit

Aus Sicht der Linux-Kernel-Hacker, für die Git ja ursprünglich entwickelt wurde, macht so ein Feature definitiv Sinn, denn ein großes verteiltes Projekt wie der Linux-Kernel ist sicher ein Garant für lange Abende der Fehlersuche in großen Commit-Histories. Jedoch kann oft auch jedes andere Projekt von diesen Features profitieren, wenn man denn davon weiß. Wie immer gilt: es lohnt sich seine Tools zu beherrschen und sich mit seinen Kollegen darüber auszutauschen, vielleicht existieren ja Tricks an die man noch nicht einmal gedacht hat!

Matthias Jentsch

Autor: Matthias Jentsch

Aktuell studiert Matthias Informatik an der Ohm und arbeitet als Werkstudent bei uns. Seinen hohen Kaffeeverbrauch kompensiert er mit Javascript und NodeJS und erstellt für uns interne Tools zur Ressourcenverwaltung.