Chapters ▾ 2nd Edition

10.6 Git bakom kulisserna - Överföringsprotokoll

Överföringsprotokoll

Git kan överföra data mellan två kodförråd på två huvudsakliga sätt: det “dumma” protokollet och det “smarta” protokollet. Detta avsnitt går snabbt igenom hur de två huvudprotokollen fungerar.

Det dumma protokollet

Om du sätter upp ett kodförråd som ska serveras skrivskyddat över HTTP är det dumma protokollet troligen det som används. Detta protokoll kallas “dumt” eftersom det inte kräver någon Git‑specifik kod på serversidan under överföringsprocessen; hämtningen är en serie HTTP‑GET‑förfrågningar där klienten kan anta layouten för Git‑kodförrådet på servern.

Notera

Det dumma protokollet används ganska sällan nuförtiden. Det är svårt att säkra eller göra privat, så de flesta Git‑värdar (både molnbaserade och lokala) vägrar använda det. Generellt rekommenderas det smarta protokollet, som vi beskriver lite längre fram.

Låt oss följa http-fetch‑processen för biblioteket simplegit:

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

Det första kommandot gör är att hämta filen info/refs. Denna fil skrivs av kommandot update-server-info, vilket är anledningen till att du måste aktivera det som en post-receive‑krok för att HTTP‑transporten ska fungera korrekt:

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

Nu har du en lista över fjärrreferenser och SHA‑1‑värden. Nästa steg är att leta efter vad HEAD‑referensen är, så att du vet vad du ska växla till när du är klar:

=> GET HEAD
ref: refs/heads/master

Du behöver växla till grenen master när du har slutfört processen. Vid det här laget är du redo att börja vandringsprocessen. Eftersom din startpunkt är incheckningsobjektet ca82a6 som du såg i filen info/refs börjar du med att hämta det:

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

Du får tillbaka ett objekt – det objektet är i löst format på servern och du hämtade det via en statisk HTTP GET‑förfrågan. Du kan zlib‑dekomprimera det, ta bort headern och titta på incheckningsinnehållet:

$ 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

Change version number

Härnäst har du två objekt till att hämta – cfda3b, som är innehållsträdet som incheckningen vi just hämtade pekar på, och 085bb3, som är föräldraincheckningen:

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

Det ger dig nästa incheckningsobjekt. Hämta trädobjektet:

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

Oj – det ser ut som att trädobjektet inte ligger i löst format på servern, så du får ett 404‑svar tillbaka. Det finns ett par skäl till detta – objektet kan ligga i ett alternativt kodförråd, eller så kan det ligga i en packfil i detta kodförråd. Git kontrollerar först om några alternativ listats:

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

Om detta kommer tillbaka med en lista över alternativa URL:er letar Git efter lösa filer och packfiler där – det är en fin mekanism för projekt som är avgreningar av varandra att dela objekt på disk. Men eftersom inga alternativ är listade i detta fall måste objektet ligga i en packfil. För att se vilka packfiler som finns på servern behöver du hämta filen objects/info/packs, som innehåller en lista över dem (också genererad av update-server-info):

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

Det finns bara en packfil på servern, så objektet ligger uppenbart där, men du kontrollerar indexfilen för att vara säker. Det är också användbart om du har flera packfiler på servern, så att du kan se vilken packfil som innehåller objektet du behöver:

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

Nu när du har packfilindexet kan du se om objektet finns i det – eftersom indexet listar SHA‑1:orna för objekten som finns i packfilen och offsetarna till dessa objekt. Ditt objekt finns där, så hämta hela packfilen:

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

Du har ditt trädobjekt, så du fortsätter vandra i dina incheckningar. De finns alla också i packfilen du just laddade ner, så du behöver inte göra fler förfrågningar till servern. Git skapar en arbetskopia av grenen master som pekades ut av HEAD‑referensen du laddade ner i början.

Det smarta protokollet

Det dumma protokollet är enkelt men lite ineffektivt, och det kan inte hantera skrivning av data från klienten till servern. Det smarta protokollet är en vanligare metod för dataöverföring, men det kräver en process på fjärränden som är tillräckligt Git‑medveten – den kan läsa lokal data, lista ut vad klienten har och behöver och generera en anpassad packfil åt den. Det finns två uppsättningar processer för att överföra data: ett par för att ladda upp och ett par för att ladda ner.

Ladda upp data

För att ladda upp data till en fjärrprocess använder Git processerna send-pack och receive-pack. Processen send-pack körs på klienten och ansluter till processen receive-pack på fjärrsidan.

SSH

