如何 "squash" 后续合并提交

How to "squash" subsequent merge commits

在我的项目中,我有这样一段历史:

    b --- c --- g
  /        \     \ 
a           f --- h --- j
  \        /           /
    d --- e --- i ----

我想删除中间合并提交,并且只有一个合并提交具有相同的内容,例如:

    b --- c --- g
  /              \ 
a                 j'
  \              /
    d --- e --- i 

一种方法是从头开始重做所有内容,但 f 大约有。 600 个冲突文件,g 实际上又是 200 个新提交,我不想再花一个星期来解决冲突。 我如何实现这一目标?

如果我知道分支结构,我可能会更清楚地解释这个过程,但通常你会这样做:

您实际上只需要在 gi 之间创建一个新的合并,因为在您绘制它时,none 个您要删除的合并位于gi 的历史记录,以及 的所有相关更改 gi 的历史记录中(即部分被删除的是 合并提交[1]).

您不想重做所有的冲突解决,但这没关系,因为您已经知道合并的预期结果。所以你需要做的是复制 j 的 parents 是 gi。至少有两种方法可以做到这一点。

最 straight-forward 的方式使用管道命令,因此它可能看起来不太熟悉。

git checkout $( git commit-tree j^{tree} -p g -p i -m "merge message here" )
git branch -f my_branch

其中 my_branch 是您当前在 j 处拥有的分支,j gi 是解析为相应提交的表达式你的图表。 (这可能是提交 iD (SHA) 值、当前指向这些提交的引用等)

请记住,提交的 parents 是有序的 - 第二个 -p 选项标识似乎是 'merged into' 第一个 -p 选项的提交。

另一种方法是检查 g(假设 g 应该是第一个提交;否则在此过程中交换 gi),并且

git merge --no-commit i
git rm -rf :/:
git checkout j -- :/:
git commit -m "merge message here"

在这种方法中,您将启动一个新的合并,清除工作树和索引,从之前的合并结果加载提交树和索引,然后使用该内容完成合并。

我觉得这有点复杂,它包含一个 rm 命令,如果您对正在发生的事情没有信心,它可能会发出危险信号,但它的优点是坚持使用适用于最终用户。


[1] 我有点假设 none 的合并会引入新的变化(除了解决冲突)。如果他们这样做,他们就是有时所谓的 "evil merges";实际上,上面的过程仍然有效,但它会导致您的新合并也是 "evil merge"。

我能想到的能够在不经过任何合并过程的情况下一次性完成的唯一方法是使用 git commit-tree。首先,使用 git cat-file:

获取 J 版本树的 ID
git cat-file -p j

复制树对象的id。

然后 运行 git 提交树,你可以用 -p 提供 2 个父修订,用 -m 提供注释,以及你从上一个 git 命令将用作调用的最后一个参数。

git commit-tree -m 'Here's the comment" -p g -p i tree-object-of-j

当您 运行 该命令时,git 将打印出创建的新修订对象的 ID。您可以将一个分支指向它,或者如果您已经检查过修订是您想要的,则移动 j 指针:

git branch -f j new-revision-of #move j branch to the new revision id

您将成为修订版的作者....如果您想更改日期或作者,您可以通过设置环境变量来实现。检查 git help commit-tree.

这是一种方法:

 git reset --hard g //reset branch to g commit (use 'i' and merge 'g' if the  branch originally came from i) 
 git merge i --no-commit  
 git reset j :/: //resolve all conflicts by replacing the staging area with all file versions from original commit j
 git commit
 optional:  git reset --hard //if you want the work tree to reflect the new merge commit