停止cherry-picking,开始合并,第3部分:通过创建新的合并基础来避免问题
原文链接: Stop cherry-picking, start merging, Part 3: Avoiding problems by creating a new merge base
原文作者:Raymond Chen,发布于2018年3月14日
该系列的前两部分讨论了如果cherry-pick的更改随后被修改,可能会发生的糟糕情况。如果你幸运,你会得到一个合并冲突。如果你不幸运,你的修改会被简单地忽略。如果只有一种方法可以进行部分合并而不是cherry-pick,这些问题本可以避免。
事实证明,git确实支持部分合并。只是没有人这样谈论它。你可以通过使用自定义合并基础进行完全合并来创建部分合并。
在我们故事的开始,我们有一个提交树,如下所示:
%%{init: { 'gitGraph': {'showBranches': true, 'showCommitLabel':true, 'mainBranchName': 'master'}} }%%
gitGraph
commit id: "A (apple)"
branch feature
checkout feature
commit id: "F1 (apple)"
checkout master
commit id: "M1 (apple)"
从共同祖先A开始,提交F1发生在feature分支上,提交M1发生在master分支上。现在你意识到需要对两个分支都应用一个修复。你不想将整个feature分支合并到master分支中,因为这也会包含提交F1。
这里有一个技巧:创建第三个分支并将其合并到master和feature分支中。
%%{init: { 'gitGraph': {'showBranches': true, 'showCommitLabel':true, 'mainBranchName': 'master'}} }%%
gitGraph
commit id: "A (apple)"
branch patch
checkout patch
commit id: "P (berry)"
branch feature
checkout feature
commit id: "F1 (apple)"
merge patch id: "F2 (berry)"
checkout master
commit id: "M1 (apple)"
merge patch id: "M2 (berry)"
我们基于共同祖先提交A创建了一个名为patch的新分支,并将我们的修复提交到patch分支作为提交P。然后我们将提交P合并到master分支和feature分支中,分别产生提交M2和F2。
和以前一样,master和feature分支的工作继续进行,最终确定了问题的根本原因,并在feature分支中撤销了补丁并应用了适当的修复。
%%{init: { 'gitGraph': {'showBranches': true, 'showCommitLabel':true, 'mainBranchName': 'master'}} }%%
gitGraph
commit id: "A (apple)"
branch patch
checkout patch
commit id: "P (berry)"
branch feature
checkout feature
commit id: "F1 (apple)"
merge patch id: "F2 (berry)"
commit id: "F3 (apple)"
checkout master
commit id: "M1 (apple)"
merge patch id: "M2 (berry)"
commit id: "M3 (berry)"
在master分支上,提交M3进行了与我们的补丁无关的额外工作。同时,在我们的feature分支中,我们找出了正确的修复方法并将其提交为F3。提交F3将该行改回apple(撤销我们的补丁)以及包含适当的修复。
最终,是时候将feature分支合并到master分支了。合并选择提交P作为合并基础,因为它是最近的共同祖先。参与三路合并的提交是P(基础),M3(master分支的头部)和F3(feature分支的头部)。让我们删除所有其他提交,因为它们不参与合并。
%%{init: { 'gitGraph': {'showBranches': true, 'showCommitLabel':true, 'mainBranchName': 'master'}} }%%
gitGraph
commit id: "P (berry)"
branch feature
checkout feature
commit id: "F3 (apple)"
checkout master
commit id: "M3 (berry)"
相对于合并基础,master分支中问题所在的行没有变化,但在feature分支中,berry变成了apple。因此,合并结果将是apple。
%%{init: { 'gitGraph': {'showBranches': true, 'showCommitLabel':true, 'mainBranchName': 'master'}} }%%
gitGraph
commit id: "A (apple)"
branch patch
checkout patch
commit id: "P (berry)"
branch feature
checkout feature
commit id: "F1 (apple)"
merge patch id: "F2 (berry)"
commit id: "F3 (apple)"
checkout master
commit id: "M1 (apple)"
merge patch id: "M2 (berry)"
commit id: "M3 (berry)"
merge feature id: "M4 (apple)"
但是等待,M1和F1中的更改怎么办?它们被提交P绕过了,不是吗?这些更改会丢失吗?
不会,这些更改会被很好地合并,因为它们也存在于M3和F3中。这与你在日常操作中遇到的情况相同,当你在处理功能时定期从master合并到feature分支:
%%{init: { 'gitGraph': {'showBranches': true, 'showCommitLabel':true, 'mainBranchName': 'master'}} }%%
gitGraph
commit id: "X"
branch feature
checkout feature
commit id: "T1"
checkout master
commit id: "X1"
checkout feature
merge master id: "T2"
commit id: "T3"
checkout master
commit id: "X2"
checkout feature
merge master id: "T4"
commit id: "T5"
checkout master
commit id: "X3"
commit id: "X4"
commit id: "X5"
在上面的图表中(一个与前面图表无关的全新图表),你从master分支的某个提交X创建了一个feature分支。master分支的工作继续作为提交X1、X2等。同时,feature分支的工作继续作为提交T1、T2等。但是每隔一段时间,feature分支从master分支进行合并,以便两者不会相差太远。
假设你现在准备将feature分支合并回master分支。feature分支最后一次从master分支合并是当它合并提交X4时,在feature分支上产生提交T5。这使得提交X4成为合并基础。你是否担心这个即将到来的合并会丢弃提交T1到T4中的更改,因为合并基础提交X4晚于它们?不,你不会担心,因为你知道T1到T4中的更改也存在于T5中,它们将作为合并的一部分进入master分支。
好的,回到我们的原始故事。创建patch分支并将其合并到master和feature分支中保留了各自分支中两个提交之间的连接,特别是将它们标识为同一基础更改(即提交P)的两种表现形式。两个分支的最终合并识别了这种关系,不会重复应用更改。
基本上,patch分支将原本是cherry-pick的操作转换为合并。是cherry-pick导致了所有问题,解决方法是摆脱cherry-pick,改用合并。临时的patch分支给了我们部分合并的能力。
这就是基本思想。还有很多问题需要回答,比如"如何找到正确的合并基础?","如果我选择了错误的合并基础会怎样?","如果我需要执行两次cherry-pick怎么办?",或者"如果我已经做了cherry-pick;我能以某种方式修复损害并防止未来的合并冲突或ABA问题吗?"下次我们将开始深入探讨这些问题。