如何从我的Git仓库中删除未被引用的blob

164

我有一个GitHub代码库,其中有两个分支-masterrelease

release分支包含二进制分发文件,这些文件导致存储库的大小非常大(超过250 MB),因此我决定进行清理。

首先我通过git push origin :release删除了远程release分支。

然后我删除了本地的release分支。首先我尝试使用git branch -d release,但Git说"error:The branch 'release' is not an ancestor of your current HEAD."是真的,所以我执行git branch -D release来强制删除它。

但是我的存储库大小,在本地和GitHub上仍然很大。然后我按照常规的Git命令列表运行了git gc --prune=today --aggressive,但没有任何结果。

通过遵循Charles Bailey在SO 1029969中的说明,我可以获取最大blob的SHA-1哈希列表。然后我使用SO 460331中的脚本找到了blob ......而且最大的五个不存在,尽管找到了较小的blob,所以我知道该脚本正在工作。

我认为这些博客是发布分支的二进制文件,并且它们在删除该分支后被留下。正确的方法是如何摆脱它们?


你使用的 Git 版本是什么?你尝试过 https://dev59.com/y3NA5IYBdhLWcg3wEZaT#1108084 吗? - VonC
git版本1.6.2.3我尝试过使用不同的参数运行gc和prune。我还没有尝试过repack -a -d -l,只是运行了它,但没有变化。 - kkrugler
2
新信息 - 从GitHub进行全新克隆后,不再具有未引用的blob,并且大小已减少至“仅”84MB,原先为250MB。 - kkrugler
11个回答

278

我向你介绍这个有用的命令,“git-gc-all”,它能够保证删除所有 Git 垃圾,直到可能出现额外的配置变量:

git -c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 -c gc.rerereresolved=0 \
    -c gc.rerereunresolved=0 -c gc.pruneExpire=now gc

您可能需要先运行类似这些的内容:

