Git
Chapters ▾ 2nd Edition

10.4 Git Interna - Packdateien (engl. Packfiles)

Packdateien (engl. Packfiles)

Wenn Sie alle Anweisungen im Beispiel aus dem vorherigen Abschnitt befolgt haben, sollten Sie 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. Jetzt fügen Sie 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 'added repo.rb'
[master 484a592] added 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 Sie sich den resultierenden Baum ansehen, sehen Sie den SHA-1-Wert, der für Ihr 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

Sie können dann git cat-file verwenden, um zu sehen, wie groß das Objekt ist:

$ git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5
22044

Ändern Sie nun diese Datei ein wenig und sehen Sie, was passiert:

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

Überprüfen Sie den von diesem letzten Commit erstellten Baum. Dabei werden Sie 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 Sie nur eine einzelne Zeile am Ende einer Datei mit 400 Zeilen hinzugefügt haben, diesen neuen Inhalt als ein komplett neues Objekt gespeichert hat:

$ git cat-file -s b042a60ef7dff760008df33cee372b945b6e884e
22054

Sie haben jetzt zwei nahezu identische 22-KB-Objekte auf Ihrer 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 Sie zu viele lose Objekte haben, wenn Sie den Befehl git gc manuell ausführen oder wenn Sie einen Push an einen Remote-Server senden. Um zu sehen, was passiert, können Sie Git manuell auffordern, die Objekte zu packen, indem Sie den Befehl git gc aufrufen:

$ 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 Sie in Ihr objects-Verzeichnis schauen, werden Sie feststellen, dass die meisten Ihrer 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 Sie zuvor erstellt haben. Da Sie sie nie zu Commits hinzugefügt haben, gelten sie als unreferenziert und sind nicht in Ihrem neuen Packfile gepackt.

Die anderen Dateien sind Ihr neues packfile und ein Index. Das packfile ist eine einzelne Datei, die den Inhalt aller Objekte enthält, die aus Ihrem Dateisystem entfernt wurden. Der Index ist eine Datei, die Offsets in diesem packfile enthält, sodass Sie schnell nach einem bestimmten Objekt suchen können. 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. Sie haben die Festplattennutzung halbiert, indem Sie Ihre Objekte gepackt haben.

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. Sie können im packfile schauen und sehen, was Git getan hat, um Platz zu sparen. Mit dem Befehl git verify-pack können Sie 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 033b4-Blob, der, wenn Sie sich erinnern, die erste Version Ihrer repo.rb-Datei war, auf den b042a-Blob, der die zweite Version der Datei war. Die dritte Spalte in der Ausgabe ist die Größe des Objekts im Paket. Sie können 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 Sie mit größter Wahrscheinlichkeit einen schnelleren Zugriff auf die neueste Version der Datei benötigen.

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