从Git历史记录中删除一个二进制文件后,为什么我的代码库仍然很大?

26

首先声明,我知道Stackoverflow上之前有关于这个主题的问题。事实上,我已经尝试了所有能找到的解决方法,但是我的repo中有一个二进制文件一直无法删除,导致我的repo大小急剧膨胀。

我尝试过以下方法:

这两种方法都是Darhuuk在Remove files from git repo completely问题的答案中推荐的。

然而,尽管我已经尝试了这两种方法,但查找git中大文件的脚本仍然找到了那个有问题的二进制文件。不过,这个答案中的脚本已经找不到那个二进制文件所在的提交记录了。这两个脚本都是这个答案中建议的。

尽管我已经尝试过删除,但是repo的大小仍然是44MB,相对于源代码而言太大了。这表明那个查找大文件的脚本可以正常工作。我尝试将其push到Github(我做了一个fork以防万一),然后进行全新的clone,看看repo的大小是否减小,但结果仍然是相同的大小。

有人能解释一下我做错了什么或者提供另一种方法吗?

需要注意的是,不仅仅是想要从本地repo中删除该文件,我也希望能够修复Github上的远程repo。


这些方法不起作用可能是因为我有多个分支,这种情况是否可能? - James McMahon
是的...如果有任何分支(包括通过fetch检索到的远程分支)引用了一个对象,则它不会被视为无法访问而被修剪。 - Todd A. Jacobs
那么问题就变成了,我该如何从从Github拉取的仓库中删除对象,然后将不含二进制文件的仓库推回去呢? - James McMahon
我尝试了下面的方法,但都没有成功,有人能提供解决方案吗?是否有工具可以重新创建仓库,但不包括二进制文件? - James McMahon
另一个更新,我有点难为情,我的本地历史记录重写没有成功,因为我没有使用文件的完整路径(我也可以使用通配符路径)。所以我可以将本地存储库大小降低(从44mb降至1mb),但是在推送到远程Github存储库后,它仍然与具有二进制文件的存储库相同大小。 - James McMahon
4个回答

27

2017年编辑:如果您正在阅读此内容,您应该考虑查看BFG Repo-Cleaner


非常尴尬的是,我的本地存储库未缩小的原因是我在filter-branch中使用了错误的文件路径。所以,我感谢J-16 SDiZ和CodeGnome提供的答案,但我的问题出现在了我的操作上。

为了使这个问题不再成为我愚蠢的纪念碑,而是对人们有用,我花时间编写了一些步骤,以便在修剪存储库后将其重新上传到Github。希望这能帮助到其他人。


删除有问题的文件

要删除有问题的文件,请运行下面的shell脚本,根据Github删除敏感数据指南

#!/usr/bin/env bash
git filter-branch --index-filter 'git rm -r -q --cached --ignore-unmatch '$1'' --prune-empty --tag-name-filter cat -- --all

rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

我已经在我的本地代码库中遍历了所有分支并执行了此操作,但是我真的不确定是否需要这样做(您不需要在每个分支上都这样做),但是您确实需要在下一步中使用所有本地分支,所以请记住这一点。完成后,您应该会看到本地仓库的大小减小。您还应该能够运行CodeGnome答案中的blob脚本,并查看有问题的blob被删除了。如果没有,请仔细检查文件名和路径,并确保它们正确。

git filter-branch 实际上是在每个提交中运行引号中列出的命令。

其余的脚本只是清除旧数据的任何缓存版本。

推送修剪后的仓库

现在本地代码库处于所需状态,关键是将其推回Github。不幸的是,据我所知,没有完全从Github仓库中删除二进制数据的方法,以下是Github敏感数据说明文档中的引用:

请注意,强制推送不会删除远程仓库中的提交,它只会引入新的提交并将分支指针移动到它们所指向的位置。如果您担心用户通过SHA1直接访问错误的提交,您将不得不删除仓库并重新创建它。

需要重新创建Github仓库是很糟糕的,但好消息是重新创建仓库实际上非常容易。痛苦之处在于,您还必须重新创建问题和wiki中的数据,下面我将详细介绍。

我建议的做法是在Github上创建一个新的仓库,并在准备好时将其替换为旧的仓库。这可以通过将旧仓库重命名为类似于“repo name old”的名称,然后将新创建的仓库的名称更改为“repo name”来完成。确保在创建新仓库时取消选中初始化README,否则将无法从干净的状态开始。

如果您完成了上一步,那么应该已经清理好并准备好使用了。现在需要更改远程以匹配新的Github仓库位置。我直接编辑.git/config文件来完成此操作,尽管我确定有人会告诉我这不是正确的方法。

在推送之前,请确保您在本地代码库中拥有所有要推送的分支和标签。一旦准备就绪,请使用以下方式推送所有分支:

git push --all
git push --tags

现在你应该有一个与你本地裁剪过的仓库相匹配的远程仓库。请再次检查所有数据,以防万一。

如果你没有问题或者wiki,那么你已经完成了。如果有,请继续阅读。

移动Wiki

Github Wiki只是与你的主要仓库相关联的另一个仓库。所以,为了开始工作,请在某个地方克隆你的旧Wiki仓库。接下来的部分有点棘手,据我所知,你需要点击你的新仓库的Wiki选项卡才能创建Wiki,但它会使用一个初始文件来填充新创建的Wiki。所以我做的是更改远程到新创建的Wiki仓库,并使用以下命令将其推送到新位置:

git push --all --force

