Git
Chapters ▾ 2nd Edition

7.10 Git Tools - Debuggen met Git

Debuggen met Git

Additioneel aan het feit dat het primair voor versiebeheer is, levert Git ook een aantal instrumenten om je te helpen met het debuggen van problemen in je projecten. Omdat Git is ontworpen om te werken met bijna alle soorten projecten, zijn deze instrumenten redelijk generiek, maar ze kunnen je vaak helpen om een fout op te sporen of een schuldige aan te wijzen als dingen fout gaan.

Bestands annotatie

Als je op zoek bent naar een bug in je code en je wilt weten wanneer deze er in is geslopen en waarom, is bestands annotatie vaak het beste gereedschap. Het laat voor alle regels in alle bestanden zien welke de commit de laatste was die een wijziging aanbracht. Dus, als je ziet dat een methode in je code labiel is, kan je het bestand annoteren met git blame om te zien welke commit verantwoordelijk was voor de introductie van die regel.

Het volgende voorbeeld gebruikt git blame om te bepalen welke commit en committer verantwoordelijk zijn voor regels in de top-level Linux kernel Makefile en, vervolgens, de -L optie om de uitvoer van de geannoteerde regels te beperken tot regels 69 tot en met 82 van dat bestand:

$ 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

Merk op dat het eerste veld een deel is van de SHA-1 van de commit die het laatste die regel wijzigde. De volgende twee velden zijn waarden die uit die commit zijn gehaald — de naam van de auteur en de schrijfdatum van die commit — zodat je eenvoudig kunt zien wie de regel gewijzigd heeft en wanneer. Daarna volgt het regelnummer en de inhoud van het bestand. Merk ook de ^1da177e4c3f4 commit regels op, waar de ^ prefix de regels aangeeft dit in de initiele commit van de repository aan dit project zijn toegevoegd, en deze regels zijn sindsdien niet gewijzigd. Dit is een beetje verwarrend, omdat je nu op z’n minst drie verschillende manieren hebt gezien waarop Git het ^-teken heeft gebruikt om een SHA-1 van een commit te duiden, maar dat is wat het hier betekent.

Een ander gaaf iets van Git is dat het bestandsnaam wijzigingen niet expliciet bijhoudt. Het slaat de snapshots op en probeert dan impliciet uit te vinden dat het hernoemd is, nadat het gebeurd is. Een van de interessante toepassingen hiervan is dat je het ook kunt vragen allerhande code verplaatsingen uit te vinden. Als je -C aan git blame meegeeft, zal Git het bestand dat je annoteerd analiseren en probeert het uit te vinden waar delen van de code in dat bestand oorspronkelijk vandaan kwamen als ze van elders waren gekopieerd. Bijvoorbeeld, stel dat je een bestand genaamd GITServerHandler.m aan het herstructureren bent in meerdere bestanden, waarvan er een GITPackUpload.m heet. Door GITPackUpload.m te blamen met de -C optie, kan je zien waar delen van de code oorspronkelijk vandaan kwamen:

$ 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)

Dit is erg nuttig. Normaalgesproken krijg je als de oorspronkelijke commit, de commit waar je de code naartoe hebt gekopieerd, omdat dat de eerste keer is dat je deze regels in dit bestand hebt aangeraakt. Git geeft je de oorspronkelijke commit waarin je deze regels hebt geschreven, zelfs als dat in een ander bestand was.

Een bestand annoteren helpt je als je meteen al weet waar het probleem is. Als je niet weet wat er kapot gaat, en er zijn tientallen of honderden commits geweest sinds de laatste staat waarin je weet dat de code werkte, zal je waarschijnlijk git bisect inschakelen voor hulp. Het bisect commando voert een binair zoektocht uit door je commit historie om je te helpen zo snel als mogelijk de commit te vinden die een probleem heeft geïntroduceerd.

Stel dat je zojuist een release van je code hebt ingevoerd in een productie omgeving, je krijgt fout rapporten over iets wat niet in je ontwikkelomgeving optrad, en je kunt je niet indenken waarom de code zich zo gedraagt. Je duikt in je code en het blijkt dat je het probleem kunt reproduceren, maar je kunt maar niet vinden waar het fout gaat. Je kunt de code bisecten om dit op te sporen. Eerst roep je git bisect start aan om het proces in gang te zetten, en dan gebruik je git bisect bad om het systeem te vertellen dat de huidige commit waar je op staat kapot is. Daarna moet je bisect vertellen waar de laatst bekende goede staat was, door `git bisect good <goede_commit> te gebruiken:

$ 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 kon opzoeken dat er ongeveer 12 commit zijn geweest tussen de commit die je als de laatst correcte hebt gemarkeerd (v1.0) en de huidige slechte versie, en dat het de middelste voor je heeft uitgecheckt. Op dit moment kan je je tests laten lopen om te zien of het probleem op deze commit voorkomt. Als dit het geval is, dan was het ergens voor deze middelste commit erin geslopen; als dat het niet het geval is, dan is het probleem na deze middelste commit geïntroduceerd. Het blijkt nu dat er hier geen probleem is, en je zegt Git dit door git bisect good in te typen en je zoektocht voort te zetten:

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

Nu zit je op een andere commit, halverwege tussen de ene die je zojuist getest hebt, en je slechte commit. Je gaat weer testen en ziet nu dat deze commit kapot is, dus je zegt dit met git bisect bad:

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

Deze commit is prima, en nu heeft Git alle informatie die het nodig heeft om te bepalen waar het probleem is begonnen. Het geeft je de SHA-1 van de eerste slechte commit en laat je wat van de commit informatie zien en welke bestanden gewijzigd waren in die commit zodat je kunt uitzoeken wat er gebeurd is waardoor deze fout optreedt:

$ 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

Als je klaar bent, moet je git bisect reset aanroepen om je HEAD terug te zetten naar waar je was voordat je startte, of je verzandt in een hele vreemde status:

$ git bisect reset

Dit is een krachtig instrument dat je kan helpen met het in enkele minuten doorzoeken van honderden commits voor een opgetreden fout. Als je een script hebt dat met 0 eindigt als het project correct en niet-0 als het project fout is, kan je het git bisect proces zelfs volledig automatiseren. Allereerst vertel je het de reikwijdte van de bisect door de bekende goede en slechte commits door te geven. Je kunt dit doen door ze te tonen met de bisect start commando als je dit wilt, door de bekende slechte commit als eerste door te geven en de bekende goede commit als tweede:

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

Op deze manier wordt test-error.sh automatisch aanroepen voor elke uitgecheckte commit totdat Git de eerste kapotte commit vindt. Je kunt ook zoiets als make of make tests aanroepen of wat je ook maar hebt dat geautomatiseerde tests voor je uitvoert.