在 knittl 现有的answer 的基础上,您可以在自己舒适的家中制定一个场景,以演示所谓的“壁球合并”是多么容易导致冲突。
我们首先在master 上创建文件并提交:
$ git init
$ echo "a" >> a.txt
$ git add .
$ git commit -m'start'
现在我们创建develop 并修改该文件:
$ git branch develop
$ git checkout develop
$ echo "b" > a.txt
$ git add .
$ git commit -m'changed a to b'
我们返回master 并进行“壁球合并”,它似乎工作正常:
$ git checkout master
$ git merge --squash develop
$ git commit -m'a squash commit from develop'
到目前为止一切顺利。现在我们犯了一个可怕的错误:我们再次。我们切换到develop 并进一步修改该文件:
$ git checkout develop
$ echo "c" > a.txt
$ git add .
$ git commit -m'changed b to c'
然后我们返回master 并再次进行“壁球合并”:
$ git checkout master
$ git merge --squash develop
啊啊啊,我们发生了冲突。游戏结束。
发生了什么?好吧,问题在于“壁球合并”不是合并。这是一种自我造成的补丁。它构造索引(暂存区)的配置,然后您提交它。在我上面的法令中,我们将其作为一个单独的步骤提交;使用 GitHub,它会为您服务。但关键是那个提交,虽然它包含从真正的合并中产生的更改,但不是合并提交:它只是一个普通的提交,就好像您自己完成了这些更改,直接在 master 上工作。
那么,进行真正的合并所产生的变化意味着什么?要回答这个问题,您必须了解合并的工作原理:
-
合并从计算合并分支分叉的公共点开始:合并基数。
-
然后它计算两个差异:从合并基础到第一个分支的末尾,以及从合并基础到第二个分支的末尾。
-
最后,它在合并基础上制定两个差异。
好的,所以你自己试试吧,用一个思想实验。
对于上面我的场景中的第一个“壁球合并”,合并基础是“start”,其中 a.txt 是“a”。所以:
所以,要形成“squash merge”提交,我们只需将“a”更改为“b”。
很好,让我们继续我的场景中的第二个“壁球合并”。事情是这样的:合并基础仍然是“开始”,其中 a.txt 是 仍然“a”。所以:
-
在master 上,差异是change-"a"-to-"b"。
(请注意,master 并不“知道”这是由于任何类型的合并而发生的;它认为此更改是由直接在 master 上工作的人独立执行的。)
-
在develop 上,差异是change-"a"-to-"c"。
但你不能两者都做;这是冲突!
所以你看,重用之前被 squash-merged 的分支之所以麻烦,是因为 merge base 没有移动(历史上没有任何东西涉及任何合并);因此,每个连续的 squash 合并都可能与同一分支的早期“squash 合并”发生冲突。