版本控制中的三方合并

2019年10月28日

在学习Git时,一般会学到Git的合并方式,除了快进之外,还有三方合并(three way merge)。三方合并也是Subversion等传统版本控制软件的合并策略。本文讲解什么是三方合并,为何需要三方合并,为何不使用两方合并。

同一个文件现在有以下两个分支,1bdd3be5d6c1b0e2为这两个分支的Git版本号

1bdd3be5 d6c1b0e2
1
2
3
4
5
5
4
3
2
1

要将他们合并,请问合并后的文件是什么样?

这里不能根据上下文语义确定内容,读者看到这里估计都懵了。

但是如果知道1bdd3be5d6c1b0e2的共同祖先呢?如下图,1bdd3be5修改自ba8beb31d6c1b0e2也修改自ba8beb31。请问这两个分支将如何合并?

ba8beb31
5
4
3
4
1
1bdd3be5 d6c1b0e2
1
2
3
4
5
5
4
3
2
1

经过分析,我们把修改的行用*标识出来。

ba8beb31
5
4
3
4
1
1bdd3be5 d6c1b0e2
*1
*2
 3
 4
*5
 5
 4
 3
*2
 1

这样一看我们就清楚了,只需要把差异项合并,合并后的文件就是
1
2
3
2
5

下面是一个演示。创建一个git项目,把上面两个分支进行合并。Git提示冲突。使用Beyond Compare显示冲突,Beyond Compare利用三个文件的信息(在上面三个面板),进行了智能合并(在下面的面板)。

由此可见共同祖先对于分支合并是非常重要的,没有共同祖先,我们就难以确定如何合并,遑论机器了。这同时也说明,“两方合并”是不可行的。

不同的共同祖先对合并有影响吗,答案是肯定的。见下一个例子,设37b21605为那两个分支的共同祖先,差异行已标出。

37b21605
5
2
3
2
1
1bdd3be5 d6c1b0e2
*1
 2
 3
*4
*5
 5
*4
 3
 2
 1

这个情况下的合并结果为
1
4
3
4
5

所以,如果版本控制系统不能正确确定两个版本的共同祖先,则会造成意外的合并结果。

现在回归到Git与Subversion,鉴于Git以链表方式存储版本,非常容易确定两个分支的最近共同祖先

原文发表于2017年8月20日。