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.