1 == Aufgedeckte Geheimnisse ==
3 Wir werfen einen Blick unter die Motorhaube und erklären, wie Git seine
4 Wunder vollbringt. Ich werde nicht ins Detail gehen. Für tiefer gehende
5 Erklärungen verweise ich auf das
6 http://www.kernel.org/pub/software/scm/git/docs/user-manual.html[englischsprachige
11 Wie kann Git so unauffällig sein? Abgesehen von gelegentlichen 'Commits' und
12 'Merges' kannst Du arbeiten, als würde die Versionsverwaltung nicht
13 existieren. Das heißt, bis Du sie brauchst. Und das ist, wenn Du froh bist,
14 dass Git die ganze Zeit über Dich gewacht hat.
16 Andere Versionsverwaltungssysteme zwingen Dich ständig Dich mit
17 Verwaltungskram und Bürokratie herumzuschlagen. Dateien sind können
18 schreibgeschützt sein, bis Du einem zentralen Server mitteilst, welche
19 Dateien Du gerne bearbeiten möchtest. Die einfachsten Befehle werden bis zum
20 Schneckentempo verlangsamt, wenn die Anzahl der Anwender steigt. Deine
21 Arbeit kommt zum Stillstand, wenn das Netzwerk oder der zentrale Server weg
24 Im Gegensatz dazu hält Git seinen Verlauf einfach im `.git` Verzeichnis von
25 Deinem Arbeitsverzeichnis. Das ist Deine eigene Kopie der
26 Versionsgeschichte, damit kannst Du so lange offline bleiben, bis Du mit
27 anderen kommunizieren willst. Du hast die absolute Kontrolle über das
28 Schicksal Deiner Dateien, denn Git kann jederzeit einfach einen gesicherten
29 Stand aus `.git` wiederherstellen.
33 Die meisten Leute verbinden mit Kryptographie die Geheimhaltung von
34 Informationen, aber ein genau so wichtiges Ziel ist es Informationen zu
35 sichern. Die richtige Anwendung von kryptographischen Hash-Funktionen kann
36 einen versehentlichen oder bösartigen Datenverlust verhindern.
38 Einen SHA1-Hash-Wert kann man sich als eindeutige 160-Bit Identitätsnummer
39 für jegliche Zeichenkette vorstellen, welche Dir in Deinem ganzen Leben
40 begegnen wird. Sogar mehr als das: jegliche Zeichenfolge, die alle Menschen
41 über mehrere Generationen verwenden.
43 Ein SHA1-Hash-Wert selbst ist eine Zeichenfolge von Bytes. Wir können
44 SHA1-Hash-Werte aus Zeichenfolgen generieren, die selbst SHA1-Hash-Werte
45 enthalten. Diese einfache Beobachtung ist überraschend nützlich: suche nach
46 'hash chains'. Wir werden später sehen, wie Git diese nutzt um effizient die
47 Datenintegrität zu garantieren.
49 Kurz gesagt, Git hält Deine Daten in dem `.git/objects` Unterverzeichnis, wo
50 Du anstelle von normalen Dateinamen nur Identitätsnummern findest. Durch die
51 Verwendung von Identitätsnummern als Dateiname, zusammen mit ein paar
52 Sperrdateien und Zeitstempeltricks, macht Git aus einem einfachen
53 Dateisystem eine effiziente und robuste Datenbank.
57 Woher weiß Git, dass Du eine Datei umbenannt hast, obwohl Du es ihm niemals
58 explizit mitgeteilt hast? Sicher, Du hast vielleicht *git mv* benutzt, aber
59 das ist exakt das selbe wie *git rm* gefolgt von *git add*.
61 Git stöbert Umbenennungen und Kopien zwischen aufeinander folgenden
62 Versionen heuristisch auf. Vielmehr kann es sogar Codeblöcke erkennen, die
63 zwischen Dateien hin und her kopiert oder verschoben wurden! Jedoch kann es
64 nicht alle Fälle abdecken, aber es leistet ordentliche Arbeit und diese
65 Eigenschaft wird immer besser. Wenn es bei Dir nicht funktioniert, versuche
66 Optionen zur aufwendigeren Erkennung von Kopien oder erwäge einen Upgrade.
70 Für jede überwachte Datei speichert Git Informationen wie deren Größe, ihren
71 Erstellzeitpunkt und den Zeitpunkt der letzten Bearbeitung in einer Datei
72 die wir als 'Index' kennen. Um zu ermitteln, ob eine Datei verändert wurde,
73 vergleicht Git den aktuellen Status mit dem im Index gespeicherten. Stimmen
74 diese Daten überein, kann Git das Lesen des Dateiinhalts überspringen.
76 Da das Abfragen des Dateistatus erheblich schneller ist als das Lesen der
77 Datei, kann Git, wenn Du nur ein paar Dateien verändert hast, seinen Status
80 Wir haben früher festgestellt, dass der Index ein Bereitstellungsraum
81 ist. Warum kann ein Haufen von Dateistatusinformationen ein
82 Bereitstellungsraum sein? Weil die 'add' Anweisung Dateien in die Git
83 Datenbank befördert und die Dateistatusinformationen aktualisiert, während
84 die 'commit' Anweisung, ohne Optionen, einen 'Commit' nur auf Basis der
85 Dateistatusinformationen erzeugt, weil die Dateien ja schon in der Datenbank
90 Dieser http://lkml.org/lkml/2005/4/6/121['Linux Kernel Mailing List'
91 Beitrag] beschreibt die Kette von Ereignissen, die zu Git geführt haben. Der
92 ganze Beitrag ist eine faszinierende archäologische Seite für Git
95 === Die Objektdatenbank ===
97 Jegliche Version Deiner Daten wird in der Objektdatenbank gehalten, welche
98 im Unterverzeichnis `.git/objects` liegt; Die anderen Orte in `.git/`
99 enthalten weniger wichtige Daten: den Index, 'Branch' Namen, Bezeichner
100 ('tags'), Konfigurationsoptionen, Logdateien, die Position des aktuellen
101 'HEAD Commit' und so weiter. Die Objektdatenbank ist einfach aber trotzdem
102 elegant und sie ist die Quelle von Git's Macht.
104 Jede Datei in `.git/objects` ist ein 'Objekt'. Es gibt drei Arten von
105 Objekten die uns betreffen: 'Blob'-, 'Tree'-, und 'Commit'-Objekte.
109 Zuerst ein Zaubertrick. Suche Dir einen Dateinamen aus, irgendeinen. In
110 einem leeren Verzeichnis:
112 $ echo sweet > DEIN_DATEINAME
115 $ find .git/objects -type f
117 Du wirst folgendes sehen:
118 +.git/objects/aa/823728ea7d592acc69b36875a482cdf3fd5c8d+.
120 Wie konnte ich das wissen, ohne den Dateiname zu kennen? Weil der
123 "blob" SP "6" NUL "sweet" LF
125 aa823728ea7d592acc69b36875a482cdf3fd5c8d ist. Wobei SP ein Leerzeichen ist,
126 NUL ist ein Nullbyte und LF ist ein Zeilenumbruch. Das kannst Du
127 kontrollieren, durch die Eingabe von:
129 $ printf "blob 6\000sweet\n" | sha1sum
131 Git ist 'assoziativ': Dateien werden nicht nach Ihren Namen gespeichert,
132 sondern eher nach dem SHA1-Hash-Wert der Daten, welche sie enthalten, in
133 einer Datei, die wir als 'Blob'-Objekt bezeichnen. Wir können uns den
134 SHA1-Hash-Wert als eindeutige Identnummer des Dateiinhalts vorstellen, was
135 sinngemäß bedeutet, dass die Dateien über ihren Inhalt adressiert
136 werden. Das führende `blob 6` ist lediglich ein Vermerk, der sich aus dem
137 Objekttyp und seiner Länge in Bytes zusammensetzt; er vereinfacht die
140 So konnte ich einfach vorhersagen, was Du sehen wirst. Der Dateiname ist
141 irrelevant: nur der Dateiinhalt wird zum Erstellen des 'Blob'-Objekt
144 Du wirst Dich fragen, was mit identischen Dateien ist. Versuche Kopien
145 Deiner Datei hinzuzufügen, mit beliebigen Dateinamen. Der Inhalt von
146 +.git/objects+ bleibt der selbe, ganz egal wieviele Dateien Du
147 hinzufügst. Git speichert den Dateiinhalt nur ein einziges Mal.
149 Übrigens, die Dateien in +.git/objects+ sind mit zlib komprimiert, Du
150 solltest sie also nicht direkt anschauen. Filtere sie durch
151 http://www.zlib.net/zpipe.c[zpipe -d], oder gib ein:
153 $ git cat-file -p aa823728ea7d592acc69b36875a482cdf3fd5c8d
155 was Dir das Objekt im Klartext anzeigt.
159 Aber wo sind die Dateinamen? Sie müssen irgendwo gespeichert sein. Git kommt
160 beim 'Commit' dazu sich um die Dateinamen zu kümmern:
162 $ git commit # Schreibe eine Bemerkung.
163 $ find .git/objects -type f
165 Du solltest nun drei Objekte sehen. Dieses mal kann ich Dir nicht sagen, wie
166 die zwei neuen Dateien heißen, weil es zum Teil vom gewählten Dateiname
167 abhängt, den Du ausgesucht hast. Fahren wir fort mit der Annahme, Du hast
168 eine Datei ``rose'' genannt. Wenn nicht, kannst Du den Verlauf so
169 umschreiben, dass es so aussieht als hättest Du es:
171 $ git filter-branch --tree-filter 'mv DEIN_DATEINAME rose'
172 $ find .git/objects -type f
174 Nun müsstest Du die Datei
175 +.git/objects/05/b217bb859794d08bb9e4f7f04cbda4b207fbe9+ sehen, denn das ist
176 der SHA1-Hash-Wert ihres Inhalts:
178 "tree" SP "32" NUL "100644 rose" NUL 0xaa823728ea7d592acc69b36875a482cdf3fd5c8d
180 Prüfe, ob diese Datei tatsächlich dem obigen Inhalt entspricht, durch
183 $ echo 05b217bb859794d08bb9e4f7f04cbda4b207fbe9 | git cat-file --batch
185 Mit zpipe, ist es einfach den SHA1-Hash-Wert zu prüfen:
187 $ zpipe -d < .git/objects/05/b217bb859794d08bb9e4f7f04cbda4b207fbe9 | sha1sum
189 Die SHA1-Hash-Wert Prüfung mit 'cat-file' ist etwas kniffliger, da dessen
190 Ausgabe mehr als die rohe unkomprimierte Objektdatei enthält.
192 Diese Datei ist ein 'Tree'-Objekt: eine Liste von Datensätzen, bestehend aus
193 dem Dateityp, dem Dateinamen und einem SHA1-Hash-Wert. In unserem Beispiel
194 ist der Dateityp 100644, was bedeutet, dass `rose` eine normale Datei ist
195 und der SHA1-Hash-Wert entspricht dem 'Blob'-Objekt, welches den Inhalt von
196 `rose` enthält. Andere mögliche Dateitypen sind ausführbare Programmdateien,
197 symbolische Links oder Verzeichnisse. Im letzten Fall zeigt der
198 SHA1-Hash-Wert auf ein 'Tree'-Objekt.
200 Wenn Du 'filter-branch' aufrufst, bekommst Du alte Objekte, welche nicht
201 länger benötigt werden. Obwohl sie automatisch über Bord geworfen werden,
202 wenn ihre Gnadenfrist abgelaufen ist, wollen wir sie nun löschen, damit wir
203 unserem Beispiel besser folgen können.
205 $ rm -r .git/refs/original
206 $ git reflog expire --expire=now --all
209 Für reale Projekte solltest Du solche Anweisungen üblicherweise vermeiden,
210 da Du dadurch Datensicherungen zerstörst. Wenn Du ein sauberes 'Repository'
211 willst, ist es am besten, einen neuen Klon anzulegen. Sei auch vorsichtig,
212 wenn Du +.git+ direkt manipulierst: was, wenn zeitgleich ein Git Kommando
213 ausgeführt wird oder plötzlich der Strom ausfällt? Generell sollten
214 Referenzen mit *git update-ref -d* gelöscht werden, auch wenn es gewöhnlich
215 sicher ist +refs/original+ von Hand zu löschen.
219 Wir haben nun zwei von drei Objekten erklärt. Das dritte ist ein
220 'Commit'-Objekt. Sein Inhalt hängt von der 'Commit'-Beschreibung ab, wie
221 auch vom Zeitpunkt der Erstellung. Damit alles zu unserem Beispiel passt,
222 müssen wir ein wenig tricksen:
224 $ git commit --amend -m Shakespeare # Ändere die Bemerkung.
225 $ git filter-branch --env-filter 'export
226 GIT_AUTHOR_DATE="Fri 13 Feb 2009 15:31:30 -0800"
227 GIT_AUTHOR_NAME="Alice"
228 GIT_AUTHOR_EMAIL="alice@example.com"
229 GIT_COMMITTER_DATE="Fri, 13 Feb 2009 15:31:30 -0800"
230 GIT_COMMITTER_NAME="Bob"
231 GIT_COMMITTER_EMAIL="bob@example.com"' # Manipuliere Zeitstempel und Autor.
232 $ find .git/objects -type f
234 Du solltest nun +.git/objects/49/993fe130c4b3bf24857a15d7969c396b7bc187+
235 finden, was dem SHA1-Hash-Wert seines Inhalts entspricht:
238 "tree 05b217bb859794d08bb9e4f7f04cbda4b207fbe9" LF
239 "author Alice <alice@example.com> 1234567890 -0800" LF
240 "committer Bob <bob@example.com> 1234567890 -0800" LF
244 Wie vorhin, kannst Du 'zpipe' oder 'cat-file' benutzen um es für Dich zu
247 Das ist der erste 'Commit' gewesen, deshalb gibt es keine
248 Eltern-'Commits'. Aber spätere 'Commits' werden immer mindestens eine Zeile
249 enthalten, die den Eltern-'Commit' identifiziert.
251 === Von Magie nicht zu unterscheiden ===
253 Git's Geheimnisse scheinen zu einfach. Es sieht so aus als müsste man nur
254 ein paar Kommandozeilenskripte zusammenmixen, einen Schuß C-Code hinzufügen
255 und innerhalb ein paar Stunden ist man fertig: eine Mischung von
256 grundlegenden Dateisystemoperationen und SHA1-Hash-Berechnungen, garniert
257 mit Sperrdateien und Synchronisation für Stabilität. Tatsächlich beschreibt
258 dies die früheste Version von Git. Nichtsdestotrotz, abgesehen von
259 geschickten Verpackungstricks um Speicherplatz zu sparen und geschickten
260 Indizierungstricks um Zeit zu sparen, wissen wir nun, wie Git gewandt ein
261 Dateisystem in eine Datenbank verwandelt, das perfekt für eine
262 Versionsverwaltung geeignet ist.
264 Angenommen, wenn irgendeine Datei in der Objektdatenbank durch einen
265 Laufwerksfehler zerstört wird, dann wird sein SHA1-Hash-Wert nicht mehr mit
266 seinem Inhalt übereinstimmen und uns sagen, wo das Problem liegt. Durch
267 Bilden von SHA1-Hash-Werten aus den SHA1-Hash-Werten anderer Objekte,
268 erreichen wir Integrität auf allen Ebenen. 'Commits' sind elementar, das
269 heißt, ein 'Commit' kann niemals nur Teile einer Änderung speichern: wir
270 können den SHA1-Hash-Wert eines 'Commits' erst dann berechnen und speichern,
271 nachdem wir bereits alle relevanten 'Tree'-Objekte, 'Blob'-Objekte und
272 Eltern-'Commits' gespeichert haben. Die Objektdatenbank ist immun gegen
273 unerwartete Unterbrechungen wie zum Beispiel einen Stromausfall.
275 Wir können sogar den hinterhältigsten Gegnern widerstehen. Stell Dir vor,
276 jemand will den Inhalt einer Datei ändern, die in einer älteren Version
277 eines Projekt liegt. Um die Objektdatenbank intakt aussehen zu lassen,
278 müssten sie außerdem den SHA1-Hash-Wert des korrespondierenden 'Blob'-Objekt
279 ändern, da die Datei nun eine geänderte Zeichenfolge enthält. Das heißt
280 auch, dass sie jeden SHA1-Hash-Wert der 'Tree'-Objekte ändern müssen, welche
281 dieses Objekt referenzieren und demzufolge alle SHA1-Hash-Werte der
282 'Commit'-Objekte, welche diese 'Tree'-Objekte beinhalten, zusätzlich zu
283 allen Abkömmlingen dieses 'Commits'. Das bedeutet auch, dass sich der
284 SHA1-Hash-Wert des offiziellen HEAD von dem des manipulierten 'Repository'
285 unterscheidet. Folgen wir dem Pfad der differierenden SHA1-Hash-Werte,
286 finden wir die verstümmelte Datei, wie auch den 'Commit', in dem sie
289 Kurz gesagt, so lange die 20 Byte, welche den SHA1-Hash-Wert des letzen
290 'Commit' repräsentieren sicher sind, ist es unmöglich ein Git 'Repository'
293 Was ist mit Git's berühmten Fähigkeiten? 'Branching'? 'Merging'? 'Tags'? Nur
294 Kleinigkeiten. Der aktuelle HEAD wird in der Datei +.git/HEAD+ gehalten,
295 welche den SHA1-Hash-Wert eines 'Commit'-Objekts enthält. Der SHA1-Hash-Wert
296 wird während eines 'Commit' aktualisiert, genauso bei vielen anderen
297 Anweisungen. 'Branches' sind fast das selbe: sie sind Dateien in
298 +.git/refs/heads+. 'Tags' ebenso: sie stehen in +.git/refs/tags+ aber sie
299 werden durch einen Satz anderer Anweisungen aktualisiert.