一个 1 MB 的文件在 git 里占多少空间?每改一行会多占多少?这篇测不同类型文件的 blob 大小、每次修改的松散对象增长、gc 之后的缩小,以及压缩级别和随机二进制文件的 delta。
$ git add -A text.txt 原始 1,000,050 字节 → 松散对象 430,279 字节 (43.0%) random.bin 原始 1,000,000 字节 → 松散对象 1,000,324 字节 (100.0%) zeros.bin 原始 1,000,000 字节 → 松散对象 4,395 字节 (0.4%)
blob 先 zlib 压缩再存。文本约压到 43%,随机字节没法压缩,反而因为头部多出几百字节,全零几乎不占空间。不要拿原文件大小估算仓库大小。
提交 v0 之后 objects 目录: 430,446 字节 提交 v1 之后: 860,986 字节(这次新增 430,540) 提交 v2 之后: 1,291,543 字节(这次新增 430,557) 提交 v3 之后: 1,722,106 字节(这次新增 430,563) 提交 v4 之后: 2,152,685 字节(这次新增 430,579) 提交 v5 之后: 2,583,273 字节(这次新增 430,588) $ git count-objects -v | grep -E '^(count|size|in-pack|size-pack)' count: 18 size: 2592 in-pack: 0 size-pack: 0 size-garbage: 0
每提交一个新版本,不管只改了一行还是一万行,都新增一个完整的压缩副本。1 MB 文本改 5 次,objects 目录就是 2.58 MB。松散对象本身没有跨版本的复用。
$ git gc -q; git count-objects -v | grep -E "^(count|size|in-pack|size-pack)" count: 0 size: 0 in-pack: 18 size-pack: 380 size-garbage: 0 objects 目录总大小: 391,354 字节(gc 前 2,583,273)
git gc 把对象打进 packfile,版本之间用 delta 编码。同样 6 个版本,目录从 2.58 MB 缩到 391 KB。这是 git 能放得下很长历史的原因(见后面 packfile 与 delta 的文章)。
level 0: 松散对象 1,000,219 字节 add 耗时 18 ms level 1: 松散对象 430,279 字节 add 耗时 25 ms level 4: 松散对象 390,613 字节 add 耗时 29 ms level 6: 松散对象 386,597 字节 add 耗时 32 ms level 9: 松散对象 386,597 字节 add 耗时 32 ms
core.compression 调的是 zlib 级别。默认松散对象用 level 1;提高到 4 省约 9%,再往上几乎无变化,耗时也只是十几毫秒的差别。真正的收益来自 delta,不是调高压缩级别。
gc 前 objects: 2,001,016 字节 $ git gc -q; git count-objects -v | grep -E "^(in-pack|size-pack)" in-pack: 6 size-pack: 978 gc 后 objects 目录: 1,003,371 字节(两个版本的原始字节共 2,000,000)
delta 按字节差异存储,与可不可压缩无关。1 MB 随机文件改 10 个字节,gc 之后两个版本只比一个版本多一点点。真正会让仓库膨胀的是整个重新生成的大文件(每次内容全变),而不是小改动。