父级合并后如何处理主题分支的合并

How to deal with merges of topic branches after a squashed merge of parent

假设我有以下场景:

A -- B -- C -- D -- H master
     \            /
      E -- F -- G   topicA
           \
            I -- J -- K topicB

topicA 使用 --squash 开关合并到 master,这意味着 master 不知道历史topicA.

如果我现在将 master 合并到 topicB 中,然后执行 diff master...topicB,则 diff 会变得混乱并且包含许多不应该存在的更改或撤消。

在那种情况下,我通常将master合并到topicA,然后再将topicA合并到topicB,然后再执行上一段中所说的。然而,有时这是不可能的(例如分支被删除)并且我以很多冲突结束。

在这种情况下我应该如何处理?我有什么误解吗?

rebase --onto master topicA topicB 是正确的解决方案吗?

作为,你的计划看起来很合理。

(其余部分太长太乱;它是在其他任务之间/期间写的。)

题外话和细节

由于Hgit merge --squash创作的,画错了。它应该是:

A -- B -- C -- D -- H master
     \
      E -- F -- G   topicA
           \
            I -- J -- K topicB

关键区别在于提交 H E--F--G 序列相关,至少在任何 Git 检测不到感觉。提交 Hcontent 受到 E--F--G 序列中发生的任何事情的影响(当然,也受到 C--D 序列中发生的任何事情的影响)但据 Git 现在所知,有人在 E--F--G.

的情况下写了 H

我现在要说一个相当大的题外话了。


If I now merge master into topicB

如果你进行了真正的合并

好的,让我们将其绘制为提交图,以确保这意味着您想要的。我将使用我通常的形式(稍微更紧凑,分支名称的箭头向右移动一点):

A--B--C---D----H       <-- master (still points to H)
    \           \
     E--F--G     \     <-- topicA (still points to G)
         \        \
          I--J--K--L   <-- topicB (points to new L)

请注意,我画了一个 真实的 合并,不是假的,根本不是合并 "squash merge"。正如我们将看到的,这确实很重要。

当Git去制作这个新提交L时,它必须合并提交HK。为此,它必须找到它们的合并基(在某些情况下可能有多个合并基,但这里只有一个)。

任何两个提交的合并基础是/是最低公共祖先:即最接近可到达的两个起始提交(HK)的提交来自 个开始提交。

让我们先从HK他们自己说起。 H 可以从 H 到达(当然)但不能从 K 到达。 K 可以从 K 访问(当然),但不能从 H 访问。现在我们可以检查 DHKD 可以从 H 到达,但不能从 K 到达。现在我们可以检查 J,但无法从 H 访问它。现在我们考虑 CI,以及 F,和 E,但是直到我们回到提交 B 时,我们才找到一个HK 均可提交。提交 A 也可以,但它距离 HK 更远,因此提交 B 是合并基础。

然后合并从两个差异开始:

git diff B H

git diff B K

第一个差异显示了我们从 BH 所做的更改。当然,H 包含我们在 CD 中更改的内容,以及我们在 EFG 中更改的内容。第二个差异显示了我们从 BK 所做的更改。当然,K 包含我们在 E 中更改的内容,加上我们在 I--J--K 中更改的内容。

这有我们在 E 两次 中更改的内容,但 Git 通常 - 不总是,但 通常 ——很好地注意到了这一点,并且只接受了一次更改。所以提交 L 可能 包含以前每次提交的所有内容,只完成一次。

and then do a diff master...topicB

请注意,这是使用 three-dot ... 语法,而不是 two-dot .. 句法。我不确定你在这里的意图,但三点语法本质上意味着 "find the (or a) merge base"。那么让我们再做一遍这个练习:master 仍然指向提交 HtopicB 现在指向新的合并提交 L,我们找到 [= 的合并基础16=] 和 L,现在我们有了一个真正的合并(none 这个愚蠢的 "squash merge" fake 为我们合并东西,没办法! ).

所以让我们先从 HL 开始。 L 可以从 L 到达(当然)但不能从 H 到达。 H 可以从 H 到达(当然),也可以从 L 到达。这意味着 HL 的合并基数是 H: mastertopicB 的合并基数是 master.

... diff master...topicB

由于 master 在三点的左边,它被替换为合并基础,即提交 H。三点的右侧解析为它的提交,即提交 L。然后 diff 会显示 HL.

