Git
Chapters ▾ 2nd Edition

7.10 Git Tools - Debuggen mit Git

Debuggen mit Git

Git ist zwar primär ein Tool für die Versionskontrolle, es bietet jedoch auch ein paar Befehle, die dir beim Debuggen deiner Quellcode-Projekte helfen können. Da Git für fast jede Art von Inhalt entwickelt wurde, sind diese Werkzeuge ziemlich allgemein gehalten. Sie können dir jedoch oft helfen, nach einem Fehler oder Code-Autor zu suchen, wenn etwas schief läuft.

Datei-Annotationen

Wenn du einen Fehler in deinem Code findest und wissen willst, wann und warum er eingeführt wurde, ist die Datei-Annotation oft dein bestes Werkzeug. Es zeigt dir, welcher Commit als letzter jede Zeile einer Datei geändert hat. Wenn du also bemerkst, dass eine Methode in deinem Code fehlerhaft ist, kannst du die Datei mit git blame annotieren um festzustellen, welcher Commit für die Einführung dieser Zeile verantwortlich war.

Das folgende Beispiel verwendet git blame, um zu bestimmen, welcher Commit und Committer für die Zeilen im Makefile des Linux-Kernels der obersten Ebene verantwortlich war. Außerdem verwendet es die Option -L, um die Ausgabe der Annotation auf die Zeilen 69 bis 82 dieser Datei zu beschränken:

$ git blame -L 69,82 Makefile
b8b0618cf6fab (Cheng Renquan  2009-05-26 16:03:07 +0800 69) ifeq ("$(origin V)", "command line")
b8b0618cf6fab (Cheng Renquan  2009-05-26 16:03:07 +0800 70)   KBUILD_VERBOSE = $(V)
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 71) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 72) ifndef KBUILD_VERBOSE
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 73)   KBUILD_VERBOSE = 0
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 74) endif
^1da177e4c3f4 (Linus Torvalds 2005-04-16 15:20:36 -0700 75)
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 76) ifeq ($(KBUILD_VERBOSE),1)
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 77)   quiet =
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 78)   Q =
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 79) else
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 80)   quiet=quiet_
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 81)   Q = @
066b7ed955808 (Michal Marek   2014-07-04 14:29:30 +0200 82) endif

Beachte, dass das erste Feld der partielle SHA-1 des Commits ist, der diese Zeile zuletzt geändert hat. Die nächsten beiden Felder sind Werte, die aus diesem Commit extrahiert wurden — der Name des Autors und das Datum dieses Commits — so dass du leicht sehen kannst, wer diese Zeile wann geändert hat. Danach folgen die Zeilennummer und der Inhalt der Datei. Beachte auch die ^1da177e4c3f4 Commit-Zeilen, wobei das ^-Präfix Zeilen angibt, daß diese Code-Zeilen mit dem allerersten Commit des Repositorys eingeführt wurden und seitdem unverändert geblieben sind. Das ist ein bisschen verwirrend, denn jetzt hast du mindestens drei verschiedene Möglichkeiten gesehen, wie Git das Zeichen ^ verwendet, um einen Commit SHA-1 zu modifizieren. Hier ist die Bedeutung eine andere.

Eine weitere coole Sache an Git ist, dass es Dateiumbenennungen nicht explizit verfolgt. Es zeichnet die Snapshots auf und versucht dann im Nachhinein herauszufinden, was implizit umbenannt wurde. Eines der interessanten Features ist, dass man Git bitten kann, auch alle Arten von Codebewegungen herauszufinden. Wenn du -C an git blame übergibst, analysiert Git die Datei, die du annotierst und versucht herauszufinden, woher die Codeschnipsel darin ursprünglich kamen, wenn sie von woanders kopiert wurden. Nehmen wir an, du zerlegst eine Datei namens GITServerHandler.m in mehrere Dateien, von denen eine GITPackUpload.m ist. Indem du GITPackUpload.m mit git blame -C aufrufst, annst du sehen, wo Teile des Codes ursprünglich herkamen:

