正确地将一个特性合并到 master 中

Correctly merging a feature into master

问题:

我总是 运行 在涉及多个相关功能分支时遇到同样的问题。

master ---A---B---C
           \
feature1    D---E---F
                     \
feature2              G---H

我有这样的东西。假设这两个分支都被审查过,我将把 feature1 合并到 master。然后结帐并拉取 master,然后结帐并将 feature2 变基为 master。这样做,我总是一遍又一遍地看到相同的冲突,尤其是在多个分支上。

良好的变基:

我在网上看到上面的例子,首先我应该做这样的事情来确保我正确地变基:

feature1 变基为 master

master ---A---B---C
           \       \
feature1    \       D'--E'--F'
             \       
feature2      D---E---F---G---H

Rebase feature2 到新的 feature1 提交

master ---A---B---C
                   \
feature1            D'--E'--F'
                             \
feature2                      G'--H'

我会使用 git rebase --onto feature1 feature1@{1} feature2 来做到这一点。

困惑:

据我了解,最好像这样变基,因为当你变基时,你的分支实际上将包含全新的提交(即上面的 FF'),这可能会导致不必要的冲突.

考虑到所有这些,正确的方法是什么:

我想尝试学习推荐的方法,我可以确信我不会每次都遇到多个依赖分支的痛苦冲突。

注意,这个答案分为两部分。第一个是关于变基的机制,第二个是变基与合并。

复杂变基的机制

I would do this using git rebase --onto feature1 feature1@{1} feature2

是的:这通过将参数拆分为 rebase 来实现:现在不再是 feature1,而是 --onto feature1 feature1@{1}.

通常,我们运行 git rebase <em>name</em>,例如git rebase feature1。我们要先git checkout feature2。添加 feature2 到命令的末尾(如上引用)只是为我们做了这个 git checkout 步骤。因此,如果我在这里调用“正常”,git rebase 只有一个参数,通常是另一个分支名称,如 feature1.

但是git rebase所做的是复制一些提交,为此,它需要知道两个事情:

  • 要复制的内容(提交列表),以及
  • 在哪里放置 新副本。

那个参数 feature1 为其中的 做了繁重的工作。这很好当它工作时,但是为了将 feature2 变基到已经变基的 feature1,它并不总是有效。

它不起作用的原因(当它不起作用时)与您绘制的图表有关。你以一种奇怪的方式画了它们,左边有分支名称。这种绘图具有误导性:它暗示每个提交都在 one 分支上,这是不正确的。

又是第一张图:

master ---A---B---C
           \
feature1    D---E---F
                     \
feature2              G---H

问:A 提交到哪个分支? (技巧问题!)

答:在所有个分支上。

绘制这些图表时应在左侧显示提交,在右侧上显示标签,标签指向一个特定的提交——因为这就是这些东西在Git中的实际工作方式。这是相同的图表,重新绘制:

--A--B--C   <-- master
   \
    D--E--F   <-- feature1
           \
            G--H  <-- feature2

通过从右边开始并跟随(内部的并且总是向后指向的)父链接从提交到提交,我们可以看到 CBA在主人身上; FEDAfeature1 上;例如 HGFEDAfeature2 上。

现在我们可以在第一个git rebase之后绘制第二个图,像这样:

          D'-E'-F'   <-- feature1
         /
--A--B--C   <-- master
   \
    D--E--F   [reflog: feature1@{1}]
           \
            G--H  <-- feature2

这就是 git rebase 的拆分参数的用武之地。

通常,Git 找到 git rebase 将使用以下方式复制的提交集:

git rev-list <argument>..<current-branch>

这里是 feature1..feature2。这意味着 可从 feature2 到达的所有提交,但可从 feature1 到达的所有提交除外。现在, 我们移动 feature1 之前,那是正确的提交集:它是 HG。但是我们移动了 feature1,现在它是 错误的 组提交,因为它也包括 FD

一旦我们说 feature1@{1}..feature2,但是,我们再次得到 正确的 一组提交。但是现在我们已经失去了复制的地方,这就是我们需要 --onto 的原因:那就是 放置副本的地方

当第二次rebase完成后,我们应该绘制最终结果如下:

                  G'-H'   <-- feature2
                 /
          D'-E'-F'   <-- feature1
         /
--A--B--C   <-- master
   \
    D--E--F   [reflog: feature1@{1}]
           \
            G--H  [reflog: feature2@{1}]

并且由于引用日志通常是不可见的,我们可以完全删除图表的下半部分。

变基与合并

As I understand it, it's better to rebase like this because ...

更好 是一个非常狡猾的术语。

变基有两个问题:

  • 复制 提交,然后放弃原件以支持新副本。其他人有原件吗?如果是这样,你也让他们为难了:他们也必须放弃原件,转而使用新的副本。

  • 复制 提交。新副本至少与原件有些不同(否则它们实际上 原件)。确切地说,副本有什么不同?那很重要么?你在这个过程中有没有破坏某些东西,即引入错误?

使用git merge可以避免这些问题。相反,它插入了它自己的问题:

  • 历史现在很纠结。如果有必要弄清楚发生了什么,无论是谁在探索历史,都可能不得不俯视合并的两条“腿”。一些人认为这是积极的,而不是消极的,因为这是真实的历史,而不是后来经过清理的人造历史。

    (我倾向于赞成清理历史,但这个论点的双方都有其优点。)

  • 合并本身可能会引入错误。

如果您有一组非常好的测试,这有助于解决“引入错误”这两个问题。

如果它有足够的帮助,它只留下 一个 变基问题(假设你足够聪明,或者有一个很好的工具,像这种情况一样做你自己的复杂变基) : 别人有原件吗?平衡你对该问题的回答与你对纠结的历史是否糟糕的回答,以便决定合并还是变基。

如果你没有有很好的测试,那么... :-)