Git 合并原理:三向合并与冲突判定
Git 合并的本质是:找到共同祖先 → 三向比对 → 自动合并或报冲突。理解这个模型,遇到任何合并场景都能准确预判结果。
一、三向合并(Three-Way Merge)
1.1 为什么需要三向合并
直觉上合并两个文件只需要比较这两个文件(二向合并),但仅凭两个文件无法确定改动归属。
例如两个分支同一行代码:
- 分支A:Print("hello")
- 分支B:Print("world")
Git 无法判断:是 A 改了、B 改了,还是两边都新增了。
1.2 共同祖先(Common Ancestor)
三向合并引入一个共同祖先作为参照基准。Git 合并时比较的三个版本:
- Base(祖先):两个分支分叉之前的版本
- Ours(当前分支):所在分支的最新版本
- Theirs(待合并分支):要合并进来的分支的最新版本
合并逻辑以 Base 为参照:
| 1 | o | o | b | 取 b |
| 2 | o | a | o | 取 a |
| 3 | o | a | a | 取 a |
| 4 | o | a | b | 冲突 |
简而言之:以 Base 为参照,谁改了就用谁的;两边都改了且不同,就冲突。
1.3 冲突的本质
冲突的本质是三方都互不相同。相对于共同祖先,当前分支和待合并分支对同一处都做了修改,且修改内容不同。
Git 在冲突文件中标记双方内容:
<<<<<<< HEAD
(当前分支的内容)
=======
(待合并分支的内容)
>>>>>>> branch-name
二、合并策略
2.1 Fast-forward(快进合并)
触发条件:当前分支是待合并分支的直接祖先,两个分支没有分叉,存在线性路径。
做法:Git 将当前分支指针直接移动到目标分支的最新提交,不产生新的合并提交。
main → A ← B ← C (feature)
↓
git checkout main
git merge feature
↓
main/feature → A ← B ← C
特点:历史保持线性;如需强制生成合并提交,使用 git merge –no-ff。
2.2 Recursive(递归三向合并)
触发条件:两个分支有分叉,无法快进合并。这是 Git 合并有分叉分支时的默认策略。
核心算法:递归寻找路径最短的共同祖先节点(即 merge base),以其为 base 进行三向合并。
2.3 多个共同祖先的处理
当两个分支存在多个共同祖先时,Git 会先递归地将所有共同祖先合并成一个虚拟提交(virtual commit),再以这个虚拟提交作为新的 base 进行三向合并。这就是"递归"二字的由来——不断向上追溯,直到找到唯一共同祖先。
源码层面,递归合并算法实现在 Git 源码 merge-recursive.c 中,单文件三向合并在底层由 xdl_merge 函数完成。
2.4 其他策略
| Ours | 保留当前分支内容,忽略待合并分支改动 |
| Theirs | 采用待合并分支内容 |
| Octopus | 同时合并两个以上分支 |
三、详细示例
3.1 简单三向合并
B (ours): 文件1=6, 文件2=2
/
A (base): 文件1=1, 文件2=2
\\
C (theirs): 文件1=8, 文件2=24
当前在 B 分支执行 git merge C:
| 文件1 | 1 | 6 (改了) | 8 (改了) | 冲突 |
| 文件2 | 2 | 2 (未改) | 24 (改了) | 取 24 |
文件1 需手动解决冲突,文件2 自动变为 24。
3.2 多个共同祖先(递归合并)
合并 B 和 D 时,共同祖先有 A 和 C 两个:
B
/ \\
A C
\\ /
D
处理流程:
虚拟提交 X 就是"递归"产生的中间结果。
3.3 一个经典陷阱:revert 后重新合并
A → B (dev新增http.js) → E (合并到master) → E' (revert撤回合并)
\\ /
dev继续开发 → D (新增main.js,import http.js)
再次将 D 合并到 master 时,http.js 文件消失,且没有任何冲突提示。
原因:revert 一个 merge commit 不会改变共同祖先的位置。Git 认为 http.js 在 master 上已经被删除(通过 revert),而 dev 上虽然存在,但合并时"删除"操作的优先级更高,于是文件就消失了。
理解 Git 的合并原理,才能避免这类"Git 有 bug"的错觉。
四、总结
| 三向合并 | 比较 Base、Ours、Theirs 三个版本,以 Base 为参照判断改动 |
| 共同祖先 | 合并的基准点,决定哪些改动是"新增的" |
| Fast-forward | 无分叉时的指针移动,不产生合并提交 |
| Recursive | 有分叉时的默认策略,递归处理多个共同祖先 |
| 冲突 | 三方都不同时产生,需手动解决 |
网硕互联帮助中心







评论前必须登录!
注册