Om de manier waarop Git brancht echt te begrijpen, moeten we een stap terug doen en onderzoeken hoe Git zijn gegevens opslaat.
Zoals je je misschien herinnert van ch01-getting-started.asc, slaat Git zijn gegevens niet op als een reeks van wijzigingen of delta’s, maar in plaats daarvan als een serie snapshots.
Als je in Git commit, dan slaat Git een commit object op dat een verwijzing bevat naar het snapshot van de inhoud die je gestaged hebt. Dit object bevat ook de naam en mailadres van de auteur, het merge bericht dat ingetypt was, verwijzingen naar de commit of commits die de directe ouders van deze commit waren: nul ouders voor de eerste commit, één ouder voor een normale commit, en meerdere ouders voor een commit die het resultaat is van een merge van twee of meer branches.
Om dit te visualiseren, gaan we aannemen dat je een directory hebt met drie bestanden, en je staget en commit ze allemaal. Door de bestanden te stagen krijgen ze allemaal een checksum (de SHA-1 hash waar we het in ch01-getting-started.asc over hadden), worden die versies de bestanden in het Git repository (Git noemt ze blobs) opgeslagen, en worden die checksums aan de staging area toegevoegd:
$ git add README test.rb LICENSE
$ git commit -m 'initial commit of my project'
Als je de commit aanmaakt door git commit
uit te voeren zal Git iedere directory in het project (in dit geval alleen de root) van een checksum voorzien en slaat ze als boomstructuur (tree
) objecten in de Git repository op.
Daarna creëert Git een commit
object dat de metagegevens bevat en een verwijzing naar de hoofd-tree
-object van het project zodat Git deze snapshot opnieuw kan oproepen als dat nodig is.
Je Git repository bevat nu vijf objecten: drie blobs (een blob voor de inhoud van ieder van de drie bestanden), een tree die de inhoud van de directory weergeeft en specificeert welke bestandsnamen opgeslagen zijn als welke blob, en een commit met de verwijzing naar die hoofd-tree en alle commit-metagegevens.
Als je wat wijzigingen maakt en nogmaals commit, dan slaat de volgende commit een verwijzing op naar de commit die er direct aan vooraf ging.
Een branch in Git is simpelweg een lichtgewicht verplaatsbare verwijzing naar een van deze commits.
De standaard branch-naam in Git is master
.
Als je commits begint te maken, krijg je een master
-branch die wijst naar de laatste commit die je gemaakt hebt.
Iedere keer als je commit, beweegt de pointer van de master
-branch automatisch vooruit.
Note
|
De |
Wat gebeurt er als je een nieuwe branch maakt?
Nou, het aanmaken zorgt ervoor dat er een nieuwe verwijzing (pointer) voor je wordt gemaakt die je heen en weer kan bewegen.
Stel dat je een nieuwe branch maakt en die testing
noemt.
Je doet dit met het git branch
commando:
$ git branch testing
Dit maakt een nieuwe pointer op dezelfde commit als waar je nu op staat.
Hoe weet Git op welke branch je nu zit?
Het houdt een speciale verwijzing bij genaamd HEAD
.
Let op dat dit heel anders is dan het concept van HEAD
in andere VCS’s waar je misschien gewend aan bent, zoals Subversion of CVS.
In Git is dit een verwijzing naar de lokale branch waar je nu op zit.
In dit geval zit je nog steeds op master
.
Het git branch
-commando heeft alleen een nieuwe branch aangemaakt - we zijn nog niet overgeschakeld naar die branch.
Je kunt dit simpelweg zien door een eenvoudige git log
commando uit te voeren wat je laat zien waar de branch pointers naar verwijzen.
Deze optie heet --decorate
.
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project
Je kunt de master'' en
testing'' branches zien die direct naast de f30ab
commit staan.
Om te schakelen (switch) naar een bestaande branch, kan je het git checkout
commando gebruiken.
Laten we eens switchen naar de nieuwe testing
-branch:
$ git checkout testing
Dit vertelt HEAD
om te verwijzen naar de testing
-branch.
Wat is daar nu belangrijk aan? Nou, laten we nog eens een commit doen:
$ vim test.rb
$ git commit -a -m 'made a change'
Dit is interessant: omdat je testing
-branch nu naar voren is bewogen, maar je master branch nog steeds op het punt staat waar je was toen je git checkout
uitvoerde om van branch te switchen.
Laten we eens terug switchen naar de master
-branch:
$ git checkout master
Dat commando deed twee dingen.
Het verplaatste de HEAD pointer terug om te verwijzen naar de master
-branch, en het draaide de bestanden terug in je werk directory naar de stand van de snapshot waar master
naar verwijst.
Dit betekent ook dat de wijzigingen die je vanaf nu maakt zullen afwijken van een oudere versie van het project.
In essentie draait dit het werk terug dat je in je testing
-branch gedaan hebt zodat je in een andere richting kunt bewegen.
Note
|
Branches switchen wijzigt bestanden in je directory
Het is belangrijk op te merken dat wanneer je tussen branches switcht in Git, bestanden in je werk directory zullen wijzigen. Als je naar een oudere branch switcht, zal je werk directory teruggedraaid worden zodat de inhoud gelijk is aan hoehet eruit zag toen je voor het laatst committe op die branch. Als Git dat niet op een nette manier kan doen, zal het je niet laten switchen. |
Laten we een paar wijzigingen aanbrengen en opnieuw committen:
$ vim test.rb
$ git commit -a -m 'made other changes'
Nu is je project historie uiteengelopen (zie Uiteengelopen histories).
Je hebt een branch gemaakt en bent er naartoe overgeschakeld, hebt er wat werk op gedaan, en bent toen teruggeschakeld naar je hoofd-branch en hebt nog wat ander werk gedaan.
Al die veranderingen zijn geïsoleerd van elkaar in aparte branches: je kunt heen en weer schakelen tussen de branches en ze mergen als je klaar bent.
En je hebt dat alles gedaan met eenvoudige branch
, checkout
en commit
commando’s.
Je kunt dit ook eenvoudig zien met het git log
commando.
Als je git log --oneline --decorate --graph --all
uitvoert zal het de historie van jouw commits afdrukken, laten zien waar jouw branch pointers zijn en hoe je historie uiteengelopen is.
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project
Omdat een branch in Git in werkelijkheid een eenvoudig bestand is dat de SHA-1 checksum van 40 karakters bevat van de commit waar het naar verwijst, zijn branches goedkoop om te maken en te verwijderen. Een nieuwe branch maken is net zo snel en simpel te maken als 41 bytes naar een bestand te schrijven (40 karakters en een newline).
Dit is in schril contrast met de manier waarop de meeste oudere VCS applicaties branchen, wat het kopiëren van alle bestanden van het project in een tweede directory inhoudt. Dit kan een aantal seconden of zelfs minuten duren, afhankelijk van de grootte van het project. Dit terwijl bij Git het proces altijd onmiddelijk gereed is. Daarbij komt dat, omdat we de ouders bijhouden als we een commit maken, het vinden van een goede merge basis voor het merge proces automatisch voor ons gedaan is en dit doorgaans eenvoudig te doen is. Deze kenmerken helpen ontwikkelaars aan te moedigen om vaak en veel branches aan te maken.
Laten we eens kijken waarom je dat zou moeten doen.
Note
|
Het maken van een nieuwe branch en tegelijkertijd ernaar switchen
Het is vrij normaal om een nieuwe branch te maken en dan meteen naar die nieuwe branch te switchen. Dit kan worden gedaan in een operatie met |