A Little Big Git
I’ve poked a bit with git and decided to have a bit of fun with git trees and git object compression.
Big Git:
A 120KB git that consumes 32GB of diskspace upon clone or checkout.
99.9996% compression factor.
A wonderful zip bomb 🤯.
And you can trivially make a similar sized git consume more diskspace than ever manufactured.
A Matryoshka doll
of git references and object compression,
expanding like crazy.
Or to quote Nine Inch Nails:
A copy of a copy of a…
I’m not sure how obvious or surprising this is to your average folks. This is super obvious to most Git experts, but I talked to tech people who found it surprising and/or funny.
If you found this interesting, you may also be interested in A Self Referential Git!
Table of Contents
- The Gist of It
- Exploring Git Objects with git cat-file
- Git Tree Compression
- Git File Compression
- Sparse files
The Gist of It
Git compresses objects with zlib. As long as your objects are highly compressible, zlib will do wonders.
-
Step 0 is to create a file that is highly compressible.
-
Step 1 is to create a tree (directory) that is highly compressible, by containing several instances of the file, with simple file names.
-
Step
iis to create a tree (directory) that is highly compressible, by containing several instances of the directoryi-1with simple file names. -
Final step
Nnow hasN+1layers of zlib compression, and an exponential amount of files…
Git Object view:
ddbc38 (commit)
|
+-> fa1334 (tree)
|
+-> 5e49c (tree) dir0
-> 5e49c (tree) dir1 [and so on...]
|
+-> 7614aa (tree) dir0
-> 7614aa (tree) dir1 [and so on...]
|
+-> d52dd (tree) dir0
-> d52dd (tree) dir1 [and so on...]
|
+-> 5c08a (tree) dir0
-> 5c08a (tree) dir1 [and so on...]
|
+-> ea8e4 (blob) file-0.bin
-> ea8e4 (blob) file-1.bin [and so on...]
Exploring Git Objects with git cat-file
You can use git cat-file to investigate the git:
git cat-file -p ddbc38b347441733e08053d6e17618910c9354db
tree fa13348bf59dc645af77b34d818d37d2c9d33812
parent b7c4c7b7be63cef7e944f8d0b942ec77d0d35963
author Generator <tool@synthetic.local> 1779202073 +0000
committer Generator <tool@synthetic.local> 1779202073 +0000
Synthetic structure generation step
git cat-file -p fa13348bf59dc645af77b34d818d37d2c9d33812
040000 tree 5e49c320ca4abb26c91b4804352bffadb0886597 dir-0
040000 tree 5e49c320ca4abb26c91b4804352bffadb0886597 dir-1
040000 tree 5e49c320ca4abb26c91b4804352bffadb0886597 dir-2
040000 tree 5e49c320ca4abb26c91b4804352bffadb0886597 dir-3
040000 tree 5e49c320ca4abb26c91b4804352bffadb0886597 dir-4
040000 tree 5e49c320ca4abb26c91b4804352bffadb0886597 dir-5
040000 tree 5e49c320ca4abb26c91b4804352bffadb0886597 dir-6
040000 tree 5e49c320ca4abb26c91b4804352bffadb0886597 dir-7
git cat-file -p 5e49c320ca4abb26c91b4804352bffadb0886597
040000 tree 7614aacc2886b51bd0042fc5dc60ba171fc2ef31 dir-0
040000 tree 7614aacc2886b51bd0042fc5dc60ba171fc2ef31 dir-1
040000 tree 7614aacc2886b51bd0042fc5dc60ba171fc2ef31 dir-2
040000 tree 7614aacc2886b51bd0042fc5dc60ba171fc2ef31 dir-3
040000 tree 7614aacc2886b51bd0042fc5dc60ba171fc2ef31 dir-4
040000 tree 7614aacc2886b51bd0042fc5dc60ba171fc2ef31 dir-5
040000 tree 7614aacc2886b51bd0042fc5dc60ba171fc2ef31 dir-6
040000 tree 7614aacc2886b51bd0042fc5dc60ba171fc2ef31 dir-7
Git Tree Compression
A git tree is basically just a list of references,
either tree or object:
git cat-file -p 5c08a7a43a6cfd7975a8f0c839d0dd3c4eb9e0bf
100644 blob ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b file-0.bin
100644 blob ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b file-1.bin
100644 blob ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b file-2.bin
100644 blob ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b file-3.bin
100644 blob ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b file-4.bin
100644 blob ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b file-5.bin
100644 blob ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b file-6.bin
100644 blob ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b file-7.bin
Or in hex for the the more hacker-inclined readers:
git cat-file tree 5c08a7a43a6cfd7975a8f0c839d0dd3c4eb9e0bf | xxd
00000000: 3130 3036 3434 2066 696c 652d 302e 6269 100644 file-0.bi
00000010: 6e00 ea8e 482b 990b 87c0 f69d 29fd 1dd6 n...H+......)...
00000020: a41d 0f1a 514b 3130 3036 3434 2066 696c ....QK100644 fil
00000030: 652d 312e 6269 6e00 ea8e 482b 990b 87c0 e-1.bin...H+....
00000040: f69d 29fd 1dd6 a41d 0f1a 514b 3130 3036 ..).......QK1006
00000050: 3434 2066 696c 652d 322e 6269 6e00 ea8e 44 file-2.bin...
00000060: 482b 990b 87c0 f69d 29fd 1dd6 a41d 0f1a H+......).......
00000070: 514b 3130 3036 3434 2066 696c 652d 332e QK100644 file-3.
00000080: 6269 6e00 ea8e 482b 990b 87c0 f69d 29fd bin...H+......).
00000090: 1dd6 a41d 0f1a 514b 3130 3036 3434 2066 ......QK100644 f
000000a0: 696c 652d 342e 6269 6e00 ea8e 482b 990b ile-4.bin...H+..
000000b0: 87c0 f69d 29fd 1dd6 a41d 0f1a 514b 3130 ....).......QK10
000000c0: 3036 3434 2066 696c 652d 352e 6269 6e00 0644 file-5.bin.
000000d0: ea8e 482b 990b 87c0 f69d 29fd 1dd6 a41d ..H+......).....
000000e0: 0f1a 514b 3130 3036 3434 2066 696c 652d ..QK100644 file-
000000f0: 362e 6269 6e00 ea8e 482b 990b 87c0 f69d 6.bin...H+......
00000100: 29fd 1dd6 a41d 0f1a 514b 3130 3036 3434 ).......QK100644
00000110: 2066 696c 652d 372e 6269 6e00 ea8e 482b file-7.bin...H+
00000120: 990b 87c0 f69d 29fd 1dd6 a41d 0f1a 514b ......).......QK
For a small tree, we “only” get a 73% (304 to 81 byte) size reduction;
git cat-file tree 5c08a7a43a6cfd7975a8f0c839d0dd3c4eb9e0bf | wc -c
304
wc -c .git/objects/5c/08a7a43a6cfd7975a8f0c839d0dd3c4eb9e0bf
81 .git/objects/5c/08a7a43a6cfd7975a8f0c839d0dd3c4eb9e0bf
But keep in mind that if we spam a few more entries to the tree the tree will just grow bigger and bigger while barely increasing in size.
Git File Compression
For my example file we get about 99.6% file compression:
git cat-file -p ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b | wc -c
1048576
wc -c .git/objects/ea/8e482b990b87c0f69d29fd1dd6a41d0f1a514b
4419
The file is just a megabyte of incremental bytes;
git cat-file -p ea8e482b990b87c0f69d29fd1dd6a41d0f1a514b | xxd | head
00000000: 0001 0203 0405 0607 0809 0a0b 0c0d 0e0f ................
00000010: 1011 1213 1415 1617 1819 1a1b 1c1d 1e1f ................
00000020: 2021 2223 2425 2627 2829 2a2b 2c2d 2e2f !"#$%&'()*+,-./
00000030: 3031 3233 3435 3637 3839 3a3b 3c3d 3e3f 0123456789:;<=>?
00000040: 4041 4243 4445 4647 4849 4a4b 4c4d 4e4f @ABCDEFGHIJKLMNO
00000050: 5051 5253 5455 5657 5859 5a5b 5c5d 5e5f PQRSTUVWXYZ[\]^_
00000060: 6061 6263 6465 6667 6869 6a6b 6c6d 6e6f `abcdefghijklmno
00000070: 7071 7273 7475 7677 7879 7a7b 7c7d 7e7f pqrstuvwxyz{|}~.
00000080: 8081 8283 8485 8687 8889 8a8b 8c8d 8e8f ................
00000090: 9091 9293 9495 9697 9899 9a9b 9c9d 9e9f ................
Sparse files
In theory you could just have zeros in the files.
However several file systems support Sparse files. A simple files only containing zeros won’t actually allocate any space on storage.
So if the goal is for git clone, git checkout, etc. to actually consume
a lot of space, you need files to contain some pattern not supported by
the sparse file handling.
Not an expert on different file systems, but I think writing ones 0xff
or incremental 0x01 0x02 0x03.. will allocate space on most file systems.