Git
Chapters ▾ 2nd Edition

10.6 Git Interna - Transfer Protokolle

Transfer Protokolle

Git kann Daten zwischen zwei Repositorys hauptsächlich auf zwei Arten übertragen: mittels dem „dummen“ Protokoll und dem „intelligenten“ Protokoll. In diesem Abschnitt wird kurz erläutert, wie diese beiden Hauptprotokolle funktionieren.

Das dumme Protokoll

Wenn du ein Repository einrichtest, das schreibgeschützt über HTTP bereitgestellt wird, wird wahrscheinlich das dumme Protokoll verwendet. Dieses Protokoll wird als „dumm“ bezeichnet, da es während des Transportvorgangs keinen Git-spezifischen Code auf der Serverseite erfordert. Der Abrufprozess besteht aus einer Reihe von HTTP GET Abfragen, bei denen der Client das Layout des Git-Repositorys auf dem Server übernehmen kann.

Anmerkung

Das dumme Protokoll wird heutzutage ziemlich selten verwendet. Es ist schwierig, es sicher oder privat einzurichten, daher lehnen die meisten Git-Hosts (sowohl cloudbasiert als auch on-premise) die Verwendung ab. Es wird generell empfohlen, das smarte Protokoll zu verwenden, welches wir weiter unten beschreiben.

Folgen wir dem http-fetch Prozess für die simplegit-Bibliothek:

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

Das erste, was dieser Befehl tut, ist das pullen der Datei info/refs. Diese Datei wird mit dem Befehl update-server-info geschrieben. Aus diesem Grund musst du diesen als post-receive Hook aktivieren, damit der HTTP-Transport ordnungsgemäß funktioniert:

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

Jetzt hast du eine Liste der remote Referenzen und der SHA-1s. Als Nächstes suchst du nach der HEAD-Referenz, damit du weißt, was du abschließend überprüfen musst:

=> GET HEAD
ref: refs/heads/master

Du musst den 'master'-Branch auschecken, wenn du den Vorgang abgeschlossen hast. Jetzt kannst du mit dem laufenden Prozess beginnen. Da dein Ausgangspunkt das Commit-Objekt ca82a6 ist, das du in der Datei info/refs gesehen hast, rufst du zunächst Folgendes ab:

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

Du erhältst ein Objekt zurück – das Objekt befindet sich in losem Format auf dem Server und du hast es über einen statischen HTTP-GET-Aufruf abgerufen. Du kannst es zlib-dekomprimieren, den Header entfernen und den Commit-Inhalt anzeigen:

$ 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

Als nächstes musst du zwei weitere Objekte abrufen – cfda3b, das ist der Inhaltsbaum, auf den das gerade abgerufene Commit verweist; und 085bb3, welches das übergeordnete Commit ist:

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

Damit hast du dein nächstes Commit-Objekt. Hole dir nun das Baumobjekt:

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

Hoppla – es sieht so aus, als ob das Baum-Objekt auf dem Server nicht im losen Format vorliegt, sodass du eine 404-Antwort erhältst. Dafür gibt es mehrere Gründe: Das Objekt befindet sich möglicherweise in einem alternativen Repository oder in einemr Paketdatei (engl. packfile) in diesem Repository. Git sucht zuerst nach allen gelisteten Alternativen:

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

Falls dies eine Liste alternativer URLs zurückgibt, sucht Git dort nach losen Dateien und packfiles. Dies ist ein nützlicher Mechanismus für Projekte, die sich gegenseitig forken, um Objekte auf der Festplatte zu teilen. Da in diesem Fall jedoch keine Alternativen aufgeführt sind, muss sich dein Objekt in einem packfile befinden. Um zu sehen, welche packfiles auf diesem Server verfügbar sind, musst du die Datei objects/info/packs abrufen, die eine Liste dieser Dateien enthält (dies wird ebenfalls mittels update-server-info generiert):

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

Es gibt nur ein packfile auf dem Server, sodass sich dein Objekt offensichtlich dort befindet. Überprüfe jedoch den Index, um dies auch sicherzustellen. Dies ist ebenfalls nützlich, wenn sich mehrere packfiles auf dem Server befinden. Du kannst so prüfen, welches packfile das gewünschte Objekt enthält:

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

Nachdem du nun den packfile-Index hast, kannst du sehen, ob sich dein Objekt darin befindet. Der Index listet die SHA-1-Werte der in dem packfile enthaltenen Objekte und die Offsets zu diesen Objekten. Dein Objekt ist vorhanden, also holen dir das ganze packfile:

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

Nun hast du dein Baumobjekt und kannst deine Commits weiter durchgehen. Sie befinden sich alle in dem gerade heruntergeladenen packfile, sodass du keine weiteren Anfragen an deinen Server stellen musst. Git checkt eine Arbeitskopie des master Branches aus, auf die in der HEAD-Referenz verwiesen wurde, die du zu Beginn heruntergeladen hast.

Das smarte Protokoll

Das dumme Protokoll ist einfach, aber ein bisschen ineffizient. Es kann keine Daten vom Client auf den Server schreiben. Das Smart-Protokoll wird oft zum Übertragen von Daten genutzt. Es erfordert jedoch einen intelligenten Git-Prozess auf der Remote-Seite. Es kann lokale Daten lesen, herausfinden, was der Client hat und benötigt, und eine benutzerdefiniertes packfile dafür generieren. Es gibt zwei Arten von Prozessen um Daten zu übertragen: zwei zum Hochladen von Daten und zwei zum Herunterladen von Daten.

Daten hochladen

Um Daten remote hochzuladen, verwendet Git die Prozesse send-pack und receive-pack. Der send-pack Prozess wird auf dem Client ausgeführt und stellt eine Verbindung zu einem receive-pack Prozess auf der Remote-Seite her.

