Git
对象库里的每个版本片段——文件内容、目录快照、提交元数据——都以松散对象(loose
object)形式存放在 .git/objects/
下,直到 git repack 把它们打进
pack。读不懂「对象头 + zlib +
以哈希命名路径」这三件事,就无法手工校验对象是否损坏,也看不懂
git fsck 报错里的
corrupt loose object。
本文说明四种对象类型在未打包时的磁盘格式、SHA-1
对象 ID 的计算方式,以及路径 objects/ab/cdef…
的切分规则。对象之间的引用关系见 对象图。
一、对象 ID 从哪来
对每个 Git 对象,先构造 ASCII 对象头:
<type> <size>\0
再接上类型相关的载荷(payload)。对整个字节串做 SHA-1,得到 40 位十六进制对象 ID(SHA-256 仓库为 64 位,见 第 16 篇)。
磁盘路径:取 ID 前 2 字符为目录名,剩余 38 字符为文件名:
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
文件内容是 zlib 压缩后的
header + payload,不是明文。
二、blob 实测:从文件到磁盘
工作区文件 hello.txt 内容为
hello\n(6
字节)。git hash-object hello.txt 输出:
ce013625030ba8dba906f756967f9e9ca394464a
git hash-object -w hello.txt
会写入上述路径。用 Python 解压验证(Git 2.54.0
实验仓库):
import zlib
raw = open(".git/objects/ce/013625030ba8dba906f756967f9e9ca394464a", "rb").read()
dec = zlib.decompress(raw)
# dec == b'blob 6\x00hello\n'| 字段 | 值 |
|---|---|
| 压缩后大小 | 21 字节 |
| 解压后头 | blob 6\0 |
| 载荷 | hello\n |
git cat-file -p ce013625… 只打印载荷部分
hello,与解压结果一致。
复现:
echo 'hello' > hello.txt
git hash-object hello.txt
git hash-object -w hello.txt
python3 -c "import zlib; print(zlib.decompress(open('.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a','rb').read()))"(若内容不同,SHA 会变化;应以你本地的
git hash-object 输出为准。)
三、四种对象类型与载荷形状
| 类型 | 载荷概要 |
|---|---|
| blob | 原始文件字节 |
| tree | 多行
mode SP name NUL 20-byte-binary-SHA |
| commit | tree …\nparent …\nauthor …\ncommitter …\n\nmessage |
| tag | object …\ntype …\ntag …\ntagger …\n\nmessage(annotated
tag) |
类型名与空格、数字、NUL 构成头;载荷格式详见
gitformat-commit
等官方文档。
tree 与 commit 的文本展开示例见 第 03 篇。
四、松散对象的权限与只读位
新写入的松散对象通常为
只读(0444)。这是刻意设计:对象库内容不可变,防止误改。要做
fsck 损坏实验 时需
chmod u+w 再截断文件。
五、与 pack 的关系
松散对象是对象库的默认落盘形态;当数量或体积增长时,git repack
把多个对象合并进
.git/objects/pack/*.pack,并可能
prune 已被 pack 收录的松散文件(第
07、第 10
篇)。对象 ID 不变——pack 只是另一种物理编码。
六、本文边界
- 不推导 pack 内 OFS_DELTA/REF_DELTA(第 07–08 篇)。
- 不实现完整
git hash-object的 C 代码路径。 - 默认 SHA-1;SHA-256 哈希宽度在第 16 篇。
参考资料
- Git documentation, gitrepository-layout(objects 目录)
- Git documentation, gitformat-commit
- 实验环境:Git 2.54.0,WSL2 Linux
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Git 内部】.git 目录全景:三棵树与仓库布局
git init 之后 .git 里每个路径干什么?对照 gitrepository-layout 与本地 find 清单,建立 bare/non-bare、三棵树、objects/refs/index 的磁盘级地图。
【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 的格式与生成时机。
【Git 内部】reflog:logs/ 下的追加式历史
git reflog 读的是哪个文件?logs/HEAD 与 logs/refs 的二进制条目格式:旧 SHA、新 SHA、提交者、时间戳与消息。