核心问题:Git 如何把工作区中的修改组织成快照、提交和可追溯的历史?
本文只讨论:工作区、暂存区、版本库、快照、提交和引用的心智模型。
本文暂不讨论:具体命令操作。
Git 是分布式版本控制工具,本地就能拥有仓库历史。但在 Git 中,当前正在修改的内容、下一次准备提交的内容和已经形成的历史,并不是同一份东西。
理解 Git 如何将一次修改记录为可追溯的版本,可以先从三个核心区域入手:
- 工作区(Working Tree);
- 暂存区(Staging Area,也叫 Index);
- 版本库(Repository,普通工作仓库的数据通常保存在
.git中)。
用一次超市购物理解三个核心区域
我觉得超市购物比抽象名词更容易建立第一印象。
这个类比只帮助理解“从许多变化中选择本次要记录的内容”,并不等同于 Git 的实际存储结构。
| Git 概念 | 超市购物中的对应物 | 代表什么 |
|---|---|---|
| 工作区 | 整个超市 | 眼前所有可以查看、选择和调整的内容 |
| 暂存区 | 超市里的购物车,也可以叫手推车 | 这一次准备拿去结账的商品 |
| 提交 | 到收银台结账 | 把本次选择正式记录下来 |
| 一次提交 | 结账后拿到的一张小票 | 这次买了什么,以及这次交易的信息 |
| 版本库 | 保存下来的全部小票和交易关系 | 已经形成、可以追溯的记录 |
工作区就像正在逛的整个超市。超市里有很多商品,但不会全部购买。
把商品放进购物车,相当于从当前所有变化中选出“这次准备记录的内容”。购物车让我们可以在结账前检查、增加或拿出商品。
到收银台完成结账,相当于形成一次提交。一张小票对应一次正式记录;很多张小票按先后关系积累起来,就形成可以追溯的历史。
工作区:Working Tree
在普通的非裸仓库中,工作区是检出到磁盘、供我们查看和修改的项目目录。编辑器只是修改这些文件的一种工具,不是工作区本身。
新建文件、修改代码、删除文档,首先改变的都是工作区。这里可以同时存在功能代码、文档修改、调试日志和临时配置,就像超市里同时摆着很多不同商品。
工作区表示“我现在有哪些内容”,不等于“我准备把哪些内容写进历史”。
暂存区:Staging Area 或 Index
暂存区更接近“下一次提交的内容方案”,而不是普通的临时文件夹。
它把两个问题分开:
- 我在工作区改过什么?
- 我这一次准备记录什么?
这就像超市里商品很多,但购物车里只放本次准备结账的商品。暂存区让一次提交拥有明确边界。
某个版本的文件内容进入暂存区以后,工作区中的后续编辑不会自动更新暂存区。于是,同一个文件可以同时存在“已经准备提交的变化”和“暂存之后继续产生的变化”。下一篇会用status和diff观察这两层状态。
版本库:Repository
版本库保存已经形成的对象和历史关系。在普通的非裸仓库中,这些核心数据通常位于项目的.git目录,其中包括:
- 文件内容对象(blob);
- 表示某个时刻目录结构的树对象(tree);
- 记录目录树、父提交、作者、提交者和说明的提交对象(commit);
- 分支等指向提交的引用。
Git Directory是官方资料中描述这个存储位置时使用的技术名称。这里把它作为版本库的物理存储说明,不用它替换中文心智模型中的“版本库”。
一次提交像一张带有时间和交易内容的小票,但版本库并不只保存一张小票,而是保存完整历史以及历史之间的关系。
一次修改怎样变成版本历史
三个核心区域描述了修改的流转过程:
在工作区修改文件 ↓ 在暂存区组织下一次快照 ↓ 在版本库中生成提交并保存对象 ↓ 提交通过父提交关系连接成历史 ↓ 分支引用指向这段历史中的最新提交工作区负责产生变化,暂存区负责决定本次记录的边界,版本库负责保存内容对象、提交对象和引用。
只有三个部分还不足以形成版本控制。Git 还需要用快照描述每次项目状态,让提交保存指向父提交的连接,再用分支等引用标记当前关注的历史位置。HEAD用来表示当前检出位置;在通常的分支状态下,它通过当前分支间接定位到当前提交。分支和HEAD的细节将在后续专题展开。
Git 保存的是快照还是差异
从使用者的心智模型看,Git 把每次提交理解成项目在某个时刻的快照。
快照并不意味着每次提交都机械复制整个项目。内容没有变化时,新的项目状态可以继续引用已有对象;出现新内容时,Git 保存或复用与该内容对应的对象。底层存储还可以使用压缩等优化,但不改变“每次提交代表一个项目快照”的使用者模型。
因此,一次提交不只是一段修改说明。它会连接:
- 当时的项目状态;
- 作者和时间;
- 这次提交的说明;
- 它的父提交。
根提交没有父提交,合并提交可以有多个父提交;普通提交通常有一个父提交。这些向父提交的连接把提交组织成可以追溯的历史。
我的理解
工作区是当前可编辑内容,暂存区是下一次提交的内容方案,版本库保存对象、引用和历史关系。
如果用超市购物理解:工作区是整个超市,暂存区是购物车,提交是到前台买单,一次提交像结账小票,版本库则保存着全部小票以及它们前后的关系。
参考资料
- Git 官方资料:What is Git?
- Git 官方资料:Git Objects
- Git 官方资料:Git References
- Git 官方资料:Packfiles