pack 的价值不只是「少文件」——对相似内容,Git 存
delta(相对基对象的二进制 diff),大幅缩小
pack
体积。git pack-objects(git repack
/ git gc 内部调用)在窗口内挑对象建 delta
链。理解链式结构,才能读懂 git verify-pack 里的
chain length 与 REF_DELTA 行。
本文从格式视角说明 delta 类型与实测现象;不拆
git/git 里 diff-delta.c
的实现细节。pack 字节布局见 第 07
篇。
一、为何需要 delta
松散 blob 按完整内容寻址去重——同一字节序列只存一份。但近似内容(只改几个字符的 20 个文件)会产生 20 个几乎不同的 SHA,无法去重。pack 阶段在同一 pack 内选基对象,存「补丁」而非全文。
两类 delta(gitformat-pack):
- OFS_DELTA:基对象在同一 pack 中,以向前偏移定位。
- REF_DELTA:基对象以 20 字节 SHA 给出(可跨 pack,常用于 thin pack 传输)。
二、实测:20 个相似文件
构造仓库:20
个结构相同的文本文件,先提交,再全部改一字提交第二次,git repack -ad:
git verify-pack -v .git/objects/pack/*.pack | tail -5输出片段:
6fb7127d7c0e0eba2c781114f513d21e0c580fcc commit 65 77 193 1 a5f7463aa0e4a38ee4bc052c58b85c6d52285b00
…
non delta: 43 objects
chain length = 1: 1 object
第二行 commit 末尾的 1 a5f7463… 表示
REF_DELTA,基对象为前一 commit。大量 blob
仍为 non delta(短文本、互不相同),但已出现至少一条 delta
链。pack 总大小约
16K(du -sh .git/objects/),相对 43
个松散对象仍更紧凑。
窗口与启发式由
pack.window、pack.depth
等配置影响;具体压缩率随内容分布变化,本文不给出未对照基线的倍率结论。
三、thin pack
thin pack
在传输时使用:接收方已有基对象,发送方 pack 内 REF_DELTA
指向接收方库中已有 SHA,pack
内可不包含基对象全文。git index-pack
在入库时可能需 fix-thin 补全。这与 fetch/push
落盘 直接相关。
四、delta 链长度
git verify-pack 统计:
chain length = 1: 1 object
表示有 1 个对象的 delta 链长度为 1(一层
delta)。链过长会增加读取成本(需逐级应用
delta),pack.depth 限制建链深度以权衡空间与
CPU。
五、与 gc 的配合
git gc 调用 repack 重建
pack、丢弃冗余松散对象(第 10
篇)。历史越长、相邻版本越相似,delta
收益越明显——与版本控制系统存储模型一致。
六、本文边界
- 不给出 delta 二进制指令集逐字节表(见官方 pack format)。
- 不对
pack-objectsCPU 耗时做 benchmark。
复现脚本思路:循环创建
f1.txt…f20.txt,两次提交后
git repack -ad && git verify-pack -v .git/objects/pack/*.pack。
参考资料
- Git documentation, gitformat-pack
- Git documentation, gitprotocol-pack(thin pack)
- 实验环境:Git 2.54.0
系列索引:Git 内部结构 · 上篇:pack/idx 格式 · 下篇:commit-graph 与 bitmap
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Git 内部】.git 目录全景:三棵树与仓库布局
git init 之后 .git 里每个路径干什么?对照 gitrepository-layout 与本地 find 清单,建立 bare/non-bare、三棵树、objects/refs/index 的磁盘级地图。
【Git 内部】松散对象:zlib 载荷与 SHA-1 路径
blob/tree/commit 在磁盘上如何编码?对象头 type size、zlib 压缩、objects/ab/cdef 路径命名,对照 git hash-object 与手工 SHA-1 验证。
【Git 内部】对象图:tree、commit、tag 的链式结构
一次提交在对象库里如何连成链?tree 条目 mode/name/hash、commit 的 tree/parent 字段、annotated tag,用 git cat-file -p 展开并画对象图。
【Git 内部】refs、HEAD 与 packed-refs
分支和标签在磁盘上只是一行 SHA?refs/heads、符号引用 HEAD、packed-refs 文件头与 peeled tag 的格式与生成时机。