Close

Git refs: An overview

Leer de basisprincipes van Git met deze tutorial met spaces.

Veel manieren om naar een commit te verwijzen

Als je de vele manieren begrijpt waarop je naar een commit kunt verwijzen, zijn deze opdrachten allemaal veel nuttiger. In dit hoofdstuk werpen we wat licht op de interne werking van veelgebruikte opdrachten zoals git checkout, git branch en git push door de vele methoden te verkennen om naar een commit te verwijzen.

We verkennen ook hoe je schijnbaar 'verloren' commits opnieuw kunt gebruiken door ze te openen via het reflog-mechanisme van Git.


Hashes


De meest directe manier om naar een commit te verwijzen is via de SHA-1-hash. Dit fungeert als de unieke ID voor elke commit. Je kunt de hash van al je commits vinden in de output van git log.

commit 0c708fdec272bc4446c6cabea4f0022c2b616eba Author: Mary Johnson  Date: Wed Jul 9 16:37:42 2014 -0500 Some commit message

Als je de commit doorgeeft aan andere Git-opdrachten, hoef je alleen maar genoeg tekens op te geven om de commit uniek te identificeren. Je kunt de bovenstaande commit bijvoorbeeld inspecteren met git show door de volgende opdracht uit te voeren:

git show 0c708f

Soms is het nodig om een branch, tag of andere referentie op te lossen in de bijbehorende commit-hash. Hiervoor kun je de opdracht git rev-parse gebruiken. Het volgende geeft de hash van de commit terug die wordt aangewezen door de main-branch:

Databases
gerelateerd materiaal

Een volledige Git-repository verplaatsen

Logo Bitbucket
Oplossing bekijken

Git leren met Bitbucket Cloud

git rev-parse main

Dit is met name handig wanneer je aangepaste scripts schrijft die een commit-referentie accepteren. In plaats van de commit-referentie handmatig te parseren, kun je git rev-parse de input voor je laten normaliseren.

Refs


Een ref is een indirecte manier om naar een commit te verwijzen. Je kunt het zien als een gebruiksvriendelijke alias voor een commit-hash. Dit is het interne mechanisme van Git om branches en tags weer te geven.

Refs worden opgeslagen als normale tekstbestanden in de map .git/refs waar .git gewoonlijk .git wordt genoemd. Om de refs in een van je repository's te bekijken, navigeer je naar .git/refs. Je zou de volgende structuur moeten zien, maar die bevat verschillende bestanden, afhankelijk van welke branches, tags en remotes je in je repo hebt:

.git/refs/ heads/ main some-feature remotes/ origin/ main tags/ v0.9

De heads-map bevat alle lokale branches in je repository. Elke bestandsnaam komt overeen met de naam van de bijbehorende branch, en in het bestand vind je een commit-hash. Deze commit-hash is de locatie van de tip van de branch. Probeer de volgende twee opdrachten uit te voeren vanaf de root van de Git-repository om dit te verifiëren:

# Output the contents of `refs/heads/main` file: cat .git/refs/heads/main # Inspect the commit at the tip of the `main` branch: git log -1 main

De commit-hash die door de opdracht cat wordt geretourneerd, moet overeenkomen met de commit-ID die wordt weergegeven in git log.

Om de locatie van de main-branch te wijzigen, hoeft Git alleen maar de inhoud van het refs/heads/main-bestand te wijzigen. Zo is het aanmaken van een nieuwe branch gewoon een kwestie van een commit-hash naar een nieuw bestand schrijven. Dit is een deel van de reden waarom Git-branches zo licht zijn in vergelijking met SVN.

De map met tags werkt op precies dezelfde manier, maar bevat tags in plaats van branches. De remotes-map bevat alle externe repository's die je hebt gemaakt met git remote als afzonderlijke submappen. In elke branch vind je alle externe branches die zijn opgehaald in je repository.

Specifying refs

Als je een ref doorgeeft aan een Git-opdracht, kun je ofwel de volledige naam van de ref opgeven, of een korte naam gebruiken en Git laten zoeken naar een bijbehorende ref. Je zou al bekend moeten zijn met korte namen voor refs, want dit gebruik je elke keer als je naar een branch verwijst met de naam ervan.

git show some-feature

