Git:如何将现有的“merge”转换为“merge --squash”?

5

我进行了多个merge提交,但它们应该是merge --squash。冲突解决花费了一天以上的时间,所以我不能手动重新合并。

有没有办法将merge转换为merge --squash


也许可以中止合并并重新开始? - Oswald
你已经合并了吗? - torek
@torek 是的,但是我们可以强制推送而没有问题。 - Julien__
@Oswald 合并已经完成...但很遗憾,由于太多冲突而无法重新开始。 - Julien__
1个回答

19

值得注意的是,git mergegit merge --squash密切相关,但git merge --squash不会创建合并

这里的措辞非常重要,特别是在“a”在“merge”前面的文章中,“a merge”是一个名词,而“to merge”是一个动词。两个命令都执行合并操作,区别在于结果如何保存

还值得快速提醒一下,通过提交记录图表的形式,说明合并的外观方式。这里每个圆形“o”节点都代表一个提交,较早提交的向左侧。分支名称是指向一个特定提交(分支的tip提交)的标签。例如,您可以从这里开始:

...--o--*--o--o   <-- main
         \
          o--o--o   <-- feature

然后你决定将一个特定的功能分支合并回分支,因此你运行:

$ git checkout main && git merge feature

这个操作执行了合并(动词),产生了一个合并结果(名词),最终结果如下:

...--o--*--o--o---o   <-- main
         \       /
          o--o--o   <-- feature

Git新增了一个提交到main,而这个新的提交是一个合并提交:它指向回到main之前的上一次提交,同时也指向回(在这种情况下,也向下)到仍然当前的feature的顶部。 (名称feature继续指向与以前相同的提交。)

git merge --squash所做的是修改最后一步。 它不是创建一个合并提交,而是完全抑制提交-没有明显的原因-并强制您运行git commit。 当您运行git commit时,会产生一个普通的提交,而不是合并提交,因此结果看起来像这样:

...--o--*--o--o---o   <-- main
         \
          o--o--o   <-- feature

以下是两个关键点:

  • 新提交的内容相同。这两个新提交都是从索引(即Git术语中的“下一次提交所需的内容”)进行的。索引由merge-as-a-verb过程设置。第一个父提交是“主线”提交,来自我们在合并时所处的分支。第二个父提交是另一个提交,即我们刚刚合并的那个。

  • 新提交的父链接不同。一个真实的合并具有两个前面的提交作为父级,但“压缩合并”只有一个前面的提交作为父级 - “主线”提交。这意味着它不能记住哪个提交被合并。

对于有冲突(但是真实的)合并,git merge无法单独创建新提交,因此它停止并强制您解决冲突。完成解决冲突后,必须手动运行git commit,就像您始终需要使用git merge --squash及其虚假合并一样(出于没有明显原因)。您还可以请求任何真正的合并停止并让您检查结果,使用git merge --no-commit

将真实合并转换为虚假(压缩)合并的简单方法是只要真实合并还没有提交

  • 对于git commit,它依赖于冲突(或--no-commit)合并留下的文件才能进行合并。此文件命名为.git/MERGE_HEAD。(它还会留下一个名为.git/MERGE_MSG的文件,尽管此附加文件是无害的。)

  • 因此,您可以简单地删除.git/MERGE_HEAD并运行git commit。(一旦编写了带有消息的提交,您可能还需要删除MERGE_MSG文件。在那之前,您可以将其用作消息,或者以其起点。)git commit步骤将不再知道如何进行合并提交,而将代替创建普通提交 - 然后你就完成了压缩合并。


如果已经进行了真实的合并提交,该过程可能会更加困难。特别是,如果您已经发布合并,则现在必须让每个获得此合并的人将其取回。否则,真实合并将返回(他们可能会再次合并它),或者为他们创建问题,甚至两者都有可能。然而,如果可以这样做,或者尚未发布,则只需“把合并推到一边”,然后插入一个虚假的合并提交。

让我们重新绘制我们拥有的内容,以腾出一些空间。这与之前相同的图表,只是分布在更多行上:

...--o--*----o----o 
         \         \
          \         o   <-- main
           \       /
            o--o--o   <-- feature

如果我们能将 main 移回到第一行,然后再进行一次新的提交,会发生什么呢?那么我们将得到以下图示:

...--o--*----o----o--o   <-- main
         \         \
          \         o   [abandoned]
           \       /
            o--o--o   <-- feature
注意所有箭头——包括用于记录历史的内部提交箭头(由于它们在文本绘图中太难生成,因此未在此处显示)——都指向左侧,因此在任何顶线提交中都没有关于它们下面的提交的信息:那些都“在它们的右边”。只有底线提交以及我们将要放弃的提交知道关于顶线提交的任何信息。
此外,如果我们要完全放弃中线提交,让我们停止绘制它以及它的两个合并箭头:
...--o--*----o----o--o   <-- main
         \
          \
           \
            o--o--o   <-- feature

看,我们刚刚绘制了一个假的压缩合并提交要求的提交图。只要我们在新的提交中获得正确的内容,这正是我们想要的。

但是——这是一种练习;在继续之前看看你是否知道答案——新提交的内容来自哪里?答案在上面,第一个项目的黑体字中。(如果你忘记了,请返回检查)


现在您知道了 git commit 使用索引来创建新提交,让我们考虑现在的真实合并提交。它有内容。 (所有提交都有内容。)他们从哪里来? 他们来自索引! 如果我们能够以某种方式将这些内容放回索引中,那就太好了。 实际上,我们可以:我们只需要使用 git checkout 检出该提交,或者已经将其作为当前提交。

由于我们刚刚创建了新的合并提交,因此我们已经将合并提交作为当前提交。 索引很干净-如果我们运行git status,它会说没有要提交的内容。 所以现在我们使用 git reset --soft 将分支指针 main 后退一步,以获取我们的中间绘图。 选项 --soft告诉 Git:“移动分支指针,但不要更改索引和工作树。” 因此,我们仍将拥有原始合并的索引(和工作树)。 现在我们只需运行git commit 来创建普通提交,提供适当的提交消息,完成了:我们有了一个压缩合并提交。 原始合并现在被放弃了,并且最终-默认情况下,30天后-Git 将注意到它不再使用并删除它。

(要向后移动一步,您可以使用 HEAD~1HEAD^;两者意思相同。 因此,命令序列只是 git reset --soft HEAD^ && git commit,假设当前提交是您希望用虚假合并替换的真实合并。)


即使您进行了多个合并提交,上面的较长方法也可以使用。 不过,您必须决定是否要多个虚假合并,还是一个大的虚假合并。 例如,假设您有以下内容:

...--o--o---o--o--o   <-- develop
      \  \    /  /
       \  o--o  /   <-- feature1
        \      /
         o----o   <-- feature2

你希望你的最终图片看起来像这样吗:

...--o--o---o--o--o   <-- develop
      \  \
       \  o--o    <-- feature1
        \
         o----o   <-- feature2

你是想要把 develop 分支上最后的 两个 提交记录作为伪合并提交,还是只想要一个大的伪合并提交:

...--o--o---o--o   <-- develop
      \  \
       \  o--o    <-- feature1
        \
         o----o   <-- feature2

最终结果在哪个提交?如果你想要两个虚假的“squash”,你需要保留原始的合并提交,直到将它们放入索引并从中进行两个普通提交。如果您只想要最终合并的一个大的虚假" squash ",那只需要两个Git命令:

$ git reset --soft HEAD~2 && git commit

由于我们将保留第二个合并的索引,所以向后移动两步,然后进行新提交,推开这两个合并。

同样重要的是,没有任何实际的合并已被发布,或者如果它们已发布,你需要说服所有其他使用它们的人停止使用它们。


我希望我能够两次接受你的答案。非常清晰和有用。谢谢! 你能详细说明如何创建“两个”虚拟压缩文件吗? - Julien__
1
制作两者都更难:需要使用原始提交ID或创建指向多个真实合并提交的标签。一般解决方案不使用plumbing命令是在设置索引以匹配每个所需的提交状态的同时创建新的提交。要将特定提交的状态放置到位,您可以清空索引(git rm -r .),然后从其他提交重新填充它,而不更改分支(git checkout <commit-specifier> -- .)。 - torek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接