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/