删除Git仓库中的大文件
Git是用来管理源代码的一个工具,很多时候,我们不想让Git来跟踪较大的二进制文件。但是如果不小心将某个文件加入到Git的缓存区后,不管后面怎么删除这个大文件,Git始终都保存有这个文件的历史记录,因此项目会很大。拿下面例子来说,我们有个500M的文件cnn.model
,通过下面的命令加入到git暂存区或提交到远端(提交时自动执行git gc命令,生成pack文件):
1 | $ git add cnn.model |
经过这步操作,用du -sh .
命令查看项目大小的话,发现足足有1000多M,因为本地文件cnn.model
以及.git
目录中的object也有一份这个文件的记录。
即使使用git rm
命令删除当前的cnn.model
文件,.git
目录中还是记录有这个大文件的记录,因此后面别人clone这个项目后,项目还是很大。因此这里需要使用git filter-branch
命令来删除.git
目录中的文件记录:
1 | $ git filter-branch --index-filter 'git rm -r --cached --ignore-unmatch <file/dir>' -- --all |
这是在你已知大文件的名字和目录情况下的删除过程。如果过了很久或者是有很多大文件,我们需要有一系列的命令来找出大文件,然后对其进行过滤。下面详细阐述整个过程。
识别出大文件对象
Git中会对大文件进行打包,生成git pack格式的.pack
文件以及对应的同名的.idx
文件,存放在.git/object/pack
目录中。通常来说,Git仓库的大文件都是.pack
格式的,存放在这个目录中。
我们可以使用git verify-pack -v <SHA-1-code>.idx
命令来查看打包文件*.pack
的内容,如下面是该命令的一个示例输出:
1 | $ git verify-pack -v .git/objects/pack/pack-2a54c6297dea8fa7feaa30b9738459765bb369a5.idx |
可以看到这个pack压缩包中有3个文件,对应输出的2-4行,每行的格式如下:
1 | SHA-1 type size size-in-packfile offset-in-packfile |
因此我们可以根据每行的第3项的值,即文件的大小对压缩包中的文件进行排序,然后根据大小排序找出大文件。具体的命令如下:
1 | git verify-pack -v .git/objects/pack/<SHA-1-code>.idx | sort -k 3 -n |tail -n 20 |
上述命令会对对应的压缩文件进行分析,找出其中最大的20个文件。下面是一个示例输出:
1 | git verify-pack -v .git/objects/pack/pack-318f6bd223ffc6f1cd5675946e9fe7fe11bbaa16.idx | sort -k 3 -n |tail -n 20 |
其中每行是一个Git的对象(Object)。
找出Git对象对应的文件名
由于上述步骤得到的Git对象只有一长串的SHA-1的值,而没有具体的对应的在文件系统中的文件名字,因此我们需要找出Git对象对应的文件名。
我们可以使用git rev-list <commit-id>
来达到此功能。 这个命令用来显示某次提交前的所有的提交对象(commit object),而加了--objects
则用来显示某次提交时所有的Git对象。使用--all
则显示所有的提交,而不是某次特定的提交下的对象信息。因此用下面的命令可以查看Git对象和对应的文件路径:
1 | git rev-list --objects --all |grep <SHA-1-code> |
以上个步骤中输出的最后一个文件为例,执行这条的命令(注意SHA-1的值只用输入前6位即可):
1 | $ git rev-list --objects --all |grep 705d52 |
可以看出,这个Git对象对应的文件路径是data/model-4000M.caffemodel
。
找出修改这个文件的所有commit
我们需要从commit历史中找到所有修改该文件的commit然后修改这些commit。这里我们使用git log
来操作,具体如下:
1 | git log --pretty=online -- <file-name> |
以data/model-400M.caffemodel
文件为例,这个命令的具体形式为:
1 | $ git log --pretty=oneline -- data/model-400M.caffemodel |
重写所有修改这个文件的提交
找到所有修改这个对象的commit后,我们找到最早的修改,然后使用git filter-branch
命令来操作,具体如下:
1 | $ git filter-branch --index-filter 'git rm --cached --ignore-unmatch data/model-400M.caffemodel' -- 32a9f5 |
也可以将这步和上面一步合在一起,直接从所有提交中删除这个对象:
1 | $ git filter-branch --index-filter 'git rm --cached --ignore-unmatch data/model-400M.caffemodel' -- --all |
必要的时候,需要用-f
选项来强制地进行删除:
1 | git filter-branch -f --index-filter 'git rm --cached --ignore-unmatch data/model-400M.caffemodel' -- --all |
删除引用并重新打包
这里需要删除.git/refs
目录下的一些引用文件并重新打包,具体命令如下,比较固定:
1 | $ rm -Rf .git/refs/original |
之后可以用du -sh
等命令查看项目目录的大小。
如果git push
提示冲突的话,需要用git push -f
命令来强制推送代码到远端。虽然不建议用-f
选项,但是特殊情况特殊处理~