Git版本库管理

在开发过程中往往会使用第三方库,可有时候会因为某种原因不使用了,我们可以使用 reset或是直接删除的方式将库删除,比如开发中用到了某度的SDK,可是发现crash的概率太大需要舍弃之。可是,这些库的影子还在版本库的历史中可以找到,会增大库的体积。这时候我们就需要完全清楚这些“残留”文件。
本篇内容可在Git权威指南第14章中找到。

松散对象

Git中对于以SHA1值作为目录名和文件保存的对象称之为松散对象。因此 add,commit等操作都会产生一系列的松散对象。大量的松散对象会造成访问效率低下,占用过多存储空间。在Git中可以通过打包的方式提高访问效率,同时也方便通过增量更新的方式节省存储空间。

暂存区中的临时对象

添加到暂存区的文件发现不需要了,我们应该怎么办? 首先我们复制2个稍大的(10M)的文件到工作区,然后添加到暂存区:

$ cp /Users/xxxxx/Desktop/test.zip bigfile
$ cp /Users/xxxxx/Desktop/test.zip bigfile.dup
$ git add bigfile bigfile.dup

此时查看工作区和版本库的大小:

$ du -sh
31M .
$ du -sh .git/
10M .git/

因为添加到库中的文件Git是以blob对象的形式存储,对于相同文件只存储一份,所以库的大小是10M。
查看添加后的对象:

$ find .git/objects/ -type f
.git/objects//5d/1150e0e75f30161bba7799170c887d8f1104a0

然后我们把添加的文件撤出暂存区:

$ git reset HEAD
$ git status -s
?? bigfile
?? bigfile.dup

这时再查看大小:

$ du -sh
31M .
$ du -sh .git/
10M .git/

发现大小并没有变化,这说明版本库中还保存着那个对象,可以通过fsck命令查看:

$ git fsck
Checking object directories: 100% (256/256), done.
Checking objects: 100% (65/65), done.
dangling blob 5d1150e0e75f30161bba7799170c887d8f1104a0

要将这个文件从版本库彻底删除那么需要执行prune

$ git prune
$ du -sh .git/
172K    .git/

此时可以看出版本库已经不包含文件对象

重置操作引入的对象

对于已经添加到版本库中的文件,又该如何清除相应的文件对象呢?我们还是添加2个文件并commit:

$ git add bigfile bigfile.dup
$ git commit -m "add bigfiles"
[master a5feb75] add bigfiles
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 bigfile
create mode 100644 bigfile.dup

此时查看文件对象:

$ find .git/objects/ -type f
.git/objects//5d/1150e0e75f30161bba7799170c887d8f1104a0
.git/objects//6b/ff29c5d2ae75913cb00de0887f6bc1e4dfbc31
.git/objects//a5/feb75fd7255b0085b92448a14d2fae62ea75be

使用 git cat-file -t 5d11可以查看文件对象的类型,上述3个对象的类型分别为:树,提交,和blob。 接下来使用reset:

$ git reset --hard HEAD^
HEAD is now at 6652a0d Add Images for git treeview.

现在查看文件对象会发现还是存在上述的3个对象:

$ find .git/objects/ -type f
.git/objects//5d/1150e0e75f30161bba7799170c887d8f1104a0
.git/objects//6b/ff29c5d2ae75913cb00de0887f6bc1e4dfbc31
.git/objects//a5/feb75fd7255b0085b92448a14d2fae62ea75be

这时执行fsck会发现没有找到未关联的对象:

$ git fsck
Checking object directories: 100% (256/256), done.
Checking objects: 100% (65/65), done.

很显然,所有的对象都是关联的,因为我们还可以撤销该次提交。要找到该文件对象,需要使用:

$ git fsck --no-reflogs
Checking object directories: 100% (256/256), done.
Checking objects: 100% (65/65), done.
dangling commit a5feb75fd7255b0085b92448a14d2fae62ea75be

由于所有的记录都存储在reflog中,所以我们需要清除reflog,在这里需要注意:

$git reflog expire --all
$ git reflog
6652a0d HEAD@{0}: reset: moving to HEAD^
a5feb75 HEAD@{1}: commit: add bigfiles

这只会清空90天前的数据,可以带date参数来指定日期:

$ git reflog expire --expire=now --all
$ git reflog

此时记录全部清除,可以使用fsckprune了:

$ git fsck
Checking object directories: 100% (256/256), done.
Checking objects: 100% (65/65), done.
dangling commit a5feb75fd7255b0085b92448a14d2fae62ea75be
$ git prune
$ du -sh .git/
164K    .git/

git gc

实际使用中很少使用prune,而是使用git gc,该命令可以执行一系列的优化:主要是打包文件,丢弃过期的历史,清除未关联的对象等等。

$git gc
//将未关联的对象从打包文件中移出,成为松散文件
$git gc --expire=now
//直接清除未关联的文件,释放存储空间

实际中,git的某些操作会自动执行gc命令,因此不需要手动执行gc进行版本库的清理了。