git - 减少 Bitbucket 上 git 存储库的大小

标签 git bitbucket

经过几个月(提交和推送)我的项目,Bitbucket 上存储库的大小逐渐增加!大约 1 GB,我试图删除一些不重要的数据库文件夹。
搜索后,我发现大部分建议都在提出:

git filter-branch -f --tree-filter 'rm -rf folder/subfolder' HEAD

删除几个文件夹后,我通过 -- force 将更改推送到存储库,如
git push origin master --force

我终于发现每次使用这些命令时,存储库都会变大!!
显然,存储库变大了 2.5 GB!!

请问有什么建议吗?

编辑
根据下面的建议,我尝试了以下命令
(对于所有大文件)

git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch $files" --tag-name-filter cat -- --all



(删除临时历史 git-filter-branch 否则会留下很长时间)

rm -rf .git/refs/original/


git reflog expire --all
git gc --aggressive --prune

但是文件夹 .git/objects 仍然很大!!!!

最佳答案

好的,根据您对评论的回答,我们现在可以说出发生了什么。
git filter-branch 的作用是将您的(部分或全部)提交复制到新的提交,然后更新引用。这意味着您的存储库变得更大(而不是更小),至少最初是这样。

复制的提交是通过给定的引用可以访问的提交。在这种情况下,您给出的引用是 HEAD(git 变成“您当前的分支”,可能是 master ,但无论您当前的分支在 filter-branch 命令时是什么)。如果(且仅当)新副本与原始副本完全相同,那么它实际上是原始副本,并且没有制作实际副本(而是重复使用原始副本)。但是,一旦您进行任何更改——例如删除 folder/subfolder ,从那时起,这些就真的是副本。

在这种情况下,复制的内容较小,因为您删除了一些项目。 (它通常不会小很多,因为 git 可以很好地压缩项目。)但是您仍在向存储库添加更多内容:新提交,它指的是新树,幸运的是,它指的是相同的旧 blob(文件对象)和以前一样,这次只是稍微少了一些(folder/subfolder 文件的对象仍在存储库中,但复制的提交和树对象不再引用它们)。

从图形上看,在 filter-branch 进程的这一点上,我们现在有两个旧提交:

R--o--o---o--o   <-- master
    \    /
     o--o        <-- feature

