Docs Library

Git 三层模型:工作区、暂存区与对象库

解释 Git 最核心的三层架构——工作区(Working Tree)、暂存区(Index)与对象库(Objects Database)——以及它们如何协同工作。

适合谁看
  • 想先理解历史图再看命令的人
前置知识
  • 知道提交不是文件快照列表那么简单
常见风险
  • 把概念页当命令说明页使用

一句话理解

Git 的核心不是"文件管理系统",而是一个三层快照引擎:你在工作区编辑文件,通过暂存区准备快照,最终提交到对象库形成永久历史。

Git 的三层数据流工作区的文件经过 git add 进入暂存区,再通过 git commit 写入对象库。任何时候你都可以从暂存区恢复工作区,或者从对象库恢复暂存区。
工作区checkout.tsedited and visible on disk
暂存区next snapshotstaged content for the next commit
对象库blob / tree / commitdurable history recorded by Git

理解这三层关系,是掌握所有 Git 命令的关键。很多"为什么我的提交没有包含最新修改"这类问题,本质上都源于对这三层边界的混淆。

第一层:工作区(Working Tree)

工作区是你看得见、摸得着的项目文件。就是你在编辑器里打开、修改、保存的那些文件。

工作区的特征

  • 它是你磁盘上的实际文件
  • 你可以用任何编辑器修改它们
  • 这些修改 Git 不会自动记录
  • git status 会显示哪些文件在工作区被修改过

示例

# 查看工作区有哪些修改
git status

# 查看具体修改内容
git diff

当你修改了一个文件但还没做任何 Git 操作时,这个修改只存在于工作区。

第二层:暂存区(Index / Staging Area)

暂存区是 Git 独有的概念——它是一个中间缓冲层,用来精确控制下一次提交包含哪些内容。

暂存区的特征

  • 它不是一个你能直接看到的目录
  • 它存储在当前项目的 .git/index 文件中
  • 它是下一次提交的精确蓝图
  • 你可以反复修改暂存区的内容,而不影响工作区或历史

为什么需要暂存区

很多版本控制系统没有这个概念,它们只有"修改 → 提交"两步。Git 引入暂存区,让你可以在提交前做三件重要的事:

  1. 拆分一次混乱的开发为多个逻辑提交:你可以把 bug 修复和重构分开提交
  2. 审查即将进入历史的内容:用 git diff --staged 最后确认
  3. 保留实验性改动:有些改动还在试验阶段,不想混进正式提交

示例

# 把修改加入暂存区
git add src/login.ts

# 按代码块交互式选择
git add --patch

# 查看暂存区里有什么
git diff --staged

# 从暂存区撤回
git restore --staged src/login.ts

第三层:对象库(Objects Database)

对象库是 Git 的永久历史存储,位于 .git/objects/ 目录下。一旦内容写入这里,除非执行垃圾回收,否则它会一直存在。

对象库的特征

  • 它存储在 .git/objects/
  • 内容按 SHA 哈希值组织,相同内容只存一份
  • 包含三种对象类型:blob(文件内容)、tree(目录结构)、commit(提交记录)
  • 写入后不可变——你无法修改一个已有的对象

三种对象类型

类型作用类比
blob存储文件内容(不含文件名)一个文件的"内容本身"
tree存储目录结构和文件名一个文件夹的"目录清单"
commit存储提交信息、作者、父提交、指向 tree一次"提交记录"

示例

# 查看对象库中的对象
git cat-file -p HEAD^{tree}

# 查看某个 blob 的内容
git cat-file -p <blob-hash>

# 查看对象类型
git cat-file -t <hash>

三层之间的数据流动

理解三层模型,最关键的是理解数据如何在它们之间流动:

工作区 ──git add──→ 暂存区 ──git commit──→ 对象库
  ↑                   ↓                      ↓
  └──git restore──────┘                      │
  └──git checkout / git restore──────────────┘

从上到下(正向流程)

  1. 编辑文件 → 修改只存在于工作区
  2. git add → 选中的修改复制到暂存区
  3. git commit → 暂存区快照写入对象库

从下到上(恢复流程)

  1. git restore <file> → 用暂存区的内容覆盖工作区
  2. git checkout <commit> -- <file> → 用对象库的某个版本覆盖工作区
  3. git reset HEAD → 清空暂存区,但不改变工作区

用三层模型理解常见命令

git status 告诉你什么

git status 的输出实际上是在比较这三层之间的差异:

  • "Changes not staged for commit" → 工作区 vs 暂存区 的差异
  • "Changes to be committed" → 暂存区 vs 最新提交 的差异
  • "Untracked files" → 工作区中有但完全不在 Git 管理中的文件

git diff 的三种模式

# 工作区 vs 暂存区(默认)
git diff

# 暂存区 vs 最新提交
git diff --staged

# 工作区 vs 最新提交(跳过暂存区)
git diff HEAD

git reset 的三层影响

模式HEAD暂存区工作区
--soft移动不变不变
--mixed(默认)移动重置不变
--hard移动重置重置

常见误区

"我改了文件,为什么提交里没有?"

因为你只修改了工作区,还没有 git add 把修改放入暂存区。

"我 add 过了,为什么后续修改没包含?"

git add 只是把那一刻的快照放入暂存区。之后你对工作区的新修改需要再次 git add

"git add . 有什么问题?"

它会把工作区所有修改都加入暂存区,包括你还没准备好的调试代码、临时日志等。更安全的做法是明确指定文件或使用 --patch

一个实用心智模型

把 Git 想象成拍照:

  • 工作区 = 你在布置场景
  • git add = 你在取景器里选择构图
  • git commit = 按下快门,照片存入相册
  • git diff = 对比当前场景和上一张照片的差别
  • git restore = 重新摆回上一张照片里的样子

这个模型帮你记住:暂存区是你提交前的"最后检查站",不是"自动保存"。

继续学习建议

理解三层模型后,建议继续学习:

  1. git add —— 如何精确控制暂存内容
  2. git commit —— 如何把暂存快照变成永久历史
  3. git diff —— 如何比较不同层之间的差异
  4. Git 内部对象 —— blob、tree、commit 的详细结构