我的代码库中有大约10000个提交,我只想保留最后1000个提交,并删除其余部分。基本上,我想要做的是说“将第一个提交向前移动到X”。
起初,我以为我可以重装和合并所有这些提交成为一个提交,但这会在重装过程中导致很多合并冲突。如果有一种方法可以压缩提交,使得压缩后的版本成为最后一个提交,那也可以。
警告:以下操作非常危险,会重写历史记录。在进行此类重大历史重写之前,请务必确保备份您的存储库。
请将下面的哈希替换为您想要作为新的第一次提交的父提交的哈希。
git filter-branch --parent-filter '
read parent
if [ "$parent" = "-p 5bdd44e5919cb0a95a9924817529cd7c980f88b5" ]
then
echo
else
echo "$parent"
fi'
这将重写每个提交的父级;对于大多数提交,它们保持不变,但是与给定哈希值匹配的父级,将替换为空父级,这意味着它现在将成为一个没有父级的提交。这将使您的旧历史记录全部脱离。
请注意,如果您想使某个合并提交成为您的第一个提交,则需要针对合并提交的每个父级以正确的顺序匹配类似于-p parent1 -p parent2 -p parent3
的内容。
如果您想将此应用于所有分支和标签而不仅仅是当前分支,请在命令(脚本)的结尾处传递--all
。
完成此操作并检查其是否正常工作后,您可以删除原始分支并运行gc
以清除未引用的提交:
git update-ref -d refs/original/refs/heads/master
请注意,由于git
倾向于尽可能保留数据,因此为了实际释放空间,您还需要从reflog中删除提交,并运行gc
进行清理。
git reflog expire --expire-unreachable=all --all
git gc --prune=all
如果你不是为了节省空间或清除旧的提交历史,可以在一个分支中保留旧的历史记录,例如 git branch old-master refs/original/refs/heads/master
; 你甚至可以使用 git replace
"虚拟重新连接"它,此时你将有两个不相关的历史记录(因此当你推送到远程仓库时,只会推送截断的历史记录),但当你在本地仓库中查看历史记录时,你将看到完整的历史记录。
cp -a
复制了仓库,因为我想要所有的分支,然后当我运行这个命令时,根据 git log 的记录,我想要作为第一个提交的那个现在消失了,但是之前的所有提交都还在,这正是我想要删除的。在你能得到更好的答案之前,我不会使用它。 - blamb对我来说最简单的方法是使用git replace
(编辑:已成功测试!)。
首先将您想要压缩的所有提交合并为一个: (我们将称最后一个要压缩的提交的 sha 为 "last_commit_sha", 第一个提交的 sha 为 "root_commit_sha")
git checkout -b big_squash <LastSha>
git reset --soft <RootSha>
git commit --amend -m "My new root"
现在,您必须将分支big_squash
指向一个新的根(这里称为<NewRootSha>
)。我们只对sha1感兴趣,一旦您成功完成操作,该分支最终可以被删除。
然后您有两种选择:
git rebase --onto
命令重叠后续提交记录(这是Git书中推荐的解决方案,但在测试了其他方案后,这不是我的首选;))git replace
隐藏旧的历史记录(历史记录仍然在存储库中!但我们将使用git filter-branch
使其永久化)要用新创建的提交替换要合并的最后一个提交:
git replace <RootSha> <NewRootSha>
现在,你可以在 git replace
后进行 git filter-branch
操作以使其变为永久性的!
在替换后,执行以下操作:
git filter-branch master, <put here the name of all your branches>
如果您满意结果,那么请删除文件夹.git/refs/original
(其中包含所有git filter-branch
之前保存的引用)和文件夹.git/refs/replace
(其中包含您不再需要的替换引用)。此解决方案的优点是简单和可逆的(除了您删除文件夹后的最后一步操作 ;))。
完成了!
这里可以找到文档:
git replace got smarter
部分)git filter-branch master
。 - Vinay Wgit replace
命令了。 - Philippereplace
的目标,它将”隐藏”所有需要合并的提交,而filter-branch
将重新构建所有分支的历史记录并更新SHA1。 “master”将被修改,我可以向您保证! - Philippe.git/refs/original
目录下的更改的说明,然后再删除它。我认为这是最好的解决方案,感谢您的帮助! - dmviannagit clone --depth 1000
进行浅层克隆。浅层克隆仍具有完全的提交能力,详见https://github.com/git/git/commit/82fba2b9d39163a0c9b7a3a2f35964cbc039e1a。您想要的东西并不完全符合实际情况,因为您无法从存储库中删除任何内容,只能向其中添加新内容。
简单来说,通过提交图形绘制,您现在拥有的是:
<jumble of commits> - K - L - M - etc ... <-- master
\ / (merges) <-- etc
(branches)
你想要的是什么(同样简化):
K - L - M - etc ... <-- master
\ / (merges) <-- etc
(branches)
因此K
现在成为了根提交。
您无法得到那个,但是您可以获得一个几乎完全相同于K
的新根提交,只有两个重要的差异:不同的SHA-1和没有父提交ID。该提交将与提交K
具有相同的树和所有相同的文件。
将K
复制到K'
后,您可以将L
复制到L'
等等,以便您得到一个具有相同形状和相同文件等的新提交图,只是所有新的SHA-1 ID。
执行此操作的git命令是filter-branch
。
使用filter-branch
至少有两种方法可以实现此目标。一种方法是使用提交筛选器:
K
,然后K
本身)(然后添加通常的--tag-name-filter cat
等)。这种方法稍微有些麻烦,因为提交筛选器不会被eval
评估,所以您必须在外部“记住”跳过/保留状态(例如,在文件中)。
另一种方法是使用--parent-filter
,如Brian Campbell所述。
它们之间的区别在于--parent-filter
方法更容易,但也复制了所有“pre-K
”提交,因此您会在副本中获得两个独立的图形。您可能需要这样做,也可能不需要;并且如果在清除refs/original
名称空间后没有对“pre-K'
”提交的引用,则它们将像往常一样被垃圾收集器回收,使差异消失。
git filter-branch
的任何方法都会通过refs/original/...
备份分支保留旧提交记录。由于我编写的--parent-filter
仅涉及一个提交记录,因此对于早于该点的所有提交记录,它将是无操作的,因此它们将与您在refs/original
备份分支中保留的完全相同。 - Brian Campbellrefs/original
备份之后会发生什么。如果你省略了“copies”(正如你所指出的那样,它们实际上只是重用原始对象),filter-branch也会执行“remap to ancestor”的操作。假设一些早期提交(比如E
)有一个分支或标签指向它,如果你“复制”,它仍然会指向E
。如果E
及更早的提交都不存在了,我其实不确定remap-to-ancestor会怎么做... - torek
git filter-branch
来完成,就像Brian的回答中所示,但要小心谨慎。 - jub0bsgit
中,并且在早期做了很多这样的操作,现在需要清理它,以便你不会永远携带这些历史记录,或者类似的情况。虽然有一些合理的理由去这么做,但通常最好尽量避免陷入这些情况。 - Brian Campbell