Overdracht protocollen
Git kan gegevens tussen twee repositories hoofdzakelijk overdragen op twee manieren: via HTTP en via de zogenaamde slimme protocollen die in de file://
, ssh://
en git://
overdrachten gebruikt worden. Deze sectie zal laten zien hoe deze hoofdprotocollen werken.
Het domme Protocol
Naar Git overdracht over HTTP wordt vaak gerefereerd als het domme protocol, omdat het geen Git-specifieke code vereist op de server gedurende het overdrachtsproces. Het fetch proces is een reeks van GET verzoeken, waarbij de client de opmaak van het Git repository van de server kan overnemen. Laten we het http-fetch
proces eens volgen voor de simplegit bibliotheek:
$ git clone http://github.com/schacon/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, en dat is de reden waarom je dat als een post-recieve
haak in moet stellen voordat de HTTP overdracht naar behoren werkt:
=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
Nu heb je een lijst met de remote referenties en SHA’s. Daarna kijk je naar wat de HEAD referentie is, zodat je weet wat je uit moet checken zodra je klaar bent:
=> GET HEAD
ref: refs/heads/master
Je moet de master
branch uitchecken zodra je het proces afgerond hebt. Op dit punt ben je klaar om het doorloop proces te starten. Omdat je startpunt het ca82a6
commit object is dat je in het info/refs
bestand zag, begin je met dit op te halen:
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)
Je krijgt een object terug – dat object staat in los formaat op de server, en je hebt het gehaald door een statisch HTTP GET verzoek. Je kunt het met zlib decomprimeren, de kop eraf halen, 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
Daarna heb je nog twee objecten op te halen – cfda3b
, wat de boom is met inhoud waar de commit die je zojuist hebt opgehaald naar wijst; en 085bb3
, wat de ouder commit is:
=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)
Dat geeft je je volgende commit object. Pak het boom object:
=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)
Oops – het ziet ernaar uit dat dat boom object niet in het losse formaat op de server bestaat, dus krijg je een 404 antwoord. Er zijn hiervoor een aantal redenen – het object kan in een ander repository staat, of het kan in een packfile in dit repository staat. Git gaat eerst naar de genoemde alternatieven kijken:
=> GET objects/info/http-alternates
(empty file)
Als dit een lijst met alternatieve URL’s bevat, zal Git daar voor losse bestanden en packfiles gaan kijken – dit is een fijn mechanisme voor projecten die forks zijn van een ander zodat ze objecten kunnen delen op de schijf. Maar, omdat er in dit geval geen alternatieven vermeld staan, moet je object in een packfile zitten. Om te zien welke packfiles beschikbaar zijn op deze server, moet je het objects/info/packs
bestand halen, wat een lijst hiervan bevat (ook gegenereerd door update-server-info
):
=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
Er is slechts één packfile op de server, dus je object zit daar natuurlijk in, maar je bekijkt het index bestand om het zeker te weten. Dit is ook handig als je meerdere packfiles op de server hebt, zodat je kunt zien welke packfile het object dat je nodig hebt bevat:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)
Nu dat je de packfile index hebt, kun je zien of je object hier in zit – omdat de index de SHA’s van de objecten in de packfile toont en de offset naar die objecten. Je object is aanwezig, dus ga ervoor en haal de hele packfile op:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)
Je hebt je boom object, dus je kunt verder gaan met het doorlopen van je commits. Ze zitten ook allemaal in de packfile die je zojuist gedownload hebt, dus je hoeft geen verzoeken meer te doen aan je server. Git checked een werkkopie uit van de master
branch waarnaar gewezen werd door de HEAD referentie, die je aan het begin gedownload hebt.
Het gehele uitvoer van dit proces ziet er zo uit:
$ git clone http://github.com/schacon/simplegit-progit.git
Initialized empty Git repository in /private/tmp/simplegit-progit/.git/
got ca82a6dff817ec66f44342007202690a93763949
walk ca82a6dff817ec66f44342007202690a93763949
got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Getting alternates list for http://github.com/schacon/simplegit-progit.git
Getting pack list for http://github.com/schacon/simplegit-progit.git
Getting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835
Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835
which contains cfda3bf379e4f8dba8717dee55aab78aef7f4daf
walk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
walk a11bef06a3f659402fe7563abf99ad00de2209e6
Het slimme protocol
De HTTP methode is eenvoudig, maar een beetje inefficiënt. Slimme protocollen gebruiken is een meer gebruikte manier van gegevensoverdracht. Deze protocollen hebben een proces aan de remote kant dat bewust is van Git – het kan lokale gegevens lezen en uitvinden wat de client heeft, of nodig heeft en hier eigen gegevens voor genereren. Er zijn twee sets processen voor gegevensoverdracht: een paar voor het uploaden van gegevens, en een paar voor het downloaden van gegevens.
Gegevens uploaden
Om gegevens te uploaden naar een remote proces, gebruikt Git de send-pack
en receive-pack
processen. Het send-pack
proces draait op de client en maakt contact met een receive-pack
proces aan de remote kant.
Bijvoorbeeld, stel dat je git push origin master
uitvoert in je project, en origin
is gedefinieerd als een URL dat het SSH protocol gebruikt. Git start het send-pack
proces, wat een verbinding initieert, via SSH, naar je server. Het probeert een commando op de remote server uit te voeren met behulp van een SSH aanroep die er ongeveer zo uit ziet:
$ ssh -x git@github.com "git-receive-pack 'schacon/simplegit-progit.git'"
005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status delete-refs
003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic
0000
Het git-receive-pack
commando antwoord onmiddellijk met één regel voor iedere referentie die het momenteel heeft – in dit geval alleen de master
branch en zijn SHA. De eerste regel bevat ook een lijst van de mogelijkheden van de server (in dit geval, report-status
en delete-refs
).
Iedere regel begint met een hexadecimale waarde van 4 bytes, die specificeert hoe lang de rest van de regel is. Je eerste regel begint met 005b, wat 91 in hex is, wat betekend dat er nog 91 bytes over zijn op deze regel. De volgende regel begint met 003e, wat 62 is, zodat je de overgebleven 62 bytes leest. De volgende regel is 0000, wat betekent dat de server klaar is met het tonen van zijn referenties.
Nu dat het de status van de server weet, bepaalt je send-pack
proces welke commits dat het heeft, die de server nog niet heeft. Voor iedere referentie die deze push zal vernieuwen, verteld het send-pack
het receive-pack
proces die informatie. Bijvoorbeeld, als je de master
branch vernieuwt en een experiment
branch toevoegt, zou het send-pack
antwoord er zo uit kunnen zien:
0085ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status
00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment
0000
Een SHA-1 waarde met alleen ‘0’ betekent dat er nog niets was – omdat je de experiment referentie toevoegt. Als je een referentie aan het verwijderen was, zou je het tegenovergestelde zien: allemaal ‘0’ aan de rechterkant.
Git stuurt een regel voor iedere referentie die je vernieuwt, met de oude SHA, de nieuwe SHA en de referentie die vernieuwd wordt. De eerste regel bevat ook de mogelijkheden van de client. Vervolgens upload de client een packfile met alle objecten die de server nog niet heeft. Als laatste antwoord de server met een succes (of mislukking) indicatie:
000Aunpack ok
Gegevens downloaden
Op het moment dat je gegevens download zijn de fetch-pack
en upload-pack
processen betrokken. De client start een fetch-pack
proces dat verbinding maakt met een upload-pack
proces aan de remote kant om te onderhandelen welke gegevens gestuurd moeten worden.
Er zijn verschillende manieren om het upload-pack
proces op de remote repository te starten. Je kunt het uitvoeren via SSH, op dezelfde manier als het receive-pack
proces. Je kunt het proces ook starten via de Git daemon, die standaard op poort 9418 luistert. Het fetch-pack
proces stuurt gegevens, die er zo uitzien voor de daemon na het maken van de verbinding:
003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0
Het begint met de 4 bytes die specificeren hoeveel gegevens er volgen, daarna het commando gevolgd door een null byte, en dan de hostname van de server gevolgd door een laatste null byte. De Git daemon bekijkt of dat commando uitgevoerd kan worden, dat het repository bestaat en dat het publieke permissies heeft. Als alles OK is, dan start het het upload-pack
proces en geeft hier het verzoek aan door.
Als je de fetch via SSH dper, voert het fetch-pack
in plaats daarvan zoiets als dit uit:
$ ssh -x git@github.com "git-upload-pack 'schacon/simplegit-progit.git'"
In beide gevallen stuurt upload-pack
, nadat fetch-pack
verbinding gemaakt heeft, zoiets als dit:
0088ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_ack thin-pack \
side-band side-band-64k ofs-delta shallow no-progress include-tag
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
003e085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 refs/heads/topic
0000
Dit komt erg overeen met waarmee receive-pack
antwoord, maar de mogelijkheden zijn verschillend. Daarnaast stuurt het de HEAD referentie zodat de client weet wat er uitgechecked moet worden als dit een clone is.
Op dit punt kijkt het fetch-pack
proces naar welk objecten dat het heeft en antwoord met de objecten die het nodig heeft door “want” te sturen, gevolgd door de SHA die het wil. Het stuurt al de objecten die het al heeft met “have” en dan de SHA. Aan het einde van deze lijst, schrijft het “done” om het upload-pack
proces te starten met het sturen van de packfile van de gegevens die het nodig heeft:
0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0000
0009done
Dat is een basaal geval van de overdrachtsprotocollen. In meer complexe gevallen ondersteunt de client multi_ack
of side-band
mogelijkheden; maar dit voorbeeld toont je de basale heen en weer gang die gebruikt wordt door de slimme protocol processen.