【问题标题】:Git: Merging in reverse direction after squashed mergeGit:压缩合并后反向合并
【发布时间】:2018-11-29 22:08:03
【问题描述】:

我的团队正在使用类似 Gitflow 的工作流程,但最近从 developmaster 的发布合并是无意中作为壁球合并完成的。当时,我们没有发出任何警报,并认为下次我们会做对的,但后来我们不得不对该版本进行热修复。现在,我们需要将该修补程序重新合并到develop,这与master 不同。我们遇到了一些冲突,其中一些是合法的,但其中大部分是由于 git 认为 master 上的 squash-merge 提交代表它自己对更改的单独提交,而不是来自 develop 的合并而产生的噪音。

图表可能有助于说明问题:

 A ---------- D -- G (master)
  \
   \- B -- C -- E -- F (develop)

在这种情况下,D 是 B 和 C 的 squash-merge 的结果(而不是我们通常所做的常规合并)。请注意,git diff C D 返回空 - 即它们的内容相同。

现在,我想将master 合并到develop;由于EFG 之间的合法重叠,我预计会出现一些冲突(因此我不能简单地说“使用我们的”或“使用他们的”)。但我在BCD 中的所有内容基本上都发生了冲突,大概是因为 git 将D (可以理解)视为它自己的提交,在master 上的一个单独的提交链中从 A 开始.

长话短说,我希望的是“相对于这两个相同的提交(CD)合并”而不是“相对于 A 合并”。有点像rebase --onto,但我猜是为了合并。

FWIW,我知道我可以手动解决冲突;我只是好奇是否有一种方法可以跳过在创建 D 时执行 squash-merge 而不是常规合并而引入的所有垃圾冲突。

【问题讨论】:

  • 只需 checkout master 和 git merge --ours C 来重新创建丢失的合并历史
  • @AndrewC 那也行;我试图避免在master 上创建不必要的提交,但这似乎是一种相当干净的方式。事实证明,我们确定我们可以在不更改 develop 的修补程序的情况下顺利进行,因此我们最终从 develop 执行 git merge -s ours master 并且一切都很好。仍然有兴趣看看是否有其他方法可以解决这个问题(比如你的)而不简单地放弃更改。

标签: git merge version-control git-merge


【解决方案1】:

更新 - 我忘记了有关其工作原理的重要信息;固定。


现在我假设进行历史重写(以及随后的清理)并不是一个实际的解决方案......

在这种特定情况下,最简单的技巧是在合并期间使用git replace。如果不是因为 CD 具有相同的内容,这不会起作用

在克隆中你可以说

git replace --graft <E> <D>

(其中&lt;E&gt;&lt;D&gt; 是解析为相应提交的表达式;在上面的示例中,这可能是git replace --graft develop^ master^)。这会创建一个“看起来像”E 的新提交 E',但它的父级是 D。然后它使用E' 作为E 的替代品——所以当一个操作将git 引导到E 时,它仍然会看到Es 提交ID,但它会从E' 获取有关该提交的其余信息.身体上你有

                E'
               /
 A ---------- D -- G (master)
  \
   \- B -- C -- E (REPLACE:E')
                 \
                  F (develop)

那么你

git chekcout develop
git merge master

(或者可能是git merge hotfix-branch,但您没有在图表中显示,所以我们将使用简化示例)...现在merge 命令“看到”

A ---------- D -- G (master)
              \
               (E|E') -- F (develop)

所以它使用D 作为合并基础。因为CD的内容是一样的,所以计算出来的合并结果是应该的。结果“看起来像”

A ---------- D ------------ G (master)
              \              \
               (E|E') -- F -- M (develop)

这是真的

                E'
               /
 A ---------- D ------ G (master)
  \                     \
   \                F -- M (develop)
    \              /
     \- B -- C -- E (REPLACE:E')

现在D..M 周围的历史“看起来很有趣”,这是这样做的缺点(而不是用真正的合并替换 D 的历史更正)。 M 是一个边界“邪恶合并”,但由于默认合并无论如何都会发生冲突(并且由于计算结果是冲突的“自然”解决方案),这并不可怕。

完成此操作后,您不再需要更换。 masterdevelop 之间的未来合并无论如何都会将 G 视为合并基础,因此之前的混乱并不重要。

【讨论】:

  • 感谢您的回答,我不熟悉git replace。但是,它的工作方式与您在此处描述的不完全一样-由于某种原因,它发现合并基为A,这意味着我仍然存在与B/C/D 相关的冲突。有趣的是,如果我执行git merge-base without git replace,它实际上会找到来自develop 的最后一个提交,该提交被合并到master 以创建A(在我的示例中未显示)。
  • 嗯...重新测试,我同意,这不会像我所说的那样工作。这很奇怪,因为我很确定我以前使用过这种技术。但至少在当前测试中,替换对象显示但具有原始 ID;这意味着合并基础看起来不会“相同”。我现在将删除此答案,如果进一步调查将其改进为可行的形式,我将重新发布。
  • 这个版本应该可以。替换的“幻觉”在被替换的提交本身上并不完整,而只是在其父级;因此,与其直接将相同的提交 (C) 替换为 D,不如将​​其子 (E) 替换为与 E 相同但其父位于 D 而不是 @ 的假设提交987654368@。幸运的是,--graft 选项创建了假设的提交(所以我猜它不再是假设的)并设置了替换。
  • 成功了!我得出了相同的结论,即确实需要在图表的下一级进行替换,因为我们需要将压扁的提交作为(虚拟)合并基础-但还没有把我的头放在--graft 上.事实证明,我们已经找到了使用git merge -s ours master 的方法(即忽略修补程序更改),但我会接受您的回答,因为这正是我最初想要的。谢谢!
猜你喜欢
  • 1970-01-01
  • 2014-06-30
  • 2016-05-22
  • 2020-02-24
  • 2011-04-03
  • 2018-11-05
  • 2020-10-29
  • 2017-09-26
  • 2023-03-25
相关资源
最近更新 更多