Kapitel ▾ 2. Auflage

10.6 Git Internals - Übertragungsprotokolle

Übertragungsprotokolle

Git kann Daten zwischen zwei Repositories auf zwei Hauptarten übertragen: dem „dummen“ Protokoll und dem „intelligenten“ Protokoll. Dieser Abschnitt behandelt kurz, wie diese beiden Hauptprotokolle funktionieren.

Das dumme Protokoll

Wenn Sie ein Repository einrichten, das schreibgeschützt über HTTP bereitgestellt werden soll, wird wahrscheinlich das dumme Protokoll verwendet. Dieses Protokoll wird als „dumm“ bezeichnet, da es während des Übertragungsvorgangs keinen Git-spezifischen Code auf der Serverseite benötigt; der Abrufprozess besteht aus einer Reihe von HTTP GET-Anfragen, bei denen der Client das Layout des Git-Repositorys auf dem Server annehmen kann.

Hinweis

Das dumme Protokoll wird heutzutage eher selten verwendet. Es ist schwierig zu sichern oder privat zu machen, daher werden die meisten Git-Hosts (sowohl cloudbasiert als auch lokal) sich weigern, es zu verwenden. Es wird generell empfohlen, das intelligente Protokoll zu verwenden, das wir etwas weiter unten beschreiben.

Betrachten wir den http-fetch-Prozess für die simplegit-Bibliothek

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

Das Erste, was dieser Befehl tut, ist das Herunterladen der Datei info/refs. Diese Datei wird vom Befehl update-server-info geschrieben, weshalb Sie diesen als post-receive-Hook aktivieren müssen, damit der HTTP-Transport ordnungsgemäß funktioniert.

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

Nun haben Sie eine Liste der Remote-Referenzen und SHAs. Als Nächstes suchen Sie nach der HEAD-Referenz, um zu wissen, was Sie nach Abschluss auschecken müssen.

=> GET HEAD
ref: refs/heads/master

Sie müssen den master-Branch auschecken, wenn Sie mit dem Prozess fertig sind. An diesem Punkt sind Sie bereit, mit dem Walking-Prozess zu beginnen. Da Ihr Ausgangspunkt das Commit-Objekt ca82a6 ist, das Sie in der Datei info/refs gesehen haben, beginnen Sie damit, dieses abzurufen.

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

Sie erhalten ein Objekt zurück – dieses Objekt liegt im Server im losen Format vor und Sie haben es über eine statische HTTP GET-Anfrage abgerufen. Sie können es zlib-dekomprimieren, den Header entfernen und den Commit-Inhalt betrachten.

$ 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 müssen Sie zwei weitere Objekte abrufen – cfda3b, was der Baum des Inhalts ist, auf den der gerade abgerufene Commit verweist; und 085bb3, was der Eltern-Commit ist.

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

Das liefert Ihnen Ihr nächstes Commit-Objekt. Holen Sie sich das Baum-Objekt.

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

Ups – es sieht so aus, als ob dieses Baum-Objekt nicht im losen Format auf dem Server vorhanden ist, daher erhalten Sie eine 404-Antwort. Dafür gibt es mehrere Gründe – das Objekt könnte sich in einem alternativen Repository befinden oder es könnte sich in einem Packfile in diesem Repository befinden. Git prüft zuerst auf aufgeführte Alternativen.

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

Wenn dies mit einer Liste alternativer URLs zurückkommt, prüft Git dort nach losen Dateien und Packfiles – dies ist ein nützlicher Mechanismus für Projekte, die Forks voneinander sind, um Objekte auf der Festplatte zu teilen. Da in diesem Fall jedoch keine Alternativen aufgeführt sind, muss Ihr Objekt in einem Packfile sein. Um zu sehen, welche Packfiles auf diesem Server verfügbar sind, müssen Sie die Datei objects/info/packs abrufen, die eine Liste davon enthält (ebenfalls von update-server-info generiert).

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

Es gibt nur ein Packfile auf dem Server, also ist Ihr Objekt offensichtlich darin enthalten, aber Sie werden die Indexdatei überprüfen, um sicherzugehen. Dies ist auch nützlich, wenn Sie mehrere Packfiles auf dem Server haben, damit Sie sehen können, welches Packfile das benötigte Objekt enthält.

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

Nachdem Sie den Packfile-Index haben, können Sie sehen, ob Ihr Objekt darin enthalten ist – da der Index die SHAs der im Packfile enthaltenen Objekte und die Offsets zu diesen Objekten auflistet. Ihr Objekt ist dort, also holen Sie sich das gesamte Packfile.

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

Sie haben Ihr Baum-Objekt, also setzen Sie das Durchlaufen Ihrer Commits fort. Sie befinden sich alle ebenfalls im gerade heruntergeladenen Packfile, sodass Sie keine weiteren Anfragen an Ihren Server stellen müssen. Git checkt eine Arbeitskopie des master-Branches aus, auf den die von Ihnen zu Beginn heruntergeladene HEAD-Referenz verwiesen hat.

Das intelligente Protokoll

Das dumme Protokoll ist einfach, aber etwas ineffizient und kann keine Daten vom Client zum Server schreiben. Das intelligente Protokoll ist eine gängigere Methode zur Datenübertragung, erfordert jedoch einen Prozess am Remote-Ende, der Git-spezifisch ist – er kann lokale Daten lesen, ermitteln, was der Client hat und benötigt, und ein benutzerdefiniertes Packfile dafür generieren. Es gibt zwei Sätze von Prozessen für die Datenübertragung: ein Paar für das Hochladen von Daten und ein Paar für das Herunterladen von Daten.

