首页 > 解决方案 > Git - 在特定提交之前压缩历史中的所有提交

问题描述

我有一个正在转换为 Git 的 Mercurial 存储库。提交历史非常大,我不需要新仓库中的所有提交历史。一旦我将提交历史记录转换为 Git(并且在推送到新的 repo 之前),我想将某个标记之前的所有提交压缩为一个提交。

所以,如果我有:

commit 6
commit 5
commit 4
commit 3
commit 2
commit 1 -- First commit ever

我想结束:

commit 6
commit 5
commit X -- squashed 1, 2, 3, 4

注意:我需要压缩数千个提交。因此,不能一个一个地手动选择/标记它们。

标签: git

解决方案


到目前为止,其他答案建议变基。在某些情况下,这可能会起作用,具体取决于转换为 Git 存储库中的提交图。新的鸽友变基--rebase-merges绝对可以做到。但这是一种笨拙的方式。执行此操作的理想方法是从您要保留的第一个提交开始转换提交。 也就是说,让您的 Mercurial 导出器导出到 Git,作为 Git 的第一次提交,您想要假装的修订是根。让 Mercurial 导出器继续导出该提交的后代,一次一个到导入器,就像导出器总是要做这项工作一样(不管是什么方式)。

您是否以及如何做到这一点取决于您用于转换的工具。(我实际上并没有进行任何这些转换,但大多数人似乎都使用hg-fast-exportand git fast-import。我没有看太多的内部细节,hg-fast-export但没有明显的原因它不能这样做。)


从根本上(内部),Mercurial 将提交存储为变更集。Git不是这种情况:Git 存储的是快照。但是,Mercurial通过根据需要将变更集汇总在一起来检查(即提取)快照,因此,如果您的工具通过 do hg checkout(或其内部等效项)工作,那么首先这里没有问题:您只需避免检查修订在您想要的第一个快照之前,并将它们导入 Git,生成的 Git 历史记录将从所需的点开始。


但是,如果您使用的工具使这不方便,请注意,在将整个存储库历史记录(包括所有分支和合并)转换为 Git 快照后,您的Git存储库会相对容易地进行第二次操作。例如,您的 Git 历史可能如下所示:

          o-..-o            o--o   <-- br1
         /      \          /
...--o--o--....--o--*--o--o--o--o   <-- br2
      \         /             \
       o--...--o               o   <-- master

其中 commit*是您希望在 Git 存储库中看到的第一个提交。(请注意,如果有多个历史可以追溯到之前*,那么您会遇到不同的问题,并且如果没有额外的历史修改,首先就无法进行这种转换。但只要*处于某种瓶颈,就像它在这个图,很容易在这里剪断图。)

要删除之前的所有内容*,只需使用进行与commitgit replace非常相似*但没有父级的替代提交:

git replace --graft <hash-of-*>

您现在有了一个大多数 Git 将使用的替代品,而不是*没有父提交的替代品。然后git filter-branch使用 no-op 过滤器遍历所有分支和标签:

git filter-branch --tag-name-filter cat -- --all

或者,曾经git filter-repo包含在 Git 中(或者如果你已经安装了它):

git filter-repo --force

(使用 : 时要小心--force选项,filter-repo这会破坏这个存储库中的旧历史,但在这个 csae 中,这就是我们想要的)。

这会将每个可访问的提交(包括替代*但不包括在内)*及其自己的历史记录复制到新提交,然后更新您的分支和标签名称。

如果使用过滤器分支,请删除refs/originals/名称空间(有关详细信息,请参阅文档git filter-branch,如果您愿意,可以强制早期清除原始对象(额外的提交最终会自行消失),您就完成了。


推荐阅读