Git
Chapters ▾ 2nd Edition

7.1 Git Tools - Revisie Selectie

Op dit moment heb je de meeste van de alledaagse commando’s en workflows geleerd die je nodig hebt om een Git repository te onderhouden of te beheren voor het bijhouden van je eigen bron code. Je hebt de basale taken van tracken en committen van bestanden volbracht, en je hebt de kracht van de staging area onder de knie gekregen en het lichtgewicht topic branchen en mergen.

Nu zal je een aantal erg krachtige dingen die Git kan doen onderzoeken die je niet perse elke dag zult doen maar die je op een gegeven moment wel nodig kunt hebben.

Revisie Selectie

Git laat je op verschillende manieren specifieke commits of een reeks van commits aangeven. Ze zijn niet echt voor de hand liggend, maar zijn zeer nuttig om te kennen.

Enkele revisies

Je kunt vanzelfsprekend refereren aan een commit met de SHA-1 hash die eraan is gegeven, maar er zijn ook meer mens-vriendelijke manieren om aan commit te referen. Deze paragraaf toont de verschillende manieren waarmee je kunt refereren aan een enkele commit.

Verkorte SHA-1

Git is slim genoeg om uit te knobbelen welke commit je wilde typen als je de eerste paar karakters geeft, zolang als je verkorte SHA-1 op z’n minst 4 karakters lang is en eenduidig; dus, slechts één object in de huidige repository begint met dat deel-SHA-1.

Bijvoorbeeld, om een specifieke commit te zien, stel dat je een git log commando gebruikt en de commit waar je een bepaalde functie hebt toegevoegd identificeert:

$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

Neem 1c002dd.... in dit geval. Als je die commit git show-t, zijn de volgende commando’s gelijkwaardig (aangenomen dat de verkorte versies eenduidig zijn):

$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d

Git kan met een korte, unieke afkorting van je SHA-1 waarden overweg. Als je --abbrev-commit doorgeeft aan het git log commando, zal de uitvoer de korte waarden gebruiken, maar ze uniek houden; het gebruikt standaard 7 karakters, maar zal ze langer maken als dat nodig is om ze eenduidig te houden.

$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit

Over het algemeen zijn acht tot tien karakters meer dan genoeg om binnen een project uniek te zijn.

Om een voorbeeld te geven: de Linux kernel, wat best een groot project is met meer dan 450.000 commits en 3,6 miljoen objecten heeft geen twee objecten waarvan de SHA-1s meer dan de eerste 11 karakters overlappen.

Note
EEN KORTE NOOT OVER SHA-1

Veel mensen worden op een gegeven moment bezorgd dat ze, door domme toeval, twee objecten in hun repository hebben waarvan de hash dezelfde SHA-1 waarde is. Wat dan?

Als het je overkomt dat je een object commit dat naar dezelfde SHA-1 waarde hasht als een vorig object in je repository, zal Git het vorige object in je Git database zien en aannemen dat het al was weggeschreven. Als je op dat moment dat object weer zou gaan uitchecken, zal je altijd de gegevens van het eerste object krijgen.

Echter, je moet beseffen hoe belachelijk onwaarschijnlijk dit scenario is. De SHA-1 cijferruimte is 20 bytes of 160 bits. De hoeveelheid willekeurig gehashde objecten die nodig zijn om een 50% waarschijnlijkheid van een enkele botsing te garanderen is ongeveer 280 (de formule om de waarschijnlijkheid van een botsing te bepalen is p = (n (n-1)/2) * (1/2^160)). 280 is 1,2 x 1024 of 1 miljoen miljard miljard. Dat is 1.200 keer het aantal zandkorrels op de aarde.

Hier is een voorbeeld om je een idee te geven wat er nodig is om een SHA-1 botsing te krijgen. Als alle 6,5 miljard mensen op Aarde zouden programmeren, en elke seconde zou elk van hen code opleveren ter grootte aan de gehele Linux kernel historie (3,6 miljoen Git objecten) en deze pushen naar een gigantiesche Git repository, zou het ongeveer 2 jaar duren voordat de repository genoeg objecten zou bevatten om een 50% waarschijnlijkheid te krijgen van een enkele SHA-1 object botsing. Er is een grotere kans dat elk lid van je programmeerteam wordt aangevallen en gedood door wolven in ongerelateerde gebeurtenissen op dezelfde avond.

Branch referenties

De meest directe manier om een commit aan te duiden vereist dat je een branch referentie ernaar hebt wijzen. Dan kan je de naam van de branch in elke Git commando die een commit object of SHA-1 waarde verwacht gebruiken. Bijvoorveeld, als je het laatste commit object op een branch wilt laten zien, zijn de volgende commando’s gelijk, aangenomen dat de topic1 branch wijst naar ca82a6d:

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