$ git blame -C -L 141,153 GITPackUpload.m
f344f58d GITServerHandler.m (Scott 2009-01-04 141)
f344f58d GITServerHandler.m (Scott 2009-01-04 142) - (void) gatherObjectShasFromC
f344f58d GITServerHandler.m (Scott 2009-01-04 143) {
70befddd GITServerHandler.m (Scott 2009-03-22 144)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 145)
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 146)         NSString *parentSha;
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 147)         GITCommit *commit = [g
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 148)
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 149)         //NSLog(@"GATHER COMMI
ad11ac80 GITPackUpload.m    (Scott 2009-03-24 150)
56ef2caf GITServerHandler.m (Scott 2009-01-05 151)         if(commit) {
56ef2caf GITServerHandler.m (Scott 2009-01-05 152)                 [refDict setOb
56ef2caf GITServerHandler.m (Scott 2009-01-05 153)

Das ist wirklich nützlich. Normalerweise erhältst du als Original-Commit den Commit, mit dem du den Code kopiert hast, denn das ist das erste Mal, dass du diese Zeilen in dieser Datei angefasst hast. Mit der Option -C gibt dir Git den ursprünglichen Commit, in welchem du diese Zeilen geschrieben hast, auch wenn das in einer anderen Datei war.

Das Annotieren einer Datei hilft, wenn du weißt, wo das Problem liegt. Wenn du nicht weißt, was kaputt ist und es Dutzende oder Hunderte von Commits seit dem letzten funktionierendem Zustand gab, kannst du es mit git bisect versuchen. Der Befehl bisect führt eine binäre Suche durch deine Commit-Historie durch, um dir zu helfen so schnell wie möglich zu identifizieren, welcher Commit das Problem eingeführt hat.

Nehmen wir an, du hast gerade eine Version deines Codes in eine Produktionsumgebung released. Du erhältst einen Fehlerberichte über ein Verhalten, das so nicht in deiner Entwicklungsumgebung aufgetreten ist und du kannst dir nicht erklären, warum der Code sich so verhält. Du siehst dir deinem Code an und du kannst den Fehler reproduzieren. Du kannst jedoch nicht herausfinden, was schief läuft. Du kannst den Code teilen (engl. bisect), um es herauszufinden. Zuerst rufst du git bisect start auf. Dann benutzt du git bisect bad, um dem System mitzuteilen, dass der aktuelle Commit, auf dem du dich befindest nicht funktioniert. Dann musst du git bisect sagen, wann der letzte bekannte funktionierende Zustand war, indem du git bisect good <good_commit> verwendst:

$ git bisect start
$ git bisect bad
$ git bisect good v1.0
Bisecting: 6 revisions left to test after this
[ecb6e1bc347ccecc5f9350d878ce677feb13d3b2] Error handling on repo

Git hat herausgefunden, dass etwa 12 Commits zwischen dem Commit, den du als letzten guten Commit (v1.0) markiert hast, und der aktuellen schlechten Version liegen. Anschließend ist git zu dem mittleren Commit gewechselt (interner git checkout). Jetzt kannst du deinen Test durchführen, um zu sehen, ob der Fehler zum Zeitpunkt dieses Commits existiert. Wenn ja, dann wurde er irgendwann vor diesem mittleren Commit eingeführt. Wenn nicht, dann wurde das Problem irgendwann nach dem mittleren Commit eingeführt. In diesem Beispiel stellt sich heraus, dass es hier kein Problem gibt. Du sagst Git das, indem du git bisect good tippst und so deine Reise fortsetzt:

$ git bisect good
Bisecting: 3 revisions left to test after this
[b047b02ea83310a70fd603dc8cd7a6cd13d15c04] Secure this thing

Jetzt bist du auf einem anderen Commit. Du bist auf halbem Weg zwischen dem, den du gerade getestet hast und dem schlechten Commit. Du führst deinen Test noch einmal durch und stellst fest, dass dieser Commit fehlerhaft ist. Also sagst du Git das mit git bisect bad:

$ git bisect bad
Bisecting: 1 revisions left to test after this
[f71ce38690acf49c1f3c9bea38e09d82a5ce6014] Drop exceptions table

Dieser Commit ist in Ordnung, und jetzt hat Git alle Informationen, die es braucht, um festzustellen, wo das Problem eingeführt wurde. Es gibt dir den SHA-1 des ersten fehlerhaften Commits und zeigt einige der Commit-Informationen und welche Dateien in diesem Commit verändert wurden, so dass du herausfinden kannst, was diesen Fehler eingeführt haben könnte:

$ git bisect good
b047b02ea83310a70fd603dc8cd7a6cd13d15c04 is first bad commit
commit b047b02ea83310a70fd603dc8cd7a6cd13d15c04
Author: PJ Hyett <pjhyett@example.com>
Date:   Tue Jan 27 14:48:32 2009 -0800

    Secure this thing

:040000 040000 40ee3e7821b895e52c1695092db9bdc4c61d1730
f24d3c6ebcfc639b1a3814550e62d60b8e68a8e4 M  config

Wenn du fertig bist, solltest du git bisect reset ausführen, um deinen HEAD wieder auf den Stand vor dem Start zurückzusetzen (ansonsten landest du in einem undefinierten Zustand):

$ git bisect reset

Dies ist ein mächtiges Werkzeug, das dir helfen kann, hunderte von Commits in Minuten auf einen Fehler zu überprüfen. Wenn du ein Skript hast, das mit 0 als Rückgabewert, wenn das Projekt funktioniert und mit ungleich 0 als Rückgabewert, wenn das Projekt nicht funktioniert, kannst du git bisect vollständig automatisieren. Zuerst teilst du Git wieder den Umfang der Bisection mit, indem du die bekannten schlechten und guten Commits angibst. Du kannst dies tun, indem du dem Befehl bisect start den bekannten schlechten Commit als Ersten und den bekannten guten Commit als zweiten Parameter mitgibst:

$ git bisect start HEAD v1.0
$ git bisect run test-error.sh

Dabei wird automatisch test-error.sh bei jedem ausgecheckten Commit ausgeführt, bis Git den ersten fehlerhaften Commit findet. Du kannst auch etwas wie make or make tests oder was auch immer du hast, das automatische Tests für dich ausführt, nutzen.

scroll-to-top