Anta till exempel att du kör git push origin master i ditt projekt och att origin är definierad som en URL som använder SSH‑protokollet. Git startar processen send-pack, som initierar en anslutning över SSH till din server. Den försöker köra ett kommando på fjärrservern via ett SSH‑anrop som ser ut ungefär så här:

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

Kommandot git-receive-pack svarar omedelbart med en rad för varje referens det har för närvarande – i det här fallet bara grenen master och dess SHA‑1. Den första raden innehåller också en lista över serverns kapabiliteter (här report-status, delete-refs och några andra, inklusive klientidentifieraren).

Data överförs i block. Varje block börjar med ett 4‑teckens hexvärde som anger hur långt blocket är (inklusive de 4 byte som anger längden). Block innehåller vanligtvis en enda rad data och en avslutande radmatning. Ditt första block börjar med 00a5, som är hexadecimalt för 165, vilket betyder att blocket är 165 byte långt. Nästa block är 0000, vilket betyder att servern är klar med sin referenslista.

Nu när den känner till serverns tillstånd avgör din send-pack‑process vilka incheckningar den har som servern inte har. För varje referens som den här uppskickningen ska uppdatera berättar send-pack‑processen för receive-pack‑processen den informationen. Om du till exempel uppdaterar grenen master och lägger till en gren experiment kan send-pack‑svaret se ut ungefär så här:

0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \
	refs/heads/master report-status
006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
	refs/heads/experiment
0000

Git skickar en rad för varje referens du uppdaterar med radens längd, den gamla SHA‑1:an, den nya SHA‑1:an och referensen som uppdateras. Den första raden innehåller också klientens kapabiliteter. SHA‑1‑värdet med bara nollor betyder att inget fanns där tidigare – eftersom du lägger till referensen experiment. Om du raderade en referens skulle du se det motsatta: bara nollor på högersidan.

Härnäst skickar klienten en packfil med alla objekt som servern ännu inte har. Till sist svarar servern med en indikation om framgång (eller misslyckande):

000eunpack ok
HTTP(S)

Denna process är i stort sett densamma över HTTP, även om handskakningen är lite annorlunda. Anslutningen initieras med denna förfrågan:

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 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

Det här är slutet på det första klient‑server‑utbytet. Klienten gör sedan ytterligare en förfrågan, den här gången en POST, med data som send-pack tillhandahåller.

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

POST‑förfrågan inkluderar send-pack‑utdata och packfilen som nyttolast. Servern indikerar sedan framgång eller misslyckande med sitt HTTP‑svar.

Tänk på att HTTP‑protokollet kan kapsla in denna data ytterligare i en styckad överföringskodning.

Ladda ner data

När du laddar ner data är processerna fetch-pack och upload-pack inblandade. Klienten initierar en fetch-pack‑process som ansluter till en upload-pack‑process på fjärrsidan för att förhandla om vilken data som ska överföras ned.

SSH

Om du hämtar över SSH kör fetch-pack något i stil med detta:

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

Efter att fetch-pack ansluter skickar upload-pack tillbaka något i stil med detta:

00dfca82a6dff817ec66f44342007202690a93763949 HEAD□multi_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
003fe2409a098dc3e53539a9028a94b6224db9d6a6b6 refs/heads/master
0000

Det här är mycket likt vad receive-pack svarar med, men kapabiliteterna är annorlunda. Dessutom skickar det tillbaka vad HEAD pekar på (symref=HEAD:refs/heads/master) så att klienten vet vad som ska läggas ut om detta är en klon.

Vid det här laget tittar fetch-pack‑processen på vilka objekt den har och svarar med de objekt den behöver genom att skicka “want” och sedan SHA‑1:an den vill ha. Den skickar alla objekt den redan har med “have” och sedan SHA‑1:an. I slutet av listan skriver den “done” för att initiera upload-pack‑processen att börja skicka packfilen med den data den behöver:

003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000
HTTP(S)

Handskakningen för en uppdatering‑operation kräver två HTTP‑förfrågningar. Den första är en GET till samma slutpunkt som används i det dumma protokollet:

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD□multi_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

Det här är mycket likt att anropa git-upload-pack över en SSH‑anslutning, men det andra utbytet görs som en separat förfrågan:

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

Återigen är detta samma format som ovan. Svaret på denna förfrågan indikerar framgång eller misslyckande och inkluderar packfilen.

Sammanfattning av protokollen

Detta avsnitt innehåller en mycket grundläggande översikt av överföringsprotokollen. Protokollet innehåller många andra funktioner, som kapabiliteterna multi_ack eller side-band, men att täcka dem ligger utanför den här bokens omfattning. Vi har försökt ge dig en känsla för det allmänna fram‑och‑tillbaka mellan klient och server; om du behöver mer kunskap än detta vill du troligen ta en titt på Gits källkod.