Git
Chapters ▾ 2nd Edition

10.4 Git Interna - Packdateien (engl. Packfiles)

Packdateien (engl. Packfiles)

Wenn du alle Anweisungen im Beispiel aus dem vorherigen Abschnitt befolgt hast, solltest du jetzt ein Test-Git-Repository mit 11 Objekten haben – vier Blobs, drei Bäumen, drei Commits und einem Tag:

$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/95/85191f37f7b0fb9444f35a9bf50de191beadc2 # tag
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

Git komprimiert den Inhalt dieser Dateien mit zlib. Alle diese Dateien zusammen belegen nur 925 Byte. Füge jetzt dem Repository einige größere Inhalte hinzu, um eine interessante Funktion von Git zu demonstrieren. Zur Veranschaulichung fügen wir die Datei repo.rb aus der Grit-Bibliothek hinzu. Hierbei handelt es sich um eine 22-KB-Quellcodedatei:

$ curl https://raw.githubusercontent.com/mojombo/grit/master/lib/grit/repo.rb > repo.rb
$ git checkout master
$ git add repo.rb
$ git commit -m 'Create repo.rb'
[master 484a592] Create repo.rb
 3 files changed, 709 insertions(+), 2 deletions(-)
 delete mode 100644 bak/test.txt
 create mode 100644 repo.rb
 rewrite test.txt (100%)

Wenn du dir den resultierenden Baum ansiehst, siehst du den SHA-1-Wert, der für dein neues repo.rb-Blob-Objekt berechnet wurde:

$ git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5      repo.rb
100644 blob e3f094f522629ae358806b17daf78246c27c007b      test.txt

Du kannst dann git cat-file verwenden, um zu sehen, wie groß das Objekt ist:

$ git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5
22044

Ändere nun diese Datei ein wenig und schauen wir, was passiert:

$ echo '# testing' >> repo.rb
$ git commit -am 'Modify repo.rb a bit'
[master 2431da6] Modify repo.rb a bit
 1 file changed, 1 insertion(+)

Überprüfe den von diesem letzten Commit erstellten Baum. Dabei wirst du etwas Interessantes sehen:

$ git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92      new.txt
100644 blob b042a60ef7dff760008df33cee372b945b6e884e      repo.rb
100644 blob e3f094f522629ae358806b17daf78246c27c007b      test.txt

Der Blob ist jetzt ein anderer Blob. Das bedeutet, dass Git, obwohl du nur eine einzelne Zeile am Ende einer Datei mit 400 Zeilen hinzugefügt hast, diesen neuen Inhalt als ein komplett neues Objekt gespeichert hat:

$ git cat-file -s b042a60ef7dff760008df33cee372b945b6e884e
22054

Du hast jetzt zwei nahezu identische 22-KB-Objekte auf deiner Festplatte (jedes auf ca. 7 KB komprimiert). Wäre es nicht schön, wenn Git eines davon vollständig speichern könnte, aber dann das zweite Objekt nur als Delta zwischen dem ersten und dem anderen?

Wie sich herausstellen wird, geht das. Das ursprüngliche Format, in dem Git Objekte auf der Festplatte speichert, wird als „loses“ Objektformat bezeichnet. Allerdings packt Git gelegentlich mehrere dieser Objekte in eine einzige Binärdatei namens „packfile“, um Platz zu sparen und effizienter zu sein. Git tut dies, wenn du zu viele lose Objekte hast, wenn du den Befehl git gc manuell ausführst oder wenn du einen Push an einen Remote-Server sendest. Um zu sehen, was passiert, kannst du Git manuell auffordern, die Objekte zu packen, indem du den Befehl git gc aufrufst:

$ git gc
Counting objects: 18, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (18/18), done.
Total 18 (delta 3), reused 0 (delta 0)

Wenn du in dein objects Verzeichnis schaust, wirst du feststellen, dass die meisten deiner Objekte verschwunden sind und ein paar neue Dateien auftauchen:

$ find .git/objects -type f
.git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/info/packs
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack

