© DedMityay/Shutterstock.com
Wie Git intern funktioniert

Unter der Haube


In diesem Artikel erkläre ich Ihnen, wie Git intern Änderungen speichert und nachverfolgt, und was Branches und Tags sind. Dieses Wissen ist wichtig, wenn Sie erweiterte Funktionen wie Rebase, Reset oder Cherry Picking verwenden.

Beim Umstieg auf Git lernen die meisten sehr schnell, dass man damit sehr mächtige Dinge tun kann: den Verlauf manipulieren, Commits nachbessern und zusammenfassen oder einzelne Commits in andere Branches übertragen. Die ersten Versuche, dies in der Praxis anzuwenden, sind dann aber häufig frustrierend. Das liegt normalerweise daran, dass noch nicht richtig verstanden wurde, wie Git intern funktioniert. Dabei ist Git eigentlich ganz einfach. Man muss nur zwei Konzepte verstanden haben: wie die Hashfunktion für Dateien funktioniert und was ein gerichteter Graph beziehungsweise Baum ist

Der Hash

Ein Hash ist ein kryptografischer Prüfwert zur Integritätsprüfung von – unter anderem – Dateien. Durch Vergleich des Hashwerts kann man feststellen, ob sich eine Datei geändert hat. Eine Datei ergibt immer nur dann denselben Hashwert, wenn sie nicht verändert wurde.

In Git wird seit jeher der Secure Hash Algorithm (SHA-1) verwendet. Der ursprüngliche SHA wurde wegen eines Konstruktionsfehlers schon 1995 korrigiert und spielt deswegen keine Rolle mehr. Er ist heute als SHA-0 bekannt, die korrigierte Variante als SHA-1.

Der SHA-1 weist aber kryptografische Schwächen auf und wird deshalb langfristig in Git von SHA-256 abgelöst. Mit der aktuellen Git-Version (2.29), kann man sich erstmals beim Anlegen eines neuen Repository entscheiden, ob man das Repository mit SHA-1 oder SHA-256 initialisieren möchte:

$ git init --object-format=sha256

Im Unterschied zu einem SHA-1-Prüfwert besteht der SHA-256 aus 64 anstelle von 40 Zeichen. Ansonsten merkt man als Benutzer keinen Unterschied. Aktuell können nur komplett neue Repositories mit SHA-256 betrieben werden. In zukünftigen Versionen von Git soll dann auch ein gemischter Betrieb und irgendwann eine komplette Umstellung erfolgen.

Der gerichtete azyklische Graph

Das zweite Konzept, das man kennen sollte, ist das eines gerichteten azyklischen Graphen (in Englisch Directed Acyclic Graph – kurz DAG). Es handelt sich dabei um einen Baum aus Knoten (Vertex oder Node), die durch Kanten (Edge) verbunden sind. Ein Knoten, der immer mit seinem Vorgänger verbunden ist, weist deshalb eine gerichtete Kante auf: Diese zeigt immer in eine Richtung. Folgt man der Richtung, kommt man irgendwann am Ursprung des Graphen an (Origin Vertex). Der Graph ist also kreisfrei beziehungsweise azyklisch (Abb. 1).

kaufmann_haube_1.tif_fmt1.jpgAbb. 1: Ein gerichteter azyklischer Graph

Als Entwickler kennen Sie solche Bäume zur Genüge: Das Dateisystem eines Computers mit Ordnern und Dateien ist ein gerichteter azyklischer Graph, aber auch XML-Knoten in einem Dokument. Stellen Sie sich einfach einen gewurzelten Baum vor, bei dem Sie von jedem Knoten immer zum Vorgänger gelangen, bis Sie bei der Wurzel angekommen sind.

Das Repository

Wenn Sie ein Repository mit git init initialisieren, legt Git einen versteckten Ordner .git an, in dem sich einige Unterordner und Dateien befinden (Abb. 2). Dieser Ordner ist das Einzige, was das Repository von einem normalen Verzeichnis unterscheidet. Wenn Sie ihn löschen, dann ist Ihr Repository wieder ein ganz gewöhnliches Verzeichnis.

