记一次删除Git记录中的大文件的过程

app/test/target/

#查看大文件
git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5 | awk '{print$1}')"


#删除大文件或者目录
git filter-branch --force --index-filter 'git rm -rf --cached --ignore-unmatch app/test/target/' --prune-empty --tag-name-filter cat -- --all

#强制覆盖分支
git push origin ANT01983399_20170525_kbinfocollector --force

 

 

最近在开发一个新应用,有一天在gitlab上clone代码的时候发现个人应用居然有170+M,明明是一个全新的应用,代码都没有几行呢,为何会有这么大呢?git

后来通过了解Git的原理,解决了这个问题,把相关内容记录下来。分享一下。github

Whyapp

个人一个新应用居然要170+M,这是打死我我也不会信的,因而就开始分析为何会这么大。gitlab

step 1. 把代码拉到本地spa

git clone git@github.com:hollischuang/Architecture-Evolution.git3d

只是用这个地址举例,实际并非这个项目。版本控制

step 2. 查看哪一个文件占用的空间比较大 $cd Architecture-Evolution$du -d 1 -h 174M ./.git 264K ./test 96K .日志

因而,发现是.git目录本身就占用了174M,了解Git的人都知道,.git目录是git本身生成的,记录了git仓库的相关信息的。看到这里其实并不难知道缘由。code

Git 维护着一个微型的文件系统,其中的文件也被称做数据对象。全部的数据对象均存储于项目下面的 .git/objects中。对象

通过个人验证,确实是.git/objects这个文件夹中的文件占了磁盘上174M的空间。

也就是说,只要我有一次将一个大文件误提交了,那么即便我后面把它删除了,可是,实际上在.git中,这个文件仍是存在的,虽然咱们可能不再须要他了,可是他还在那里默默的存在着。。。

Git与大部分版本控制系统的差异是很大的,好比Subversion、CVS、Perforce、Mercurial 等等,使用的是“增量文件系统” (Delta Storage systems), 就是说它们存储每次提交(commit)之间的差别。Git正好与之相反,它会把你的每次提交的文件的所有内容(snapshot)都会记录下来。这会是在使用Git时的一个很重要的理念。

也就是说,若是我又一次把一个大文件务提交到git仓库中了,那么,下次提交时,即便你只改动了某个文件的一行内容,Git 也会生成一个全新的对象来存储新的文件内容。

由于以上两个特性,我回想起个人一次手残行为: 刚刚建立一个应用以后,我快速的写完代码,编译,运行,发现没啥问题以后,我准备先把他发布掉,因而我开始建立git仓库,并尝试把代码提交上去,这时我并无建立.gitignore文件,我直接git add .git commit -m 'init' git push一鼓作气的执行了熟悉的操做。

相信聪明的人已经发现了,逗比啊,我在编译代码以后,会有不少jar被我down到target目录下。我直接git add.把target下面的jar包,war包等这些也直接提交了。。。虽而后面我意识到,而且删除了这些文件,而后再次提交,可是因为刚咱们说过的缘由,这些文件依然占用着个人空间。。。

更多关于git的原理内容参见:Git 内部原理

How

问题已经定位到了,接下来就是要解决问题了。若是对git的原理及命令了解的比较多的话,这个问题仍是比较好解决的,因为当时博主并不十分了解git的原理,因此作了一些知识储备以后才开始动手的。(Git 之术与道 -- 对象、为何你的 Git 仓库变得如此臃肿)

Step 1 查看哪些历史提交过文件占用空间较大

使用如下命令能够查看占用空间最多的五个文件:

git rev-list --objects --all | grep "$(git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5 | awk '{print$1}')"

rev-list命令用来列出Git仓库中的提交,咱们用它来列出全部提交中涉及的文件名及其ID。 该命令能够指定只显示某个引用(或分支)的上下游的提交。

--objects:列出该提交涉及的全部文件ID。

--all:全部分支的提交,至关于指定了位于/refs下的全部引用。

verify-pack命令用于显示已打包的内容。

step 2. 重写commit,删除大文件

使用如下命令,删除历史提交过的大文件:

git filter-branch --force --index-filter 'git rm -rf --cached --ignore-unmatch big-file.jar' --prune-empty --tag-name-filter cat -- --all

上面脚本中的big-file.jar请换成你第一步查出的大文件名,或者这里直接写一个目录。

filter-branch命令能够用来重写Git仓库中的提交

--index-filter参数用来指定一条Bash命令,而后Git会检出(checkout)全部的提交, 执行该命令,而后从新提交。

--all参数表示咱们须要重写全部分支(或引用)。

在重写提交的过程当中,会有如下日志输出:

Rewrite 6cdbb293d453ced07e6a07e0aa6e580e6a5538f4 (266/266)# Ref 'refs/heads/master' was rewritten

若是显示 xxxxx unchanged, 说明repo里没有找到该文件, 请检查路径和文件名是否正确,重复上面的脚本,把全部你想删除的文件都删掉。

step 3. 推送修改后的repo

以强制覆盖的方式推送你的repo, 命令以下:

git push origin master --force

step 4. 清理和回收空间

虽然上面咱们已经删除了文件, 可是咱们的repo里面仍然保留了这些objects, 等待垃圾回收(GC), 因此咱们要用命令完全清除它, 并收回空间,命令以下:

rm -rf .git/refs/original/git reflog expire --expire=now --allgit gc --prune=now

至此,咱们已经完全的删除了咱们不想要的文件。

参考资料

Git 内部原理

Git 之术与道 -- 对象

为何你的 Git 仓库变得如此臃肿

Git如何永久删除文件(包括历史记录) 

相关文章
相关标签/搜索