14.4 Git管家:git-gc

前面两节介绍的是比较极端的情况,实际操作中会很少用到git prune命令来清理版本库,而是会使用一个更为常用的命令git gc。命令git gc就好比Git版本库的管家,会对版本库进行一系列的优化动作:

(1)对分散在.git/refs下的文件进行打包,打包到文件.git/packed-refs中。

如果没有将配置gc.packrefs关闭,就会执行命令:git pack-refs—all—prune实现对引用的打包。

(2)丢弃90天前的reflog记录。

会运行reflog过期命令:git reflog expire—all。因为采用了默认参数调用,因此只会清空reflog中90天前的记录。

(3)对松散对象进行打包。

运行git repack命令,凡是有引用关联的对象都被打在包里,未被关联的对象仍旧以松散对象的形式保存。

(4)清除未被关联的对象。默认只清除2周以前的未被关联的对象。

可以向git gc提供—prune=<date>参数,其中的时间参数传递给git prune—expire<date>,实现对指定日期之前的未被关联的松散对象进行清理。

(5)其他清理。

如运行git rerere gc对合并冲突的历史记录进行过期操作。

从上面的描述中可见命令git gc完成了相当多的优化和清理工作,并且最大限度地照顾了安全性的需要。例如像暂存区操作引入的没有关联的临时对象会最少保留2个星期,而因为重置而丢弃的提交和文件则会保留最少3个月。

下面就把前面的例子用git gc再执行一遍,不过这一次添加的两个大文件要稍有不同,以便看到git gc打包所实现的对象增量存储的效果。

复制两个大文件到工作区。


$cp/tmp/bigfile bigfile

$cp/tmp/bigfile bigfile.dup


在文件bigfile.dup后面追加些内容,以造成bigfile和bigfile.dup内容不同。


$echo "hello world">>bigfile.dup


将这两个稍有不同的文件提交到版本库。


$git add bigfile bigfile.dup

$git commit-m "add bigfiles."

[master c62fa4d]add bigfiles.

2 files changed,0 insertions(+),0 deletions(-)

create mode 100644 bigfile

create mode 100644 bigfile.dup


可以看到版本库中提交进来的两个不同的大文件是不同的对象。


$git ls-tree HEAD|grep bigfile

100644 blob 2ebcd92d0dda2bad50c775dc662c6cb700477aff bigfile

100644 blob 9e35f946a30c11c47ba1df351ca22866bc351e7b bigfile.dup


做版本库重置,抛弃最新的提交,即抛弃添加两个大文件的提交。


$git reset—hard HEAD^

HEAD is now at 6652a0d Add Images for git treeview.


此时的版本库有多大呢,还是像之前添加两个相同的大文件时占用11MB的空间么?


$du-sh.git/

22M.git/


版本库空间占用居然扩大了一倍!这显然是因为两个大文件分开存储造成的。可以用下面的命令在对象库中查看对象的大小。


$find.git/objects-type f-printf "%-20p\t%s\n"

.git/objects/0c/844d2a072fd69e71638558216b69ebc57ddb64 233

.git/objects/2e/bcd92d0dda2bad50c775dc662c6cb700477aff 11184682

.git/objects/9e/35f946a30c11c47ba1df351ca22866bc351e7b 11184694

.git/objects/c6/2fa4d6cb4c082fadfa45920b5149a23fd7272e 162

.git/objects/info/packs 54

.git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.idx 2892

.git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.pack 80015


输出的每一行用空白分隔,前面是文件名,后面是以字节为单位的文件大小。从上面的输出中可以看出来,打包文件很小,但是有两个大的文件各自占用了11MB左右的空间。

执行git gc并不会删除任何对象,因为这些对象都还没有过期。但是会发现版本库的占用空间变小了。

执行git gc对版本库进行整理。


$git gc

Counting objects:69,done.

Delta compression using up to 2 threads.

Compressing objects:100%(49/49),done.

Writing objects:100%(69/69),done.

Total 69(delta 11),reused 63(delta 8)


版本库空间占用小了一半!


$du-sh.git/

11M.git/


原来是因为对象库重新打包,两个大文件采用了增量存储使得版本库变小。


$find.git/objects-type f-printf "%-20p\t%s\n"|sort

.git/objects/info/packs 54

.git/objects/pack/pack-7cae010c1b064406cd6c16d5a6ab2f446de4076c.idx 3004

.git/objects/pack/pack-7cae010c1b064406cd6c16d5a6ab2f446de4076c.pack 11263033


如果想将抛弃的历史数据彻底丢弃,进行如下操作。

(1)不再保留90天的reflog,而是将所有reflog全部即时过期。


$git reflog expire—expire=now—all


(2)通过git fsck可以看到有提交成为了未被关联的提交。


$git fsck

dangling commit c62fa4d6cb4c082fadfa45920b5149a23fd7272e


(3)这个未被关联的提交就是添加大文件的提交。


$git show c62fa4d6cb4c082fadfa45920b5149a23fd7272e

commit c62fa4d6cb4c082fadfa45920b5149a23fd7272e

Author:Jiang Xin<jiangxin@ossxp.com>

Date:Thu Dec 16 20:18:38 2010+0800

add bigfiles.

diff—git a/bigfile b/bigfile

new file mode 100644

index 0000000..2ebcd92

Binary files/dev/null and b/bigfile differ

diff—git a/bigfile.dup b/bigfile.dup

new file mode 100644

index 0000000..9e35f94

Binary files/dev/null and b/bigfile.dup differ


(4)不带参数调用git gc虽然不会清除尚未过期(未到2周)的大文件,但是会将未被关联的对象从打包文件中移出,成为松散文件。


$git gc

Counting objects:65,done.

Delta compression using up to 2 threads.

Compressing objects:100%(45/45),done.

Writing objects:100%(65/65),done.

Total 65(delta 8),reused 63(delta 8)


(5)未被关联的对象重新成为松散文件,所以.git版本库的空间占用又反弹了。


$du-sh.git/

22M.git/

$find.git/objects-type f-printf "%-20p\t%s\n"|sort

.git/objects/0c/844d2a072fd69e71638558216b69ebc57ddb64 233

.git/objects/2e/bcd92d0dda2bad50c775dc662c6cb700477aff 11184682

.git/objects/9e/35f946a30c11c47ba1df351ca22866bc351e7b 11184694

.git/objects/c6/2fa4d6cb4c082fadfa45920b5149a23fd7272e 162

.git/objects/info/packs 54

.git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.idx 2892

.git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.pack 80015


(6)实际上如果使用立即过期参数—prune=now调用git gc,就不用再等2周了,直接就可以完成对未关联的对象的清理。


$git gc—prune=now

Counting objects:65,done.

Delta compression using up to 2 threads.

Compressing objects:100%(45/45),done.

Writing objects:100%(65/65),done.

Total 65(delta 8),reused 65(delta 8)


(7)清理过后,版本库的空间占用降了下来。


$du-sh.git/

240K.git/