【问题标题】:Merging a feature branch that is based off another feature branch合并基于另一个功能分支的功能分支
【发布时间】:2017-07-01 19:57:19
【问题描述】:

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

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

当我在 feat/feature-b 工作时,feat/feature-a 被合并到 master 中。所以现在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 分支,我现在正试图“复制”那些以合并冲突结束的更改。

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

【问题讨论】:

  • 它是真正的合并、壁球、变基还是其他什么?
  • 现在我正在通过 git rebase -i masterfeat/feature-b 与 master 重新绑定。我将feat/feature-b 上的所有提交压缩为 1 个提交。
  • 我想问的是,feature-a 的合并是真正的合并。但是,如果 feature-b 的合并不是真正的合并,那也会破坏它
  • 是的。 feat/feature-a 被压缩成一个提交,然后合并到 master,产生一个合并提交。
  • 那个压缩的提交还在feat/feature-a 上吗?所以这是feat/feature-b的父级?

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


【解决方案1】:

总结:使用git rebase --onto &lt;target&gt; &lt;limit&gt;

作为Useless suggested in a comment,如果您进行了真正的合并,则不应发生这种情况。这就是我所说的“真正的合并”,以及如果您绘制相关提交图时分支的外观图。我们从这样的事情开始:

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

这里有两个提交(尽管确切的数字无关紧要)feat/feature-b 上,分别称为 IJboth 功能分支上有两个提交,称为 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 开始”。 (更准确地说,commit G 将是 merge base 用于以后的合并。)

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

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

请注意缺少从 KG 的反向指针。

因此,当你去合并(真正的 squash-not-really-a-“合并”)feat/feature-b,Git 认为它应该以E 开头。 (从技术上讲,E 是合并基础,而不是前面真正的合并案例中的G。)正如您所看到的,这最终会给您带来合并冲突。 (通常它仍然“正常工作”,但有时——比如在这种情况下——它不起作用。)

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

您在这里要做的是复制独家-feat/feature-b 提交到 提交,在K 之后。也就是说,我们希望图片看起来像这样:

              I'-J'  <-- feat/feature-b
             /
...--E---H--K        <-- master
      \
       F--G          <-- feat/feature-a
           \
            I--J     [no longer needed]

最简单的方法是rebase 这些提交,因为rebase 意味着 复制。问题是一个简单的git checkout feat/feature-b; git rebase master 会复制太多次提交。

解决方案是告诉git rebase哪个提交要复制。您可以通过将参数从 master 更改为 feat/feature-a(或提交 G 的原始哈希 ID — 基本上,任何标识第一个1 提交 not 复制)。但这告诉git rebase 将它们复制到它们已经存在的位置;所以这不好。因此问题的解决方案是添加--onto,它可以让你将“副本去哪里”部分与“要复制的内容”部分分开:

git checkout feat/feature-b
git rebase --onto master feat/feature-a

(假设您仍然有名称 feat/feature-a 指向提交 G;如果没有,您将不得不找到其他方法来命名提交 G——您可能希望绘制自己的图表和/或或仔细查看git log 输出,以找到提交哈希)。


1Git 风格的“First”倒退方式,即。我们从最近的提交开始,然后将连接向后跟踪到较旧的提交。 Git 做的一切都是倒着的,所以在这里倒着思考是有帮助的。 :-)

【讨论】:

  • 这是一个完美的解释。谢谢!
【解决方案2】:

简单模型如下所示:

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

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

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

其中的特征-b 是基于任何压缩后的特征-a,并且 正常合并。这种情况应该正常工作,因为 git 可以看到 AB 的祖先,并且您已经合并了它。

为了比较,这不会干净地工作:

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

你把 A0..n 压缩成 As 之前合并 feature-a,但是 feature-b 是从 An 分支出来的。

现在 git 不知道 As 和 A0..n 是如何相关的,因此合并和(简单)变基都不会自动工作。如果您想使用 rebase --onto 来解决这种情况,请参阅 torek 的出色回答。

【讨论】:

    猜你喜欢
    • 2013-12-05
    • 1970-01-01
    • 2017-07-04
    • 2020-12-15
    • 2013-01-31
    • 1970-01-01
    • 1970-01-01
    • 2010-12-04
    • 2013-09-11
    相关资源
    最近更新 更多