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.
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 <[email protected]> 1205815931 -0700
committer Scott Chacon <[email protected]> 1240030591 -0700
Change 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 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.
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.
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).
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. Ihr 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 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
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.
Denken Sie daran, dass das HTTP-Protokoll diese Daten möglicherweise zusätzlich in einen „chunked transfer“ Code verpackt.
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.
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
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.
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.