Git 对象模型¶
如果你只能记住 Git 的一个核心事实,那应该是这一句:
Git 本质上是一个基于内容寻址的对象数据库。
这句话比“Git 是版本控制工具”更接近底层真实。
什么叫内容寻址¶
“内容寻址”指的是:对象的身份来自它的内容,而不是来自文件名、路径名或者用户手工编号。
在 Git 里,一个对象在写入对象库之前,会先被拼成这样的字节串:
[type] [size]\x00[content]
然后对这整段字节计算 SHA-1。这个 SHA-1 结果就是对象 ID。
所以:
- 如果内容不变,对象 ID 不变;
- 如果内容变化,对象 ID 一定变化;
- 两个不同路径但内容完全相同的文件,可以共享同一个 blob 对象。
这就是 Git 去重和快照高效性的起点。
Git 的四类核心对象¶
Git 里最重要的对象有四类:
blobtreecommittag
blob¶
blob 只存文件内容本身,不存文件名,不存目录层级。
这意味着:
- 文件名不是 blob 身份的一部分;
- 两个不同名字的文件,只要内容一样,就能对应同一个 blob;
- Git 在对象层最先关心的是“内容”,不是“文件路径”。
tree¶
tree 用来描述目录快照。它会记录:
- 文件模式;
- 文件名;
- 指向的对象 ID。
对文件来说,tree 指向 blob。
对子目录来说,tree 指向另一个 tree。
所以一个 tree 实际上就是“这一层目录长什么样”的描述。
commit¶
commit 记录的是一次历史节点。它至少会包含:
- 指向根 tree 的对象 ID;
- 零个或多个父提交;
- 作者和提交者信息;
- 时间戳;
- 提交说明。
注意:commit 保存的是“这个历史节点对应哪个快照”,不是“这一堆差异操作命令”。
tag¶
tag 有两种常见形态:
- 轻量标签:本质是一个 ref;
- 附注标签:本质是一个单独的 tag 对象,再由 ref 指向它。
附注标签能保存更完整的信息,比如标签说明、标签创建者等。
为什么对象模型这么重要¶
你以后看到的大多数 Git 行为,都是从对象模型自然推出的:
add是把文件内容放进对象库并记录到 index;commit是把 index 固化成 tree,再创建 commit;branch只是给某个 commit 起一个可移动名字;merge是比较几个 commit 所指向的 tree;push和fetch其实是在传输对象和更新 refs。
所以,如果对象模型没理解,后面的命令行为就只能靠死记。
这个模型和普通文件系统有什么不同¶
普通文件系统通常先关心:
- 路径;
- 文件名;
- 当前目录结构。
Git 更先关心:
- 内容;
- 快照;
- 历史节点;
- 引用关系。
这也是为什么 Git 能同时具备:
- 内容去重;
- 快照式历史;
- 轻量分支;
- 高效合并;
- 高效打包传输。
在 python-git-reproduction 里的对应实现¶
这个项目的 .pygit 也是按这个模型实现的:
- loose object 采用官方 Git 风格的 header + zlib 压缩;
- 支持 blob、tree、commit、tag;
- 对象 ID 使用 SHA-1;
- 对象写入和读取都带有完整性校验。
所以学懂 Git 对象模型,不只是理论,也是在学这个项目的真正底层。