Als je wilt zien anar welke specifieke SHA-1 een branch wijst, of als je wilt zien waar elk van deze voorbeelden op neerkomt in termen van SHA-1s, kan je het Git binnenwerk instrument (plumbing tool) geheten rev-parse gebruiken. Je kunt Git Binnenwerk bekijken voor meer informatie over plumbing tools; het komt erop neer dat rev-parse er is voor onder-water operaties en dat het niet bedoeld is voor het dagelijks gebruik. Dit gezegd hebbende, het kan soms handig zijn als het nodig is om te zien wat er echt gebeurt. Hier kan je rev-parse op je branch laten lopen.

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

RefLog verkorte namen

Een van de dingen die Git op de achtergrond doet als je aan het werk bent is een “reflog” bijhouden - een logboek waarin wordt bijgehouden waar je HEAD en branch referenties in de afgelopen paar maanden zijn geweest.

Je kunt de reflog zien door git reflog te gebruiken:

$ git reflog
734713b HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by recursive.
1c002dd HEAD@{2}: commit: added some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD

Elke keer als de punt van je branch voor welke reden dan ook wordt bijgewerkt, slaat Git die informatie voor je op in deze tijdelijke historie. En je kunt ook aan oudere commits refereren met deze gegevens. Als je de de vijfde vorige waarde van de HEAD van je repository wilt zien, kan je de @{n} referentie gebruiken die je in de reflog uitvoer ziet:

$ git show HEAD@{5}

Je kunt deze syntax ook gebruiken om te zien waar een branch was op een specifieke moment in het verleden. Als je bijvoorbeeld wilt zien waar je master branch gister was, kan je dit typen

$ git show master@{yesterday}

Dat toont je waar de punt van de branch gister was. Deze techniek werkt alleen voor gegevens die nog steeds in je reflog staat, dus je kunt het niet gebruiken om commits op te zoeken die ouder dan een paar maanden zijn.

Om de reflog informatie geformatteerd te tonen zoals de git log utivoer, kan je git log -g uitvoeren:

$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: fixed refs handling, added gc auto, updated
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

Het is belangrijk op te merken dat de reflog informatie puur lokaal is; het is een log van wat jij gedaan hebt in jouw repository. De referentie zal niet hetzelfde zijn op de kopie van een ander van de repository, en direct nadat je initieel een kloon van een repository gemaakt hebt zal je een lege reflog hebben, omdat er nog geen activiteiten hebben plaatsgevonden op jouw repository. Het uitvoeren van git show HEAD@{2.months.ago} zal alleen werken als je het project op z’n minst 2 maanden geleden hebt gekloond, als je het vijf minuten geleden hebt gekloond zal je geen resultaten krijgen.

Voorouder referenties

De andere veelgebruikte manier om een commit te specificeren is via zijn voorouders. Als je een ^ aan het eind van een referentie plaatst, zal Git dit interpreteren als een referentie aan de ouder van deze commit. Stel dat je naar de historie van je project kijkt:

$ git log --pretty=format:'%h %s' --graph
* 734713b fixed refs handling, added gc auto, updated tests
*   d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added some blame and merge stuff
|/
* 1c36188 ignore *.gem
* 9b29157 add open3_detach to gemspec file list

Dan kan je de vorige commit zien door HEAD^ te specificeren, wat “de ouder van HEAD” betekent:

$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

Je kunt ook een getal aangeven na de ^, bijvoorbeeld: d921970^2 wat “de tweede ouder van d921970” betekent. Deze syntax is alleen nuttig voor merge commits, waar je meer dan een ouder hebt. De eerste ouder is de branch waar je op stond toen je mergde, en de tweede is de commit op de branch die je aan het in mergen was:

$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <paul+git@mjr.org>
Date:   Wed Dec 10 22:22:03 2008 +0000

    Some rdoc changes

De andere belangrijke voorouder specificatie is de ~. Deze refereert ook aan de eerste ouder, dus HEAD~ en HEAD^ zijn aan elkaar gelijk. Het verschil wordt duidelijk wanneer je een getal specificeert. HEAD~2 betekent “de eerste ouder van de eerste ouder”, of “de grootouder”, het loopt het aantal keren terug over de eerste ouders dat je specificeert. Even weer als voorbeeld, in de historie van eerder, HEAD~3 zou dit opleveren:

$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

Dit kan ook als HEAD^^^ worden geschreven, wat wederom de eerste ouder van de eerste ouder van de eerste ouder aanduidt:

$ git show HEAD^^^
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

