Git
Chapters ▾ 2nd Edition

10.6 Git Binnenwerk - Uitwisseling protocollen

Uitwisseling protocollen

Git kan op twee belangrijke manieren gegevens uitwisselen tussen twee repositories: het “domme” (dumb) protocol en het “slimme” (smart) protocol. In dit gedeelte zal de manier van werken van beide snel worden besproken.

Het domme protocol

ALs je een repository opzet die alleen gelezen hoeft te worden via HTTP, is het domme protocol het meest waarschijnlijke zijn die zal worden gebruikt. Dit protocol wordt “dom” genoemd omdat het geen Git-specifieke code nodig heeft aan de kant van de server tijdens het uitwisselingsproces; het fetch proces is een reeks van HTTP GET requests, waar het werkstation aannames mag doen over de inrichting van de Git repository op de server.

Note

Het domme protocol wordt tegenwoordig nog maar sporadisch gebruikt. Het is moeilijk te beveiligen of af te schermen, dus de meeste Git hosts (zowel in de cloud als op locatie) zullen het weigeren te gebruiken. Het wordt over het algemeen aangeraden om het slimme protocol te gebruiken, die we iets verderop zullen bespreken.

Laten we het http-fetch proces voor de simplegit library eens volgen:

$ git clone http://server/simplegit-progit.git

Het eerste wat dit commando doet is het info/refs bestand pullen. Dit bestand wordt geschreven door het update-server-info commando, wat de reden is waarom je dit als post-receive hook moet activeren om het uitwisselen via HTTP goed te laten werken:

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

Nu heb je een lijst met de remote referenties en SHA-1 waarden. Vervolgens ga je op zoek naar de HEAD referentie zodat je weet wat je moet uitchecken als je klaar bent:

=> GET HEAD
ref: refs/heads/master

Je moet de master branch uitchecken als je het proces hebt voltooid. Op dit punt ben je gereed om het proces te doorlopen. Omdat je vertrekpunt het ca82a6 commit object is die je in het info/refs bestand zag, begin je met die te fetchen:

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)

Je krijgt een object terug - dat object is in los (loose) formaat op de server, en je hebt het met een statische HTTP GET request gefetched. Je kunt het nu met zlib-uncompress uitpakken, de header ervan afhalen, en naar de commit inhoud kijken:

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700

changed the version number

Vervolgens heb je nog twee objecten op te halen - cfda3b, wat de boom met inhoud is waar de commit die we zojuist opgehaald hebben naar wijst, en 085bb3, wat de ouder-commit is:

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)

En daarmee krijg je je volgende commit object. Haal het boom-object op:

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)

Oeps, het ziet er naar uit dat het boom-object niet in los formaat op de server is, dus je krijgt een 404 antwoord. Hier zijn een aantal mogelijke oorzaken voor - het object kan in een andere repository zitten, of het zou in een packfile in deze repository kunnen zitten. Git controleert eerst of er alternatieven zijn opgegeven:

=> GET objects/info/http-alternates
(empty file)

Dit geeft een lijst met alternatieve URLs terug, Git controleert daar op losse bestanden en packfiles - dit is een aardig mechanisme voor projecten die forks zijn van elkaar zijn om objecten te delen op schijf. Echter, omdat er in dit geval geen alternatieven worden gegeven, moet het object in een packfile zitten. Om te zien welke packfiles er beschikbaar zijn op deze server, moet je het objects/info/packs bestand te pakken krijgen, waar een opsomming hiervan in staat (wat ook door de update-server-info wordt gegenereerd):

=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

Er is maar één packfile op de server, dus het is duidelijk dat jouw object daar in zit, maar je gaat het index bestand toch controleren om er zeker van te zijn. Dit is ook handig als je meerdere packfiles op de server hebt, omdat je dan kan zien welk packfile het object wat je nodig hebt bevat:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)

Nu je de packfile index hebt, kan je kijken of jouw object daar in zit - omdat de index de SHA-1 waarden van de objecten bevat die in de packfile zitten en de relatieve afstand (offset) naar deze objecten. Jouw object is er, dus ga je verder en haalt de hele packfile op:

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)

Je hebt je boom object, dus je gaat verder met je commits af te lopen. Die zitten ook allemaal in de packfile die je zojuist hebt gedownload, dus je hoeft geen verzoeken meer te doen naar de server. Git checkt een werk-kopie van de master branch uit waarnaar werd verwezen door de HEAD referentie die je aan het begin hebt gedownload.

Het slimme protocol

Het domme protocol is eenvoudig, maar een beetje inefficiënt, het kan geen gegevens aan die van het werkstation naar de server moet worden geschreven. Het slimme protocol is een meer gebruikelijke methode van gegevens uit te wisselen, maar het heeft een proces aan de remote kant nodig die op de hoogte is van Git - het moet lokale gegevens kunnen lezen, uitvinden wat het werkstation al heeft en nog nodig heeft, en een op maat gemaakte packfile hiervoor maken. Er zijn twee groepen van processen voor het uitwisselen van gegevens: een paar voor het uploaden van gegevens en een paar voor het downloaden van gegevens.

Het uploaden van gegevens

Om gegevens te uploaden naar een remote proces, gebruikt Git de send-pack en receive-pack processen. Het send-pack proces loopt op het werkstation en maakt verbinding met een receive-pack proces aan de remote kant.

SSH

