packfile 里到底是什么?这篇不用 git 命令,而是用 Python 逐字节解析一个真实的 pack:文件头、每个对象条目的头部编码、OFS_DELTA 的偏移,甚至自己按 copy / insert 指令把 delta 还原,最后和 git verify-pack 对账。
$ git gc -q; ls .git/objects/pack; git count-objects -v | grep -E "^(count|in-pack)" pack-b125bfdeae8a910cf841b4d832bf429dd1e7e8d2.idx pack-b125bfdeae8a910cf841b4d832bf429dd1e7e8d2.pack pack-b125bfdeae8a910cf841b4d832bf429dd1e7e8d2.rev count: 0 in-pack: 15 0000 50 41 43 4b 00 00 00 02 00 00 00 0f 99 0c 78 9c PACK..........x. 0010 95 8b 41 0a c2 30 10 00 ef 79 45 ee 82 6c d2 4d ..A..0...yE..l.M …(共 4888 字节) 版本 2,对象数 15,文件 4,888 字节
pack 以 12 字节开头:魔数 PACK、版本号 2(4 字节大端)、对象个数(4 字节大端)。后面紧接着第一个对象条目。
偏移 类型 解压后大小 包内大小 头部字节 verify-pack 里的同一行
12 commit 201 140 2 verify-pack: commit 201 140
152 commit 201 140 2 verify-pack: commit 201 140
292 commit 201 141 2 verify-pack: commit 201 141
433 commit 201 140 2 verify-pack: commit 201 140
573 commit 153 112 2 verify-pack: commit 153 112
685 blob 7998 3846 3 verify-pack: blob 7998 3846
4531 tree 35 46 2 verify-pack: tree 35 46
4577 tree 35 46 2 verify-pack: tree 35 46
4623 ofs-delta 12 23 3 verify-pack: blob 12 23 depth=1
4646 tree 35 46 2 verify-pack: tree 35 46
4692 ofs-delta 17 30 4 verify-pack: blob 17 30 depth=1
4722 tree 35 46 2 verify-pack: tree 35 46
4768 ofs-delta 22 33 4 verify-pack: blob 22 33 depth=1
4801 tree 35 45 2 verify-pack: tree 35 45
4846 ofs-delta 12 22 2 verify-pack: blob 12 22 depth=2每个条目的第一个字节:最高位是“后面还有头部字节”,接着 3 位类型(1 commit、2 tree、3 blob、4 tag、6 OFS_DELTA、7 REF_DELTA)和 4 位大小;如果还有后续字节,每个字节补 7 位大小。然后是这个对象 zlib 压缩后的数据。条目之间没有分隔符。
偏移 4623 的 ofs-delta → 基础对象在偏移 685(向后 3938 字节) 偏移 4692 的 ofs-delta → 基础对象在偏移 685(向后 4007 字节) 偏移 4768 的 ofs-delta → 基础对象在偏移 685(向后 4083 字节) 偏移 4846 的 ofs-delta → 基础对象在偏移 4768(向后 78 字节) 完整存放的那个 blob 是不是最新版本(HEAD:doc.txt): True
delta 条目在头部后面多一个“向后偏移量”(变长编码),指出基础对象在 pack 里的位置;REF_DELTA 则直接放基础对象的 hash。基础对象本身可以是另一个 delta,所以形成链(本例最深为 2)。git 选择让最新版本完整存放、旧版本做 delta,最常用的版本读取最快。
偏移 4623:delta 本体 12 字节,源 7998 字节 → 目标 7986 字节,共 2 条指令
copy 源[0:4276] (4276 字节)
copy 源[4288:7998] (3710 字节)
重建出的 blob 的 sha = cb2faa714be2a904c5031c0d411529ced2a85184;verify-pack 列出的 = cb2faa714be2a904c5031c0d411529ced2a85184
全部 15 个对象(含 delta)还原后 sha 与 verify-pack 一致: Truedelta 的本体先是源长度和目标长度两个变长整数,然后是指令序列:最高位为 1 的是 copy(从基础对象复制 偏移 + 长度),其余是 insert(后面直接跟着新字节)。本例中一行改动只需要两条 copy 指令。
尾部 : b125bfdeae8a910cf841b4d832bf429dd1e7e8d2 重新算 : b125bfdeae8a910cf841b4d832bf429dd1e7e8d2 文件名 : pack-b125bfdeae8a910cf841b4d832bf429dd1e7e8d2.pack $ git verify-pack /work/l13/.git/objects/pack/pack-b125bfdeae8a910cf841b4d832bf429dd1e7e8d2.pack && echo verify-pack 通过 verify-pack 通过
pack 末尾 20 字节是前面全部内容的 SHA-1,也是文件名的一部分;git verify-pack 会校验它。