Je kunt deze syntaxen ook combineren; je kunt de tweede ouder van de vorige referentie krijgen (aangenomen dat het een merge commit was) door HEAD~3^2 te gebruiken, en zo voort.

Commit reeksen

Nu dat je individuele commits kunt aanwijzen, laten we eens kijken hoe je een reeks van commits kunt aanduiden. Dit is in het bijzonder nuttig voor het beheren van je branches, als je veel branches hebt, kan je reeks-specificaties gebruiken om vragen te beantwoorden als “Welk werk zit op deze branch die ik nog niet gemerged heb in mijn hoofdbranch?”.

Tweevoudige punt

De meest gebruikte reeks specificatie is de tweevoudige punt (double-dot) syntax. Dit vraagt Git gewoon om een reeks commits op te halen die bereikbaar zijn van de ene commit maar niet vanaf een andere. Bijvoorbeeld, stel dat je een commit historie hebt die eruit ziet als Voorbeeld historie voor reeks selectie..

Voorbeeld historie voor reeks selectie.
Figure 137. Voorbeeld historie voor reeks selectie.

Je wilt zien wat er in je experimentele branch zit wat nog niet in je master branch gemerged is. Je kunt Git vragen om een log te laten zien van alleen die commits met master..experiment, hiermee wordt bedoeld “alle commits bereikbaar voor experiment die niet bereikbaar zijn voor master.” Om het kort en duidelijk te houden in deze voorbeelden, gebruik ik de letters van de commit objecten uit het diagram in plaats van de eigenlijke log uitvoer in de volgorde dat ze getoond zouden zijn:

$ git log master..experiment
D
C

Als je, aan de andere kant, je het tegenovergestelde wilt zien - alle commits in master die niet in experiment zitten, kan je de branch namen omdraaien. experiment..master laat je alles in master zien wat niet vanuit experiment bereikbaar is:

$ git log experiment..master
F
E

Dit is nuttig als je de experiment branch bij wilt houden en alvast wilt zien wat je op het punt staat in te mergen. Waar deze syntax ook vaak voor wordt gebruikt is om te zien wat je op het punt staat te pushen naar een remote:

$ git log origin/master..HEAD

Dit commando laat je alle commits in je huidige branch zien die niet in de master branch zitten op je origin remote. Als je een git push laat lopen en je huidige branch tracket origin/master, zijn de commits die worden getoond door git log origin/master..HEAD de commits die naar de server zullen worden gestuurd. Je kunt ook een kant van deze sytax weglaten om Git te laten aannemen dat hier HEAD wordt bedoeld. Bijvoorbeeld, kan je dezelfde resultaten bereiken als in het vorige voorbeeld door git log origin/master.. te typen; Git gebruikt HEAD als een van de twee kanten ontbreekt.

Meerdere punten

De twee-punten syntax is nuttig als een afkorting; maar misschien wil je meer dan twee branches aanwijzen om je revisie aan te geven, zoals het zien welke commits er zijn in een willekeurig aantal branches die niet in de branch zitten waar je nu op zit. Git staat je toe dit te doen door ofwel het ^ karakter te gebruiken of --not voor elke referentie waarvan je niet de bereikbare commits wilt zien. Dis deze drie commando’s zijn gelijkwaardig:

$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA

Dit is nuttig omdate je met deze syntax meer dan twee referenties in je query kunt aangeven, wat je niet kunt doen met de dubbele-punt syntax. Als je bijvoorbeeld alle commits wilt zien die bereikbaar zijn vanaf refA of refB maar niet van refC, kan je een van deze intypen:

$ git log refA refB ^refC
$ git log refA refB --not refC

Dit vormt een hele krachtige revisie uitvraag systeem die je kan helpen om uit te vinden wat er in je branches zit.

Drievoudige punt

De laatste belangrijke reeks-selectie syntax is de drievoudige punt (triple dot) syntax, welke alle commits aanduidt die door een van beide referenties bereikbaar is maar niet door beide. Kijk even terug naar het voorbeeld van commit historie in Voorbeeld historie voor reeks selectie.. Als je wilt zien wat in master of experiment zit maar geen gedeelde referenties kan je dit laten lopen

$ git log master...experiment
F
E
D
C

Wederom, dit geeft je een normale log uitvoer, maar laat je alleen de commit informatie zien voor deze vier commits, getoond op de reguliere commit datum volgorde.

Een gebruikelijke optie om te gebruiken bij het log commando in dit geval is --left-right, welke je laat zien welke zijde van de reeks elke commit in zit. Dit helpt om de gegevens meer bruikbaar te maken:

$ git log --left-right master...experiment
< F
< E
> D
> C

Met deze instrumenten kan je eenvoudiger Git laten weten welke commit of commits je wilt onderzoeken.