Git 数据结构与合并模式

201 阅读6分钟

Git 数据结构与合并模式

Git是一个内容寻址文件系统,这意味着Git的核心部分是一个简单的键值对数据库(key-value data source)

data-model-4.png

图 1 Git的主要数据结构及其关系

引用(Reference)

图1 橙色部分。提交的指针,存储在.git的refs目录下,如refs/x/y/z。

提交(Commit)

图1 黄色部分。Git存储中管理的核心元数据,其具有一个或多个父提交的引用,作者与提交者的信息,指向树对象的引用。

二进制(Blob)

图1 灰色部分。每一个Blob对象都代表了一个文件的内容,并且每一个Blob对象都有一个唯一的SHA-1哈希值,用于标识该文件的具体版本。

分支(Branch)

分支是使用refs/heads前缀的引用,如refs/heads/masterrefs/heads/test。其所指向的提交即是该分支最新的提交,即通常所说的HEAD。

实际上HEAD是一个指向分支的指针,用于标记用户所在的分支,当解析引用时,会得到对应的提交。在一个仓库中内只有一个HEAD指针,随着用户切换分支而不断改写。

分支合并模式

Git支持灵活的分支策略也支持多种合并模式。这种灵活性为专业用户提供了便利,也为普通的Git用户制造了一道困难的选择题。这一节我们将详细介绍几种常见的Git合入模式,以及隐含的维护问题。

快进(Fast-forward)

图片1.png

图 2 合入前:目标分支无新提交,依旧指向 A

图片2.png

图 3 快进合入后

Dec-29-2020-fast-forward.gif

图 4 快进式合并

这种模式要求源分⽀ src 合⼊前⽬标分⽀ dst ⽆修改(始终指向源分⽀和⽬标分⽀的分岔点 A)。因此,当有⼤量合⼊的时候,多个待合⼊分⽀存在竞争关系⸺除了第⼀个合⼊的分⽀,其余分⽀都需要进⾏变基等操作,保证合⼊可以进⾏。

优点缺点
概念简单脏提交污染主⼲
Blame 信息清晰CR、测试只关⼼分⽀的最新提交 D,⽽不关⼼中间提交 B、C。因此,在合并时会引⼊若⼲未经
脚本开发容易脏提交污染主⼲
合⼊前提交与合⼊后提交相同CR、测试只关⼼分⽀的最新提交 D,⽽不关⼼中间提交 B、C。因此,在合并时会引⼊若⼲未经CR 及测试的提交。
Blame 信息清晰⽆法在⼤团队使⽤
团队规模越⼤ refs/heads/dst 变化越快,合⼊竞争越强烈

变基(Rebase)

图片3.png

图 4 合并前:目标分支有新提交 B

图片4.png

图 5 图 4 变基合入后

Dec-31-2020-rebase&fast-forfward.gif

变基合并是指,将源分支的差异 Diff(A,C)、Diff(C,D) 应用到目标分支结尾 B,形成与原提交 C、D 对应的新提交C 、D。这种模式与快进相似,但是放松了对目标分支的约束。只要应用差异过程中不存在冲突,即可合入。

优点缺点
Blame 信息清晰概念复杂, 变基过程较为复杂,对于新手来说非常不容易理解。
二分查找定位错误方便脏提交污染主干
脚本开发容易大团队使用困难同快进合入.另外由于变基需要修复沿途所有提交的冲突,变基解决冲突的工作量会随着提交的数量增加而增加。
合入前提交与合入后提交 Hash 不同,源分支中的提交不会出现在主干内.可能让一些基于提交 Hash 存储信息的系统工作异常.

合并(Merge)

图片6.png

Dec-29-2020-merge.gif

合并是指,将源分支的所有修改 Diff(A,D) 应用到目标分支的最新提交 B,创建一个新的提交 M。该提交的父指针除了指向 B 外,还指向源分支的最新提交 D。合并模式是最常见的模式,对于合入双方的约束很弱,只要补丁与 B 不存在冲突就可以合入。

优点缺点
使用简单概念复杂但伪装良好

合并模式是所有 Git 合并模式中最为复杂的一个,但却是 Git 的默认行为。
大团队使用容易无效提交信息

Git 默认产生形如“Merge branch X into Y...”的合并提交信息,该信息的信息量几乎为零.
工具支持好Blame 信息混乱

Blame 使用提交记录为每一行代码提供修改上下文。当合并期间间发生冲突时,需要在合并提交的树对象上解决冲突。因此该提交可能无法与源分支的 C、D 建立有效的关联,因而 Blame 可能无法找到 C、D 中实际的修改上下文,而只能局限在合并提交.
二分查找定位错误困难

与 Blame 类似,当使用二分查找定位错误时,我们可能无法深入 D 找到引入错误的实际提交,而只能定位到合并提交。如果合并提交较大(分支开发时经常如此),定位引入错误的具体提交难于上青天。
提交偏序关系混乱

当我们广泛使用合并提交时,仓库历史记录会变得像一张网,很难识别提交间的偏序关系。
影响脚本开发

由于多父指针的引入,许多 git 命令都无法在不进行人为干预的情况下产生理想的结果,如 merge-base 等。
合入前提交与合入后提交不同

压缩合并(Squash and Merge)

压缩合并与合并相似,但不会创建指向源分支最新提交的父指针。

优点缺点
概念简单

压缩合并与正常提交无异,不涉及多父提交等问题,保持仓库历史记录为线性、简单、可理解。
主干无分支开发历史记录

严格讲,这不是缺陷,而是按预期工作。一方面,开发历史不会被发布上线,因此对于日后维护并没有任何作用。另一方面,我们可以通过 CR 系统获得这些历史记录。
大团队使用容易
工具支持好
Blame 信息清晰
二分查找定位错误方便
提交偏序关系清晰
脚本开发容易
无脏提交

CR 过程、测试过程引入的提交,都不会被主干提交关联。同时这些提交信息也会被舍弃,并由合入时所编写的信息替代(如 MR 描述。)

引用

深入理解git合并操作

一文讲透 Git 底层数据结构和原理