之间的任何不同之处

在这种情况下,效果和git diff master..topicB一样,意思和git diff master topicB一样:比较提交HL,按此顺序。

这应该是一个非常合理的差异,尽管我们最初制作 H 时使用了可怕的假挤压合并。 real 合并修复了这个问题,至少 H vs L.

如果你做了假的,不是合并 "squash merge"

让我们再次绘制这个东西,但这次使用假的非合并 git merge --squash 技术。我们新提交 Lcontents 将与我们进行真正的合并时相同,但 graph 会有所不同:

A--B--C---D----H       <-- master (still points to H)
    \
     E--F--G           <-- topicA (still points to G)
         \
          I--J--K--L   <-- topicB (points to new L)

现在我们回到:

diff master...topicB

再次,我们需要找到HL之间的合并基,但是现在L没有指向回两者 K H,但仅限于KHL 都不是合并基础。 DJ 都不起作用:我们不能从 L 倒退到 D,也不能从 [= 倒退到 J 16=]。事实上,合并基础提交再次提交 B,所以这意味着同样的事情:

git diff B L

而且这个差异会很不一样。

我不知道你对差异的期望是什么,所以我无法解决这部分:

the diff is messed up and contains a lot of changes or undoings which shouldn't be there.


返回合并、变基等

现在让我们return回答问题:

In that case, I usually merge master into topicA and then topicA into topicB before doing what was said in the previous paragraph. However, sometimes that it's not possible (e.g. the branch was deleted) and I end with a lot of conflicts.

请注意,删除分支 name 不会立即影响其 commits。它所做的是停止保护那些提交。也就是说,因为每个分支名称都会提交 reachable,这些提交对于 Grim Collector ...呃... 是安全的]死神 垃圾收集器。我们做了几次可达性的事情来找到合并基地; Git 但是,在 GC 期间更频繁地查找要保留的提交和要丢弃的提交;在 pushfetch 期间承诺转移;等等。如果提交受到其他一些方式的保护——通过真正的合并实现可达性,或者通过另一个分支或标签名称的可达性,或者其他任何方式——它们会保留下来。如果你能通过哈希 ID 找到它们,你可以把它们带回来。

更重要的是你的rebase案例,如果你能通过git log找到他们,你就可以切断他们。我们稍后会看到这个。

壁球的一般注意事项 "merge"

因为 squash "merges" 实际上根本不是合并,它们不会保护其他提交链,而且——这通常是未来合并冲突的关键—他们不提供 future 与更新的合并基础的合并。这意味着那些未来的合并必须检查巨大的差异,而不是小的差异,然后 Git 的自动 "redundant change" 检测失败。

这在实践中意味着什么取决于您如何使用这些 squash not-a-merge "merges"。当您使用它们进行开发并将其减少为单个提交时,完全停止使用其他开发线可能是个好主意。您可以保存它(使用分支或标记名称,甚至是分支和标记名称空间之外的一些其他引用,这样您通常不会看到它,这可以防止提交链被 GC-ed)或者让它得到收割了,但无论哪种方式,您可能都不应该继续处理它,这包括您从其上的某些提交中分叉出来的任何其他分支。

使用git rebase

Is rebase --onto master topicA topicB the right solution?

使用 git rebase,您可以将这些其他链(在本例中为您的 topicB)复制到新链,然后将标签 (topicB) 指向复制链。您要复制的提交是那些未被压缩的提交:这里是 I--J--K 链。使用 topicA 作为 <upstream> 参数 git rebase select 正确的提交集。请注意,topicA 达到提交 GFEBA,而 topicB 达到 KJIFE等;所以使用 topicA 作为 <upstream> 砍掉后面 F 的所有内容,但随后需要您提供的显式 --onto

如果标签 topicA 被删除,你仍然可以做这个变基,只是变得更棘手了。您需要做的是通过哈希 ID 指定提交 GF 中的一个,以便切断提交 F 和更早的提交。 G 的哈希 ID 从难以找到(GC 尚未删除它但无法从任何实时引用访问)到不存在(GC 已将其删除)。然而,F 的 ID 就在 topicB 链中:K 的父级是 JJ 的父级是 II 的父级是 F。问题是 没有简单的方法来确定提交 F 是否在早期 git merge --squash 处理的链中的提交集中。

(这与我之前加粗的评论有关,但不完全相同。)