GIT Deep Dive

Tobias Janssen

Backend Software-Developer und GIT Enthusiast bei traperto GmbH

Wenn es um die Versionskontrolle GIT geht, fallen zwangsläufig Begriffe wie Commit oder Befehle wie git add.

In diesem Blogpost möchte ich euch vorstellen was passiert, wenn wir Änderungen vorgenommen haben und diese committen möchten.

Für diesen Blogpost sind GIT-Erfahrungen von Vorteil. Bei Fragen gerne per Twitter oder per Email melden.

Allgemeines

Sobald mit git init ein Repository erstellt wurde, wird ein verstecktes .git Verzeichnis angelegt. Der Inhalt besteht aus folgenden Dateien und Ordnern:

├── HEAD
├── config
├── description
├── hooks
│   ├──...
├── info
│   └── exclude
├── objects
│   ├── info
│   └── pack
└── refs    
    ├── heads
    │   └── main    
    └── tags

Wirft man einen Blick auf die HEAD Datei, steht folgendes dort drin:

ref: refs/heads/main

Hier steht eine Referenz zu einer Datei main in dem Unterverzeichnis refs/heads/. 
Nach der Initialisierung wird automatisch der Branch Main erzeugt. Erstelle ich nun einen neuen Branch Namens featureBranch und checke diesen aus, steht in der Head Datei folgendes:

ref: refs/heads/featureBranch

Das bedeutet, dass sich in der Datei HEAD immer die aktuelle Referenz befindet, mit der ich arbeite → der aktuell ausgecheckte Branch.

Änderungen dem Index hinzufügen

Nachdem wir Änderungen innerhalb unseres Repositories vorgenommen haben, würden wir diese mit git add der Staging Area hinzufügen. Damit wären die Änderungen bereit für einen Commit. Bei einem git add passiert jedoch mehr. Es werden Befehle wie git hash-object und git update index verwendet. Zur Erklärung dieser Befehle habe ich als Beispiel eine Datei Namens Buch.txt erstellt.

git hash-object

Alle Änderungen in git werden über sogenannte Hash Objekte dargestellt.
Mit dem Befehl git hash-object wird aus einer Änderung ein Hash erzeugt.

$git hash-object -w Buch.txt
 
9a8144d20e269e8f3152ddb578d9f20aae48106c

Sehen wir uns jetzt nochmal die Verzeichnisstruktur von git an, sehen wir im Object Ordner einen neuen Ordner /9a und eine Datei 8144d20e269e8f3152ddb578d9f20aae48106c.

├── HEAD
├── config
├── description
├── hooks
│   ├── ...
├── info
│   └── exclude
├── objects
│   ├── 9a
│   │   └── 8144d20e269e8f3152ddb578d9f20aae48106c
│   ├── info
│   └── pack
└── refs    
    ├── heads
    │   └── main    
    └── tags

Innerhalb der Datei 8144d20e269e8f3152ddb578d9f20aae48106c steht folgendes:

x^AKÊÉOR0`^@^@  °^Að

Git komprimiert die Änderungen hinter dem erzeugten Hash, damit das Repository bei vielen Änderungen nicht zu schnell zu groß wird.
Mit dem Befehl git cat-file <Hash> wird die Datei entpackt und zeigt folgendes an:

$git cat-file -p 9a8144d20e269e8f3152ddb578d9f20aae48106c
 
hi, ich bins. Tobi ;)

Wir sehen also den Inhalt / Änderungen innerhalb des Hash Objekts. Der Parameter -p steht für pretty und gibt den Inhalt mit der richtigen Formatierung aus. Mit git cat-file -t <hash> wird uns der Typ des Hash Objekts zurückgegeben. In unserem Fall ist es der Typ blob.

Git speichert also alle Änderung einer Datei als Datei in den Objects Ordner.

git update-index

Wie oben einmal kurz beschrieben, werden bei einem git add die Dateien in die Staging Area verschoben. Diese Staging Area wird auch Index genannt. Bei einem git add wird also zusätzlich neben der erzeugten Datei ein Index erstellt/aktualisiert. Wie der Index manuell aktualisiert wird zeigt folgender Befehl:

$git update-index --add --cacheinfo 100644 9a8144d20e269e8f3152ddb578d9f20aae48106c Buch.txt

Bei der Zahl 100644 handelt es sich um eine Dateiberechtigung, auf die hier nicht weiter eingegangen wird.
Mit git status sehen wir dann, dass die Datei der Staging Area hinzugefügt wurde.

Commit erzeugen

Nun wollen wir einen Commit erzeugen. Ein git commit besteht aus drei Teilen:

1. git write tree
2. git commit tree
3. git update ref

git write tree

Ein Tree ist ebenfalls ein git Objekt, welches es ermöglicht eine Gruppe von Dateien zusammenzulegen. 
Innerhalb eines Trees sind alle geänderten Dateien als Hash Objekte vorhanden. Wie wir aus einzelnen Änderungen ein git Objekt erzeugen, haben wir weiter oben gelernt.

Mit dem Befehl git write tree wird ein Tree erzeugt. Dieser Befehl erzeugt einen Hash und auch zwei neue Ordner sowie neue Dateien im Object Ordner.

$git write-tree
ec2f676d206011978bc1bbf0341f0931c1b12d2a
 
$git cat-file -t ec2f676d206011978bc1bbf0341f0931c1b12d2a
tree
 
$git cat-file -p ec2f676d206011978bc1bbf0341f0931c1b12d2a
04000 tree 9a8144d20e269e8f3152ddb578d9f20aae48106c Buch.txt
git commit tree

Ein Commit Tree, oder auch Commit Objekt genannt, beinhaltet neben dem oben erstellten Tree zusätzliche Informationen darüber wer die Änderungen, wann und warum vorgenommen hat.

$git commit-tree ec2f676d206011978bc1bbf0341f0931c1b12d2a -m "mein commit"

6d1dab6db3fb319207b7aa8080afa461ae8640c0
  • Wer die Änderung vorgenommen hat, ist in der git config als Author beschrieben
  • Wann: Bei dem Erstellen des Commit Objekt wird der Zeitpunkt festgehalten
  • Warum eine Änderung vorgenommen wurde, wird bei jedem Commit als Nachricht mit angegeben

Wie zuvor einmal erwähnt, kann mit git cat-file -t herausgefunden werden um was für ein Typ von Hash Objekt es sich handelt.

$git cat-file -t 6d1dab6db3fb319207b7aa8080afa461ae8640c0
commit
 
$git cat-file -p 6d1dab6db3fb319207b7aa8080afa461ae8640c0
tree ec2f676d206011978bc1bbf0341f0931c1b12d2a
author Tobias Janssen <example@example.com> 1614799314 +0100
committer Tobias Janssen <example@example.com> 1614799314 +0100
 
mein commit

Hierbei handelt es sich um ein Hash Objekt von Typ Commit. Auch diese Datei finden wir in unserer Ordnerstruktur unter Objects wieder.

git update ref

Zu guter letzt muss die aktuelle Referenz auf den Commit Hash aktualisiert werden. 

$git update ref ref/heads/main 6d1dab6db3fb319207b7aa8080afa461ae8640c0

Mit git log sehen wir nun unseren erstellten Commit auf dem Main Branch.

6d1dab6 (HEAD -> main) mein commit

Ein Branch ist also nichts anderes als eine Referenz auf einen Hash, der vom Typ Commit ist.