Git Step by Step (4):探索.git目錄
原文出處: 田小計劃的博客
前面一篇文章介紹了Git對象模型,接下來我們就進入”.git”目錄看看到底有什么東西,目錄中哪些東西又跟Git對象模型相關。結合這個目錄,我們將進一步了解Git的工作原理。
.git目錄
下面就開始進入.git目錄了,通過”ls”命令可以看到.git目錄中的文件和子目錄:
對于這些文件和目錄,下面給出了一些基本的描述。在后面后有logs、objects、refs、index和HEAD更詳細的介紹
- (D) hooks:這個目錄存放一些shell腳本,可以設置特定的git命令后出發相應的腳本;在搭建gitweb系統或其他git托管系統會經常用到hook script
- (D) info:包含倉庫的一些信息
- (D) logs:保存所有更新的引用記錄(會在后面介紹引用)
- (D) objects:所有的Git對象都會存放在這個目錄中,對象的SHA1哈希值的前兩位是文件夾名稱,后38位作為對象文件名
- (D) refs:這個目錄一般包括三個子文件夾:heads、remotes和tags,heads中的文件標識了項目中的各個分支指向的當前commit
- (F) COMMIT_EDITMSG:保存最新的commit message,Git系統不會用到這個文件,只是給用戶一個參考
- (F) config:這個是Git倉庫的配置文件
- (F) description:倉庫的描述信息,主要給gitweb等git托管系統使用
- (F) index:這個文件就是我們前面文章提到的暫存區(stage),是一個二進制文件
- (F) HEAD:這個文件包含了一個當前分支(branch)的引用,通過這個文件Git可以得到下一次commit的parent
- (F) ORIG_HEAD:HEAD指針的前一個狀態
Git引用
Git中的引用是個非常重要的概念,對于理解分支(branch)、HEAD指針以及reflog非常有幫助。
Git系統中的分支名、遠程分支名、tag等都是指向某個commit的引用。比如master分支,origin/master遠程分支,命名為V1.0.0.0的tag等都是引用,它們通過保存某個commit的SHA1哈希值指向某個commit。
重新認識HEAD
HEAD也是一個引用,一般情況下間接指向你當前所在的分支的最新的commit上。HEAD跟Git中一般的引用不同,它并不包含某個commit的SHA1哈希值,而是包含當前所在的分支,所以HEAD直接指向當前所在的分支,然后間接指向當前所在分支的最新提交。
為了更形象的解釋上面的描述,我們首先查看”.git/HEAD”的內容:
ref: refs/heads/master
這就表示HEAD是一個指向master分支的引用,然后我們可以根據引用路徑打開”refs/heads/master”文件,內容如下:
4ea6c317a67e73b0befcb83c36b915c1481f2efe
根據前面一片文章的介紹,我們通過這個哈希值查看對象的類型和內容,可以看到這個哈希值對應一個commit,并且通過”git log”可以發現這個commit就是master分支上最新的提交。
所以可以看到,所有的內容都是環環相扣的,我們通過HEAD找到一個當前分支,然后通過當前分支的引用找到最新的commit,然后通過commit可以找到整個對象關系模型,看下圖:
引用和分支
直到現在我們都沒有開始介紹分支(branch),這里也不準備介紹分支,只是想大概展示一下引用和分支的關系。
假設我們現在除了master分支,又創建了一個release-1.0.0.1的分支,再次查看”.git/refs/heads/”目錄,可以看到除了master文件之外,又多了一個release-1.0.0.1文件,查看給文件的內容也是一個哈希值。
通過”git show-ref –heads”命令就可以產看所有的頭,這些都是HEAD的候選值:
根據前面的講解,這個commit就是就是release-1.0.0.1分支上最新的提交。同樣,當我們把當前分支切換到release-1.0.0.1的時候,HEAD文件的內容也會相應的變成:
ref: refs/heads/release-1.0.0.1
再看reflog
看過第二篇文章的同學一定還記得我們是怎么根據reflog去得到一個commit哈希值,然后把repo退回到一個指定的狀態。
接下了,我們進入”.git/logs”文件夾,可以看到這個文件夾也有一個HEAD文件和refs目錄,些就是記錄reflog的地方。
查看HEAD文件的內容,發現這個文件將會包含所有分支的reflog記錄:
0000000000000000000000000000000000000000 601b527296fea232c84b3661abcbff0576b1272c WilberTian <Wilber***.com> 1419759347 +0800 commit (initial): add calc.py into repo 601b527296fea232c84b3661abcbff0576b1272c c2163e267380f71373f29f922e7089abbb741772 WilberTian <Wilber***.com> 1419769538 +0800 commit: add sub function in calc.py c2163e267380f71373f29f922e7089abbb741772 4ea6c317a67e73b0befcb83c36b915c1481f2efe WilberTian <Wilber***.com> 1419771391 +0800 commit: add app.py, __init__.py and calc.py 4ea6c317a67e73b0befcb83c36b915c1481f2efe 4ea6c317a67e73b0befcb83c36b915c1481f2efe WilberTian <Wilber***.com> 1419822744 +0800 checkout: moving from master to release-1.0.0.1
進入”.git/logs/refs”目錄,同樣會有master和release-1.0.0.1兩個文件,兩個文件將會保存各自分支的reflog記錄
master的內容:
0000000000000000000000000000000000000000 601b527296fea232c84b3661abcbff0576b1272c WilberTian <Wilber***.com> 1419759347 +0800 commit (initial): add calc.py into repo 601b527296fea232c84b3661abcbff0576b1272c c2163e267380f71373f29f922e7089abbb741772 WilberTian <Wilber***.com> 1419769538 +0800 commit: add sub function in calc.py c2163e267380f71373f29f922e7089abbb741772 4ea6c317a67e73b0befcb83c36b915c1481f2efe WilberTian <Wilber***.com> 1419771391 +0800 commit: add app.py, __init__.py and calc.py
release-1.0.0.1的內容:
0000000000000000000000000000000000000000 4ea6c317a67e73b0befcb83c36b915c1481f2efe WilberTian <Wilber***.com> 1419822744 +0800 branch: Created from master
Git索引(index)
前面文章我們也提到過index/stage,就是更新的暫存區,下面就來看看index文件。
index(索引)示一個存放了已排序的路徑的二進制文件,并且每個路徑都對應一個SHA1哈希值。在Git系統中,可以通過”git ls-files –stage”來顯示index文件的內容:
從命令的輸出可以看到,所有的記錄都對應倉庫中的文件(包含全路徑)。通過”git cat-file”命令查看app.py對應的哈希值,可以看到這個哈希值就是代表app.py的blob對象。
現在我們更新app.py文件,加上一個”div(16, 4)”的調用并通過”git add”添加到暫存區,這時發現index中app.py對象的哈希值已經變化了。
通過這個例子,我們也可以理解diff操作應該會有怎樣的輸出了:
- git diff:比較WorkSpace和stage,add之前有diff輸出;add之后沒有diff輸出
- git diff HEAD:比較WorkSpace和repo,add之前之后都有diff輸出
- git diff –cached:比較stage和repo,add之前沒有diff輸出;add之后有diff輸出
對象的存儲
前面提到所有的Git對象都會存放在”.git/objects”目錄中,對象SHA1哈希值的前兩位是文件夾名稱,后38位作為對象文件名。
所以,我們前面提到的master上最新的commit對象的哈希值是”4ea6c317a67e73b0befcb83c36b915c1481f2efe”,那么這個對象會被存儲在”.git/objects/4e/a6c317a67e73b0befcb83c36b915c1481f2efe”。進入objects目錄后,我們確實找到了這個文件。
在Git系統中有兩種對象存儲的方式,松散對象存儲和打包對象存儲。
松散對象(loose object)
松散對象存儲就是前面提到的,每一個對象都被寫入一個單獨文件中,對象SHA1哈希值的前兩位是文件夾名稱,后38位作為對象文件名。
打包對象(packed object)
對于松散存儲,把每個文件的每個版本都作為一個單獨的對象,它的效率比較低,而且浪費空間。所以就有了通過打包文件(packfile)的存儲方式。
Git使用打包文件(packfile)去節省空間.。在這個格式中,,Git只會保存第二個文件中改變了的部分,然后用一個指針指向相似的那個文件。
一般Git系統會自動完成打包的工作,在已經發生過打包的Git倉庫中,”.git/objects/pack”目錄下會成對出現很多”pack-***.idx”和”pack-***.pack”文件。關于打包就介紹這么多了,暫時還沒有去研究兩個文件的內容和原理。
總結
這篇文章結合了前一篇的Git對象模型,探索了.git文件夾,通過引用,reflog以及索引的介紹,相信會對Git的工作原理有了更多的了解。
通過這兩篇文章介紹下來,感覺對謎一樣的Git也慢慢的熟悉了起來。