从GitHub上克隆一个示例版本库,这个版本库在“历史穿梭”一章就已经克隆过一次了,如今要从新克隆一份。为了和原来的克隆相区别,克隆到另外的目录。执行下面的命令。git
$ cd /path/to/my/workspace/ $ git clone git://github.com/ossxp-com/gitdemo-commit-tree.git i-am-admin Cloning into i-am-admin... remote: Counting objects: 65, done. remote: Compressing objects: 100% (53/53), done. remote: Total 65 (delta 8), reused 0 (delta 0) Receiving objects: 100% (65/65), 78.14 KiB | 42 KiB/s, done. Resolving deltas: 100% (8/8), done.
进入克隆的版本库,使用git show-ref命令看看所含的引用。github
$ cd /path/to/my/workspace/i-am-admin $ git show-ref 6652a0dce6a5067732c00ef0a220810a7230655e refs/heads/master 6652a0dce6a5067732c00ef0a220810a7230655e refs/remotes/origin/HEAD 6652a0dce6a5067732c00ef0a220810a7230655e refs/remotes/origin/master c9b03a208288aebdbfe8d84aeb984952a16da3f2 refs/tags/A 1a87782f8853c6e11aacba463af04b4fa8565713 refs/tags/B 9f8b51bc7dd98f7501ade526dd78c55ee4abb75f refs/tags/C 887113dc095238a0f4661400d33ea570e5edc37c refs/tags/D 6decd0ad3201ddb3f5b37c201387511059ac120c refs/tags/E 70cab20f099e0af3f870956a3fbbbda50a17864f refs/tags/F 96793e37c7f1c7b2ddf69b4c1e252763c11a711f refs/tags/G 476e74549047e2c5fbd616287a499cc6f07ebde0 refs/tags/H 76945a15543c49735634d58169b349301d65524d refs/tags/I f199c10c3f1a54fa3f9542902b25b49d58efb35b refs/tags/J
其中以refs/heads/开头的是分支;以refs/remotes/开头的是远程版本库分支在本地的映射,会在后面章节介绍;以refs/tags/开头的是里程碑。按照以前的经验,在.git/refs目录下应该有这些引用所对应的文件才是。看看都在么?安全
$ find .git/refs/ -type f .git/refs/remotes/origin/HEAD .git/refs/heads/master
为何才有两个文件?实际上当运行下面的命令后,引用目录下的文件会更少:服务器
$ git pack-refs --all $ find .git/refs/ -type f .git/refs/remotes/origin/HEAD
那么本应该出如今.git/refs/目录下的引用文件都到哪里去了呢?答案是这些文件被打包了,放到一个文本文件.git/packed-refs中了。查看一下这个文件中的内容。性能
$ head -5 .git/packed-refs # pack-refs with: peeled 6652a0dce6a5067732c00ef0a220810a7230655e refs/heads/master 6652a0dce6a5067732c00ef0a220810a7230655e refs/remotes/origin/master c9b03a208288aebdbfe8d84aeb984952a16da3f2 refs/tags/A ^81993234fc12a325d303eccea20f6fd629412712
再来看看Git的对象(commit、blob、tree、tag)在对象库中的存储。经过下面的命令,会发现对象库也不是原来熟悉的模样了。优化
$ find .git/objects/ -type f .git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.idx .git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.pack
对象库中只有两个文件,本应该一个一个独立保存的对象都不见了。读者应该可以猜到,全部的对象文件都被打包到这两个文件中了,其中以.pack结尾的文件是打包文件,以.idx结尾的是索引文件。打包文件和对应的索引文件只是扩展名不一样,都保存于.git/objects/pack/目录下。Git对于以SHA1哈希值做为目录名和文件名保存的对象有一个术语,称为松散对象。松散对象打包后会提升访问效率,并且不一样的对象能够经过增量存储节省磁盘空间。spa
能够经过Git一个底层命令能够查看索引中包含的对象:3d
$ git show-index < .git/objects/pack/pack-*.idx | head -5 661 0cd7f2ea245d90d414e502467ac749f36aa32cc4 (0793420b) 63020 1026d9416d6fc8d34e1edfb2bc58adb8aa5a6763 (ed77ff72) 3936 15328fc6961390b4b10895f39bb042021edd07ea (13fb79ef) 3768 1a588ca36e25f58fbeae421c36d2c39e38e991ef (86e3b0bd) 2022 1a87782f8853c6e11aacba463af04b4fa8565713 (e269ed74)
为何克隆远程版本库就能够产生对象库打包以及引用打包的效果呢?这是由于克隆远程版本库时,使用了“智能”的通信协议,远程Git服务器将对象打包后传输给本地,造成本地版本库的对象库中的一个包含全部对象的包以及索引文件。无疑这样的传输方式——按需传输、打包传输,效率最高。code
克隆以后的版本库在平常的提交中,产生的新的对象仍旧以松散对象存在,而不是以打包的形式,日积月累会在本地版本库的对象库中造成大量的松散文件。松散对象只是进行了压缩,而没有(打包文件才有的)增量存储的功能,会浪费磁盘空间,也会下降访问效率。更为严重的是一些非正式的临时对象(暂存区操做中产生的临时对象)也以松散对象的形式保存在对象库中,形成磁盘空间的浪费。下一节就着手处理临时对象的问题。对象
暂存区操做有可能在对象库中产生临时对象,例如文件反复的修改和反复的向暂存区添加,或者添加到暂存区后不提交甚至直接撤销,就会产生垃圾数据占用磁盘空间。为了说明临时对象的问题,须要准备一个大的压缩文件,10MB便可。
在Linux上与内核匹配的initrd文件(内核启动加载的内存盘)就是一个大的压缩文件,能够用于此节的示例。将大的压缩文件放在版本库外的一个位置上,由于这个文件会屡次用到。
$ cp /boot/initrd.img-2.6.32-5-amd64 /tmp/bigfile $ du -sh bigfile 11M bigfile
将这个大的压缩文件复制到工做区中,拷贝两份。
$ cd /path/to/my/workspace/i-am-admin $ cp /tmp/bigfile bigfile $ cp /tmp/bigfile bigfile.dup
查看一下磁盘空间占用:
$ du -sh . 33M .
若是再有谁说版本库空间占用必定比工做区大,能够用这个例子回击他。
$ du -sh .git/ 11M .git/
看看版本库中对象库内的文件,会发现多出了一个松散对象。之因此添加两个文件而只有一个松散对象,是由于Git对于文件的保存是将内容保存为blob对象中,和文件名无关,相同内容的不一样文件会共享同一个blob对象。
$ find .git/objects/ -type f .git/objects/2e/bcd92d0dda2bad50c775dc662c6cb700477aff .git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.idx .git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.pack
若是不想提交,想将文件撤出暂存区,则进行以下操做。
$ git status -s A bigfile A bigfile.dup
$ git reset HEAD
$ git status -s ?? bigfile ?? bigfile.dup
文件撤出暂存区后,在对象库中产生的blob松散对象仍然存在,经过查看版本库的磁盘占用就能够看出来。
$ du -sh .git/ 11M .git/
Git提供了git fsck命令,能够查看到版本库中包含的没有被任何引用关联松散对象。
$ git fsck dangling blob 2ebcd92d0dda2bad50c775dc662c6cb700477aff
标识为dangling的对象就是没有被任何引用直接或者间接关联到的对象。这个对象就是前面经过暂存区操做引入的大文件的内容。如何将这个文件从版本库中完全删除呢?Git提供了一个清理的命令:
用git prune清理以后,会发现:
$ git fsck
$ du -sh .git/ 236K .git/
上一节用git prune命令清除暂存区操做时引入的临时对象,可是若是是用重置命令抛弃的提交和文件就不会轻易的被清除。下面用一样的大文件提交到版本库中试验一下。
$ cd /path/to/my/workspace/i-am-admin $ cp /tmp/bigfile bigfile $ cp /tmp/bigfile bigfile.dup
将这两个大文件提交到版本库中。
$ git add bigfile bigfile.dup
$ git commit -m "add bigfiles." [master 51519c7] add bigfiles. 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 bigfile create mode 100644 bigfile.dup
$ du -sh .git/ 11M .git/
作一个重置操做,抛弃刚刚针对两个大文件作的提交。
$ git reset --hard HEAD^
重置以后,看看版本库的变化。
$ du -sh .git/ 11M .git/
$ find .git/objects/ -type f .git/objects/info/packs .git/objects/2e/bcd92d0dda2bad50c775dc662c6cb700477aff .git/objects/d9/38dee8fde4e5053b12406c66a19183a24238e1 .git/objects/51/519c7d8d60e0f958e135e8b989a78e84122591 .git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.idx .git/objects/pack/pack-969329578b95057b7ea1208379a22c250c3b992a.pack
$ git cat-file -t 51519c7 commit $ git cat-file -t d938dee tree $ git cat-file -t 2ebcd92 blob
向上一节同样,执行git prune命令,期待版本库空间占用会变小。但是:
$ git prune $ du -sh .git/ 11M .git/
$ git fsck
$ git fsck --no-reflogs dangling commit 51519c7d8d60e0f958e135e8b989a78e84122591
还记得reflog么?reflog是防止误操做的最后一道闸门。
$ git reflog 6652a0d HEAD@{0}: HEAD^: updating HEAD 51519c7 HEAD@{1}: commit: add bigfiles.
能够看到撤销的操做仍然记录在reflog中,正因如此Git认为撤销的提交和大文件都还被能够被追踪到,还在使用着,因此没法用git prune命令删除。
若是确认真的要丢弃不想要的对象,须要对版本库的reflog作过时操做,至关于将.git/logs/下的文件清空。
$ git reflog expire --all $ git reflog 6652a0d HEAD@{0}: HEAD^: updating HEAD 51519c7 HEAD@{1}: commit: add bigfiles.
$ git reflog expire --expire=now --all $ git reflog
使用now做为时间参数,让 reflog 的所有记录都过时。没有了 reflog,即回滚的添加大文件的提交从 reflog 中看不到后,该提交对应的 commit 对象、tree 对象和 blob 对象就会成为未被关联的 dangling 对象,能够用git prune命令清理。下面能够看到清理后,版本库变小了。
$ git prune $ du -sh .git/ 244K .git/
前面两节介绍的是比较极端的状况,实际操做中会不多用到git prune命令来清理版本库,而是会使用一个更为经常使用的命令git gc。命令git gc就比如Git版本库的管家,会对版本库进行一系列的优化动做。
若是没有将配置gc.packrefs关闭,就会执行命令:git pack-refs –all –prune实现对引用的打包。
会运行使reflog过时命令:git reflog expire –all。由于采用了缺省参数调用,所以只会清空reflog中90天前的记录。
运行git repack命令,凡有引用关联的对象都被打在包里,未被关联的对象仍旧以松散对象形式保存。
能够向git gc提供--prune=<date>参数,其中的时间参数传递给git prune –expire <date>,实现对指定日期以前的未被关联的松散对象进行清理。
如运行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 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
若是想将抛弃的历史数据完全丢弃,以下操做。
$ git reflog expire --expire=now --all
$ git fsck dangling commit c62fa4d6cb4c082fadfa45920b5149a23fd7272e
$ 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
$ 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)
$ 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
$ 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)
$ du -sh .git/ 240K .git/
对于老版本库的Git,会看到帮助手册中建议用户对版本库进行周期性的整理,以便得到更好的性能,尤为是对于规模比较大的项目,可是对于整理的周期都语焉不详。
实际上对于1.6.6及之后版本的Git已经基本上不须要手动执行git gc命令了,由于部分Git命令会自动调用git gc –auto命令,在版本库确实须要整理的状况下自动开始整理操做。
目前有以下Git命令会自动执行git gc –auto命令,实现对版本库的按需整理。
执行命令git merge进行合并操做后,对版本库进行按需整理。
执行命令git receive-pack,即版本库接收其余版本库推送(push)的提交后,版本库会作按需整理操做。
当版本库接收到其余版本库的推送(push)请求时,会调用git receive-pack命令以接收请求。在接收到推送的提交后,对版本库进行按需整理。
执行命令git rebase -i进行交互式变基操做后,会对版本库进行按需整理。
执行命令git am对mbox邮箱中经过邮件提交的补丁在版本库中进行应用的操做后,会对版本库作按需整理操做。
对于提供共享式“写操做”的Git版本库,能够免维护。所谓的共享式写操做,就是版本库做为一个裸版本库放在服务器上,团队成员能够经过推送(push)操做将提交推送到共享的裸版本中。每一次推送操做都会触发git gc –auto命令,对版本库进行按需整理。
对于非独立工做的本地工做区,也能够免维护。由于和他人协同工做的本地工做区会常常执行git pull操做从他人版本库或者从共享的版本库拉回新提交,执行git pull操做会,会触发git merge操做,所以也会对本地版本库进行按需整理。
Git管家命令使用--auto参数调用,会进行按需整理。由于版本库整理操做对于大的项目可能会很是费时,所以实际的整理并不会常常被触发,即有着很是苛刻的触发条件。想要观察到触发版本库整理操做是很是不容易的事情。
主要的触发条件是:松散对象只有超过必定的数量时才会执行。并且在统计松散对象数量时,为了下降在.git/objects/目录下搜索松散对象对系统形成的负担,实际采起了取样搜索,即只会对对象库下一个子目录.git/objects/17进行文件搜索。在缺省的配置下,只有该目录中对象数目超过27个,才会触发版本库的整理。至于为何只在对象库下选择了一个子目录进行松散对象的搜索,这是由于SHA1哈希值是彻底随机的,文件在由前两位哈希值组成的目录中差很少是平均分布的。至于为何选择17,不知道对于做者Junio C Hamano有什么特殊意义,也许是向Linus Torvalds被评选为二十世纪最有影响力的100人中排名第17位而进行致敬。
能够经过配置gc.auto的值,调整Git管家自动运行时触发版本库整理操做的频率,可是注意不要将gc.auto设置为0,不然git gc –auto命令永远不会触发版本库的整理。