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 een specifieke commit, een groep van 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 uiteraard refereren aan een commit met de SHA-1 hash die eraan is gegeven, maar er zijn ook meer mens-vriendelijke manieren om aan commits 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

Stel dat we geinteresseerd zijn in de commit waarvan de hash begint met 1c002dd..... Je kunt deze commit bekijken met elk van de volgend variaties van het git show commando (aangenomen dat de kortere versies uniek 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 zeven karakters, maar zal ze langer maken als dat nodig is om de SHA-1 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: per februari 2019 bevat de Linux kernal (wat een redelijk groot project is) meer dan 875.000 commits en bijna zeven miljoen objecten in de object database, waarin geen twee objecten waavan de SHA-1s in de eerste 12 karakters gelijk zijn.

Noot
EEN KORTE NOOT OVER SHA-1

Veel mensen zijn bezorgd dat op een gegeven moment 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 ander object in je repository, zal Git het vorige object in je Git database zien, aannemen dat het al was weggeschreven en simpelweg herbruiken. 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 (6,5 miljoen Git objecten) en deze pushen naar een gigantische 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

Een directe manier om naar een specifieke commit te verwijzen is als het de commit is aan de punt van een branch; in dat geval kan je de branchnaam in elke Git commando gebruiken die een referentie naar een commit verwacht. 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 naar 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 the 'recursive' strategy.
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 bijvoorbeeld de vijfde vorige waarde van de HEAD van je repository wilt zien, kan je de @{5} 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 master-branch gister was. Deze techniek werkt alleen voor gegevens die nog steeds in je reflog staan, 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 uitvoer, 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 recentelijker geleden hebt gekloond zal je alleen je eerste lokale commit zien.

Tip
Zie de reflog als Git’s versie van shell history

Als je een UNIX of Linux achtergrond hebt, kan je de reflog zien als Git’s versie van shell history, wat benadrukt dat wat er hier te zien is duidelijk alleen relevant is voor jou en jouw “sessie” en niets te doen heeft met anderen die misschien toevallig op dezelfde machine werken.

Voorouder referenties

De andere veelgebruikte manier om een commit te specificeren is via zijn voorouders. Als je een ^ (caret) 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'
Noot
De caret escapen op Windows

Voor cmd.exe op Windows, is ^ een speciaal teken en moet anders behandeld worden. Je kunt het verdubbelen of de referentie naar de commit in quotes zetten:

$ git show HEAD^     # werkt NIET op Windows
$ git show HEAD^^    # OK
$ git show "HEAD^"   # OK

Je kunt ook een getal aangeven na de ^ om aan te geven welke ouder je wilt; 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 ouderr van een merge commit is van de branch waar je op stond toen je mergede (meestal master), terwijl de tweede ouder van een merge commit van de branch is die werd gemerged (zeg, topic):

$ 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 ~ (tilde). 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 hiervoor, 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.
Figuur 137. Voorbeeld historie voor reeks selectie.

Je wilt zien wat er in je experiment-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 trackt 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. Dus deze drie commando’s zijn gelijkwaardig:

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

Dit is nuttig omdat 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.

scroll-to-top