合并基于另一个功能分支

时间:2017-02-13 18:11:18

标签: git version-control merge merge-conflict-resolution

几天前,我有一个master分支,它具有完全线性的历史记录。然后我创建了一个功能分支,我们称之为feat/feature-a。我在该分支上工作,然后提交代码审查以合并到master

在审核feat/feature-a时,我想开发另一个依赖于feat/feature-a引入的代码的功能。所以我从feat/feature-b分支创建了一个feat/feature-a分支。

当我在feat/feature-b工作时,feat/feature-a已合并为主人。所以现在master拥有feat/feature-a引入的代码。我现在想将feat/feature-b合并到master中,但是我遇到了很多看起来像这样的合并冲突:

<<<<<<< HEAD
=======
    // Some code that was introduced by feat/feature-b
>>>>>>> c948094... My commit message from feat/feature-b

我的猜测是,因为我对feat/feature-a分支进行了feat/feature-b次更改,我现在正试图“复制”那些以合并冲突结束的更改。

我可以手动解决这些问题,但它们会在数十个文件中存在多次,所以如果有的话,我想知道更好的解决方案。

2 个答案:

答案 0 :(得分:11)

摘要:使用git rebase --onto <target> <limit>

作为Useless suggested in a comment,如果你有一个真正的合并,这不应该发生。这就是我所说的“真正的合并”,以及如果你绘制相关提交图形时分支的外观图。我们从这样的事情开始:

...--E---H         <-- master
      \
       F--G        <-- feat/feature-a
           \
            I--J   <-- feat/feature-b

这里有两个提交(虽然确切的数字无关紧要)feat/feature-b上只有 ,在此处称为IJ; 两个功能分支上有两个提交,称为FG;并且master上有一个仅 的提交,名为H。 (提交E及更早版本的所有三个分支。)

假设我们在master上进行了真正的合并,以引入FG。看起来像这样,以图表形式:

...--E---H--K      <-- master
      \    /
       F--G        <-- feat/feature-a
           \
            I--J   <-- feat/feature-b

请注意,真正的合并K作为其父提交历史记录指针,具有提交H(在master上)和G(在feat/feature-a上) 。因此,Git后来知道合并J意味着“从G开始”。 (更准确地说,提交G将是后来合并的合并基础。)

合并将起作用。但这不是之前发生的事情:相反,合并的人使用了所谓的“壁球合并”功能。虽然squash-merge带来了与实际合并相同的更改,但它根本不会产生合并。相反,它会产生一个单独的提交,它复制了已合并的许多提交的工作。在我们的例子中,它重复了FG的工作,所以它看起来像这样:

...--E---H--K      <-- master
      \
       F--G        <-- feat/feature-a
           \
            I--J   <-- feat/feature-b

请注意,从KG没有后退指针。

因此,当你去合并(真正的 squash-not-really-a-“merge”)feat/feature-b时,Git认为它应该以{{1​​}}开头。 (从技术上讲,E是合并基础,而不是早期真正合并案例中的E。)正如您所看到的,这最终会给您一个合并冲突。 (无论如何它通常仍然“正常工作”,但有时 - 就像在这种情况下 - 它没有。)

或许这对未来很好,但现在的问题是如何解决它。

您要在此处执行的操作是复制专有 - G提交到提交,这些提交在feat/feature-b之后。也就是说,我们希望图片看起来像这样:

K

最简单的方法是 rebase 这些提交,因为rebase 意味着复制。问题是,简单的 I'-J' <-- feat/feature-b / ...--E---H--K <-- master \ F--G <-- feat/feature-a \ I--J [no longer needed] 会复制太多提交。

解决方案是告诉提交要复制的git checkout feat/feature-b; git rebase master 。您可以通过将参数从git rebase更改为master(或提交feat/feature-a的原始哈希ID - 基本上,标识第一个 1 提交的任何内容来执行此操作em>不要复制)。但这告诉G将它们复制到已有的位置;所以这不好。因此,问题的解决方案是添加git rebase,这样您就可以将“副本去哪里”部分从“要复制的内容”部分拆分:

--onto

(这假设您仍然有名称git checkout feat/feature-b git rebase --onto master feat/feature-a 指向提交feat/feature-a;如果没有,您将不得不找到一些其他方式来命名提交G - 您可能希望绘制自己的图形和/或仔细查看G输出,以查找提交哈希值。

1 以Git式向后的“第一”,即。我们从最近的提交开始,然后按照连接向后提交旧提交。 Git向后做所有事情,所以在这里思考倒退会很有帮助。 : - )

答案 1 :(得分:1)

简单模型如下所示:

X  -> MA  <master
  \  /
   A      <feature-a

这里,feature-a may 已经通过rebase压缩成单个提交,但合并仍然是真正的合并。然后,你有

X -> MA -> MB  <master
 \  /     /
  A ---> B     <feature-b

其中feature-b基于feature-a之后的任何压缩,正常合并。这个案例只是工作,因为git可以看到AB的祖先,并且你已经合并了它。

相比之下,这不会干净利落:

X -> MA -> ...                  <master
|\  /      
| As                            <feature-a
|  |
|  ^--squash------<--
 \                   \
  A0 -> A1 -> ... -> An -> B    <feature-b

你在合并feature-a之前将A0..n压缩为As,但是feature-b从An分支。

现在git不知道As和A0..n是如何相关的,所以合并和(简单)rebase都不会自动运行。如果您想使用rebase --onto来解决这种情况,请参阅torek的优秀答案。