Git Internals

Tree 对象与目录快照教程

说明 tree 对象如何表达目录层级,以及提交为何能表示完整文件树快照。

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

一句话理解

如果说 blob 负责“内容本身”,那 tree 负责的就是“这些内容在目录结构里如何组织”。

1. 为什么 tree 很重要

很多人知道 Git 会保存文件内容,但不容易说清楚:

  • 一个提交为什么能表示整个项目状态
  • Git 是怎么知道某个文件在哪个目录里
  • 重命名和移动路径为什么更多像结构变化而不是内容变化

这些问题都要回到 tree。

2. tree 保存的到底是什么

Tree 如何组织目录快照根 tree 指向多个子 tree 和 blob,形成完整的目录层级快照。commit 最终指向根 tree,表达整个项目的当前状态。
目录结构
src/app.ts → blob:app_hashsrc/utils.ts → blob:utils_hashREADME.md → blob:readme_hashtests/ → sub-tree
快照表达
tree: src/ (含 app, utils)tree: tests/ (含 test files)tree: 根目录 (含 README, src/, tests/)
每次提交都指向一个完整的 tree 快照,不是差异。Git 通过对象复用保证存储效率。

tree 不直接保存文件内容,它保存的是目录条目。每个条目通常包括:

  • 名字
  • 模式
  • 指向哪个 blob 或子 tree

所以可以把 tree 理解成“目录层级快照”。

3. 根 tree 为什么重要

一个 commit 最终会指向一个根 tree。
这个根 tree 再向下连接更多 tree 和 blob,于是就能把“整个项目当前是什么样”表达出来。

这也是 Git 更接近“快照系统”而不是“纯补丁列表”的根本原因。

4. tree 和 blob 的分工

可以把它们这样记:

  • blob:文件内容
  • tree:目录结构

举个最实用的理解:

  • 文件改内容,通常更多影响 blob
  • 文件改路径,通常更多影响 tree
  • 目录结构变了,本质上一定会影响 tree

5. 为什么 Git 更像保存快照

很多人会把 Git 想成“每次只保存差异”。
从阅读体验上,这样想有时方便;但从对象模型上,更准确的理解是:

  • 一个提交会指向一个完整项目快照
  • 这个快照由 tree 层级表达
  • 底层存储再进一步做对象复用和压缩优化

也就是说,“逻辑上是快照,物理上可以高效复用”。

6. tree 如何帮助你理解常见操作

重命名

如果文件内容没变,只是路径和名字改了,变化重点往往在 tree,而不是 blob。

新增目录

本质上是 tree 层级结构里多了一段新的目录组织。

checkout / switch

它们最终会让工作区去匹配目标提交所指向的 tree 结构。

7. 为什么一个提交能“恢复整个项目”

因为 commit 指向的是根 tree,而根 tree 又把目录和内容全串起来了。

所以 checkout 到某个提交时,Git 不需要靠“从头把所有补丁重放一遍”才能知道项目状态,而是能直接根据那棵 tree 所表达的结构恢复出对应工作区。

常见误区

tree 就是普通文件夹

不准确。tree 是 Git 里的目录结构对象,不是你文件系统里的真实目录实体本身。

Git 只保存修改过的几行

从逻辑表达上,更贴近 Git 本质的说法仍然是“提交表示一个快照”,而 tree 正是快照结构的核心部分。

文件名保存在 blob 里

不是。文件名和路径属于 tree 层。

一个最值得记住的结论

blob 让 Git 知道“内容是什么”,tree 让 Git 知道“这些内容如何组成一个项目”。