压缩合并之前的旧 git 提交

Squashing old git commits that were before a merge

我正在清理 git 存储库以使其更易于理解。到目前为止,它一直是私有的,所以我可以改变历史。

大多数情况下,我一直将提交压缩成有意义的集合。

问题是该项目在其历史早期是其他两个项目的合并。我在压缩合并之前的提交时遇到问题。

我的问题是:我该怎么做?

具体来说:我有

xxxxxx * master: Latest commit
xxxxxx * another commit
xxxxxx *   Merge projectA and projectB
xxxxxx |\
0000A6 | * a minor commit in project A
0000A5 | * another minor commit

我想把 0000A5 和 0000A6 压在一起。

当我尝试交互式变基时,magit(我碰巧使用的 git 的 emacs 前端)警告我 "Proceed despite merge in rebase range?",当我继续时,它以下失败(取自我的实际工作,而不是上面的简化示例)。我不确定合并之前的 rebase 提交是否存在问题,或者其他问题("untracked files" 行是可疑的,因为合并包括将客户端项目移动到 "client/" 的子目录中更大的项目。

Last commands done (5 commands done):
   pick fb5de84 Simplified schema:
   squash 96ac7ac Revert schema to full complexity
Next commands to do (29 remaining commands):
   pick 4ad389a Just indentation, for readability
   pick 4241835 First pass at schema
You are currently editing a commit while rebasing branch 'master' on 'ad91ab4'.

Untracked files:
    client/

No changes
You asked to amend the most recent commit, but doing so would make
it empty. You can repeat your command with --allow-empty, or you can
remove the commit entirely with "git reset HEAD^".

Could not apply 96ac7ac7753c03e83b8c1296d892ce9c5fea44c7... Revert schema to full complexity

这里似乎发生了一些事情。我不熟悉您正在使用的前端,但看起来它正在尝试自动执行 rebase 并在出现通常需要手动干预但不知道如何修复的暂停时退出。

虽然通过合并进行变基可能很棘手,但我认为这与当前问题没有任何关系。在我看来,96ac7ac 只是 fb5de84 的还原,所以当您将它们压缩在一起时,您会得到一个空提交。这是允许的,但需要人工干预。 (变基会停止,你会说 git commit --allow-empty 然后继续。)

您可以通过

确认 96ac7ac 是否真的是一个完美的还原
git diff 96ac7ac fb5de84^

如果是,并且您的前端无法适应变基所需的干预,那么您可以放弃这两个提交,而不是压缩它们。

接下来您可能 运行 进入的是 rebase 将尝试使历史线性化,除非您提供 --preserve-merges 选项。我认为这就是您的前端警告您的内容,一旦您告诉它继续,我不知道它是否通过了该选项。

即使使用正确的选项,通过合并进行变基的问题在于,超出默认自动解析范围的原始合并的任何工作都可能丢失。如果需要手动解决冲突,那么 git 将再次停止并等待您执行解决方案(您的前端可能不会配合)。此外,如果自动解析成功但合并包含手动应用的更改(谢天谢地很少见但并非不可能),更改可能会悄无声息地丢失。

另一种选择是 "work around" 合并(如果它们不是太多的话)。如果你有

X --- A --- B --- M --- C <--(master)
  \             /
   D --- E --- F

并且你想压缩 EF,那么你可以先

git checkout F
git checkout -b temp_branch
git rebase -i D

并设置壁球,给你

X --- A --- B --- M --- C <--(master)
  \             /
   D --- E --- F
    \
     EF <--(temp_branch)

然后重做合并

git checkout B
git merge temp_branch

在此合并期间,您必须重现在原始合并中完成的所有工作 M。如果 M 只是自动解析,那么喵喵合并 (M') 也应该如此。你可以用

确认它是好的
git diff M M`

如果这显示出差异,您将必须手动应用它们(这可能也是 M 发生的情况)。我相信你可以让工作树看起来正确并提交 --amend

当然,如果合并信号发生冲突,您将不得不解决它们;再次与 M 进行比较应该会提供很好的指导。

完成后你就有了

         <*>- M --- C <--(master)
             /
X --- A --- B --- M' <--((HEAD))
  \              /     
   D --------- EF <--(temp_branch)
    \
     E --- F -<*>

(抱歉奇怪的符号,图表对我来说太疯狂了;<*>s 应该是从 FM 的单行。但别担心,我们即将解开它。)

清理并对 post-merge 提交进行最终变基:

git branch --delete temp_branch
git rebase --onto M` M master

屈服

X --- A --- B --- M' --- C` <--(master)
  \              /     
   D --------- EF

您可以看到这可能非常乏味,尤其是在有许多合并 and/or 合并包含手动冲突解决或其他非自动更改的情况下。验证最终状态是关键。 (你可以使用 reflog 来帮助解决这个问题,或者在开始所有这些之前标记原始主 HEAD。例如使用 reflog:

git diff master master@{1}

如果你还想验证每个历史提交,这个符号可能会变得混乱......)

另一种变体是,您可以压缩 EF,然后设置 git filter-branch--parent-filterMB , F 重新设置为 B , EF。有关如何使用 --parent-filter 的详细信息,请参阅 git filter-branch 文档(尽管您必须稍微调整示例以处理合并)。这可能有效,因为您特别希望结果具有未更改的树。