-
1. Los geht's
- 1.1 Wozu Versionskontrolle?
- 1.2 Die Geschichte von Git
- 1.3 Git Grundlagen
- 1.4 Git installieren
- 1.5 Git konfigurieren
- 1.6 Hilfe finden
- 1.7 Zusammenfassung
-
2. Git Grundlagen
-
3. Git Branching
- 3.1 Was ist ein Branch?
- 3.2 Einfaches Branching und Merging
- 3.3 Branch Management
- 3.4 Branching Workflows
- 3.5 Externe Branches
- 3.6 Rebasing
- 3.7 Zusammenfassung
-
4. Git auf dem Server
- 4.1 Die Protokolle
- 4.2 Git auf einen Server bekommen
- 4.3 Generiere deinen öffentlichen SSH-Schlüssel
- 4.4 Einrichten des Servers
- 4.5 Öffentlicher Zugang
- 4.6 GitWeb
- 4.7 Gitosis
- 4.8 Gitolite
- 4.9 Git Daemon
- 4.10 Git Hosting
- 4.11 Einrichten eines Benutzeraccounts
- 4.12 Zusammenfassung
-
5. Distribuierte Arbeit mit Git (xxx)
-
6. Git Tools
- 6.1 Revision Auswahl
- 6.2 Interaktives Stagen
- 6.3 Stashing
- 6.4 Änderungshistorie verändern
- 6.5 Mit Hilfe von Git debuggen
- 6.6 Submodule
- 6.7 Subtree Merging
- 6.8 Zusammenfassung
-
7. Git individuell einrichten
-
8. Git und andere Versionsverwaltungen
- 8.1 Git und Subversion
- 8.2 Zu Git umziehen
- 8.3 Zusammenfassung
-
9. Git Internas
- 9.1 Plumbing und Porcelain
- 9.2 Git Objekte
- 9.3 Git Referenzen
- 9.4 Pack-Dateien
- 9.5 Die Refspec
- 9.6 Transfer Protokolle
- 9.7 Wartung und Datenwiederherstellung
- 9.8 Zusammenfassung
9.6 Git Internas - Transfer Protokolle
Transfer Protokolle
Git kann Daten zwischen zwei Repositories im wesentlichen auf zwei Arten transportieren: über HTTP und über sogenannte smarte Protokolle, die mit file://, ssh:// und git:// verwendet werden. Die folgende Sektion gibt einen kurzen Überblick über diese Protokolle und wie sie funktionieren.
Das dumme Protokoll
Git's HTTP Transfer Protokoll wird oft auch als "dummes" Protokoll bezeichnet, weil es auf der Server Seite keinen Git-spezifischen Code benötigt. Der fetch Prozeß besteht aus einer Reihe von GET Requests, für die der Client Vorannahmen über das Layout des Git Repositories auf dem Server machen kann. Schauen wir uns den http-fetch Prozeß der simplegit Bibliothek an:
$ git clone http://github.com/schacon/simplegit-progit.git
Der Befehl lädt zunächst die info/refs Datei herunter. Diese Datei wird vom update-server-info Befehl geschrieben, den man als einen post-receive Hook einrichten muß, damit das HTTP Protokoll richtig funktionieren kann.
=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949 refs/heads/master
Jetzt hat man eine Liste aller Referenzen und SHAs in diesem Repository. Als nächstes schaut man die HEAD Referenz nach, um zu wissen, was ausgecheckt werden muß:
=> GET HEAD
ref: refs/heads/master
D.h., wenn wir mit dem Prozeß fertig sind, wir müssen den master Branch auschecken.
Wir können jetzt loslegen. Weil wir in der info/refs Datei der Commit ca82a6 angegeben ist, fangen wir damit an, dieses Objekt herunter zu laden:
=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)
Wir erhalten also ein Objekt zurück. Dieses Objekt ist im losen Format auf dem Server gespeichert, und wir haben es über einen statischen HTTP GET Request herunter geladen. Jetzt können wir es mit zlib dekomprimieren, den Header entfernen und den Inhalt des Commits durchsehen:
$ 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 brauchen wir also zwei weitere Objekte: cfda3b, welches der Tree der Inhalte dieses Commits ist, und 085bb3, den Commit Parent:
=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)
Das gibt uns das nächste Commit Objekt. Versuchen wir, das Tree Objekt zu holen:
=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)
Huch. Es sieht so aus als ob der Tree nicht im losen Format auf dem Server gespeichert ist, weshalb wir einen 404 Response ("Not found") erhalten. Dafür kann es verschiedene Gründe geben. Das Objekt könnte in einem anderen, alternativen Repository liegen, oder es könnte sich in einem Packfile befinden. Git sucht deshalb zunächst nach alternativen Repositories:
=> GET objects/info/http-alternates
(empty file)
=> GET objects/info/http-alternates
(leere Datei)
Wenn wir hier eine Liste alternativer URLs erhalten, schaut Git dort nach losen Dateien und Packfiles. Auf diese Weise können Repositories, die Forks von anderen Repositories sind, mit diesen Objekte im Dateisystem teilen. In unserem Fall sind allerdings keine Alternativen vorhanden, weshalb sich das gesuchte Objekt in einem Packfile befinden muß. Um die vorhandenen Packfiles nachzuschlagen, holt Git die objects/info/packs Datei, die eine entsprechende Auflistung enthält (und ebenfalls mit update-server-info erzeugt wird)
=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
Es gibt nur ein einziges Packfile auf dem Server, weshalb sich unser Objekt darin befinden muß. Aber wir prüfen die Index Datei, um sicher zu sein. Gäbe es mehrere Packfiles auf dem Server, könnten wir auf diese Weise herausfinden, welches Packfile das gesuchte Objekt enthält:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k binäre Daten)
Nachdem wir jetzt den Packfile Index haben, können wir prüfen, ob sich unser Objekt darin befindet: der Index enthält eine Liste der SHA Hashes der Objekte, die sich im Packfile befinden und die jeweiligen Offsets dieser Objekte. Unser Objekt ist vorhanden, also laden wir das Packfile herunter:
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)
=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k binäre Daten)
Du hast jetzt das Tree Objekt, also kannst du jetzt damit fortfahren, über die Commits zu iterieren. Sie sind in unserem Fall allesamt in dem Packfile enthalten, das du gerade heruntergeladen hast.
Die Ausgabe des ganzen Vorgangs sieht dann in etwa so aus:
$ git clone http://github.com/schacon/simplegit-progit.git
Initialized empty Git repository in /private/tmp/simplegit-progit/.git/
got ca82a6dff817ec66f44342007202690a93763949
walk ca82a6dff817ec66f44342007202690a93763949
got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Getting alternates list for http://github.com/schacon/simplegit-progit.git
Getting pack list for http://github.com/schacon/simplegit-progit.git
Getting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835
Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835
which contains cfda3bf379e4f8dba8717dee55aab78aef7f4daf
walk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
walk a11bef06a3f659402fe7563abf99ad00de2209e6
Das Smart Protokoll (xxx)
Die HTTP Methode ist simpel, aber sie ist auch ein bißchen ineffizient. Deshalb ist es üblicher, ein smartes Protokoll für den Datentransfer zu verwenden. Diese Protokolle umfassen serverseitige Prozesse, die Wissen über Git besitzen. Sie können lokale Daten lesen und herausfinden, was auf dem Client schon vorhanden ist oder fehlt und darauf zugeschnitten Daten generieren. Es gibt zwei Sets von Prozessen für den Datentransfer: ein Paar für den Upload und ein Paar für den Download von Daten.
Daten hochladen
Um Daten an einen serverseitigen Prozeß zu schicken, verwendet Git die send-pack und receive-pack Prozesse. Der send-pack Prozeß läuft auf dem Client und verbindet sich mit einem receive-pack Prozeß auf dem Server.
Nehmen wir z.B. an du führst git push origin master in deinem Projekt aus und origin ist als eine URL mit SSH Protokoll definiert. Git startet dann einen send-pack Prozeß, der eine SSH Verbindung zum Server initiiert. Dieser versucht, via SSH auf dem Server einen Befehl wie den folgenden auszuführen:
$ ssh -x git@github.com "git-receive-pack 'schacon/simplegit-progit.git'"
005bca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status delete-refs
003e085bb3bcb608e1e84b2432f8ecbe6306e7e7 refs/heads/topic
0000
Der git-receive-pack Befehl antwortet dann mit jeweils einer Zeile pro Referenz, die er kennt - in diesem Fall sind das lediglich der master Branch und dessen SHA. Die erste Zeile listet außerdem Features, die der Server beherrscht (in unserem Fall report-status und delete-refs).
Jede Zeile beginnt mit einem 4 Byte Hexadezimalzahl Wert, der angibt, wie lang der Rest der Zeile ist. Die erste Zeile beginnt mit 005b, d.h. dezimal 91. ALso ist der Rest der Zeile 91 Zeichen lang. Die nächste Zeile fängt mit 003e an, also dezimal 62. Die letzte Zeile ist 0000, was das Ende der Liste anzeigt.
Nachdem dein send-pack Prozeß jetzt den Zustand des Servers kennt, kann er als nächstes evaluieren, welche Commits lokal, aber nicht auf dem Server vorhanden sind. der send-pack Prozeß schickt diese Information für jede Referenz, auf die sich der push Befehl bezieht, an den receive-pack Prozeß. Wenn du beispielsweise den master Branch aktualisierst und einen experiment Branch hinzufügst, dann könnte die Antwort auf send-pack so aussehen:
0085ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status
00670000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment
0000
Der SHA-1 Wert, der nur aus Nullen besteht, heißt, daß dort zuvor nichts war: du fügst die experiment Referenz ja neu hinzu. Würdest du eine Referenz löschen, würdest du das Gegenteil sehen: nur Nullen auf der rechten Seite (xxx hu? wo ist die rechte seite? xxx)
Pro Referenz, die du aktualisierst, schickt Git eine Zeile mit dem alten SHA, dem neuen SHA und der jeweiligen Referenz, die aktualisiert wird. Die erste Zeile listet zudem die Server Features. Als nächstes lädt der Client ein Packfile aller Objekte hoch, die der Server noch nicht kennt. Abschließend antwortet der Server mit einer Erfolgs- oder Fehlermeldung:
000Aunpack ok
Downloading Data
Wenn du Daten herunterlädst, sind daran die fetch-pack und upload-pack Prozesse beteiligt. Der Client startet einen fetch-pack Prozeß, der sich mit einem upload-pack Prozeß auf dem Server verbindet, um auszuhandeln, welche Daten heruntergeladen werden sollen.
Es gibt verschiedene Möglichkeiten, den upload-pack Prozeß auf dem Server zu starten: einerseits via SSH auf die gleiche Weise wie den receive-pack Prozeß. Und adnererseits über den Git Daemon, der standardmäßig auf dem Server auf dem Port 9418 läuft. Der fetch-pack Prozeß schickt etwa folgendes an den Daemon:
003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0
Diese Zeile beginnt wiederum mit 4 Bytes, die angeben, wieviel Daten folgen. Dann kommt der auszuführende Befehl und ein Null Byte, und schließlich der Hostname des Servers und ein weiteres Null Byte. Der Git Daemon prüft, ob der Befehl ausgeführt werden kann, das Repository existiert und Schreibzugriff erlaubt. Wenn alles stimmt, startet er den upload-pack Prozeß und gibt den Request dorthin weiter.
Wenn du den fetch Befehl über SSH verwendest, führt fetch-pack statt dessen etwas aus wie:
$ ssh -x git@github.com "git-upload-pack 'schacon/simplegit-progit.git'"
In beiden Fällen wird, nachdem fetch-pack verbunden ist, upload-pack eine Antwort wie die folgende zurück schicken:
0088ca82a6dff817ec66f44342007202690a93763949 HEAD\0multi_ack thin-pack \
side-band side-band-64k ofs-delta shallow no-progress include-tag
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
003e085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 refs/heads/topic
0000
Die Antwort ähnelt der, mit der receive-pack antwortet, aber die aufgelisteten Features sind andere. Zusätzlich wird die HEAD Referenz mitgeschickt, so daß der Client weiß, was er auschecken muß, falls es sich um einen Clone handelt.
Der fetch-pack Prozeß inspiziert jetzt die vorhandenen Objekte und antwortet mit einer Liste von Objekten, wobei er das Schlüsselwort "want" für Objekte verwendet, die benötigt werden, und "have" für Objekte, die bereits vorhanden sind. Am Ende der Liste folgt das Schlüsselwort "done". Der upload-pack Prozeß schickt dann ein Packfile mit allen benötigten Objekten:
0054want ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0000
0009done
Das ist ein sehr einfaches Beispiel. In komplexeren Fällen unterstützt der Client die multi_ack oder side-band Features. Aber obiges Beispiel verdeutlicht den grundlegenden Request-Response Zyklus der Smart Protokoll Prozesse.