從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命令永遠不會觸發版本庫的整理。