如何将 Git 存储库压缩为单个提交并销毁其他所有内容?

How to squash a Git repository to a single commit and destroy everything else?

我想将整个 Git 存储库压缩为单个提交,并实际上删除所有其他提交。

我找到了几个建议,包括:

$ git reset --soft <root-commit>

这适用于压缩,但如果您知道它们的 ID,仍然可以检查以前的提交。我怎样才能摆脱它们?

也许最简单的解决方案是删除 .git 目录,然后再次 运行 git init,不是吗?如果我重新添加原点,然后使用 git push --force,我什至可以保留相同的 GitHub 存储库,对吗?

PS:在中我已经阐明了我真正想要实现的目标。

以前的修订版(本地)将被垃圾收集删除。 git 实施了一些保护措施,尽量不要立即删除内容,但可以通过选项对其进行黑客攻击,以删除某些引用未指向的所有内容(标签、分支、其他内容,如 reflog 引用、存储, ETC)。如果你正在考虑 "remote" 个分支,那么你可以强制推入它们,这样它们也会丢失以前的修订。

更新 - cantSleepNow 的评论让我想到了一些关于我的回答的注意事项。

  • 您想了解未跟踪文件的状态,尤其是当您重建存储库时。这究竟意味着什么取决于您如何使用您的工作树,以及您的忽略规则是如何设置的。

  • 您可能还需要考虑特定于存储库的配置。

未跟踪的文件

我通常将我的工作树保持在 "clean" 状态,这意味着 git status 大多数时候不应报告任何未跟踪的内容。此外,我尝试将 .gitignore 用于我的忽略规则,理想情况下应该很少(输出目录的基于目录的规则,IDE 生成的文件的基于模式的规则可能散布在整个工作树...)

如果您遵循这些相同的做法,那么您通常不必对未跟踪的文件做任何特殊的事情;当你初始化新的 repo 时,你的忽略模式仍然存在。但是,如果您之前提交的文件与您的忽略规则相匹配(如果这是故意的,您仍然想要它们),那么您必须强制将它们添加到您的新回购协议中(或者删除忽略规则,添加它们,然后重新添加忽略规则)。

如果您在 .git/info/exlcude 中有本地忽略规则,那么当您删除 .git 时,这些规则当然会消失(除非您备份它们)。

如果您保留不在忽略规则中的未跟踪文件,则必须确保您不会不小心将它们 add 到新的存储库。 (我会鼓励你对那些前进的人使用忽略规则。)如果你知道你不需要任何未跟踪文件的内容,一个解决方案是使用 git clean 来摆脱它们。

回购配置

您的 .git 目录可以包含特定于 repo 的配置设置、挂钩脚本、本地排除规则(在上面提到过)、LFS 配置(和对象内容)...

如果您对 git 的用法很简单,您可能没有这些东西。如果你做了任何特定于 repo 的事情(而不是签入/源代码控制),那么它可能存储在 .git 下,你需要检查是否备份它。如果您不确定,那么您可能需要使用不同的方法来安全地清理存储库(所以我将在下面提供一个)。

回到你的选择...

最初我建议做最简单的事情,如果你想确定历史已经消失,是

rm -rf .git
git init
git add .
git commit

任何其他程序主要只是模仿此结果的一种更长/更容易出错的方法。但是,如果您确定了要从 .git 中保留的内容,例如挂钩或本地配置,则可能需要额外的步骤。如果您不确定 .git 中的任何内容是否仍然需要,那么您需要一种方法来删除不需要的内容。

要清理内容的存储库:

首先,确保将新的单个提交所需的工作树检出到工作树中。

现在,如果您不在 master,请继续

git branch -f master
git checkout master

然后删除所有参考。您可以使用 git 命令来执行此操作(在某些情况下更安全),但是 最简单的 方法是

