在Bitbucket上减小git仓库的大小

7

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

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

删除了一些文件夹后,通过 --force 参数将更改推送到仓库中,如下:

git push origin master --force

我发现每次使用这些命令时,仓库的大小都会变大!!明显地,仓库会增加2.5GB!!
请问有什么建议吗?
编辑: 根据下面的建议,我尝试了以下命令(针对所有大文件):
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文件夹仍然很大!!!!

你有除了 master 以外的其他分支和/或标签吗?(这里可能是 HEAD 指向 master ...) - torek
在本地仓库上尝试运行 git gc --prune=now 命令,Git 会保留对之前更改的引用。如果您的本地仓库变小了,它最终也会在 Bitbucket 上变小。 - Willem D'Haeseleer
@torek:是的,我有分支和标签。 - Y.AL
@willem-dhaeseleer:我尝试了git gc --prune=now,它减少了一点,现在大约减少了2 GB! - Y.AL
我的问题已编辑 - Y.AL
1个回答

8

好的,根据您对评论的回答,我们现在可以说发生了什么。

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,它确实命名了所有引用。在这种情况下,初始的“after”图片看起来像这样:
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操作,并且删除了任何refs/originals/。 (在我的临时存储库实验中,对HEAD运行git filter-branch会使用我所在的任何分支作为重新定位的分支,并创建先前值的“originals”副本。)没有存储库的备份。现在怎么办?

好吧,作为第一步,现在备份。这样,如果情况变得更糟,您至少可以回到“仅略有损失”的状态。要备份repo,您可以简单地克隆它(或:克隆它,然后称原始副本为“备份”,然后开始使用克隆)。供将来参考,由于git filter-branch可能非常具有破坏性,通常最好从执行此备份过程开始。 (另外,我将注意到,在未push到时,bitbucket上的克隆将起作用。不幸的是,您进行了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、authorcommitter开头,然后是空行,接着是提交消息文本。
如果您查看tree,您会看到它包含子树(子目录)和文件对象(Git术语中的“blob”)的“真实名称”(SHA-1值),以及它们的模式 - 实际上只是blob是否应该设置执行权限以及它们在目录中的名称。例如,上述tree的第一行是:
100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f    .gitattributes

这意味着需要提取存储库对象5e98806...,将其放入名为.gitattributes的文件中,并设置为非可执行文件。
如果我要求git创建一个新提交,并设置其内容:
- 相同的树(73eee5d...) - 相同的父级(c4ad00f...) - 相同的作者和提交者 - 以及相同的空行和消息
那么当我让git将该提交写入存储库时,它将生成相同的“真实名称”5de7f50...。换句话说,它实际上是相同的提交:它已经在存储库中,git commit-tree只会给我返回现有的ID。虽然设置所有这些有点棘手,但这正是git filter-branch最终要做的:它提取原始提交,应用您的过滤器,设置所有内容,然后执行git commit-tree
对您的影响是什么?
在您的原始存储库上,您运行了一个git filter-branch命令,将提交复制到新的修改提交中(具有不同的tree,因此在某些时候,拥有不同的真实名称,这导致在后续提交中具有不同的父ID等)。但是,如果您通过应用这次“什么也不做”的过滤器来复制那些已复制的提交,则新的tree对象将与旧的相同。如果新的parent相同,并且作者、提交者和消息也都保持不变,则副本的新提交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'

我们对所有引用(甚至“除了master”之外的所有引用)应用一个新的filter-branch,以便以这种方式生成相同的树,这次它将再次复制R,新树将与R'的树匹配,因此复制实际上将是R'。然后它将复制第一个R节点之后的节点,进行相同的更改,复制将实际上是第一个R'o'节点。这将为所有节点重复进行,可能甚至包括R'和所有的o's。如果filter-branch复制R',则生成的副本将仍然是R',因为“删除不存在的目录”不会有任何更改:我们的过滤器对这些特定提交没有任何影响。
最后,filter-branch将移动标签,保留refs/originals/版本:
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

这实际上是期望的结果。 如果存储库看起来更像这样怎么办? 也就是说,如果没有指向原始(经过过滤前的)masterxxx或类似的标签,那么你会得到这个:
R--o
    \
     o--o        <-- feature

R'-o'-o'--o'-o'  <-- master
    \    /
     o'-o'
filter-branch脚本仍将复制R,结果仍将是R'。然后它将复制第一个o节点,结果仍将是第一个o'节点,依此类推。它不会复制已删除的节点,但也不必复制:我们已经通过分支名称master可达到这些节点。与以前一样,filter-branch可能会复制R'和各种o'节点,但这没关系,因为过滤器什么都不做,所以复制品实际上就是原件。

最后,filter-branch将像往常一样更新引用:

R--o
    \
     o--o        <-- refs/originals/refs/feature

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

这一切都能够实现的关键是过滤器不会对已经修改过的提交进行更改,因此它们的第二个“副本”只是第一个副本。1 完成后,您可以执行与 git filter-branch文档 中描述的相同缩小操作,以摆脱refs/originals/名称并垃圾回收现在未被引用的对象。

1如果您使用的过滤器不能轻易地重复(例如,一个会使用“当前时间”作为其时间戳进行新提交的过滤器),则您确实需要一个未更改的原始存储库,或者这些refs/originals/引用(其中任何一个都足以保留“原始副本”)。


在我的情况下,我检查了本地磁盘上的ref/original文件夹,它是空的!! - Y.AL
考虑一下,如果你进行了一个 filter-branch(这会复制提交记录,但只重新指向master),然后再进行一个 filter-branch(这会复制所有提交记录,包括原始的副本)。通过一些小心和/或运气,可以使一切正常工作,但更容易的方法是恢复旧的master并丢弃第一次运行的所有副本。现在你回到了全部原始提交记录。如何恢复旧的master呢?其中一种方法是使用refs/originals/refs/master引用。 - torek
谢谢,但你能帮我恢复旧的主分支吗?实际上我执行了(git filter-branch -f --tree-filter 'rm -rf folder/subfolder' HEAD)很多次,我应该使用: git reset --hard origin/master 还是其他什么命令? 对于我来说,在本地磁盘上refs/originals/是空的!!你能否提供完整的命令来恢复旧的内容? - Y.AL
我已经通过克隆的方式进行了备份。 实际上,我检查了本地磁盘上的项目文件夹,并发现大尺寸来自于此文件夹:.git/objects/pack。如果我删除它是否安全?或者缩小它? - Y.AL
pack 文件是存储(压缩)对象的地方。换句话说,这就是 Git 存储所有文件、树、提交等的地方。如果你删除了这些文件,那么你就删除了所有的文件! - torek
显示剩余2条评论

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