Git
Chapters ▾ 2nd Edition

7.10 Git Tools - Debuggen mit Git

Debuggen mit Git

Zusätzlich zu der primären Funktion der Versionskontrolle bietet Git auch ein paar Befehle, die Ihnen beim Debuggen Ihrer Quellcode-Projekte helfen. Da Git für fast jede Art von Inhalt entwickelt wurde, sind diese Werkzeuge ziemlich allgemein, aber sie können Ihnen oft helfen, nach einem Fehler oder Schuldigen zu suchen, wenn etwas schief läuft.

Datei-Annotationen

Wenn Sie einen Fehler in Ihrem Code finden und wissen wollen, wann und warum er eingeführt wurde, ist die Datei-Annotation oft Ihr bestes Werkzeug. Es zeigt Ihnen, welcher Commit als letzter jede Zeile einer Datei geändert hat. Wenn Sie also sehen, dass eine Methode in Ihrem Code fehlerhaft ist, können Sie 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

Beachten Sie, 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 Sie leicht sehen können, wer diese Zeile wann geändert hat. Danach folgen die Zeilennummer und der Inhalt der Datei. Beachten Sie auch die ^1da177e4c3f4 Commit-Zeilen, wobei das ^-Präfix Zeilen bezeichnet, die beim ersten Commit des Repositorys eingeführt wurden und seitdem unverändert geblieben sind. Das ist ein bisschen verwirrend, denn jetzt haben Sie mindestens drei verschiedene Möglichkeiten gesehen, wie Git das Zeichen ^ verwendet, um einen Commit SHA-1 zu modifizieren, aber das ist es, was es hier bedeutet.

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 Sie -C an git blame übergeben, analysiert Git die Datei, die Sie annotieren, und versucht herauszufinden, woher die Codeschnipsel darin ursprünglich kamen, wenn sie von woanders kopiert wurden. Nehmen wir an, Sie zerlegen eine Datei namens GITServerHandler.m in mehrere Dateien, von denen eine GITPackUpload.m ist. Indem Sie GITPackUpload.m mit git blame -C aufrufen, können Sie sehen, wo Abschnitte 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 erhalten Sie als Original-Commit den Commit, wo Sie den Code hinüber kopiert haben, denn das ist das erste Mal, dass Sie diese Zeilen in dieser Datei berührt haben. Mit der Option -C gibt Ihnen Git den ursprünglichen Commit, in dem Sie diese Zeilen geschrieben haben, auch wenn das in einer anderen Datei war.

Das Annotieren einer Datei hilft, wenn Sie wissen, wo das Problem liegt. Wenn Sie nicht wissen, was kaputt ist, und es gab Dutzende oder Hunderte von Commits seit dem letzten Zustand, von dem Sie wissen, dass der Code funktioniert hat, werden Sie sich wahrscheinlich an git bisect wenden, um Hilfe zu holen. Der Befehl bisect führt eine binäre Suche durch Ihre Commit-Historie durch, um Ihnen zu helfen, so schnell wie möglich zu identifizieren, welcher Commit ein Problem eingeführt hat.

Nehmen wir an, Sie haben gerade eine Version Ihres Codes in eine Produktionsumgebung ausgelagert, Sie erhalten Fehlerberichte über etwas, das nicht in Ihrer Entwicklungsumgebung passiert ist, und Sie können sich nicht vorstellen, warum der Code das tut. Sie gehen zurück zu Ihrem Code und es stellt sich heraus, dass Sie den Fehler reproduzieren können, aber Sie können nicht herausfinden, was schief läuft. Sie können den Code halbieren (engl. bisect), um es herauszufinden. Zuerst rufen Sie git bisect start auf, um die Dinge zum Laufen zu bringen. Dann benutzen Sie git bisect bad, um dem System mitzuteilen, dass der aktuelle Commit, auf dem Sie sind, nicht funktioniert. Dann müssen Sie git bisect sagen, wann der letzte bekannte gute (funktionierende) Zustand war, indem Sie git bisect good <good_commit> verwenden:

$ 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 Sie als letzten guten Commit (v1.0) markiert haben, und der aktuellen schlechten Version liegen, und ist für Sie zu dem mittleren Commit gewechselt (interner git checkout). An diesem Punkt können Sie Ihren 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, und Sie sagen Git das, indem Sie git bisect good tippen und Ihre Reise fortsetzen:

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

Jetzt sind Sie auf einem anderen Commit, auf halbem Weg zwischen dem, den Sie gerade getestet haben und Ihrem schlechten Commit. Sie führen Ihren Test noch einmal durch und stellen fest, dass dieser Commit fehlerhaft ist. Also sagen Sie 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 Ihnen den SHA-1 des ersten fehlerhaften Commits und zeigt einige der Commit-Informationen und welche Dateien in diesem Commit verändert wurden, so dass Sie herausfinden können, 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 Sie fertig sind, sollten Sie git bisect reset ausführen, um Ihren HEAD wieder auf den Stand vor dem Start zurückzusetzen (ansonsten landen Sie in einem seltsamen Zustand):

$ git bisect reset

Dies ist ein mächtiges Werkzeug, das Ihnen helfen kann, hunderte von Commits in Minuten auf einen eingeführten Fehler zu überprüfen. Tatsächlich können Sie git bisect vollständig automatisieren, falls Sie ein Skript haben, das mit einer Ausgabe von 0 beendet, wenn das Projekt funktioniert und mit einer Ausgabe ungleich 0 beendet, wenn das Projekt nicht funktioniert. Zuerst teilen Sie Git wieder den Umfang der Bisektion mit, indem Sie die bekannten schlechten und guten Commits angeben. Sie können dies tun, indem Sie sie dem bisect start-Befehl den bekannten schlechten Commit zuerst und den bekannten guten Commit als zweiten Parameter geben:

$ 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. Sie können auch etwas wie make or make tests oder was auch immer Sie haben, das automatische Tests für Sie ausführt, nutzen.