Het argument some-feature in de bovenstaande opdracht is eigenlijk een korte naam voor de branch. Git lost dit op naar refs/heads/some-feature voordat het wordt gebruikt. Je kunt ook de volledige ref specificeren op de opdrachtregel, zoals het volgende:

git show refs/heads/some-feature

Dit voorkomt dubbelzinnigheid met betrekking tot de locatie van de ref. Dit is bijvoorbeeld nodig als je zowel een tag als een branch had met de naam some-feature. Als je echter de juiste naamgevingsconventies gebruikt, zou dubbelzinnigheid tussen tags en branches over het algemeen geen probleem moeten zijn.

We zien meer volledige refs in de sectie Refspecs.

Packed refs


Voor grote repository's voert Git periodiek een afvalinzameling uit om onnodige objecten te verwijderen en refs in één bestand te comprimeren voor efficiëntere prestaties. Je kunt deze compressie forceren met de opdracht afvalinzameling:

git gc

Hiermee worden alle afzonderlijke branch- en tagbestanden in de map refs verplaatst naar een één bestand met de naam packed-refs, dat bovenaan staat in de .git -map. Als je dit bestand opent, vind je een overzicht van commit-hashes naar refs:

00f54250cf4e549fdfcafe2cf9a2c90bc3800285 refs/heads/feature 0e25143693cfe9d5c2e83944bbaf6d3c4505eb17 refs/heads/main bb883e4c91c870b5fed88fd36696e752fb6cf8e6 refs/tags/v0.9

Van buitenaf wordt de normale Git-functionaliteit op geen enkele manier beïnvloed. Maar als je je afvraagt waarom je map .git/refs leeg is, is dit de plek waar de refs zijn gebleven.

Special refs


Naast de map met refs zijn er een paar speciale refs in de bovenste .git -map. Ze worden hieronder vermeld:

  • HEAD – De huidige uitgecheckte commit/branch.
  • FETCH_HEAD – De branch die het laatst is opgehaald uit een externe repo.
  • ORIG_HEAD – Een back-upreferentie naar HEAD voordat deze drastisch wordt gewijzigd.
  • MERGE_HEAD – De commit(s) die je samenvoegt in de huidige branch met git merge.
  • CHERRY_PICK_HEAD – De commit voor 'cherry-picking'.

Deze refs worden allemaal aangemaakt en bijgewerkt door Git indien nodig. De opdracht git pull voert bijvoorbeeld eerst git fetch uit, die de referentie FETCH_HEAD bijwerkt. Vervolgens wordt git merge FETCH_HEAD uitgevoerd om het ophalen van de opgehaalde branches naar de repository te voltooien. Natuurlijk kun je deze allemaal gebruiken zoals elke andere ref, zoals je waarschijnlijk ook gedaan hebt met HEAD.

Deze bestanden bevatten verschillende inhoud, afhankelijk van het type en de staat van je repository. De HEAD ref kan ofwel een symbolic ref bevatten, wat gewoon een referentie is naar een andere ref in plaats van een commit-hash, of een commit-hash. Kijk bijvoorbeeld eens naar de inhoud van HEAD als je op de main-branch bent:

git checkout main cat .git/HEAD

Dit levert ref: refs/heads/main op, wat betekent dat HEAD verwijst naar de ref refs/heads/main. Zo weet Git dat de main-branch momenteel wordt bekeken. Als je naar een andere branch zou overstappen, zou de inhoud van HEAD bijgewerkt worden zodat deze overeenkomt met de nieuwe branch. Maar als je een commit zou bekijken in plaats van een branch, dan zou HEAD een commit-hash bevatten in plaats van een symbolic ref. Zo weet Git dat het zich in een afzonderlijke HEAD-toestand bevindt.

HEAD is voor het grootste deel de enige referentie die je rechtstreeks zult gebruiken. De andere zijn over het algemeen alleen nuttig bij het schrijven van scripts op een lager niveau die je in de interne werkzaamheden van Git moet inpassen.

Refspecs


Een refspec koppelt een branch in de lokale repository aan een branch in een externe repository. Hierdoor is het mogelijk om externe branches te beheren met lokale Git-opdrachten en om wat geavanceerd gedrag van git push en git fetch te configureren.

Een refspec is gespecificeerd als [+]<src>:<dst>. De parameter <src> is de bron-branch in de lokale repository, en de parameter <dst> is de doel-branch in de externe repository. Het optionele +-teken is bedoeld om de externe repository te dwingen een niet-'fast-forward' update uit te voeren.

