Git+Gerrit如何永久删除历史文件(大文件/私密文件)

1,116 阅读7分钟
原文链接: www.jianshu.com

一、前言

前几天同事在拉取一个项目的Git仓库时,发现项目拉取速度非常慢,半个钟都无法拉取下来,并且发现一直卡在了99%的进度上。

开始时以为是Git网络出问题了,检查了其它仓库却都可以正常的推送和拉取,后面发现经过很长时间后,这个仓库竟然拉下来了,但是拉取的文件大小竟然有700M多,整个.git文件也随即增大到1G多。

于是在Gerrit上查看了近几次提交记录发现两个非常大的临时文件被上传了,并且审核通过被推送到Git仓库中,没错就是这俩货:


真相大白,原来是推送了超大文件导致了问题出现,那么接下来就好办了,通过Git命令应该就可以了愉快的解决这个问题。

但是,凡事总有个但是,解决的过程远不是想象中那么顺利。下面就来看看我们经历了什么:

二、问题分析与解决

  1. 删除文件,再次提交

首先想到的就是将文件删除,然后推送到远程仓库,发现拉取速度一样龟速。

分析了一下,发现这样根本是行不通的。

因为远程仓库中,大文件的提交记录依然存在,这样删除只是将产生了一个新的提交记录,将当前commit中大文件去掉而已,随时可以回滚回来,pull的时候依然会将大文件的历史记录拉取下来。

  1. 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,依然可以从仓库中恢复该提交历史。所以,删除不了的原因与第一种方案是一样的。

  1. 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,可以看到有许多的权限,如代码审核权限,代码核实/推送权限等等。

Gerrit权限

其中有一项Push,这权限就是可以直接推送到Git,而不需要经过gerrit审核。如果需要强制推送,那么还需要勾选右边的Force Push。

Push权限

iii. 通过以上配置以后,再次强制推送:

推送成功

成功了。

4)最后,我们再来clone一下远程仓库

clone成功

终于可以轻松的拉取仓库,并且只有41.42M,至此,终于将错误推送到远程仓库的超大文件删除,可以轻松愉快的拉取仓库了。

三、总结

通过这次事件,可以看到:

  • 代码审核是非常重要的,而且要认真的进行审核才行,否则很容易导致错误的推送,不仅会浪费仓库容量,导致拉取变慢,甚至可能会泄漏私密文件,如密钥文件等。
  • 解决问题时,在尝试一些方案时,最好先分析一下方案的可行性,已经结果评估,否则会浪费了许多时间,还有可能导致一些不可逆转的错误。
  • 解决问题的过程需要耐心,在这过程中遇到的问题往往不止一个,我们只要耐心的一个个去解决,总会有解决的办法,而解决问题的关键往往就在下一秒,所以耐心,坚持不懈非常重要。

以上。