Git 如何将一次修改记录为可追溯的版本

核心问题:Git 如何把工作区中的修改组织成快照、提交和可追溯的历史?

本文只讨论:工作区、暂存区、版本库、快照、提交和引用的心智模型。

本文暂不讨论:具体命令操作。

Git 是分布式版本控制工具,本地就能拥有仓库历史。但在 Git 中,当前正在修改的内容、下一次准备提交的内容和已经形成的历史,并不是同一份东西。

理解 Git 如何将一次修改记录为可追溯的版本,可以先从三个核心区域入手:

  • 工作区(Working Tree);
  • 暂存区(Staging Area,也叫 Index);
  • 版本库(Repository,普通工作仓库的数据通常保存在.git中)。

用一次超市购物理解三个核心区域

我觉得超市购物比抽象名词更容易建立第一印象。

这个类比只帮助理解“从许多变化中选择本次要记录的内容”,并不等同于 Git 的实际存储结构。

Git 概念超市购物中的对应物代表什么
工作区整个超市眼前所有可以查看、选择和调整的内容
暂存区超市里的购物车,也可以叫手推车这一次准备拿去结账的商品
提交到收银台结账把本次选择正式记录下来
一次提交结账后拿到的一张小票这次买了什么,以及这次交易的信息
版本库保存下来的全部小票和交易关系已经形成、可以追溯的记录

工作区就像正在逛的整个超市。超市里有很多商品,但不会全部购买。

把商品放进购物车,相当于从当前所有变化中选出“这次准备记录的内容”。购物车让我们可以在结账前检查、增加或拿出商品。

到收银台完成结账,相当于形成一次提交。一张小票对应一次正式记录;很多张小票按先后关系积累起来,就形成可以追溯的历史。

工作区:Working Tree

在普通的非裸仓库中,工作区是检出到磁盘、供我们查看和修改的项目目录。编辑器只是修改这些文件的一种工具,不是工作区本身。

新建文件、修改代码、删除文档,首先改变的都是工作区。这里可以同时存在功能代码、文档修改、调试日志和临时配置,就像超市里同时摆着很多不同商品。

工作区表示“我现在有哪些内容”,不等于“我准备把哪些内容写进历史”。

暂存区:Staging Area 或 Index

暂存区更接近“下一次提交的内容方案”,而不是普通的临时文件夹。

它把两个问题分开:

  • 我在工作区改过什么?
  • 我这一次准备记录什么?

这就像超市里商品很多,但购物车里只放本次准备结账的商品。暂存区让一次提交拥有明确边界。

某个版本的文件内容进入暂存区以后,工作区中的后续编辑不会自动更新暂存区。于是,同一个文件可以同时存在“已经准备提交的变化”和“暂存之后继续产生的变化”。下一篇会用statusdiff观察这两层状态。

版本库: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