和新的(我假设 folder/subfolder 出现在原始根提交 R 中,因此我们在这里有一个副本 R'):
R'-o'-o'--o'-o'
    \    /
     o'-o'

在复制过程结束时,filter-branch 现在所做的是重新指向一些引用(主要是分支和标签名称)。它重新指出的那些是你告诉它的,通过将它们称为文档所说的“正面引用”。在这种情况下,如果您在 master(即 HEADmaster 的另一个名称),您提供的单个正引用是 master ... 所以这就是全部 filter-branch 重新指向它还生成名称以 refs/original/ 开头的备份引用。这意味着您现在有以下提交:
R--o--o---o--o   <-- refs/original/refs/heads/master
    \    /
     o--o        <-- feature

R'-o'-o'--o'-o'  <-- master
    \    /
     o'-o'

请注意, feature 仍然指向所有旧的(未复制的)提交,因此即使/在您删除任何 refs/original/ 引用之后,git 仍将保留所有垃圾收集事件中所有仍然引用的提交,给出:
R--o
    \
     o--o        <-- feature

R'-o'-o'--o'-o'  <-- master
    \    /
     o'-o'

要让 filter-branch 更新所有引用,您需要将它们全部命名。一个简单的方法是使用 --all ,它实际上命名了所有引用。在这种情况下,最初的“之后”图片看起来像这样:
R--o--o---o--o   <-- refs/original/refs/heads/master
    \    /
     o--o        <-- refs/original/refs/heads/feature

R'-o'-o'--o'-o'  <-- master
    \    /
     o'-o'       <-- feature

现在,如果您删除所有 refs/original/ 引用,则所有旧提交都将变为未引用并且可以被垃圾收集。好吧,也就是说,除非有标签指向它们,否则它们会这样做。

对于标记引用,如果您提供 filter-branch ,则 --tag-name-filter 只会以任何方式更新它们。通常你想要 --tag-name-filter cat ,它保持标签名称不变,但使 filter-branch 将它们指向新复制的提交。这样你就不会死守旧的提交:这个练习的重点是让所有东西都使用新的副本,扔掉旧的副本,这样大文件对象就可以被垃圾收集。

将所有这些放在一起,而不是:
git filter-branch -f --tree-filter 'rm -rf folder/subfolder'

您可以使用:
git filter-branch -f --tree-filter 'rm -rf folder/subfolder' \
    --tag-name-filter cat -- --all

(您不需要反斜杠-换行符序列;我将其放入只是为了使该行更适合 stackoverflow。请注意, --tree-filter 非常慢:对于这种特殊情况,使用 --index-filter 会快得多。此处的索引过滤器命令将是 git rm --cached --ignore-unmatch -r folder/subfolder 。)

另请注意,您需要在原始存储库的(副本)上执行所有这些操作(您确实保留了备份,对吗?)。 (如果您没有保留备份,则 refs/originals/ 可能是您的救星。)

编辑:好的,所以你做了一些 filter-branch -ing,你做了一些删除任何 refs/originals/ 的事情。 (在我对临时存储库的实验中,在 git filter-branch 上运行 HEAD 使用我所在的任何分支作为重新指向的分支,并制作了先前值的“原始”副本。)没有存储库的备份。怎么办?

好吧,作为第一步, 现在备份 。这样,如果事情变得更糟,你至少可以回到“稍微糟糕”的状态。要备份存储库,您可以简单地克隆它(或:克隆它,然后将原始称为“备份”,然后开始处理克隆)。为了将来引用,由于 git filter-branch 可能具有相当大的破坏性,因此从执行此备份过程开始通常是明智的。 (另外,我会注意到在 bitbucket 上的一个克隆,当还没有 push ed-to 时,将提供服务。不幸的是,你做了一个 push 。也许 bitbucket 可以从他们自己的一些备份或快照中检索存储库的早期版本。)

接下来,让我们注意我之前提到的提交及其 SHA-1“真实姓名”的特殊性。提交的 SHA-1 名称是其内容的加密校验和。让我们看一下 git 自己的源代码树中的示例提交(为了长度而修剪了一点,并且电子邮件地址被重击以阻止收割机):
$ git cat-file -p 5de7f500c13c8158696a68d86da1030313ddaf69
tree 73eee5d136d2b00c623c3fceceffab85c9e9b47e
parent c4ad00f8ccb59a0ae0735e8e32b203d4bd835616
author Jeff King <peff peff.net> 1405233728 -0400
committer Junio C Hamano <gitster pobox.com> 1406567673 -0700

alloc: factor out commit index

We keep a static counter to set the commit index on newly
allocated objects. However, since we also need to set the
[snip]

在这里,我们可以看到此提交的内容(其“真实姓名”为 5de7f50... )以 tree 和另一个 SHA-1、parent 和另一个 SHA-1、一个 author 和 7 个空白行开始,然后是一个空白行,然后是 0x1045提交消息文本。

如果您查看 committer,您会看到它包含子树(子目录)和文件对象(git 术语中的“blob”)的“真实名称”(SHA-1 值)及其模式——实际上,只是 blob 是否应该具有执行权限集,以及它们在目录中的名称。例如,上面 tree 的第一行是:
100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f    .gitattributes

这意味着应该提取存储库对象 tree ,放入名为 5e98806... 的文件中,并设置为不可执行。

如果我要求 git 进行新的提交并设置为它的内容:
  • 同一棵树(.gitattributes)
  • 相同的父级 (73eee5d...)
  • 同一作者和提交者
  • 和相同的空行和消息

  • 然后当我让 git 将该提交写入存储库时,它将生成相同的“真实名称” c4ad00f... 。换句话说,它实际上是相同的提交:它已经在存储库中,而 5de7f50... 只会将现有 ID 还给我。虽然设置所有这些有点棘手,但这正是 git commit-tree 最终要做的:它提取原始提交,应用您的过滤器,设置所有内容,然后执行 git filter-branch

    这对你意味着什么

    在您的原始存储库中,您运行了一个 git commit-tree 命令,该命令将提交复制到新的、修改过的提交(具有不同的 git filter-branch s,因此,在某些时候,不同的真实名称导致后续提交中的父 ID 不同,依此类推)。但是,如果您通过应用这次什么都不做的过滤器来复制那些复制的提交,那么新的 tree 对象将与旧对象相同。如果新的父级相同,并且作者、提交者和消息也都保持不变,则副本的新提交 ID 将与旧 ID 相同。

    也就是说,这些新副本终究不是副本,它们又是原件!

    任何其他提交——那些在第一次传递中没有被复制的提交——都会被复制,因此有不同的 ID。

    这就是事情变得棘手的地方。

    如果您当前的存储库看起来像这样 (从图形上讲):
    R--o--o---o--o   <-- xxx [needs a name so that filter-branch will process it]
        \    /
         o--o        <-- feature
    
    R'-o'-o'--o'-o'  <-- master
        \    /
         o'-o'
    

    我们将新的 tree 应用于所有引用(甚至“除了 filter-branch 之外的所有”),这样它这次会生成相同的树,它将再次复制 master 并且新树将与 R 匹配,因此副本将实际上是 R' 。然后它将复制第一个 post- R' 节点,进行相同的更改,复制的实际上是第一个 post- R , R' 节点。这将对所有节点重复,甚至可能包括 o' 和所有 R' s。但是,如果 o' 复制 filter-branch ,则生成的副本将再次变为 R' ,因为“删除不存在的目录”没有任何更改:我们的过滤器对这些特定提交没有任何作用。

    最后,filter-branch 将移动标签,留下 R' 版本:
    R--o--o---o--o   <-- refs/originals/refs/xxx
        \    /
         o--o        <-- refs/originals/refs/feature
    
    R'-o'-o'--o'-o'  <-- master, xxx
        \    /
         o'-o'       <-- feature
    

    这实际上是想要的结果。

    如果存储库看起来更像这样怎么办? 也就是说,如果没有 refs/originals/ 或类似的标签指向原始(预过滤) xxx ,那么你有这个:
    R--o
        \
         o--o        <-- feature
    
    R'-o'-o'--o'-o'  <-- master
        \    /
         o'-o'
    
    master 脚本仍然会复制 filter-branch 并且结果仍然是 R 。然后它将复制第一个 R' 节点,结果仍然是第一个 o 节点,依此类推。它不会复制现在删除的节点,但它不必:我们已经有了这些,可以通过分支名称 o' 访问。和以前一样,master 可能会复制 filter-branch 和各种 R' 节点,但这没关系,因为过滤器不会做任何事情,因此副本毕竟只是原件。

    最后,o' 将像往常一样更新引用:
    R--o
        \
         o--o        <-- refs/originals/refs/feature
    
    R'-o'-o'--o'-o'  <-- master
        \    /
         o'-o'       <-- feature
    

    使这一切工作的关键是过滤器保持已修改的提交不变,因此它们的第二个“副本”又只是第一个副本。 1

    一切都完成后,您可以执行 the filter-branch documentation 中描述的相同收缩以丢弃 git filter-branch 名称并垃圾收集现在未引用的对象。

    1如果您一直在使用不容易重复的过滤器(例如,使用“当前时间”作为时间戳进行新提交的过滤器),那么您真的需要一个未受影响的原始存储库,或者那些 refs/originals/ 引用(或者足以保留“原始副本”)。

    关于git - 减少 Bitbucket 上 git 存储库的大小,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25399705/

    相关文章:

    android - 如何比较 Android Studio 中的两个 Git 分支?

    git - 当您的项目中有 key 时,如何推送到 GitHub?

    git - 无法 git 推送到 Bitbucket : Unauthorized - fatal: Could not read from remote repository

    Git - 如何重置 "push"

    git - 如何使用 GIT 忽略发布中的某些文件

    git - 从 Bitbucket (git) 上的远程恢复已删除的分支

    ruby-on-rails - Capistrano::NoMatchingServersError on deploy:update_code

    rest - Bitbucket API 公共(public)问题创建

    Git hook 像 Bitbucket 一样在终端中生成 Github "Create Pull Request"链接

    git - 将功能分支 merge 到主要错误 : You are attempting to modify a pull request based on out-of-date information