Git 数据结构与合并模式
Git是一个内容寻址文件系统,这意味着Git的核心部分是一个简单的键值对数据库(key-value data source)。
图 1 Git的主要数据结构及其关系
引用(Reference)
图1 橙色部分。提交的指针,存储在.git的refs目录下,如refs/x/y/z。
提交(Commit)
图1 黄色部分。Git存储中管理的核心元数据,其具有一个或多个父提交的引用,作者与提交者的信息,指向树对象的引用。
二进制(Blob)
图1 灰色部分。每一个Blob对象都代表了一个文件的内容,并且每一个Blob对象都有一个唯一的SHA-1哈希值,用于标识该文件的具体版本。
分支(Branch)
分支是使用refs/heads前缀的引用,如refs/heads/master, refs/heads/test。其所指向的提交即是该分支最新的提交,即通常所说的HEAD。
实际上HEAD是一个指向分支的指针,用于标记用户所在的分支,当解析引用时,会得到对应的提交。在一个仓库中内只有一个
HEAD指针,随着用户切换分支而不断改写。
分支合并模式
Git支持灵活的分支策略也支持多种合并模式。这种灵活性为专业用户提供了便利,也为普通的Git用户制造了一道困难的选择题。这一节我们将详细介绍几种常见的Git合入模式,以及隐含的维护问题。
快进(Fast-forward)
图 2 合入前:目标分支无新提交,依旧指向 A
图 3 快进合入后
图 4 快进式合并
这种模式要求源分⽀ src 合⼊前⽬标分⽀ dst ⽆修改(始终指向源分⽀和⽬标分⽀的分岔点 A)。因此,当有⼤量合⼊的时候,多个待合⼊分⽀存在竞争关系⸺除了第⼀个合⼊的分⽀,其余分⽀都需要进⾏变基等操作,保证合⼊可以进⾏。
| 优点 | 缺点 |
|---|---|
| 概念简单 | 脏提交污染主⼲ |
| Blame 信息清晰 | CR、测试只关⼼分⽀的最新提交 D,⽽不关⼼中间提交 B、C。因此,在合并时会引⼊若⼲未经 |
| 脚本开发容易 | 脏提交污染主⼲ |
| 合⼊前提交与合⼊后提交相同 | CR、测试只关⼼分⽀的最新提交 D,⽽不关⼼中间提交 B、C。因此,在合并时会引⼊若⼲未经CR 及测试的提交。 |
| Blame 信息清晰 | ⽆法在⼤团队使⽤ |
| 团队规模越⼤ refs/heads/dst 变化越快,合⼊竞争越强烈 |
变基(Rebase)
图 4 合并前:目标分支有新提交 B
图 5 图 4 变基合入后
变基合并是指,将源分支的差异 Diff(A,C)、Diff(C,D) 应用到目标分支结尾 B,形成与原提交 C、D 对应的新提交C 、D。这种模式与快进相似,但是放松了对目标分支的约束。只要应用差异过程中不存在冲突,即可合入。
| 优点 | 缺点 |
|---|---|
| Blame 信息清晰 | 概念复杂, 变基过程较为复杂,对于新手来说非常不容易理解。 |
| 二分查找定位错误方便 | 脏提交污染主干 |
| 脚本开发容易 | 大团队使用困难同快进合入.另外由于变基需要修复沿途所有提交的冲突,变基解决冲突的工作量会随着提交的数量增加而增加。 |
| 合入前提交与合入后提交 Hash 不同,源分支中的提交不会出现在主干内.可能让一些基于提交 Hash 存储信息的系统工作异常. |
合并(Merge)
合并是指,将源分支的所有修改 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 描述。) |