看到Git提交页面上的增删行,容易把一次提交理解为只保存了这些差异。Git官方的核心数据模型说明给出另一幅结构:文件内容由blob保存,目录由tree表达,commit再指向这次版本的顶层tree。[1] 页面上的diff与对象里保存的内容,需要沿这条关系分别理解。

blob保存一个文件的内容。tree则在目录中逐项记录文件名、文件类型和相应对象ID,也能包含代表子目录的其他tree。这样,内容与内容在项目中的组织位置有各自的表达:知道一个blob的ID,并没有同时列出完整目录中的文件名;目录树通过条目把名称与内容对象联系起来。

commit包含顶层目录的tree ID,借此关联该版本的完整目录结构和文件内容。它还保存父提交ID、作者与创作时间、提交者与提交时间,以及提交消息。这里的作者和提交者由文档分别列出,不能在资料摘录时因为都与人和时间有关,就任意合并成一个字段。

父提交关系使单个快照进入历史。官方说明,仓库首次提交没有父提交,普通提交有一个,合并提交有两个或更多。因而,commit既关联某个时点的项目状态,也关联它直接承接的提交。只展示顶层tree关系可以解释快照组成,却还没有说明这些快照之间怎样连接。

数据复用则发生在内容对象这一层。文档用一个含一千个文件、其中两个文件改变的例子说明:两个改变的文件产生新的blob,其余九百九十八个沿用之前的blob ID。这个教学例保留了完整项目快照的含义,又不要求把未改变的文件内容全复制一遍;它不是本文测得的仓库空间数据。

对象本身创建后不会改变,每个对象的ID来自其类型和内容的哈希。与此相连,文档明确说Git并不为commit保存diff;需要显示提交差异时,再根据父提交计算。于是,同一份提交资料中可以同时出现完整快照的对象关系与相对父提交的增删显示,两种表达各自回答不同问题。

这套说明适合帮助开发工具资料读者追踪“这一行信息来自哪里”:文件内容在blob,名称及目录关系在tree,提交说明和父关系在commit。本文采用gitdatamodel页面标示的2.56.0版本内容,最后更新日期不充作首次发布日期;没有执行Git命令、改写历史或评价哈希安全。理解三类对象的连接,便能解释为什么增删行视图不等同于提交对象本身。

信息来源

本文基于上述公开资料整理,未使用来源页面的图片、视频或嵌入媒体。