Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 775e73a5c8
Fetching contributors…

Cannot retrieve contributors at this time

300 lines (234 sloc) 14.052 kb
== Aufgedeckte Geheimnisse ==
Wir werfen einen Blick unter die Motorhaube und erklären, wie Git seine
Wunder vollbringt. Ich werde nicht ins Detail gehen. Für tiefer gehende
Erklärungen verweise ich auf das
http://www.kernel.org/pub/software/scm/git/docs/user-manual.html[englischsprachige
Benutzerhandbuch].
=== Unsichtbarkeit ===
Wie kann Git so unauffällig sein? Abgesehen von gelegentlichen 'Commits' und
'Merges' kannst Du arbeiten, als würde die Versionsverwaltung nicht
existieren. Das heißt, bis Du sie brauchst. Und das ist, wenn Du froh bist,
dass Git die ganze Zeit über Dich gewacht hat.
Andere Versionsverwaltungssysteme zwingen Dich ständig Dich mit
Verwaltungskram und Bürokratie herumzuschlagen. Dateien sind können
schreibgeschützt sein, bis Du einem zentralen Server mitteilst, welche
Dateien Du gerne bearbeiten möchtest. Die einfachsten Befehle werden bis zum
Schneckentempo verlangsamt, wenn die Anzahl der Anwender steigt. Deine
Arbeit kommt zum Stillstand, wenn das Netzwerk oder der zentrale Server weg
sind.
Im Gegensatz dazu hält Git seinen Verlauf einfach im `.git` Verzeichnis von
Deinem Arbeitsverzeichnis. Das ist Deine eigene Kopie der
Versionsgeschichte, damit kannst Du so lange offline bleiben, bis Du mit
anderen kommunizieren willst. Du hast die absolute Kontrolle über das
Schicksal Deiner Dateien, denn Git kann jederzeit einfach einen gesicherten
Stand aus `.git` wiederherstellen.
=== Integrität ===
Die meisten Leute verbinden mit Kryptographie die Geheimhaltung von
Informationen, aber ein genau so wichtiges Ziel ist es Informationen zu
sichern. Die richtige Anwendung von kryptographischen Hash-Funktionen kann
einen versehentlichen oder bösartigen Datenverlust verhindern.
Einen SHA1-Hash-Wert kann man sich als eindeutige 160-Bit Identitätsnummer
für jegliche Zeichenkette vorstellen, welche Dir in Deinem ganzen Leben
begegnen wird. Sogar mehr als das: jegliche Zeichenfolge, die alle Menschen
über mehrere Generationen verwenden.
Ein SHA1-Hash-Wert selbst ist eine Zeichenfolge von Bytes. Wir können
SHA1-Hash-Werte aus Zeichenfolgen generieren, die selbst SHA1-Hash-Werte
enthalten. Diese einfache Beobachtung ist überraschend nützlich: suche nach
'hash chains'. Wir werden später sehen, wie Git diese nutzt um effizient die
Datenintegrität zu garantieren.
Kurz gesagt, Git hält Deine Daten in dem `.git/objects` Unterverzeichnis, wo
Du anstelle von normalen Dateinamen nur Identitätsnummern findest. Durch die
Verwendung von Identitätsnummern als Dateiname, zusammen mit ein paar
Sperrdateien und Zeitstempeltricks, macht Git aus einem einfachen
Dateisystem eine effiziente und robuste Datenbank.
=== Intelligenz ===
Woher weiß Git, dass Du eine Datei umbenannt hast, obwohl Du es ihm niemals
explizit mitgeteilt hast? Sicher, Du hast vielleicht *git mv* benutzt, aber
das ist exakt das selbe wie *git rm* gefolgt von *git add*.
Git stöbert Umbenennungen und Kopien zwischen aufeinander folgenden
Versionen heuristisch auf. Vielmehr kann es sogar Codeblöcke erkennen, die
zwischen Dateien hin und her kopiert oder verschoben wurden! Jedoch kann es
nicht alle Fälle abdecken, aber es leistet ordentliche Arbeit und diese
Eigenschaft wird immer besser. Wenn es bei Dir nicht funktioniert, versuche
Optionen zur aufwendigeren Erkennung von Kopien oder erwäge einen Upgrade.
=== Indizierung ===
Für jede überwachte Datei speichert Git Informationen wie deren Größe, ihren
Erstellzeitpunkt und den Zeitpunkt der letzten Bearbeitung in einer Datei
die wir als 'Index' kennen. Um zu ermitteln, ob eine Datei verändert wurde,
vergleicht Git den aktuellen Status mit dem im Index gespeicherten. Stimmen
diese Daten überein, kann Git das Lesen des Dateiinhalts überspringen.
Da das Abfragen des Dateistatus erheblich schneller ist als das Lesen der
Datei, kann Git, wenn Du nur ein paar Dateien verändert hast, seinen Status
im Nu aktualisieren.
Wir haben früher festgestellt, dass der Index ein Bereitstellungsraum
ist. Warum kann ein Haufen von Dateistatusinformationen ein
Bereitstellungsraum sein? Weil die 'add' Anweisung Dateien in die Git
Datenbank befördert und die Dateistatusinformationen aktualisiert, während
die 'commit' Anweisung, ohne Optionen, einen 'Commit' nur auf Basis der
Dateistatusinformationen erzeugt, weil die Dateien ja schon in der Datenbank
sind.
=== Git's Wurzeln ===
Dieser http://lkml.org/lkml/2005/4/6/121['Linux Kernel Mailing List'
Beitrag] beschreibt die Kette von Ereignissen, die zu Git geführt haben. Der
ganze Beitrag ist eine faszinierende archäologische Seite für Git
Historiker.
=== Die Objektdatenbank ===
Jegliche Version Deiner Daten wird in der Objektdatenbank gehalten, welche
im Unterverzeichnis `.git/objects` liegt; Die anderen Orte in `.git/`
enthalten weniger wichtige Daten: den Index, 'Branch' Namen, Bezeichner
('tags'), Konfigurationsoptionen, Logdateien, die Position des aktuellen
'HEAD Commit' und so weiter. Die Objektdatenbank ist einfach aber trotzdem
elegant und sie ist die Quelle von Git's Macht.
Jede Datei in `.git/objects` ist ein 'Objekt'. Es gibt drei Arten von
Objekten die uns betreffen: 'Blob'-, 'Tree'-, und 'Commit'-Objekte.
=== Blobs ===
Zuerst ein Zaubertrick. Suche Dir einen Dateinamen aus, irgendeinen. In
einem leeren Verzeichnis:
$ echo sweet > DEIN_DATEINAME
$ git init
$ git add .
$ find .git/objects -type f
Du wirst folgendes sehen:
+.git/objects/aa/823728ea7d592acc69b36875a482cdf3fd5c8d+.
Wie konnte ich das wissen, ohne den Dateiname zu kennen? Weil der
SHA1-Hash-Wert von:
"blob" SP "6" NUL "sweet" LF
aa823728ea7d592acc69b36875a482cdf3fd5c8d ist. Wobei SP ein Leerzeichen ist,
NUL ist ein Nullbyte und LF ist ein Zeilenumbruch. Das kannst Du
kontrollieren, durch die Eingabe von:
$ printf "blob 6\000sweet\n" | sha1sum
Git ist 'assoziativ': Dateien werden nicht nach Ihren Namen gespeichert,
sondern eher nach dem SHA1-Hash-Wert der Daten, welche sie enthalten, in
einer Datei, die wir als 'Blob'-Objekt bezeichnen. Wir können uns den
SHA1-Hash-Wert als eindeutige Identnummer des Dateiinhalts vorstellen, was
sinngemäß bedeutet, dass die Dateien über ihren Inhalt adressiert
werden. Das führende `blob 6` ist lediglich ein Vermerk, der sich aus dem
Objekttyp und seiner Länge in Bytes zusammensetzt; er vereinfacht die
interne Verwaltung.
So konnte ich einfach vorhersagen, was Du sehen wirst. Der Dateiname ist
irrelevant: nur der Dateiinhalt wird zum Erstellen des 'Blob'-Objekt
verwendet.
Du wirst Dich fragen, was mit identischen Dateien ist. Versuche Kopien
Deiner Datei hinzuzufügen, mit beliebigen Dateinamen. Der Inhalt von
+.git/objects+ bleibt der selbe, ganz egal wieviele Dateien Du
hinzufügst. Git speichert den Dateiinhalt nur ein einziges Mal.
Übrigens, die Dateien in +.git/objects+ sind mit zlib komprimiert, Du
solltest sie also nicht direkt anschauen. Filtere sie durch
http://www.zlib.net/zpipe.c[zpipe -d], oder gib ein:
$ git cat-file -p aa823728ea7d592acc69b36875a482cdf3fd5c8d
was Dir das Objekt im Klartext anzeigt.
=== 'Trees' ===
Aber wo sind die Dateinamen? Sie müssen irgendwo gespeichert sein. Git kommt
beim 'Commit' dazu sich um die Dateinamen zu kümmern:
$ git commit # Schreibe eine Bemerkung.
$ find .git/objects -type f
Du solltest nun drei Objekte sehen. Dieses mal kann ich Dir nicht sagen, wie
die zwei neuen Dateien heißen, weil es zum Teil vom gewählten Dateiname
abhängt, den Du ausgesucht hast. Fahren wir fort mit der Annahme, Du hast
eine Datei ``rose'' genannt. Wenn nicht, kannst Du den Verlauf so
umschreiben, dass es so aussieht als hättest Du es:
$ git filter-branch --tree-filter 'mv DEIN_DATEINAME rose'
$ find .git/objects -type f
Nun müsstest Du die Datei
+.git/objects/05/b217bb859794d08bb9e4f7f04cbda4b207fbe9+ sehen, denn das ist
der SHA1-Hash-Wert ihres Inhalts:
"tree" SP "32" NUL "100644 rose" NUL 0xaa823728ea7d592acc69b36875a482cdf3fd5c8d
Prüfe, ob diese Datei tatsächlich dem obigen Inhalt entspricht, durch
Eingabe von:
$ echo 05b217bb859794d08bb9e4f7f04cbda4b207fbe9 | git cat-file --batch
Mit zpipe, ist es einfach den SHA1-Hash-Wert zu prüfen:
$ zpipe -d < .git/objects/05/b217bb859794d08bb9e4f7f04cbda4b207fbe9 | sha1sum
Die SHA1-Hash-Wert Prüfung mit 'cat-file' ist etwas kniffliger, da dessen
Ausgabe mehr als die rohe unkomprimierte Objektdatei enthält.
Diese Datei ist ein 'Tree'-Objekt: eine Liste von Datensätzen, bestehend aus
dem Dateityp, dem Dateinamen und einem SHA1-Hash-Wert. In unserem Beispiel
ist der Dateityp 100644, was bedeutet, dass `rose` eine normale Datei ist
und der SHA1-Hash-Wert entspricht dem 'Blob'-Objekt, welches den Inhalt von
`rose` enthält. Andere mögliche Dateitypen sind ausführbare Programmdateien,
symbolische Links oder Verzeichnisse. Im letzten Fall zeigt der
SHA1-Hash-Wert auf ein 'Tree'-Objekt.
Wenn Du 'filter-branch' aufrufst, bekommst Du alte Objekte, welche nicht
länger benötigt werden. Obwohl sie automatisch über Bord geworfen werden,
wenn ihre Gnadenfrist abgelaufen ist, wollen wir sie nun löschen, damit wir
unserem Beispiel besser folgen können.
$ rm -r .git/refs/original
$ git reflog expire --expire=now --all
$ git prune
Für reale Projekte solltest Du solche Anweisungen üblicherweise vermeiden,
da Du dadurch Datensicherungen zerstörst. Wenn Du ein sauberes 'Repository'
willst, ist es am besten, einen neuen Klon anzulegen. Sei auch vorsichtig,
wenn Du +.git+ direkt manipulierst: was, wenn zeitgleich ein Git Kommando
ausgeführt wird oder plötzlich der Strom ausfällt? Generell sollten
Referenzen mit *git update-ref -d* gelöscht werden, auch wenn es gewöhnlich
sicher ist +refs/original+ von Hand zu löschen.
=== 'Commits' ===
Wir haben nun zwei von drei Objekten erklärt. Das dritte ist ein
'Commit'-Objekt. Sein Inhalt hängt von der 'Commit'-Beschreibung ab, wie
auch vom Zeitpunkt der Erstellung. Damit alles zu unserem Beispiel passt,
müssen wir ein wenig tricksen:
$ git commit --amend -m Shakespeare # Ändere die Bemerkung.
$ git filter-branch --env-filter 'export
GIT_AUTHOR_DATE="Fri 13 Feb 2009 15:31:30 -0800"
GIT_AUTHOR_NAME="Alice"
GIT_AUTHOR_EMAIL="alice@example.com"
GIT_COMMITTER_DATE="Fri, 13 Feb 2009 15:31:30 -0800"
GIT_COMMITTER_NAME="Bob"
GIT_COMMITTER_EMAIL="bob@example.com"' # Manipuliere Zeitstempel und Autor.
$ find .git/objects -type f
Du solltest nun +.git/objects/49/993fe130c4b3bf24857a15d7969c396b7bc187+
finden, was dem SHA1-Hash-Wert seines Inhalts entspricht:
"commit 158" NUL
"tree 05b217bb859794d08bb9e4f7f04cbda4b207fbe9" LF
"author Alice <alice@example.com> 1234567890 -0800" LF
"committer Bob <bob@example.com> 1234567890 -0800" LF
LF
"Shakespeare" LF
Wie vorhin, kannst Du 'zpipe' oder 'cat-file' benutzen um es für Dich zu
überprüfen.
Das ist der erste 'Commit' gewesen, deshalb gibt es keine
Eltern-'Commits'. Aber spätere 'Commits' werden immer mindestens eine Zeile
enthalten, die den Eltern-'Commit' identifiziert.
=== Von Magie nicht zu unterscheiden ===
Git's Geheimnisse scheinen zu einfach. Es sieht so aus als müsste man nur
ein paar Kommandozeilenskripte zusammenmixen, einen Schuß C-Code hinzufügen
und innerhalb ein paar Stunden ist man fertig: eine Mischung von
grundlegenden Dateisystemoperationen und SHA1-Hash-Berechnungen, garniert
mit Sperrdateien und Synchronisation für Stabilität. Tatsächlich beschreibt
dies die früheste Version von Git. Nichtsdestotrotz, abgesehen von
geschickten Verpackungstricks um Speicherplatz zu sparen und geschickten
Indizierungstricks um Zeit zu sparen, wissen wir nun, wie Git gewandt ein
Dateisystem in eine Datenbank verwandelt, das perfekt für eine
Versionsverwaltung geeignet ist.
Angenommen, wenn irgendeine Datei in der Objektdatenbank durch einen
Laufwerksfehler zerstört wird, dann wird sein SHA1-Hash-Wert nicht mehr mit
seinem Inhalt übereinstimmen und uns sagen, wo das Problem liegt. Durch
Bilden von SHA1-Hash-Werten aus den SHA1-Hash-Werten anderer Objekte,
erreichen wir Integrität auf allen Ebenen. 'Commits' sind elementar, das
heißt, ein 'Commit' kann niemals nur Teile einer Änderung speichern: wir
können den SHA1-Hash-Wert eines 'Commits' erst dann berechnen und speichern,
nachdem wir bereits alle relevanten 'Tree'-Objekte, 'Blob'-Objekte und
Eltern-'Commits' gespeichert haben. Die Objektdatenbank ist immun gegen
unerwartete Unterbrechungen wie zum Beispiel einen Stromausfall.
Wir können sogar den hinterhältigsten Gegnern widerstehen. Stell Dir vor,
jemand will den Inhalt einer Datei ändern, die in einer älteren Version
eines Projekt liegt. Um die Objektdatenbank intakt aussehen zu lassen,
müssten sie außerdem den SHA1-Hash-Wert des korrespondierenden 'Blob'-Objekt
ändern, da die Datei nun eine geänderte Zeichenfolge enthält. Das heißt
auch, dass sie jeden SHA1-Hash-Wert der 'Tree'-Objekte ändern müssen, welche
dieses Objekt referenzieren und demzufolge alle SHA1-Hash-Werte der
'Commit'-Objekte, welche diese 'Tree'-Objekte beinhalten, zusätzlich zu
allen Abkömmlingen dieses 'Commits'. Das bedeutet auch, dass sich der
SHA1-Hash-Wert des offiziellen HEAD von dem des manipulierten 'Repository'
unterscheidet. Folgen wir dem Pfad der differierenden SHA1-Hash-Werte,
finden wir die verstümmelte Datei, wie auch den 'Commit', in dem sie
erstmals auftauchte.
Kurz gesagt, so lange die 20 Byte, welche den SHA1-Hash-Wert des letzen
'Commit' repräsentieren sicher sind, ist es unmöglich ein Git 'Repository'
zu fälschen.
Was ist mit Git's berühmten Fähigkeiten? 'Branching'? 'Merging'? 'Tags'? Nur
Kleinigkeiten. Der aktuelle HEAD wird in der Datei +.git/HEAD+ gehalten,
welche den SHA1-Hash-Wert eines 'Commit'-Objekts enthält. Der SHA1-Hash-Wert
wird während eines 'Commit' aktualisiert, genauso bei vielen anderen
Anweisungen. 'Branches' sind fast das selbe: sie sind Dateien in
+.git/refs/heads+. 'Tags' ebenso: sie stehen in +.git/refs/tags+ aber sie
werden durch einen Satz anderer Anweisungen aktualisiert.
Jump to Line
Something went wrong with that request. Please try again.