multi-pack-index 是什么,为什么 pack 一多 git 就需要它?这篇造 20 个小 pack,解析 midx 文件的头部和四个块,验证偏移,测有无 midx 的查找耗时,再用 multi-pack-index repack 与 expire 把小 pack 合并。
$ ls .git/objects/pack/*.pack | wc -l; git count-objects -v | grep -E "^(count|in-pack|packs)"
20
count: 0
in-pack: 3060
packs: 20每个 pack 有自己的 .idx,查一个对象要依次在多个 idx 里找。multi-pack-index 把所有 pack 的对象放进一张全局索引:去重的 hash 表、全局 fanout、每个对象所在的 pack 和偏移,查一次就知道在哪。
$ git multi-pack-index write; ls -l .git/objects/pack/multi-pack-index | awk '{print $5, $9}'
87796 .git/objects/pack/multi-pack-index
签名 b'MIDX' 版本 1 哈希版本 1(1 = SHA-1) 块数 4 基础 midx 数 0 pack 数 20
0000 4d 49 44 58 01 01 04 00 00 00 00 14 50 4e 41 4d MIDX........PNAM
0010 00 00 00 00 00 00 00 48 4f 49 44 46 00 00 00 00 .......HOIDF....
…(共 87796 字节)
块 PNAM 偏移 72 长度 1000
块 OIDF 偏移 1072 长度 1024
块 OIDL 偏移 2096 长度 61200
块 OOFF 偏移 63296 长度 24480头部 12 字节:签名 MIDX、版本、哈希版本、块数、基础 midx 数、pack 数;然后是块表(每个块的 4 字节 id 加 8 字节偏移),再是 PNAM(pack 名字)、OIDF(fanout)、OIDL(hash 表)、OOFF(pack 编号加偏移)。
PNAM:20 个 pack 名字(按字典序),前 3 个:
pack-05d19cb0c0f12ec488785d2814ac095f8b949eb6.idx
pack-0b99932a485532f20674488e7c3b2005e1dd487a.idx
pack-0c044d7a4fe72a4543eb851cb28c8ab13f17f581.idx
OIDF 末项 = 3060 个对象(去重后);各 pack 的 in-pack 之和: 3060
OIDL 升序排列: True
目标 ee29687a3197…:OOFF 里 pack 编号 19 → pack-fa345978418344774a83c897ef5c20e1f6fe9279.idx,偏移 3838
该 pack 里 git verify-pack 给出的偏移 3838 一致: True
$ git multi-pack-index verify 2>&1; echo "verify 退出码 $?"
verify 退出码 0midx 里记录的 pack 编号与偏移,和 git verify-pack 给出的偏移完全一致;git multi-pack-index verify 通过。
20 个 pack,使用 midx : 14.3 ms 20 个 pack,不使用 midx(core.multiPackIndex=false): 14.6 ms
本例 20 个 pack、3060 个对象,有没有 midx 查找耗时没有差别。midx 的价值在于 pack 很多的大仓库,以及位图等需要统一对象顺序的功能(见后面 reachability bitmap 一篇)。
现有 20 个 pack,大小 9,250 ~ 9,858 字节,合计 191,084
$ git multi-pack-index repack --batch-size=1; ls .git/objects/pack/*.pack | wc -l
20
--batch-size=1:没有产生新 pack,pack 数仍是 20
$ git multi-pack-index repack --batch-size=30000; ls .git/objects/pack/*.pack | wc -l
21
$ git multi-pack-index expire; ls .git/objects/pack/*.pack | wc -l; git multi-pack-index verify && echo verify 通过; git count-objects -v | grep -E "^(in-pack|packs)"
17
verify 通过
in-pack: 3060
packs: 17
$ git multi-pack-index repack --batch-size=60000; ls .git/objects/pack/*.pack | wc -l
18
$ git multi-pack-index expire; ls .git/objects/pack/*.pack | wc -l; git multi-pack-index verify && echo verify 通过; git count-objects -v | grep -E "^(in-pack|packs)"
11
verify 通过
in-pack: 3060
packs: 11multi-pack-index repack --batch-size=N 选出一批小 pack 合成一个新 pack,expire 再删除对象已被完全覆盖的旧 pack。不需要像 git gc 那样重写全部对象,适合大仓库的增量维护。