Git
Chapters ▾ 2nd Edition

7.10 Git Tools - Debuggen met Git

Debuggen met Git

Git levert 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 wanneer elke regel van de methode voor het laatst gewijzigd is en door wie. Dit voorbeeld gebruikt de -L optie om de uitvoer te beperken tot regels 12 tot en met 22:

$ git blame -L 12,22 simplegit.rb
^4832fe2 (Scott Chacon  2008-03-15 10:31:28 -0700 12)  def show(tree = 'master')
^4832fe2 (Scott Chacon  2008-03-15 10:31:28 -0700 13)   command("git show #{tree}")
^4832fe2 (Scott Chacon  2008-03-15 10:31:28 -0700 14)  end
^4832fe2 (Scott Chacon  2008-03-15 10:31:28 -0700 15)
9f6560e4 (Scott Chacon  2008-03-17 21:52:20 -0700 16)  def log(tree = 'master')
79eaf55d (Scott Chacon  2008-04-06 10:15:08 -0700 17)   command("git log #{tree}")
9f6560e4 (Scott Chacon  2008-03-17 21:52:20 -0700 18)  end
9f6560e4 (Scott Chacon  2008-03-17 21:52:20 -0700 19)
42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 20)  def blame(path)
42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 21)   command("git blame #{path}")
42cf2861 (Magnus Chacon 2008-04-13 10:45:01 -0700 22)  end

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 ^4832fe2 commit regels op, die aangeven dat deze regels in de oorspronkelijke commit van dit bestand stonden. Deze commit is van toen dit bestand voor het eerst aan dit project was 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 vandaan 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 gaan gebruiken om je te helpen. 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 uitgechecked. 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 reis 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 uitvinden wat er gebeurd is dat deze fout mogelijk heeft veroorzaakt:

$ 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 controleren van honderden commits voor een opgetreden fout. Je zou het git bisect proces zelfs volledig kunnen automatiseren als je een script hebt dat met 0 eindigt als het project correct en niet-0 als het project fout is. 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

Door dit te doen zal 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.