一、前言
前几天同事在拉取一个项目的Git仓库时,发现项目拉取速度非常慢,半个钟都无法拉取下来,并且发现一直卡在了99%的进度上。
开始时以为是Git网络出问题了,检查了其它仓库却都可以正常的推送和拉取,后面发现经过很长时间后,这个仓库竟然拉下来了,但是拉取的文件大小竟然有700M多,整个.git文件也随即增大到1G多。
于是在Gerrit上查看了近几次提交记录发现两个非常大的临时文件被上传了,并且审核通过被推送到Git仓库中,没错就是这俩货:
真相大白,原来是推送了超大文件导致了问题出现,那么接下来就好办了,通过Git命令应该就可以了愉快的解决这个问题。
但是,凡事总有个但是,解决的过程远不是想象中那么顺利。下面就来看看我们经历了什么。
二、问题分析与解决
-
删除文件,再次提交
首先想到的就是将文件删除,然后推送到远程仓库,发现拉取速度一样龟速。
分析了一下,发现这样根本是行不通的。
因为远程仓库中,大文件的提交记录依然存在,这样删除只是将产生了一个新的提交记录,将当前commit中大文件去掉而已,随时可以回滚回来,pull的时候依然会将大文件的历史记录拉取下来。
-
git reset 命令
我们知道git reset可以将当前的内容回滚到指定的某次提交,分为两个模式:
#将内容回滚到commitid这次提交,并删除所有‘commitid’之后的提交历史内容
git reset --hard commitid
#将内容回滚到commitid这次提交,并保留所有‘commitid’之后的内容
git reset --soft commitid
由于提交大文件之后,有更新了几次提交,所以只能用soft参数,否则后面的几次提交内容就没有了。于是想到的解决方案如下。
通过git reset --soft命令,将当前提交的内容恢复到这个两个大文件提交之前,然后再次commit,再次push到远程仓库,结局可以想而知,这样就想删除文件?no way!
git reset --soft命令一样是无法将提交记录从仓库中抹掉的,虽然通过reset之后,大文件的提交记录在git log中已经查找不到,但实际上,这个记录并不会真正的从仓库中删除,只要能找到commit id,依然可以从仓库中恢复该提交历史。所以,删除不了的原因与第一种方案是一样的。
-
git filter-branch
1)前面两种修改的方式都是我们平时所熟悉的,使用频率比较高的删除某些文件或者提交记录的方式,但这些方式实际上都是生成了新的提交记录,并不会修改或者删除我们的提交历史,也就是说,想要永久删除仓库中的某个文件,这样是行不通的。
Git这么强大,肯定是存在可以永久删除历史记录的命令,找了一圈,发现确实有“后悔药”命令,那就是git filter-branch,通过以下命令,就可以永久删除你想要删除的任何文件:
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替换为删除文件的相对路径,并执行,如果有以下执行反馈,说明删除成功了。
如果所有分支都是unchanged说明要么是该分支没有要删除的文件,要么是删除文件的路径不对。
执行以后命令以后,你会发现本地目录中的.git文件并不会马上就变小,而是与原来是一样的!
不是说好了,可以永久删除记录的吗?摔!不是说好了,不能再通过commit id找回原来的大文件了吗?摔!别急,接下来就告诉你为什么。
2)原来Git仓库历史有个缓存期,如果不主动回收、清理仓库历史,一般的这些记录还会保存一段时间,以备你突然后悔了,没办法找回删掉的文件。那么怎么样才能主动回收资源能?就是通过以下命令:
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
执行以上命令,就会发现.git目录变小了。那么接下来只要把本地的记录,强制更新到远程仓库就行了。
强制更新是一个非常危险的动作,一定要确保你的本地内容是最新的,已经没有人在你之后提交了代码,否则会将其它的人提交的代码也一并删除了。
强制推送命令如下:
git push origin master --force
#其中master为你要推送的分支
3)由于我们采用的是gerrit进行代码审核,想当然地就认为,应该把这次修改强制推送到gerrit上,然后再由gerrit上审核通过,并推送到远程仓库。天知道,这竟然又没有效果啊,再摔!pull的时候,依然龟速。摔摔摔!
这是使用的错误推送命令:
git push origin HEAD:refs/for/dev --force
推送到gerrit没效果,那么直接推送到git远程仓库呢?
推送不上去,由于配置了gerrit,普通权限的开发人员是无法直接推送到远程仓库的,否则gerrit就形同虚设了呀。那么就来看看gerrit可以配置那些权限。
4)修改Gerrit推送权限
i. 首先进入Gerrit首页,然后依次选择Projects-->List-->选择项目-->Access,进入到项目的权限管理页面。
如果要修改项目的权限,那么你要有管理员权限才行。
ii. 点击Access页面上的Eidt按钮修改权限,然后点击Add Permission,可以看到有许多的权限,如代码审核权限,代码核实/推送权限等等。
其中有一项Push,这权限就是可以直接推送到Git,而不需要经过gerrit审核。如果需要强制推送,那么还需要勾选右边的Force Push。
iii. 通过以上配置以后,再次强制推送:
成功了。
4)最后,我们再来clone一下远程仓库
终于可以轻松的拉取仓库,并且只有41.42M,至此,终于将错误推送到远程仓库的超大文件删除,可以轻松愉快的拉取仓库了。
三、总结
通过这次事件,可以看到:
- 代码审核是非常重要的,而且要认真的进行审核才行,否则很容易导致错误的推送,不仅会浪费仓库容量,导致拉取变慢,甚至可能会泄漏私密文件,如密钥文件等。
- 解决问题时,在尝试一些方案时,最好先分析一下方案的可行性,已经结果评估,否则会浪费了许多时间,还有可能导致一些不可逆转的错误。
- 解决问题的过程需要耐心,在这过程中遇到的问题往往不止一个,我们只要耐心的一个个去解决,总会有解决的办法,而解决问题的关键往往就在下一秒,所以耐心,坚持不懈非常重要。
以上。