Die verbleibenden Objekte sind die Blobs, auf die von keinem Commit referenziert werden. In diesem Fall die Blobs „what is up, doc?“ Und „test content“, die du zuvor erstellt hast. Da du sie nie zu Commits hinzugefügt hast, gelten sie als unreferenziert und sind nicht in deinem neuen Packfile gepackt.

Die anderen Dateien sind dein neues packfile und ein Index. Das packfile ist eine einzelne Datei, die den Inhalt aller Objekte enthält, die aus deinem Dateisystem entfernt wurden. Der Index ist eine Datei, die Offsets in diesem packfile enthält, sodass du schnell nach einem bestimmten Objekt suchen kannst. Ein weiterer Vorteil ist, dass, obwohl die Objekte auf der Festplatte vor dem Ausführen des Befehls gc insgesamt etwa 15 KB groß waren, die neue Paketdatei nur 7 KB groß ist. Du hast die Festplattennutzung halbiert, indem du deine Objekte gepackt hast.

Wie macht Git das? Wenn Git Objekte packt, sucht es nach Dateien mit ähnlichen Namen und Größen und speichert nur die Deltas von einer Version der Datei zur nächsten. Du kannst im packfile schauen und sehen, was Git getan hat, um Platz zu sparen. Mit dem Befehl git verify-pack kannst du sehen, was gepackt wurde:

$ git verify-pack -v .git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.idx
2431da676938450a4d72e260db3bf7b0f587bbc1 commit 223 155 12
69bcdaff5328278ab1c0812ce0e07fa7d26a96d7 commit 214 152 167
80d02664cb23ed55b226516648c7ad5d0a3deb90 commit 214 145 319
43168a18b7613d1281e5560855a83eb8fde3d687 commit 213 146 464
092917823486a802e94d727c820a9024e14a1fc2 commit 214 146 610
702470739ce72005e2edff522fde85d52a65df9b commit 165 118 756
d368d0ac0678cbe6cce505be58126d3526706e54 tag    130 122 874
fe879577cb8cffcdf25441725141e310dd7d239b tree   136 136 996
d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree   36 46 1132
deef2e1b793907545e50a2ea2ddb5ba6c58c4506 tree   136 136 1178
d982c7cb2c2a972ee391a85da481fc1f9127a01d tree   6 17 1314 1 \
  deef2e1b793907545e50a2ea2ddb5ba6c58c4506
3c4e9cd789d88d8d89c1073707c3585e41b0e614 tree   8 19 1331 1 \
  deef2e1b793907545e50a2ea2ddb5ba6c58c4506
0155eb4229851634a0f03eb265b69f5a2d56f341 tree   71 76 1350
83baae61804e65cc73a7201a7252750c76066a30 blob   10 19 1426
fa49b077972391ad58037050f2a75f74e3671e92 blob   9 18 1445
b042a60ef7dff760008df33cee372b945b6e884e blob   22054 5799 1463
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob   9 20 7262 1 \
  b042a60ef7dff760008df33cee372b945b6e884e
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob   10 19 7282
non delta: 15 objects
chain length = 1: 3 objects
.git/objects/pack/pack-978e03944f5c581011e6998cd0e9e30000905586.pack: ok

Hier verweist der Blob 033b4, der, wie du dich erinnerst, die erste Version deiner repo.rb Datei war, auf den Blob b042a, der die zweite Version der Datei war. Die dritte Spalte in der Ausgabe ist die Größe des Objekts im Paket. Du kannst also sehen, dass b042a 22 KB der Datei belegt, 033b4 jedoch nur 9 Byte. Interessant ist auch, dass die zweite Version der Datei als Ganzes gespeichert wird, während die Originalversion als Delta gespeichert wird. Dies liegt daran, dass du mit größter Wahrscheinlichkeit einen schnelleren Zugriff auf die neueste Version der Datei benötigst.

Das Schöne daran ist, dass es jederzeit umgepackt werden kann. Git packt deine Datenbank gelegentlich automatisch neu und versucht dabei immer, mehr Speicherplatz zu sparen. Du kannst das Packen jedoch auch jederzeit manuell durchführen, indem du git gc manuell ausführst.

scroll-to-top