在我的项目几个月(commit& push)之后,存储库的大小在Bitbucket上逐渐增加!它大约是1 GB,我试图删除一些不重要的数据库文件夹。 搜索后我发现大多数建议都是建议:
git filter-branch -f --tree-filter 'rm -rf folder/subfolder' HEAD
删除几个文件夹后,我将更改推送到存储库 - 按
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的大小仍然很大!!!!
答案 0 :(得分:7)
好的,鉴于您对评论的回答,我们现在可以说明发生了什么。
git filter-branch
做的是复制(部分或全部)您的新提交,然后更新引用。这意味着您的存储库至少在最初时会更大(而不是更小)。
复制的提交是通过给定的引用可以访问的提交。在这种情况下,您提供的引用是HEAD
(git变为"您当前的分支"可能是master
,但无论您当前的分支是filter-branch
的时间。 1}}命令)。如果(并且仅当)新副本精确地,与原始副本一点一点地相同,那么它实际上 原始文件并且没有实际副本(原始文件被重新使用)。但是,只要您进行任何更改(例如删除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
现在所做的是重新点一些引用(主要是分支和标记名称)。它重新指出的那些是你告诉它的那些,通过提及它们是文档所称的&#34;积极参考&#34;。在这种情况下,如果您在master
上(即,HEAD
是master
的另一个名称),则您提供的单个肯定参考是master
...以便&# 39; s 所有 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
,它完全命名所有引用。在这种情况下,初始&#34;之后&#34;图片看起来像这样:
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
使用了我所在的任何分支作为重新指向的分支,并制作了一份&#34;原件&#34;副本以前的值。)没有存储库的备份。现在怎么样?
嗯,作为第一步,立即进行备份。这样一来,如果事情变得更糟,你至少可以回到&#34;只是稍微不好&#34;。要备份repo,你可以简单地克隆它(或者:克隆它,然后调用原来的&#34; backup&#34;然后开始处理克隆)。为了将来参考,由于git filter-branch
可能具有相当大的破坏性,因此通常可以从这个备份过程开始。 (另外,我注意到bitbucket上的克隆,当还没有push
编辑时,会服务。不幸的是你做了push
。也许bitbucket可以检索存储库的早期版本来自他们自己的一些备份或快照。)
接下来,让我们注意一下我之前提到的提交及其SHA-1&#34;真实姓名&#34;的特殊性。提交的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]
在这里,我们可以看到此提交的内容(其名称&#34;真名称为5de7f50...
)以tree
和另一个SHA-1开头,{{1和另一个SHA-1,parent
和author
,然后是一个空行,后跟提交消息文本。
如果您查看committer
,您会看到它包含&#34;真实姓名&#34;子树(子目录)和文件对象(&#34; blob&#34;,在git术语中)的SHA-1值及其模式 - 实际上,只是blob是否应该具有执行权限集,或者不 - 以及他们在目录中的名字。例如,上述tree
的第一行是:
tree
表示应该提取存储库对象100644 blob 5e98806c6cc246acef5f539ae191710a0c06ad3f .gitattributes
,将其放入名为5e98806...
的文件中,并设置为不可执行文件。
如果我要求git进行 new 提交,并设置其内容:
.gitattributes
)73eee5d...
)然后当我得到git将该提交写入存储库时,它将生成相同的&#34;真实名称&#34; c4ad00f...
。换句话说,它实际上是相同的提交:它已经存储在存储库中,5de7f50...
将只返回现有的ID。虽然设置这一切有点棘手,但这正是git commit-tree
最终要做的事情:它提取原始提交,应用过滤器,设置所有内容,然后执行{{{ 1}}。
在您的原始仓库中,您运行了git filter-branch
命令,该命令将提交复制到新的,已修改的提交(具有不同的git commit-tree
s,因此在某些时候,不同的真实名称会导致不同的父ID在后续提交中,等等)。但是,如果您通过应用此次不执行任何操作的过滤器来复制这些复制的提交,则新的git filter-branch
对象将与旧的{{1>}对象相同。如果新的父相同,并且作者,提交者和消息也保持不变,则副本的新提交ID将与相同旧ID。
也就是说,这些新副本毕竟不是副本,它们只是原件了!
任何其他提交 - 在第一次传递中未复制的提交都会被复制,因此具有不同的ID。
这里的事情变得棘手。
如果您当前的存储库看起来像这样(以图形方式说):
tree
我们将新的tree
应用于所有引用(甚至是&#34;除了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'
&#34;之外的所有引用),以便生成这次相同的树,它将再次复制filter-branch
,新树将与master
匹配,因此副本实际上 R
。然后它将复制第一个帖子R'
节点,进行相同的更改,副本实际上 第一个帖子 - R'
,R
节点。这将重复所有节点,甚至可能包括R'
和所有o'
。如果R'
复制o'
,则生成的副本将再次为filter-branch
,因为&#34;删除不存在的目录&#34;没有变化:我们的过滤器对这些特定的提交没有任何作用。
最后,filter-branch将移动标签,留下R'
版本:
R'
事实上,这是理想的结果。
如果存储库看起来更像这样怎么办?也就是说,如果没有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
,该怎么办,所以你有这个:
xxx
master
脚本仍将复制R--o
\
o--o <-- feature
R'-o'-o'--o'-o' <-- master
\ /
o'-o'
,结果仍为filter-branch
。然后它将复制第一个R
节点,结果仍然是第一个R'
节点,依此类推。它不会复制现在删除的节点,但它不必:我们已经有了那些,可以通过分支名称o
访问。和以前一样,o'
可以复制master
和各种filter-branch
节点,但这没关系,因为过滤器什么都不做,所以副本实际上只是原件。
最后,R'
将像往常一样更新引用:
o'
使这一切工作的关键是过滤器保留已修改的提交不受影响,以便他们的第二个&#34;副本&#34;只是第一份副本。 1
完成所有操作后,您可以执行the git filter-branch
documentation中描述的相同收缩,以丢弃filter-branch
名称并垃圾收集现在未引用的对象。
1 如果你一直在使用一个不那么容易重复的过滤器(例如,那个使用&#34进行新提交的过滤器;当前时间&#34;作为他们的时间戳),你真的需要一个原封不动的原始存储库,或那些R--o
\
o--o <-- refs/originals/refs/feature
R'-o'-o'--o'-o' <-- master
\ /
o'-o' <-- feature
引用(任何一个就足以保留一个&#34;原始副本&#34;周围)。