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 Sie ein Repository einrichten, 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.

Note

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 müssen Sie diesen als post-receive-Hook aktivieren, damit der HTTP-Transport ordnungsgemäß funktioniert:

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

Jetzt haben Sie eine Liste der remote Referenzen und der SHA-1s. Als Nächstes suchen Sie nach der HEAD-Referenz, damit Sie wissen, was Sie abschließend überprüfen müssen:

=> GET HEAD
ref: refs/heads/master

Sie müssen den master-Branch auschecken, wenn Sie den Vorgang abgeschlossen haben. Jetzt können Sie mit dem laufenden Prozess beginnen. Da Ihr Ausgangspunkt das Commit-Objekt ca82a6 ist, das Sie in der Datei info/refs gesehen haben, rufen Sie zunächst Folgendes ab:

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

Sie erhalten ein Objekt zurück – das Objekt befindet sich in losem Format auf dem Server und Sie haben es über einen statischen HTTP-GET-Aufruf abgerufen. Sie können 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

changed the version number

Als nächstes müssen Sie 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 haben Sie Ihr nächstes Commit-Objekt. Holen Sie sich 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 Sie eine 404-Antwort erhalten. Dafür gibt es mehrere Gründe: Das Objekt befindet sich möglicherweise in einem alternativen Repository oder in einem packfile Paketdatei in diesem Repository. Git sucht zuerst nach allen gelisteten Alternativen:

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

Wenn dies mit einer 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 Ihr Objekt in einem packfile befinden. Um zu sehen, welche packfiles auf diesem Server verfügbar sind, müssen Sie 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 Ihr Objekt offensichtlich dort befindet. Überprüfen Sie jedoch den Index, um dies auch sicherzustellen. Dies ist ebenfalls nützlich, wenn sich mehrere packfiles auf dem Server befinden. Sie können so prüfen, welches packfile das gewünschte Objekt enthält:

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

Nachdem Sie nun den packfile-Index haben, können Sie sehen, ob sich Ihr Objekt darin befindet. Da Index listet die SHA-1-Werte der in dem packfile enthaltenen Objekte und die Offsets zu diesen Objekten. Ihr Objekt ist vorhanden, also holen sie sich das komplette packfile:

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

Nun haben Sie Ihr Baumobjekt und gehen Ihre Commits weiter durch. Sie befinden sich alle in dem gerade heruntergeladenen packfile, sodass Sie keine weiteren Anfragen an Ihren Server stellen müssen. Git checkt eine Arbeitskopie des master-Branches aus, auf die in der HEAD-Referenz verwiesen wurde, die Sie zu Beginn heruntergeladen haben.

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, Sie führen in Ihrem 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 Ihrem 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).

Jede Zeile beginnt mit einem 4-stelligen Hex-Wert, der angibt, wie lang der Rest der Zeile ist. Ihre erste Zeile beginnt mit 00a5, was hexadezimal für 165 ist. Dies bedeutet, dass 165 Bytes in dieser Zeile verbleiben. Die nächste Zeile ist 0000, was bedeutet, dass der Server mit seiner Referenzliste fertig ist.

Jetzt, da der Server-Status bekannt ist, bestimmt Ihr 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 Sie beispielsweise den master-Branch aktualisieren und einen experiment-Branch hinzufügen, 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 Sie aktualisieren, 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 Sie die experiment-Referenz hinzufügen. Wenn Sie eine Referenz löschen, sehen Sie 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.

Daten herunterladen

Wenn Sie Daten herunterladen, 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 Sie den Abruf über SSH ausführen, 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, Ihnen ein Gefühl für das allgemeine Hin und Her zwischen Client und Server zu vermitteln. Wenn Sie mehr Informationen diesbezüglich benötigen, sollten Sie sich den Git-Quellcode ansehen.