bigywater.tech
Git · 17

packfile 格式:头部、对象类型、delta 编码

packfile 里到底是什么?这篇不用 git 命令,而是用 Python 逐字节解析一个真实的 pack:文件头、每个对象条目的头部编码、OFS_DELTA 的偏移,甚至自己按 copy / insert 指令把 delta 还原,最后和 git verify-pack 对账。

15 / 15
手工解析并还原的对象,sha 与 git verify-pack 一致的个数
12 字节
一个 delta 条目的本体大小,还原出 7986 字节的 blob
最新版本完整
被完整存放的 blob 是 HEAD 的版本,旧版本都是对它的 delta

文件头

实测 · 本机执行输出
  $ 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 压缩后的数据。条目之间没有分隔符。

OFS_DELTA

实测 · 本机执行输出
  偏移 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,最常用的版本读取最快。

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 一致: True

delta 的本体先是源长度和目标长度两个变长整数,然后是指令序列:最高位为 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 会校验它。

分步演示

packfile 的字节
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。解析器是我用 Python 写的(ll.py 里的 parse_pack / apply_delta),只读 .pack,不依赖 git;对账对象是 git verify-pack -v;pack 是 git gc 生成的(5 个提交,每次改一行,doc.txt 约 8 KB)
    • 没有验证:REF_DELTA(thin pack 或 --no-delta-base-offset 时才出现,本例全是 OFS_DELTA);大对象(大小编码超过 4 字节)与 64 位偏移的 .idx;pack 版本 3;多个 pack、cruft pack;git 选择基础对象的算法(window / depth,见后面 delta 链一篇)
    京ICP备2026067502号-1京公网安备11010502063487号