Git Internals
Git 对象数据库
理解 blob、tree、commit、tag 四类对象,以及 Git 为什么说自己是内容寻址的对象数据库。
一句话理解
Git 最底层不是“文件版本列表”,而是一个把内容写成对象、再用对象 ID 反向定位内容的数据库。
为什么对象模型是第一层基础
很多 Git 现象只有放到对象模型里才说得通:
- 为什么一次 commit 会生成新的对象
- 为什么同样内容在不同分支上仍然可以复用
- 为什么 branch 创建几乎是瞬时的
- 为什么 reset 之后旧提交往往还没有真的消失
1. 什么叫内容寻址
Git 写入对象时,不是先给对象随便编号,而是先根据内容计算对象 ID。也就是说:
- 内容先存在
- ID 由内容决定
- 之后 Git 再通过这个 ID 反向找到对象
这也是“content-addressable”最重要的含义。
2. 四类最常见对象
blob
blob 只保存文件内容,不保存文件名,也不保存它在目录树中的位置。
tree
tree 负责表达目录结构。它会记录目录下有哪些名字,以及这些名字指向哪个 blob 或哪个子 tree。
commit
commit 用来固定一次项目状态。它通常会记录:
- 当前快照对应的 tree
- 父提交
- 作者和提交者信息
- 提交说明
tag
tag 为某个对象提供更稳定、更可读的名字,常见于发布版本。
3. 为什么 blob 不保存文件名
这件事经常让人第一次接触 Git internals 时觉得反直觉。Git 把“内容”和“结构”分开保存:
- 文件内容放在 blob
- 文件名和目录位置放在 tree
好处是内容复用会更自然。只要内容一样,底层就能共享对象,而不必把“路径”和“内容”绑死。
4. 一次 commit 在对象层到底发生了什么
从对象数据库的视角看,一次提交大致是:
- 根据暂存区内容写出 tree
- 创建一个新的 commit 对象
- 让 commit 指向这次 tree 和父提交
- 再把当前分支引用移动到新提交
所以 commit 的本质不是“改写旧历史”,而是“新增对象,再移动名字”。
5. 对象和工作区不是一回事
很多混乱来自把这两层混在一起。更稳的理解是:
- 工作区:你正在编辑的文件
- 暂存区:准备写入下一次快照的内容
- 对象库:已经被 Git 固化下来的对象
只有进入对象库之后,内容才真正成为 Git 历史的一部分。
6. 为什么对象模型能解释很多命令
git add
它不是直接创建 commit,而是把当前内容朝下一次 tree 靠近。
git commit
它会真正创建新的 tree 和 commit 对象。
git branch
它通常不需要复制对象,只是新建一个指向已有提交的引用。
git checkout / git switch
它们会让工作区去匹配另一个引用所指向的对象状态。
7. 对象“存在”不等于“容易找到”
这是理解恢复操作时最关键的一点。一个对象就算还在数据库里,只要没有分支、tag、reflog 之类的名字把它接住,你就会越来越难找到它。
所以很多恢复问题其实不是“对象没了”,而是“引用丢了”。
常见误区
Git 会为每个版本完整复制整个项目吗
不应该这样理解。Git 底层是对象模型,加上后面的 packfiles 压缩和复用机制,并不是简单粗暴地重复存整份目录。
commit 只是写一段日志吗
不是。提交说明只是 commit 对象里的一部分元数据。真正关键的是它把 tree、父提交和作者信息固定了下来。
一个最实用的记忆方式
如果只记一件事,可以记住:
- blob 保存内容
- tree 保存结构
- commit 保存一次快照与父子关系
- branch 和 HEAD 只是指向这些对象的名字
后面理解 refs、历史图、rebase、reset 就会容易很多。