前面一篇文章介紹了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”的內容:
1 |
ref: refs/heads/master |
這就表示HEAD是一個指向master分支的引用,然後我們可以根據引用路徑開啟”refs/heads/master”檔案,內容如下:
1 |
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檔案的內容也會相應的變成:
1 |
ref: refs/heads/release-1.0.0.1 |
再看reflog
看過第二篇文章的同學一定還記得我們是怎麼根據reflog去得到一個commit雜湊值,然後把repo退回到一個指定的狀態。
接下了,我們進入”.git/logs”資料夾,可以看到這個資料夾也有一個HEAD檔案和refs目錄,些就是記錄reflog的地方。
檢視HEAD檔案的內容,發現這個檔案將會包含所有分支的reflog記錄:
1 2 3 4 |
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的內容:
1 2 3 |
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的內容:
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也慢慢的熟悉了起來。