Bijvoorbeeld, stel dat je git push origin master in jouw project aanroept en origin is als een URL gedefiniëerd die het SSH protocol gebruikt. Git start het send-pack proces op, die over SSH een verbinding initiëert naar je server. Het probeert een commando op de remote server aan te roepen via een SSH call die er ongeveer zo uitziet:

$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1+github-607-gfba4028 delete-refs
003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic
0000

Het git-receive-pack commando antwoordt meteen met een regel voor elke referentie die het op dit moment heeft - in dit geval alleen de master branch en zijn SHA-1. De eerste regel heeft ook een lijst met de mogelijkheden van de server (hier, report-status, delete-refs, en een aantal andere, inclusief de identificatie van de client).

Elke regel begint met een hex waarde van 4 tekens die aangeeft hoe lang de rest van de regel is. Je eerste regel begint met 005b, wat hexadecimaal is voor 91, wat weer inhoudt dat er nog 91 bytes tot die regel behoren. De volgende regel begint met 003e, wat 62 is, dus je leest de overige 62 bytes. De volgende regel is 0000, wat betekent dat de server klaar is met het uitlijsten van de referenties.

Nu dat het de status van de server kent, bepaalt je send-pack proces welke commits het heeft die de server niet heeft. Voor elke referentie die deze push gaat bijwerken, vertelt het send-pack proces het receive-pack proces deze informatie. Bijvoorbeeld, als je de master branch aan het bijwerken bent en een experiment branch toevoegt, zal het antwoord van send-pack er ongeveer zo uitzien:

0085ca82a6dff817ec66f44342007202690a93763949  15027957951b64cf874c3557a0f3547bd83b3ff6 \
	refs/heads/master report-status
00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
	refs/heads/experiment
0000

Git stuurt een regel voor elke referentie die je bijwerkt met de lengte van de regel, de oude SHA-1, de nieuwe SHA-1 en de referentie die wordt geüpdate. De eerste regel bevat ook de mogelijkheden van het werkstation. De SHA-1 waarde van allemaal '0’en houdt in dat er niets daarvoor was - omdat je de referentie van het experiment toevoegt. Als je een referentie aan het verwijderen zou zijn, zou je het omgekeerde zien: alle '0’en aan de rechterkant.

Daarna stuurt het werkstation een packfile van alle objecten die de server nog niet heeft. Als laatste antwoordt de server met een indicatie van succes (of mislukken):

000Aunpack ok
HTTP(S)

Dit proces is voor het grootste gedeelte hetzelfde als over HTTP, al is de handshaking iets anders. De verbinding wordt begonnen met dit verzoek:

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
000000ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master \
	report-status delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000

Dat is het einde van de eerste werkstation-server uitwisseling. Het werkstation stuurt daarna een ander verzoek, dit keer een POST, met de gegevens die worden geleverd door git-upload-pack.

=> POST http://server/simplegit-progit.git/git-receive-pack

Het POST verzoek bevat de uitvoer van send-pack en de packfile als zijn bagage (payload). De server geeft daarna aan of het succesvol of niet heeft verwerkt in zijn HTTP antwoord.

Gegevens downloaden

Als je gegevens download, zijn de fetch-pack en 'upload-pack` processen erbij betrokken. Het werkstation begint een fetch-pack proces die verbinding maakt met een upload-pack proces op de remote kant om te onderhandelen welke gegevens er naar het werkstation zal worden gestuurd.

SSH

Als je de fetch via SSH doet, zal fetch-pack ongeveer dit aanroepen:

$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"

Nadat fetch-pack verbinding heeft gemaakt, stuurt upload-pack zoiets als dit terug:

00dfca82a6dff817ec66f44342007202690a93763949 HEADmulti_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

Dit lijkt op waar receive-pack mee antwoordt, maar de mogelijkheden zijn anders. Daarenboven stuurt het terug waar HEAD op dit moment naar wijst (symref=HEAD:refs/heads/master), zodat het werkstation weet wat het uit moet checken als dit een kloon is.

Op dit punt kijkt het fetch-pack proces naar welke objecten het heeft en antwoordt met de objecten dat het nodig heeft door “want” te sturen en dan de SHA-1 die het wil hebben. Het stuurt alle objecten die het al heeft met “have” en daarna de SHA-1. En aan het einde van deze lijst, schrijft het “done” om het upload-pack proces aan te zetten om te beginnen met het sturen van de packfile met de gegevens die het nodig heeft:

0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0000
0009done
HTTP(S)

De handshake voor een fetch operatie vergt twee HTTP verzoeken. Het eerste is een GET naar hetzelfde adres als gebruikt in het domme protocol:

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
000000e7ca82a6dff817ec66f44342007202690a93763949 HEADmulti_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed no-done symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

Dit lijkt erg op het aanroepen van git-upload-pack over een SSH verbinding, maar de tweede uitwisseling wordt uitgevoerd als een separaat verzoek:

=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000

Wederom, dit volgt hetzelfde patroon als hierboven. Het antwoord op dit verzoek geeft succes of mislukking aan, en bevat de packfile.

Protocolen samenvatting

Deze paragraaf bevat een zeer basale overzicht van de uitwisselings-protocollen. Het protocol omvat vele andere mogelijkheden, zoals multi_ack of side-band mogelijkheden, maar de behandeling hiervan valt buiten het bestek van dit boek. We hebben geprobeerd je een idee te geven van het globale over-en-weer tussen werkstation en server; als je meer kennis nodig hebt hierover, dan kan je bijvoorbeeld een kijkje nemen in de Git broncode.