Refspecs kan worden gebruikt in combinatie met de opdracht git push om de externe branch een andere naam te geven. De volgende opdracht pusht bijvoorbeeld de main-branch naar de origin externe repo, zoals bij een gewone git-push, maar deze gebruikt qa-main als de naam voor de branch in de origin repo. Dit is handig voor QA-teams die hun eigen branches naar een externe repo pushen.

git push origin main:refs/heads/qa-main

You can also use refspecs for deleting remote branches. This is a common situation for feature-branch workflows that push the feature branches to a remote repo (e.g., for backup purposes). The remote feature branches still reside in the remote repo after they are deleted from the local repo, so you get a build-up of dead feature branches as your project progresses. You can delete them by pushing a refspec that has an empty parameter, like so:

git push origin :some-feature

Dit is erg handig, want je hoeft niet in te loggen op je externe repository en de externe branch handmatig te verwijderen. Let op dat je vanaf Git v1.7.0 de markering --delete kunt gebruiken in plaats van de bovenstaande methode. Het volgende heeft hetzelfde effect als de bovenstaande opdracht:

git push origin --delete some-feature

Door een paar regels aan het Git-configuratiebestand toe te voegen, kun je refspecs gebruiken om het gedrag van git fetch te wijzigen. Git fetch haalt standaard alle branches op in de externe repository. De reden hiervoor is de volgende sectie van het bestand .git/config :

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/*:refs/remotes/origin/*

De regel fetch vraagt git fetch om alle branches van de origin repo te downloaden. Maar voor sommige workflows zijn ze niet allemaal nodig. Veel workflows voor continue integratie hebben bijvoorbeeld alleen betrekking op de main-branch. Om alleen de main-branch op te halen, wijzig je de fetch-regel zodat deze overeenkomt met het volgende:

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main

Je kunt git push ook op een vergelijkbare manier configureren. Als je bijvoorbeeld altijd de main-branch wilt pushen naar qa-main in de origin remote (zoals we hierboven hebben gedaan), zou je het configuratiebestand wijzigen in:

[remote "origin"] url = https://git@github.com:mary/example-repo.git fetch = +refs/heads/main:refs/remotes/origin/main push = refs/heads/main:refs/heads/qa-main

Refspecs geven je volledige controle over hoe verschillende Git-opdrachten branches tussen repository's overdragen. Ze laten je branches hernoemen en verwijderen uit je lokale repository, naar/vanuit branches met verschillende namen fetchen/pushen, en git push en git fetch configureren zodat je alleen met de branches werkt die je wilt.

Relative refs


Je kunt ook verwijzen naar commits met betrekking tot een andere commit. Met het teken ~ kun je de bovenliggende commits bereiken. Het volgende toont bijvoorbeeld het hoogst gelegen van HEAD:

git show HEAD~2

Maar als je met merge-commits werkt, wordt het wat ingewikkelder. Aangezien merge-commits meer dan één bovenliggende optie hebben, is er meer dan één pad dat je kunt volgen. Bij samenvoegingen in drie richtingen is de eerste bovenliggende afkomstig van de branch waar je actief was toen je de samenvoeging uitvoerde, en de tweede bovenliggende is van de branch die je hebt doorgegeven aan de opdracht git merge.

Het teken ~ volgt altijd de eerste bovenliggende van een merge-commit. Als je een andere bovenliggende wilt volgen, moet je aangeven welke van de bovenliggende het teken ^ heeft. Als HEAD bijvoorbeeld een merge-commit is, geeft het volgende de tweede bovenliggende van HEAD als resultaat.

git show HEAD^2

Je kunt meer dan één ^-teken gebruiken om meer dan één generatie te verplaatsen. Dit toont bijvoorbeeld het hoogst gelegen van HEAD (ervan uitgaand dat het een merge-commit) die berust op de tweede bovenliggende.

git show HEAD^2^1

Om duidelijk te maken hoe ~ en ^ werken, laat de volgende afbeelding zien hoe je een willekeurige commit van A kunt bereiken met behulp van relatieve referenties. In sommige gevallen zijn er meerdere manieren om een commit te bereiken.

Commits gebruiken met behulp van relatieve refs

Relatieve refs kunnen worden gebruikt met dezelfde opdrachten als waarmee een normale ref kan worden gebruikt. Alle volgende opdrachten gebruiken bijvoorbeeld een relatieve referentie:

# Only list commits that are parent of the second parent of a merge commit git log HEAD^2 # Remove the last 3 commits from the current branch git reset HEAD~3 # Interactively rebase the last 3 commits on the current branch git rebase -i HEAD~3

The reflog


De reflog is het vangnet van Git. Deze registreert bijna elke wijziging die je aanbrengt in je repository, ongeacht of je een momentopname hebt gemaakt of niet. Je kunt deze zien als een chronologische geschiedenis van alles wat je in je lokale repo hebt gedaan. Voer de opdracht git reflog uit om de reflog te bekijken. Dit zou iets moeten opleveren dat er als volgt uitziet:

400e4b7 HEAD@{0}: checkout: moving from main to HEAD~2 0e25143 HEAD@{1}: commit (amend): Integrate some awesome feature into `main` 00f5425 HEAD@{2}: commit (merge): Merge branch ';feature'; ad8621a HEAD@{3}: commit: Finish the feature

Dat kan als volgt worden vertaald:

  • Je hebt net HEAD uitgecheckt
  • Daarvoor heb je een commit-bericht gewijzigd
  • Daarvoor heb je de feature-branch samengevoegd met main
  • Daarvoor heb je een momentopname gemaakt

Met de HEAD {}-syntaxis kun je verwijzen naar commits die zijn opgeslagen in de reflog. Het werkt ongeveer zoals de HEAD~-referenties uit de vorige sectie, maar de verwijst naar een item in de reflog in plaats van de commit-geschiedenis.

Je kunt dit gebruiken om terug te keren naar een toestand die anders verloren zou gaan. Laten we bijvoorbeeld zeggen dat je zojuist een nieuwe functie met git reset hebt geschrapt. Dit ziet er ongeveer als volgt uit:

ad8621a HEAD@{0}: reset: moving to HEAD~3 298eb9f HEAD@{1}: commit: Some other commit message bbe9012 HEAD@{2}: commit: Continue the feature 9cb79fa HEAD@{3}: commit: Start a new feature

De drie commits voor de git reset zijn nu losstaand, wat betekent dat er geen manier is om ernaar te verwijzen, behalve via een reflog. Stel dat je je realiseert dat je niet al je werk had moeten weggooien. Het enige wat je hoeft te doen is de HEAD@{1}-commit bekijken om terug te gaan naar de status van je repository voordat je de git reset uitvoerde.

git checkout HEAD@{1}

Dit plaatst de repo in een aparte HEAD-status. Van hieruit kun je een nieuwe branch aanmaken en verder werken aan je functie.

Samenvatting


You should now be quite comfortable referring to commits in a Git repository. We learned how branches and tags were stored as refs in the .git subdirectory, how to read a packed-refs file, how HEAD is represented, how to use refspecs for advanced pushing and fetching, and how to use the relative ~ and ^ operators to traverse a branch hierarchy.

We hebben ook naar de reflog gekeken, een manier om naar commits te verwijzen die op geen enkele andere manier beschikbaar zijn. Dit is een geweldige manier om van situaties te herstellen waar je denkt 'Oeps, had ik dat maar niet gedaan'.

Het doel van dit alles was om precies de commit te kunnen kiezen die je nodig hebt in een bepaald ontwikkelingsscenario. Het is heel eenvoudig om de vaardigheden die je in dit artikel hebt geleerd te gebruiken in combinatie met je bestaande Git-kennis, aangezien sommige van de meest voorkomende opdrachten refs accepteren als argumenten, waaronder git log, git show, git checkout, git reset, git revert, git rebase, en vele anderen.


Deel dit artikel
Volgend onderwerp

Aanbevolen artikelen

Bookmark deze resources voor meer informatie over soorten DevOps-teams of voor voortdurende updates over DevOps bij Atlassian.

Mensen die samenwerken met een muur vol tools

Bitbucket-blog

Toelichting DevOps

DevOps-leertraject

Demo Den Feature-demo's met Atlassian-experts

Hoe Bitbucket Cloud werkt met Atlassian Open DevOps

Meld je aan voor onze DevOps-nieuwsbrief

Thank you for signing up