git remote rm origin
rm -rf .git/refs/original/ .git/refs/remotes/ .git/*_HEAD .git/logs/
git for-each-ref --format="%(refname)" refs/original/ |
    xargs -n1 --no-run-if-empty git update-ref -d

你可能还需要删除一些标签:

git tag | xargs git tag -d

10
这篇文章应该得到更多的支持。它最终清除了许多其他方法会保留的Git对象。谢谢! - Jean-Philippe Pellet
1
点赞。哇,我不知道我刚刚做了什么,但它似乎清理了很多东西。你能详细说明一下它的作用吗?我有种感觉它清除了所有我的“对象”。那些是什么,为什么它们(显然)无关紧要? - Redsandro
2
@Redsandro,据我所知,“git rm origin”、“rm”和“git update-ref -d”命令会删除远程旧提交的引用,这可能会阻止垃圾回收。 “git gc”的选项告诉它不要保留各种旧提交,否则它将暂时保留它们。例如,gc.rerereresolved用于“您先前解决的有冲突合并的记录”,默认保留60天。这些选项在git-gc手册中。我不是git专家,也不知道所有这些东西的确切作用。我从manpages和grep .git获取了它们的提交引用。 - Sam Watkins
1
一个 git 对象是你的 git 仓库中的压缩文件、树或提交,包括历史记录中的旧内容。git gc 清除不需要的对象。它保留了当前仓库及其历史记录仍然需要的对象。 - Sam Watkins
2
所以这种方法对我没用。我发现.git/info/refs.git/packed-refs中仍然存在引用。使用vim删除这些引用,然后运行命令成功了。虽然我不确定邪恶的提交是否仍然存在于包中。因此,为了保险起见,我按照https://dev59.com/bWQn5IYBdhLWcg3wRVRs的方法解压了包。我建议人们只需克隆一份副本,然后将原始存储库删除。 - Att Righ
显示剩余14条评论

124

你可以(如此答案所述永久删除仅在reflog中引用的所有内容

警告:这将删除许多您可能想要保留的对象:

  • 您所有的存储。
  • 不在任何当前分支中的旧历史记录。

阅读文档以确保这是您想要的。

为了使reflog过期,然后修剪不在分支中的所有对象:

git reflog expire --expire-unreachable=now --all
git gc --prune=now

git reflog expire --expire-unreachable=now --all 会删除reflog中所有无法到达的提交的引用。

git gc --prune=now 则会删除提交本身。

注意:仅使用 git gc --prune = now 将不起作用,因为这些提交仍然在reflog中被引用。因此,清除reflog是必需的。另请注意,如果您使用rerere,则具有其他未被这些命令清除的引用。有关详细信息,请参阅git help rerere。此外,由本地或远程分支或标记引用的任何提交都不会被删除,因为这些都被Git视为有价值的数据。


24
操作成功,但不知何故我在过程中丢失了已保存的隐藏贮藏物(在我的情况下并不是什么大问题,只是需要提醒其他人)。 - Amro
1
为什么不使用“-aggressive”选项? - JoelFan
7
我认为这个答案需要一个清晰的警告,最好是在顶部。我的编辑建议被拒绝了,因为我猜想应该在评论中向作者提出建议?请接受这个编辑 https://stackoverflow.com/review/suggested-edits/26023988 或以你自己的方式添加一个警告。此外,这会删除所有你的暂存内容。这也应该在警告中提到! - Inigo
1
git fetch --prune 进一步减小大小,因为删除本地 blobs。 - hectorpal
如何将这些仓库清理更改推送到所有分支? - Harshal Patil
显示剩余2条评论

35

此 SO 回答所述,git gc 实际上可能会增加仓库的大小!

另请参见此线程

现在 git 有一个保护机制,运行 'git gc' 时不会立即删除未引用的对象。
默认情况下,未引用的对象会保留两周。这是为了方便您恢复意外删除的分支或提交,或避免一个刚创建但尚未被引用的对象被并行运行的 'git gc' 进程删除的竞争。

因此,为了给那些已经打包但是未被引用的对象留出时间,重新打包进程将这些未引用的对象从包中推出,使其变成松散形式,以便它们可以被标记和最终修剪。
变为未引用的对象通常不是很多。拥有 404855 个未引用的对象相当多,并且通过克隆发送这些对象本身就是愚蠢的行为,完全浪费网络带宽。

无论如何... 要解决您的问题,您只需要使用 --prune=now 参数运行 'git gc',以禁用该保护期并立即清除这些未被引用的对象(只有在同时没有其他 git 活动正在进行时才安全,在工作站上很容易确保这一点)。

此外,使用后续版本的 git(或 'git repack -a -f -d --window=250 --depth=250')进行 'git gc --aggressive' 也可以。

同一线程中提到

 git config pack.deltaCacheSize 1

这将限制 delta 缓存大小为一个字节(实际上是禁用它),而不是默认的 0,表示无限制。通过这样做,我能够在带有 4GB RAM 和使用 4 个线程(即四核心)的 x86-64 系统上使用上述的 git repack 命令重新打包该存储库。尽管驻留内存使用率增长到了近 3.3GB。

如果您的机器支持 SMP 且内存不足,则可以将线程数减少到仅为 1:

git config pack.threads 1

此外,您可以使用--window-memory参数进一步限制git repack的内存使用。
例如,使用--window-memory=128M应该能保持对增量搜索内存使用情况的合理上限,尽管这可能会导致delta匹配不太优化,如果仓库中包含大量大文件。


在filter-branch方面,您可以考虑(谨慎地)这个脚本

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch otherwise leaves behind for a long time
rm -rf .git/refs/original/ && git reflog expire --all &&  git gc --aggressive --prune

https://dev59.com/cXRC5IYBdhLWcg3wROpQ 也是使用 filter-branch 命令的好起点。 - VonC
嗨VonC - 我尝试了git gc prune=now,但没有成功。看起来像是一个git的bug,因为在删除分支后,我本地出现了未引用的blob,但是在GitHub repo的新克隆中并不存在这些问题...所以这只是一个本地repo的问题。但是我有其他要清除的文件,所以你上面提到的脚本很棒 - 谢谢! - kkrugler

22

git gc --prune=now,或者是低级的git prune --expire now


14

每次你的HEAD移动时,Git都会在reflog中跟踪这一变化。如果你删除了某些提交,因为这些提交仍被reflog所引用,所以它们仍然存在于“悬挂提交”中,时间约为30天。这就是当你意外删除提交时的安全网。

你可以使用git reflog命令删除特定的提交、重新打包等操作,或者直接使用高级命令:

git gc --prune=now

3
在执行git filter-branchgit gc命令之前,您应该先检查一下存储库中存在的标签。任何具有自动标记功能的真实系统(例如持续集成部署)都会使不需要的对象仍然由这些标签引用,因此gc无法删除它们,您仍然会想知道存储库的大小为什么还是如此之大。

摆脱所有不需要的东西的最佳方法是运行git-filtergit gc,然后将主分支推送到一个新的裸库。新的裸库将拥有清理后的树。


2

2
我运行这个命令后,我的git仓库受到了损坏。现在当我运行git push origin branchname时,会出现以下错误: fatal: 'origin' does not appear to be a git repository完整错误信息: fatal: 'origin' does not appear to be a git repository fatal: Could not read from remote repository.请确保您拥有正确的访问权限并且仓库存在。git版本为2.22.0 - gbenroscience

1

尝试使用 git-filter-branch - 它不会删除大型二进制文件,但可以从整个仓库中删除您指定的大型文件。对我而言,它将存储库大小从数百 MB 减少到 12 MB。


6
“那”是一个可怕的命令 :) 当我更熟练于git时,我会尝试一下。 - kkrugler
你可以再说一遍。我总是对任何操作库历史记录的命令持谨慎态度。当有多个人从同一个库中push和pull时,很容易出现许多Git期望的对象突然不存在的情况,事情往往会变得非常糟糕。 - Jonathan Dumaine

1
为了增加另一个提示,不要忘记使用git remote prune删除你的远程已过时的分支,然后再使用git gc。你可以通过git branch -a命令查看它们。当你从GitHub和fork的存储库中获取内容时,这通常很有用...

1
有时候,“gc” 命令不能起到太大的作用,原因是基于旧提交的未完成的变基或者存储操作。

或者旧的提交被HEAD、ORIG_HEAD、FETCH_HEAD、reflog或其他一些git自动保留的东西所引用,以确保它永远不会丢失任何有价值的东西。如果你真的想要失去所有这些东西,你必须额外努力才能做到。 - Mikko Rantalainen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接