SSH

Angenommen, du führst in deinem Projekt git push origin master aus, und origin ist als URL definiert, die das SSH-Protokoll verwendet. Git startet den send-pack Prozess, der eine Verbindung über SSH zu deinem Server aufbaut. Es wird versucht, einen Befehl auf dem Remote-Server über einen SSH-Aufruf auszuführen, der in etwa so aussieht:

$ 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

Der Befehl git-receive-pack antwortet sofort mit einer Zeile für jede Referenz, die er derzeit hat – in diesem Fall nur den master Branch und seinen SHA-1. Die erste Zeile enthält auch eine Liste der Serverfunktionen (hier report-status, delete-refs und einige andere, einschließlich der Client-ID).

Die Daten werden in Paketen (eng. chunks) übertragen. Jeder Chunk beginnt mit einem 4-stelligen Hex-Wert, der angibt, wie lang der Chunk ist (einschließlich der 4 Bytes der Gesamtlänge). Die Pakete enthalten in der Regel eine einzige Zeile mit Daten und einen abschließenden Zeilenvorschub. Dein erster Chunk beginnt mit 00a5, also hexadezimal für 165, womit das Paket 165 Bytes lang ist. Der nächste Chunk ist 0000, d.h. der Server ist mit seiner Referenzliste fertig.

Jetzt, da der Server-Status bekannt ist, bestimmt dein send-pack Prozess, welche Commits er hat, die der Server nicht hat. Für jede Referenz, die durch diesen Push aktualisiert wird, teilt der send-pack Prozess dem receive-pack Prozess diese Informationen mit. Wenn du beispielsweise den master Branch aktualisieren und einen experiment Branch hinzufügst, sieht die Antwort von send-pack möglicherweise so aus:

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

Git sendet eine Zeile für jede Referenz, die du aktualisierst, mit der Länge der Zeile, dem alten SHA-1, dem neuen SHA-1 und der Referenz, die aktualisiert wird. Die erste Zeile enthält auch die Funktionen des Clients. Der SHA-1-Wert aller Nullen bedeutet, dass zuvor nichts vorhanden war, da du die experiment-Referenz hinzufügst. Wenn du eine Referenz löschst, siehst du das Gegenteil: Alle Nullen auf der rechten Seite.

Als nächstes sendet der Client ein packfile mit allen Objekten, die der Server noch nicht hat. Schließlich antwortet der Server mit einer Erfolgs- oder Fehlermeldung:

000eunpack ok
HTTP(S)

Der Prozess über HTTP ist fast derselbe, nur das Handshaking ist ein wenig anders. Die Verbindung wird mit folgender Anfrage initiiert:

=> 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

Das ist das Ende der ersten Client-Server-Unterhaltung. Der Client stellt dann eine weitere Anfrage, diesmal einen POST, mit den Daten, die send-pack liefert.

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

Die POST Abfrage enthält die send-pack Ausgabe und das packfile als Nutzdaten. Der Server zeigt dann mit seiner HTTP-Antwort Erfolg oder Fehler an.

Denke daran, dass das HTTP-Protokoll diese Daten möglicherweise zusätzlich in einen „chunked transfer“ Code verpackt.

Daten herunterladen

Wenn du Daten herunterlädst, sind die Prozesse fetch-pack und upload-pack beteiligt. Der Client initiiert einen fetch-Pack Prozess, der eine Verbindung zu einem upload-pack Prozess auf der Remote-Seite herstellt, um auszuhandeln, welche Daten nach übertragen werden sollen.

SSH

Wenn du den Abruf über SSH ausführst, führt fetch-pack in etwa Folgendes aus:

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

Nachdem fetch-pack eine Verbindung hergestellt hat, sendet upload-pack in etwas Folgendes zurück:

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

Dies ist sehr ähnlich zu dem, was receive-pack antwortet, aber die Einsatzmöglichkeiten sind unterschiedlich. Außerdem wird zurückgesendet, worauf HEAD verweist (symref=HEAD:refs/heads/master), sodass der Client weiß, was er überprüfen muss, wenn es sich um einen Klon handelt.

Zu diesem Punkt prüft der fetch-pack Prozess, über welche Objekte er verfügt, und antwortet mit den Objekten, die er benötigt, indem er „want“ und dann den gewünschten SHA-1 sendet. Es sendet alle Objekte, die es bereits hat, mit „have“ und dann den SHA-1. Am Ende dieser Liste wird „done“ geschrieben, um den `upload-pack“-Prozess einzuleiten, der das packfile mit den benötigten Daten sendet:

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

Der Handshake für einen Abrufvorgang benötigt zwei HTTP Aufrufe. Das erste ist ein GET zum selben Endpunkt, der im dummen Protokoll verwendet wird:

=> 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

Dies ist dem Aufrufen von git-upload-pack über eine SSH-Verbindung sehr ähnlich. Der zweite Austausch wird als separater Aufruf ausgeführt:

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

Auch dies ist das gleiche Format wie oben. Die Antwort auf diese Anfrage zeigt Erfolg oder Fehler an und enthält das packfile.

Zusammenfassung der Protokolle

Dieser Abschnitt enthält eine sehr grundlegende Übersicht über die Transfer Protokolle. Das Protokoll enthält viele andere Funktionen, wie z.B. multi_ack oder side-band, die jedoch nicht in diesem Buch behandelt werden. Wir haben versucht, dir ein Gefühl für das allgemeine Hin und Her zwischen Client und Server zu vermitteln. Wenn du mehr Informationen diesbezüglich benötigst, solltest du dir den Git-Quellcode ansehen.

scroll-to-top