rm .git/packed-refs
rm -rf .git/refs/*

这会有点混乱 git,但它会让您处于索引和工作树未更改的状态(仍然是您的旧主状态),但没有可识别的父提交,所以一切都是一个新添加的文件。

git commit

您应该 git 一个没有历史记录的新提交,并且 master 应该指向它。

现在你需要去掉 reflog,因为它仍然可以到达旧的提交。同样,您可以使用 git 命令,但我的运气最好的是

rm -rf .git/logs

现在您可以使用

摆脱旧的提交
git gc --aggressive --prune=now

并验证旧提交不再被发现。

这对您的本地存储库来说没问题;但是 git中心...

您表达了保留现有存储库的愿望,但您也注意到您不希望某人能够获得旧提交 即使他们知道 SHA1.

强制推送将覆盖当前分支上游的引用(可能 master 因为您没有另外指定)。有的话不会影响其他refs(branch, tags),不会影响其他commit。

要删除提交,您需要 (1) 确保没有任何内容(缺少直接 SHA1 引用)可以到达它们,并且 (2) 运行 git gc。来自 github 支持的推文说:

We run git gc at most once per day, triggered automatically by a push.

看来您对此没有太多控制权。强制推送 可能 触发一次 gc,并且 gc 可能 清除旧的提交,但你必须测试它是否真的这样做了(清除浏览器缓存,尝试访问应该消失的提交之一)。

与本地存储库一样,如果这很重要,那么删除存储库并创建一个新存储库可能更容易也更安全。

是的,如果你删除.git,你可以从头开始。

but it's still possible to checkout the previous commits if you know their id

当然...

Maybe the simplest solution would be to delete the .git directory, and run git init again, wouldn't it? If I re-add the origin, and then use git push --force, I could even keep the same GitHub repository, right?

是的,但是正如您所注意到的,所有这些提交仍然在远程 (github) 存储库中。

从评论中,您希望删除一个从一开始就存在的文件(带有许可证)。

答:全部删除

如果您根本不关心历史记录,则继续删除所有内容,包括 GitHub 存储库。事实上,我自己会简单地创建一个新的 GitHub 存储库和一个新的本地存储库,然后从头开始;就像第一次提交一样提交所有内容(确实如此)。

B:手动变基

如果您想保留一些 历史,您完全可以这样做。这是一些伪代码:

  • 创建一个新的空本地 git 存储库 (git init /new)。
  • 对于旧存储库中的每个 $COMMIT(我们称之为 /old),从 ROOTmaster 线性:
    • cd /old ; git checkout $COMMIT
    • rm /new/* ; cp /old/* /new/; rm /new/license.txt`
      • 此语法会跳过所有以 . 开头的目录条目,即 .git。如果您确实有想要保留的以 .(例如 .gitignore)开头的文件,请对其进行优化。
    • cd /new ; git add -A ; git commit -m "$MESSAGE"
      • 从旧存储库中提取 $MESSAGE 作为练习 ;)

这基本上是一本手册 git rebase -i,它可以 100% 确保您可以 100% 控制最终存储在存储库中的内容。很简单,不会有冲突,提示什么的。

C:使用 --exec

变基

第三种方式是这样的:

cd /old
git checkout master
git rebase --exec "rm license.txt" --root 
git clone --single-branch master /old /new    

这样你也会在 /new 中得到相同的内容,但如果你有合并提交会很尴尬,取决于 how/what 更改 license.txt 你可能会变得虚假合并冲突等;我可能会尝试一次,如果开始费力,请快速切换到 B 方法。

您可以在 git rebase 中使用压缩选项,特别是在其 --interactive(或 -i)模式下(请参阅 squashing commits with rebase 以获得良好的演示)。

注意 git rebase 本身就是一个压缩机制,但是从问题开始 "Reapply commits on top of another base".

在交互模式下,您会看到一个特定的提交编辑器,它使您能够管理单个提交、选择它们或压缩它们。以及手动组合单个提交消息的能力。

典型的场景是当你想将许多小的提交合并为一个时,简单地查看历史日志。

最后,使用 git rebase 您可以在物理上和逻辑上压缩提交的基础。

还有一个 --autosquash 选项。

Rebasing 应该解决问题的压缩部分并摆脱起始提交的基础,将整体组合成一个新的单个提交。
显然,正确的解决方案取决于对分支机构的正确管理。但工作流程就像在所需的根(共同祖先)处分支并在其上变基一样简单。
然后您可以删除其余部分。