Git Internals

Blob 对象与内容寻址教程

解释 blob 如何只按内容存储,以及哈希为什么成为对象身份。

适合谁看
  • 想建立稳定 Git 心智模型的学习者
  • 经常遇到历史、引用、恢复问题的开发者
前置知识
  • 会看基础命令输出
  • 知道提交、分支、HEAD 这些名词
常见风险
  • 只背底层术语却不连接到实际命令
  • 把对象、引用、工作区混成一层理解

一句话理解

blob 是 Git 里最“纯粹”的对象类型之一:它只保存文件内容本身,不保存文件名、不保存路径,也不关心这个内容属于哪个分支。

1. 为什么先理解 blob

很多人第一次接触 Git internals,会下意识把"文件"和"Git 对象"当成一回事。
但 Git 其实把这两件事拆开了:

  • blob 负责内容
  • tree 负责结构
  • commit 负责把 tree 固定成历史节点

只要这层拆分建立起来,很多底层行为都会突然变得容易理解。

Blob → Tree → Commit 的链接关系文件内容先编码为 blob 对象,再由 tree 对象组织成目录结构,最终被 commit 对象指向,形成完整的快照。
内容层
blob: 文件 A 内容blob: 文件 B 内容blob: 文件 C 内容
结构层
tree: 根目录(含子目录)sub-tree: src/ 目录commit: 快照 + 作者 + 父提交
相同内容的文件会复用同一个 blob,blob 不保存文件名和路径。

2. blob 到底保存什么

blob 保存的是文件字节内容本身。

这意味着:

  • 同样内容的两个文件,可以对应同一个 blob
  • 文件名变了,不代表 blob 一定变了
  • 路径变了,也不代表 blob 一定变了

Git 真正关心的是“内容长什么样”,不是“你给这个内容起了什么名字”。

3. 为什么 Git 说自己是内容寻址

Git 写入对象时,不是先随便分配一个编号,而是:

  1. 先取对象内容
  2. 给内容加上对象类型和长度头部
  3. 对这段数据计算哈希
  4. 用这个哈希作为对象身份

所以对象 ID 不是外部贴上的标签,而是内容本身推导出来的身份。

4. 相同内容为什么会得到相同 blob

因为 Git 对 blob 的身份判断只看内容。

例如:

  • README.md
  • docs/readme-copy.md

如果这两个文件内容完全一样,那么它们底层完全可能指向同一个 blob。

这也是 Git 能高效复用内容的原因之一。

5. blob 为什么不保存文件名

这是 Git 对象模型里非常关键的一次分工:

  • blob 只管“内容是什么”
  • tree 才管“这个内容在目录里叫什么名字、放在哪个位置”

这种拆分的好处是:

  • 内容复用更自然
  • 重命名和移动路径不必强行改变内容对象
  • 对象模型更稳定

6. 从 blob 的角度看常见命令

git add

当工作区里的文件内容变化后,Git 最终会让这些内容朝“新的 blob / 新的暂存快照”靠近。

git commit

commit 不只是“记一条说明”,它会让一组内容最终通过 tree 和 commit 对象固化下来,其中底层文件内容就可能对应若干 blob。

git mv

如果只是改路径而内容不变,那么变化更多发生在 tree 结构层,而不一定是 blob 内容层。

7. blob 和“版本”不是同一个概念

一个 blob 只是某一份文件内容。
它本身不是“完整版本”,也不是“一个提交”。

真正能表达“某个时刻整个项目长什么样”的,不是 blob,而是:

  • 一组 tree
  • 一个根 tree
  • 一个指向这个根 tree 的 commit

8. 为什么这能帮助你理解 diff 和历史

当你知道 Git 会把内容拆成 blob,对很多现象就不会再误会:

  • 重命名不等于重新创造内容
  • 同内容复用是自然结果
  • 文件内容和目录结构是两个层次的问题

常见误区

blob 等于文件

不准确。blob 更准确地说是“文件内容对象”,而不是工作区里的那个文件实体。

同名文件一定对应同一个 blob

不一定。名字相同但内容不同,就是不同 blob。

改文件名一定会改 blob

不一定。只改名字、不改内容,往往主要影响 tree,而不是 blob。

一个最值得记住的结论

如果只记一句话,可以记:

blob 只回答“内容是什么”,从来不回答“它叫什么、放在哪里”。