【问题标题】:How to deal with merges of topic branches after a squashed merge of parent如何在父级压缩合并后处理主题分支的合并
【发布时间】:2016-07-28 18:34:06
【问题描述】:

假设我有以下场景:

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

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

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

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

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

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

【问题讨论】:

  • 似乎它应该对我有用。
  • ...随后将 master 快速合并到 topicB 以将 master 移动到 topicB。

标签: git merge rebase


【解决方案1】:

作为DavidN said in a comment,你的计划看起来很合理。

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

题外话和细节

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

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

关键区别在于提交HE--F--G 序列相关,至少在任何Git 检测到的意义上都没有。提交Hcontent 会受到E--F--G 序列中发生的任何事情的影响(当然,也受到C--D 序列中发生的任何事情的影响)但据Git 现在所知,有人来写了H,甚至没有E--F--G

我现在要在这里有一个相当大的题外话了。


如果我现在将master 合并到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)

请注意,我画了一个真正的合并,而不是一个假的、根本不是合并的“壁球合并”。正如我们将看到的,这确实很重要。

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

任何两个提交的合并基础是 / 是最低共同祖先:也就是说,最接近两个起始提交(HK)的提交可以从 both 那些开始提交。

让我们先从HK 开始。 H 可以从H(当然)访问,但不能从K 访问。 K 可以从K 访问(当然),但不能从H 访问。现在我们可以检查DHKD 可以从H 访问,但不能从K 访问。现在我们可以检查J,但无法从H 访问。现在我们考虑CIFE,但直到我们回到提交B 时,我们才发现可以从H 和@987654362 访问提交@。提交A 也可以,但它离HK 更远,所以提交B 是合并基础。

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

git diff B H

git diff B K

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

这有我们在 Etwice 中所做的任何更改,但 Git 通常——并非总是如此,但 通常——能很好地注意到这一点并接受更改只有一次。所以commit L 可能拥有之前每次提交的所有内容,只完成一次。

然后做一个diff master...topicB

请注意,这是使用 three-dot ... 语法,而不是 two-dot .. 语法。我不确定您在这里的意图是什么,但三点语法本质上意味着“找到(或)合并基础”。所以让我们再做一遍这个练习:master 仍然指向提交 HtopicB 现在指向新的合并提交 L,我们找到了 HL 的合并基,现在我们有一个真正的合并(这些愚蠢的“壁球合并”对我们来说没有合并东西,不可能!)。

让我们先从HL 开始。 L 可以从L(当然)访问,但不能从H 访问。 H 可以从H(当然)和L 访问。这意味着HL的合并基是HmastertopicB的合并基是master。

...diff master...topicB

由于master 位于三点的左侧,它被替换为合并基础,即提交H。三点的右侧被解析为它的提交,即提交L。然后差异会显示HL 之间的差异。

在这种情况下,效果与git diff master..topicB 相同,这与git diff master topicB 的含义相同:按顺序比较提交HL

这应该是一个相当合理的差异,尽管我们最初为了制作H 做了可怕的假 squash-merge。 真正的合并修复了这个问题,至少对于H vs L

如果你做了一个假的、非合并的“壁球合并”

让我们再次绘制这个东西,但这次使用伪造的 not-a-merge git merge --squash 技术。我们的新提交 L内容 将与我们进行真正的合并一样,但 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,我们也不能从 H 向后走到 J。事实上,merge base commit 又是 commit B,所以意思是一样的:

git diff B L

而且这个差异会完全不同。

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

差异被搞砸了,并且包含许多不应该存在的更改或撤消。


返回合并、变基等

现在让我们回到问题:

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

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

更重要的是,对于您的rebase 案例,如果您可以通过git log 找到它们,您可以将它们切断。我们稍后会看到。

壁球“合并”的一般注意事项

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

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

使用git rebase

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

使用git rebase,您可以将这些其他链(在本例中为您的topicB)复制到新链,然后将标签 (topicB) 指向复制链的末端。您要复制的提交是那些没有被压缩的提交:这里是I--J--K 链。使用topicA 作为&lt;upstream&gt; 的参数git rebase选择正确的提交集。注意topicA 达到提交GFEBA,而topicB 达到KJFF,@, 987654467@等;所以使用topicA 作为&lt;upstream&gt; 会砍掉后面的F 的所有内容,但随后需要您提供的明确的--onto

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

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

【讨论】:

  • 惊人的答案@torek,太棒了。仍然存在两个疑问: 1. 我似乎在将 master 真正合并到 topicB 时认为 topicB 实际上撤消了在 G 中所做的事情,因此在解决冲突时它会删除在 @ 上所做的工作987654491@(显然它认为topicB 较新,应该保留)。 2. 你说“而topicB 到达KJIE 等等”;不是也到达F吗?
  • @kopranb:解决最后一位问题:是的,哎呀,会解决的。关于第一点:Git 总是只做两个差异,从合并基础到两个技巧。如果伪造的差异键匹配(可能发生),您将获得可怕的合并。没有看到一个真实的例子,我不能说更多......
  • 谢谢,我不止一次遇到过这个问题,但我还没有把它简化为一个可重复的小例子。当我这样做时,我会回到这个问题。 :)
猜你喜欢
  • 2020-02-25
  • 2012-12-09
  • 2020-02-24
  • 2014-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-29
相关资源
最近更新 更多