之所以需要使用 force 是因为否则 git 会抱怨当前分支的 tip 不匹配。我认为这可能会让 git 仓库中的初始页面处于脱离状态,但对仓库大小的影响应该可以忽略不计。

迁移问题

此答案 中提供了一些建议。但是看到答案中链接的脚本,它似乎相当不完整,评论导入上有一个 TODO,而我也无法确定它是否会带过问题的状态。

因此,考虑到我有一个相当小的未解决问题队列,并且我不介意失去已关闭的问题,我选择手动搬迁。请注意,无法在评论中正确地归属其他人。因此,我认为对于一个更大、更成熟的项目,您需要编写一个更健壮的脚本来搬迁所有问题,但对于我的特定情况,这并不需要。


23

假设您已经使用git-filter-branch(1)等工具从历史记录中删除了Blob,Git通常会在reflogs、packfiles和loose repository objects 中保留一些内容。移除这些未引用的对象的术语为:

git prune --expire=now
git reflog expire --expire-unreachable=now --rewrite --all
git repack -a -d
git prune-packed

如果您已经这样做了,但是您的代码库仍然比您预期的要大,那么您仍然在代码库中某个地方引用了您的blob。您需要回到步骤一并将它们删除。以下内容可能有所帮助:

# List all blobs by size in bytes.
git rev-list --all --objects   |
    awk '{print $1}'           |
    git cat-file --batch-check |
    fgrep blob                 |
    sort -k3nr

我不确定我是否有较旧版本的Git,但是rev-list仅为我输出哈希值,因此awk管道是不必要的。 - vergenzt
1
修剪(prune)和reflog操作已经在Underhill的脚本中了。即使使用了额外的选项,也没有成功。 - James McMahon
我运行了你的命令,但是我的存储库中仍然有一个大文件。我用你最后一个命令找到了blob,但现在不确定该怎么做。 - northben
这个建议对于我的本地仓库有效,但我仍然不确定如何将其传播到我的远程仓库。git push <path/to/remote>只会告诉我一切都是最新的。从远程进行连续克隆仍然很大。 - worldsayshi
如果你进行了强制推送,那么不可达对象就不应该被克隆。然而,要想真正删除已打包或可达的对象,你必须直接在远程上执行命令。你不能使用客户端/服务器命令进行存储库手术;这是一种特性。 - Todd A. Jacobs
@CodeGnome,你的意思是说一旦在远程上删除了引用,克隆存储库就不应该携带非引用对象?这似乎很直观,而且今天我在远程部分尝试这样做时确实成功了。昨天我尝试在本地(裸)存储库上强制推送时似乎没有起作用。克隆出来的文件很大。无论如何,我现在很高兴。 - worldsayshi

6
查找git中大文件的脚本会检查.pack文件,即原始对象仓库。第二个脚本显示该大文件已不再被引用。如果您真的想清理它,可以执行gcrepack操作:
git gc --aggressive --prune=now
git repack -A -d

如果这仍然没有帮助,你可能在远程分支中有一个对象引用,可以尝试以下步骤:
  1. 找出哪个提交包含此对象,请参见Which commit has this blob?并运行git branch -a --contains <commit-ish>
  2. 使用git branch -r -D branchname删除远程分支

更新--什么是“远程分支”?

  • 当你运行git fetch/git pull时,远程分支是git获取内容的位置。(git pull相当于git fetch refspec+git merge remote-branch)。

  • 如果你从远程仓库克隆,删除远程分支应该没有什么影响--你总是可以使用类似git fetch origin refs/heads/master:refs/remotes/origin/master这样的命令再次从远程获取(将master分支从远程获取到远程分支remotes/origin/master)。

  • 如果这个分支是由你创建的,那么删除也应该是可以的——因为你应该有一个“普通”的(跟踪)分支。但是,你应该再次确认一下。


1
不行,执行了这两个命令后我仍然能看到文件。gc 命令已经在 Underhill 的脚本里了 :( - James McMahon
你肯定是对的,.pack 文件就是问题所在。绝大部分仓库大小都在那个文件里面。 - James McMahon
@JamesMcMahon 好的,这意味着对象在远程分支(或其他非常规分支引用)中。请查看更新后的答案。 - J-16 SDiZ
+1 适用于远程分支。我在 https://dev59.com/lXE85IYBdhLWcg3wNw14#2882485、https://dev59.com/r3RB5IYBdhLWcg3wN1AQ#685422 或 https://dev59.com/53I95IYBdhLWcg3w3h_G#2116892 中没有提到它们。 - VonC
@CodeGnome 那个脚本是问题的参考,我只是复制了它。 - J-16 SDiZ
显示剩余9条评论

1

有人能解释我做错了什么或者建议其他方法吗?

你尝试过应用DMAIC吗?Define(定义),Measure(测量),Analyze(分析),Improve(改进),Control(控制)。

D - 从git历史记录中删除文件后,我的repo仍然很大。
M - 使用git init确定新repo的大小以建立基准线。
A - 确定,验证和选择根本原因。尝试使用git-repo-analysis进行实验。
I - 确定,测试和实施解决方案。也许BFG Repo-Cleaner会有所帮助,也可能不会。
C - 维持收益。查看像Git LFS这样的东西或其他适当的控制方法。

我还想能够修复Github上的远程repo。

这将取决于您选择如何解决问题。例如,当使用BFG从历史记录中修剪文件时,它将重写历史记录并更新提交SHA,因此根据您的特定需求和期望结果,会有一些得失。

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