Hochladen von Daten

Um Daten an einen Remote-Prozess hochzuladen, verwendet Git die Prozesse send-pack und receive-pack. Der send-pack-Prozess läuft auf dem Client und verbindet sich mit einem receive-pack-Prozess auf der Remote-Seite.

SSH

Nehmen wir an, Sie führen git push origin master in Ihrem Projekt aus und origin ist als URL definiert, die das SSH-Protokoll verwendet. Git startet den send-pack-Prozess, der eine SSH-Verbindung zu Ihrem Server initiiert. Es versucht, über einen SSH-Aufruf einen Befehl auf dem Remote-Server auszuführen, der ungefähr 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 aktuell hat – in diesem Fall nur der master-Branch und sein SHA. Die erste Zeile enthält auch eine Liste der Fähigkeiten des Servers (hier report-status, delete-refs und einige andere, einschließlich des Client-Identifikators).

Die Daten werden in Chunks übertragen. Jeder Chunk beginnt mit einem 4-stelligen Hexadezimalwert, der die Länge des Chunks angibt (einschließlich der 4 Bytes der Länge selbst). Chunks enthalten normalerweise eine einzelne Datenzeile und einen abschließenden Zeilenumbruch. Ihr erster Chunk beginnt mit 00a5, was Hexadezimal für 165 ist und bedeutet, dass der Chunk 165 Bytes lang ist. Der nächste Chunk ist 0000, was bedeutet, dass der Server mit der Auflistung seiner Referenzen fertig ist.

Nachdem es nun den Zustand des Servers kennt, ermittelt Ihr send-pack-Prozess, welche Commits es hat, die der Server nicht hat. Für jede Referenz, die dieser Push aktualisieren 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, kann die send-pack-Antwort etwa so aussehen:

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

Git sendet eine Zeile für jede von Ihnen aktualisierte Referenz mit der Länge der Zeile, dem alten SHA, dem neuen SHA und der zu aktualisierenden Referenz. Die erste Zeile enthält auch die Fähigkeiten des Clients. Der SHA-Wert aus lauter Nullen bedeutet, dass vorher nichts da war – da Sie die experiment-Referenz hinzufügen. Wenn Sie eine Referenz löschen würden, sähen Sie das Gegenteil: lauter 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 Misserfolgs-) Anzeige.

000eunpack ok
HTTP(S)

Dieser Prozess ist über HTTP weitgehend derselbe, obwohl das Handshaking etwas anders ist. Die Verbindung wird mit dieser 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 des ersten Client-Server-Austauschs. Der Client macht dann eine weitere Anfrage, diesmal eine POST-Anfrage, mit den Daten, die send-pack bereitstellt.

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

Die POST-Anfrage enthält die send-pack-Ausgabe und das Packfile als Payload. Der Server zeigt dann Erfolg oder Misserfolg mit seiner HTTP-Antwort an.

Beachten Sie, dass das HTTP-Protokoll diese Daten möglicherweise weiter in eine gestückelte Transferkodierung einschließt.

Herunterladen von Daten

Beim Herunterladen von Daten sind die Prozesse fetch-pack und upload-pack beteiligt. Der Client initiiert einen fetch-pack-Prozess, der sich mit einem upload-pack-Prozess auf der Remote-Seite verbindet, um zu verhandeln, welche Daten übertragen werden.

SSH

Wenn Sie den Abruf über SSH durchführen, läuft fetch-pack ungefähr so:

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

Nachdem sich fetch-pack verbunden hat, sendet upload-pack etwa 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 dem, womit receive-pack antwortet, sehr ähnlich, aber die Fähigkeiten sind unterschiedlich. Außerdem sendet es zurück, worauf HEAD verweist (symref=HEAD:refs/heads/master), damit der Client weiß, was er auschecken soll, wenn dies ein Klon ist.

An diesem Punkt betrachtet der fetch-pack-Prozess, welche Objekte er hat, und antwortet mit den Objekten, die er benötigt, indem er „want“ und dann den gewünschten SHA sendet. Er sendet alle Objekte, die er bereits hat, mit „have“ und dann dem SHA. Am Ende dieser Liste schreibt er „done“, um den upload-pack-Prozess zu initiieren, der mit dem Senden des Packfiles der benötigten Daten beginnt.

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

Das Handshaking für eine Abrufoperation erfordert zwei HTTP-Anfragen. Die erste ist eine GET-Anfrage an denselben 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 Aufruf von git-upload-pack über eine SSH-Verbindung sehr ähnlich, aber der zweite Austausch wird als separate Anfrage durchgeführt.

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

Auch hier gilt das gleiche Format wie oben. Die Antwort auf diese Anfrage zeigt Erfolg oder Misserfolg an und enthält das Packfile.

Zusammenfassung der Protokolle

Dieser Abschnitt enthält einen sehr grundlegenden Überblick über die Übertragungsprotokolle. Das Protokoll umfasst viele weitere Funktionen wie multi_ack- oder side-band-Fähigkeiten, deren Abdeckung jedoch außerhalb des Rahmens dieses Buches liegt. Wir haben versucht, Ihnen ein Gefühl für den allgemeinen Hin- und Herverkehr zwischen Client und Server zu geben; wenn Sie mehr Wissen als dieses benötigen, sollten Sie wahrscheinlich einen Blick in den Git-Quellcode werfen.