kaufmann_haube_2.tif_fmt1.jpgAbb. 2: Ein Repository initialisieren

Im Ordner .git gibt es eine Datei HEAD. Diese enthält eine Referenz auf einen Branch unter refs/heads:

$ cat .git/HEAD > ref: refs/heads/main

In meinem Beispiel heißt der Branch main. Diesen sehen Sie auch in Ihrer Konsole, wenn diese eine Ausgabe für Git unterstützt. Wie der Branch heißt, können Sie seit der Version 2.28 konfigurieren. Davor hieß er standardmäßig immer master.

$ git config --global init.defaultbranch > main

Der Branch in HEAD zeigt aber noch ins Leere: Wie Sie in Abbildung 2 sehen können, ist der Ordner refs/heads noch leer. Das liegt daran, dass es noch keinen Commit gibt.

Der Aufbau eines Commits

Um einen Commit zu erstellen, benötigen Sie zunächst eine Datei mit Änderungen. Mit dem Befehlt git hash-object können Sie sich dann den Hash für die Datei ausgeben lassen:

$ mkdir txt $ echo "Zeile 1" > txt/MyFile.txt $ git hash-object txt/MyFile.txt > b1367440191d3abc86bb46f955d140fec7eef42a

Ändert sich die Datei, dann ändert sich auch ihr Hashwert:

$ echo "Zeile 2" >> txt/MyFile.txt $ git hash-object txt/MyFile.txt > 7b099c82b3f0c696b4394e487f04808d90cacc5f $ echo "Zeile 3" >> txt/MyFile.txt $ git hash-object txt/MyFile.txt > a3858754a246be068e1c58ccd128e2ea56850227

Wenn Sie nun die Datei mit git add dem Index hinzufügen, dann befindet sich im Ordner .git/objects ein Unterordner, der aus den ersten zwei Zeichen des Hashwerts der Datei besteht und eine Datei enthält. Der Dateiname besteht aus den letzten 38 Zeichen des SHA-1-Werts (Abb. 3). Bei SHA-256 besteht er aus den letzten 62 Zeichen.

kaufmann_haube_3.tif_fmt1.jpgAbb. 3: Eine Datei wird dem Index hinzugefügt

Wenn Sie sich den Inhalt der Datei ansehen, entdecken Sie darin nur kryptische Zeichen. Das liegt daran, dass Git den Inhalt komprimiert. Um sich den Inhalt der Datei anzusehen, können Sie den Befehl git cat-file verwenden. Er übernimmt als Parameter nicht den Pfad zu der Datei unter .git/objects, sondern den Hashwert. Sie können den Hashwert komplett angeben oder nur die ersten Zeichen. Es müssen mindestens vier Zeichen sein – außerdem genug Zeichen, damit im Repository die Referenz eindeutig ist. Da es sich um das erste Objekt handelt, reichen auf jeden Fall vier Zeichen aus. Mit dem Parameter -t bekommen Sie den Typen des Objekts – mit dem Parameter -p den kompletten Inhalt der Datei:

$ git cat-file -t a385 > blob $ git cat-file -p a385 > Zeile 1 > Zeile 2 > Zeile 3

Git hat also die komplette Datei mit Ihrem Hashwert als Dateiname in einem Blob-Objekt gespeichert. Der Befehl git add hat dabei intern git hash-object mit der Option -w (write) aufgerufen. Außerdem hat Git auch noch die Funktion git update-index aufgerufen. Die Datei .git/index enthält jetzt einen Eintrag. Sie können sich den Inhalt des Index mit dem Befehl git ls-files ansehen:

$ git ls-files --stage > 100644 a3858754a246be068e1c58ccd128e2ea56850227 0 txt/MyFile.txt

Der Index enthält also den Hash, den Pfad und die Berechtigungsmaske 100644. Diese besagen nur, dass es sich um eine reguläre, nicht ausführbare Datei handelt. 100755 wäre eine reguläre ausführbare Datei, 120000 eine...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang