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

时间:2016-11-14 11:25:26

标签: git merge git-merge squash git-squash

我执行了多次merge次提交,但它们应该是merge --squash。冲突的解决花了一天多的时间,所以我无法用手重做合并。

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

1 个答案:

答案 0 :(得分:10)

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

这里的措辞非常重要,尤其是文章" a"在"合并":"合并"是一个名词,而"合并"是一个动词。两个命令都执行合并操作。不同之处在于如何保存结果

它还值得快速提醒,以提交图的形式,合并的外观。这里的每一轮o节点代表一个提交,早期提交向左。分支名称是指向一个特定提交的标签(分支的提示提交)。你可以从这开始,例如:

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

然后,您决定将一个特定的feature分支合并回main分支,以便运行:

$ 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时,这将进行普通提交,而不是 merge 提交,因此结果如下所示:

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

这里有两个关键项目:

  • 新提交的内容是相同的。这两个新提交都是来自索引索引是Git&#39;用于&#34;用于下一次提交的内容&#34;)。此索引由merge-as-a-verb进程设置。 第一个父级是&#34;主线&#34;从我们进行合并时所在的分支提交。 第二个父级是另一个提交,即我们刚刚合并的提交。

  • 新提交的父级链接不同。一个真正的合并两个以前的提交作为其父母,但是&#34;壁球合并&#34;只有一个以前的提交作为其父级 - &#34;主线&#34;承诺。这意味着它不会 - 它可以不记得合并了哪个提交。

如果发生冲突(但实际)合并,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步骤将不再知道进行合并提交,而是生成一个普通的commit-et瞧,你已经进行了壁球合并。

如果您已经进行了真正的合并提交,这个过程可能会更难。特别是,如果你已经发布了合并,你现在必须让所有获得此合并的人都收回它。否则真正的合并将返回(它们可能会再次合并),或者为它们创建问题,甚至两者都会产生问题。但是,如果你可以这样做,或者如果已经发布,你只需要将合并推到一边&#34;,就像它一样,然后放入假的合并提交

让我们重新绘制我们拥有的东西,以腾出空间。这是与以前相同的图表,只是展开到更多行:

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

如果我们可以将main移回到第一行,然后进行新的提交,该怎么办?好吧,我们会得到以下图:

...--o--*----o----o--o   <-- main
         \         \
          \         o   [abandoned]
           \       /
            o--o--o   <-- feature

请注意,所有箭头 - 包括提交用于记录历史记录的内部提交箭头(此处未显示,因为它们在文本绘图中难以生成)-point leftward ,因此,在任何顶级提交中,对于他们之下的任何提交都不知道:这些都是他们的权利&#34;。只有底线提交,加上我们要放弃的提交,才能了解有关顶线提交的任何信息。

此外,如果我们要完全放弃中线提交,让我们停止绘制它,并且它的两个合并箭头:

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

然后看看:我们刚刚绘制了我们想要的那种假壁球&#34; merge&#34; s。这就是我们想要的,只要我们在新提交中获得正确的内容

但是 - 这是一种练习;看看你是否知道答案之前 - 新提交的内容来自哪里?答案如上所述,粗体显示在第一个要点中。 (如果您忘记了它,请返回并检查。)

现在你知道git commit使用索引来进行新的提交,让我们考虑一下我们现在拥有的真正的合并提交。它有内容。 (所有提交都有内容。)它们来自哪里?他们来自索引!如果我们能够以某种方式将那些内容重新载入索引,那么我们就是金色的。事实上,我们可以:我们所要做的就是签出提交,git checkout已将其作为当前提交

由于我们刚刚进行了新的合并提交,因此我们已将合并提交作为当前提交。索引是干净的 - 如果我们运行git status,它说没有什么可以提交的。现在我们使用git reset --soft 重置分支指针main一步,以获得我们的中间绘图。 --soft参数告诉Git:&#34;移动分支指针,但不要更改索引和工作树。&#34;所以我们仍然拥有原始合并的索引(和工作树)。现在我们只运行git commit进行普通提交,提供一些适当的提交消息,然后我们就完成了:我们有一个squash merge。原始合并现在已经放弃,最终 - 在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

最后一次提交是最终结果?如果你想要两个假squashes,你将需要保留两个原始的合并提交足够长的时间以使它们进入索引并从那些提交两个普通提交。如果你只想要一个最后合并的大型假壁球,那只需要两个Git命令:

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

因为我们将保留第二个合并索引,然后移回两个步骤,然后将新提交推到一边 合并。

同样重要的是,没有真正的合并已经发布 - 或者,如果它们已经发布,那么你说服所有已经捡起它们的其他人,停止使用它们