我执行了多次merge
提交,但它们应该是merge --squash
。解决冲突花了一天多的时间,所以我无法手动重做合并。
有没有办法将其转换merge
为merge --squash
?
我执行了多次merge
提交,但它们应该是merge --squash
。解决冲突花了一天多的时间,所以我无法手动重做合并。
有没有办法将其转换merge
为merge --squash
?
这里值得注意的是git merge
和git merge --squash
密切相关,但git merge --squash
不创建合并。
这里的分句很重要,尤其是“merge”前面的冠词“a”:“a merge”是名词,“to merge”是动词。两个命令都执行合并操作。不同之处在于结果的保存方式。
还值得以提交图的形式快速提醒一下合并的外观。这里的每个轮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
. 当您执行rungit commit
时,这将进行普通提交,而不是合并提交,因此结果如下所示:
...--o--*--o--o---o <-- main
\
o--o--o <-- feature
这里有两个关键项目:
新提交的内容是相同的。两个新的提交都是从索引中进行的(索引是 Git 的术语,意思是“你下一次提交的内容”)。该索引是由作为动词的合并过程设置的。第一个父项是“主线”提交,来自我们进行合并时所在的分支。第二个父项是另一个提交,我们刚刚合并的那个。
新提交的父链接不同。真正的合并将两个先前的提交作为其父级,但“壁球合并”只有一个先前的提交作为其父级 - “主线”提交。这意味着它不会——它不能——记住合并了哪个提交。
在发生冲突(但真实)合并的情况下,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
看看那个:我们刚刚为那些假壁球“合并”之一绘制了我们想要的那种提交图。这正是我们想要的,只要我们在新提交中获得正确的内容。
但是——这是一种练习;在开始之前看看你是否知道答案——<strong>新提交的内容从何而来?答案在上面的第一个要点中,用粗体字表示。(如果您忘记了,请返回并检查。)
既然您知道git commit
使用索引进行新提交,让我们考虑一下我们现在拥有的真正的合并提交。它有内容。(所有提交都有内容。)它们来自哪里?他们来自索引!如果我们能以某种方式将这些内容恢复到索引中,那么我们就很成功了。事实上,我们可以:我们所要做的就是检查该提交,使用git checkout
或已经将其作为当前提交。
由于我们刚刚进行了新的合并提交,因此我们已经将合并提交作为当前提交。索引是干净的——如果我们运行git status
,它表示没有什么要提交的。因此,现在我们使用git reset --soft
将分支指针向后重置main
一步,以获取我们的中间绘图。该--soft
参数告诉 Git:“移动分支指针,但不要更改索引和工作树。” 所以我们仍然会拥有来自原始合并的索引(和工作树)。现在我们只是运行git commit
一个普通的提交,提供一些适当的提交消息,我们就完成了:我们有一个壁球合并。最初的合并现在被放弃了,最终——默认情况下在 30 天后的某个时间——Git 会注意到它不再被使用并将其删除。
(要后退一步,您可以使用HEAD~1
or HEAD^
; 两者的含义相同。因此,命令序列只是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
最后一次提交是最终结果在哪里?如果您想要两个假壁球,您将需要保留两个原始合并提交足够长的时间以将它们放入索引并从中进行两次普通提交。如果你只想要最终合并的一个大的假壁球,那只需要两个 Git 命令:
$ git reset --soft HEAD~2 && git commit
因为我们将保留第二个合并的索引,所以后退两步,然后进行新的提交,将两个合并推到一边。
同样,重要的是没有一个真正的合并已经发布——或者,如果它们已经发布,你要说服其他所有捡到它们的人,停止使用它们。