起因
项目中使用到了IP离线数据库 GeoLite2-City.mmdb
,结果在git提交时就直接把该文件给提交了。
然后 commit
命令弹出了下面的警告信息:
1 | Compressing objects: 100% (125/125), done. |
后来想想其实这个文件并不需要通过git管理,只需要在项目部署时将其拷贝到服务器上的相应目录就可以了。
最重要的一点就是可以减少git仓库的大小。
但想要回滚回去还面临一个问题:也就是我在这次 commit
之后又进行多次 commit
。
主要是当时看到上面的警告提示并没有太在意,后来提交过几次后发现整个项目的代码才几百KB,而这一个文件就60多MB,而且拉取仓库还很慢。于是就后知后觉的想要把它删除。
如何解决
可以通过 count-objects
命令查看当前git项目占用的大小:
1 | ➜ git count-objects -v |
其中,size
大小是 KB
,所以整个项目大约有 38MB
左右。
移除文件跟踪
发现提交问题后,我就首先通过 git rm
命令移除了对该大文件的跟踪:
1 | ➜ git rm --cached -r files/ |
然后在 .gitignore
文件中添加目录排除:
1 | # .gitingore |
因为该大文件直接存放在项目根目录下的 files
目录下,考虑到该目录仅用作存放类似的外部引用文件,所以这里就直接对该目录做了排除处理。
移除历史记录
虽然上面已经将该大文件移除了git跟踪,但是由于之前已经执行过 commit
命令,该大文件也就存在于git的历史提交记录中,那么其所占的空间大小并不会随之减少。
后来搜索到git中有一个 filter-branch
命令,可以用来更改提交记录。
找出要清理的大文件
通过如下命令找出大小排名前5的提交记录:
1 | git verify-pack -v .git/objects/pack/pack-*.idx | sort -k 3 -g | tail -5 |
执行结果:
1 | git verify-pack -v .git/objects/pack/pack-*.idx | sort -k 3 -g | tail -5 |
查看该提交包含的文件:
1 | git rev-list --objects --all | grep 0482b283b80efeb1ab0549132fead647f7b9b9e7 |
可见提交记录中的对应文件就是 files/GeoLite2-City.mmdb
。
其中:
verify-pack
表示用于显示已打包的内容rev-list
表示用来列出Git仓库中的提交--objects
表示列出该提交涉及的所有文件ID--all
表示所有分支的提交,相当于指定了位于/refs下的所有引用
重写commit,删除大文件
通过 filter-branch
命令,来删除上面找到的大文件:
1 | git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch path-to-your-remove-file' --prune-empty --tag-name-filter cat -- --all |
注意将其中的 path-to-your-remove-file
换成具体的文件路径。
其中:
filter-branch
表示:用来重写Git仓库中的提交-f/--force
表示:git filter-branch
拒绝从现有的临时目录开始过滤,或者当已经有ref时refs/original/
,使用该参数强制执行过滤--index-filter
表示:重写索引的过滤器。用来指定一条Bash命令,然后Git会检出(checkout)所有的提交, 执行该命令,然后重新提交--ignore-unmatch
表示:--prune-empty
表示:过滤常常生成空的提交,从而使树保持不变。该参数会删除空提交--tag-name-filter
表示:重写标签名称,原始标签不会被删除,但可以被覆盖;使用--tag-name-filter cat
来简单地更新标签--
表示:分隔符--all
表示:需要重写所有分支(或引用)
执行结果如下:
1 | ➜ git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch files/GeoLite2-City.mmdb' --prune-empty --tag-name-filter cat -- --all |
通过 Sourcetree
工具查看一下所做的更改:
其中,红色框选部分是原来的提交记录,绿色框选部分是移除大文件后被更改的提交记录。
清理和回收本地空间
此时如果通过命令 git count-objects -v
去查看git仓库占用大小,会发现并没有什么变化:
1 | ➜ git count-objects -v |
这是因为,虽然我们已经删除了大文件,但在 .git
目录下仍然保留了这些 objects
,等待垃圾回收。我们可以手动将其立即删除。
执行如下命令:
1 | rm -rf .git/refs/original |
其中:
rm .git/refs/original
表示:删除git的备份文件git reflog expire --expire=now --all
表示:使所有散落的object立即失效git gc --prune=now
表示:立即执行垃圾回收gc
再次查看文件占用大小,为了便于查看,可以通过增加 -H
选项,自动将其转换为 MB
大小:
1 | ➜ git count-objects -vH |
推送修改
通过强制覆盖的方式 -f,--force
将修改后的代码提交到远端:
1 | git checkout dev |
总结
虽然通过上面的操作我们可以将已经 commit
的大文件完整移除,但我发现其中还是有一些需要注意的问题。
总结下来就是:我不建议使用 filter-branch
命令来解决此种情况。
主要有两点:
原因一:不推荐
可以看到在执行 git filter-branch
命令时给出了下面的警告信息:
WARNING: git-filter-branch has a glut of gotchas generating mangled history
rewrites. Hit Ctrl-C before proceeding to abort, then use an
alternative filtering tool such as ‘git filter-repo’
(https://github.com/newren/git-filter-repo/) instead. See the
filter-branch manual page for more details; to squelch this warning,
set FILTER_BRANCH_SQUELCH_WARNING=1.
filter-branch
会完全改写你的提交历史记录,git官方推荐使用 git filter-repo
命令来代替。
至于 filter-repo
命令的具体操作,后续再补充说明。
原因二:不安全
因为我也是第一次遇到需要对提交过的大文件进行移除的场景,所以整个的操作流程也是在网上搜索了好多的资料才敢下手操作。整个过程也是非常的 战战兢兢
生怕一步错导致整个项目出错而无法恢复。
毕竟其中不乏各种 --force
命令。
所以说,虽然这种方式能够解决,但我并不推荐这样做。
更好的方法
除了上面官方推荐的filter-repo
命令外,后来想想,其实当我在发现大文件被 commit
后的几次提交时,我就可以通过 git reset
命令将本地版本回退到没有大文件之前的版本,重新提交这些更改。
具体的操作这里暂时先不展开。
(ps. 主要是目前项目中的大文件情况已经解决,只能后续再次遇到时或者专门针对这种情况做一个深入探讨了😋)