- Git 設計原理
- Git vs SVN
- Git 儲存模型
- .git 目錄結構
- Git 基本資料物件
- Git 包檔案
- Git 引用
Git 設計原理
概括的講,Git 就是一個基於快照的內容定址檔案系統。 往下慢慢看。
Git vs SVN
Git 出現前,主流版本控制系統(SVN...)一般為基於增量(delta-based)的系統,如下圖:
Git 則是基於快照(snapshot),即針對每一個被修改的檔案生成一個快照,沒被修改的則不再重新生成快照,如下圖:
直覺上講,似乎基於增量的方式要更好些?
畢竟針對被修改的檔案,Git 生成的是完全的快照,而其他系統只是生成增量檔案。沒錯,但是當需要回滾版本或者比對多個版本間的差異時,Git 只需要取出對應版本的快照檔案進行對比即可,而基於增量的系統則需要從頭開始一步步應用增量檔案來回溯,Git 的速度優勢就很明顯了。
Git 儲存模型
.git 目錄結構
當用git init
或者 git clone
獲取一個 git 倉庫時,可以發現目錄下有一個隱藏目錄。git,它的基本結構類似如下:
├── COMMIT_EDITMSG 倉庫最後一次commit的message
├── FETCH_HEAD 每個分支的最後一次commit的SHA1值
├── HEAD 記錄了HEAD指標的指向位置
├── ORIG_HEAD 針對某些危險操作,該指標記錄了上一次安全版本的HEAD指標的位置,方便回退
├── config git的相關配置
├── index 暫存區,索引檔案
├── packed-refs 已經壓縮的分支,記錄了每個分支的最後一次commit的SHA1
├── logs/ 操作日誌,包括本地遠端的
├── objects/ 物件儲存資料夾
| ├── ... 資料夾名稱根據object的SHA1值的前2個字元確定
| ├── ...
| ├── info/
| ├── pack/ 壓縮後的資料
└── refs/ 記錄本地和遠端的最後一次commit的SHA1值
├── heads/ 分支引用
├── remotes/ 遠端地址
└── tags/ 標籤引用
這個目錄下包含了 Git 所有資訊,且都是用檔案的形式儲存,所以說 Git 是一個檔案系統。
Git 基本資料物件
- blob(二進位制大物件):也就是前面說的基於快照儲存的檔案
- tree:目錄,代表了 blob 物件的集合
- commit:提交,包含了 blob、tree 的集合
- tag:標籤物件(指 annotation 標籤),還有一種輕量標籤不記錄建立標籤人等額外資訊,不需要再單獨建立標籤物件
上述 4 種資料物件均儲存在。git/object/目錄下,git 會對每一種資料物件計算雜湊值來確定具體的儲存路徑,下面來舉個例子。
> echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4
//40位的SHA-1雜湊值,前2位位目錄名,其他38位為檔名,儲存路徑即.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
> git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content
> git cat-file -p master^{tree} // 輸出master最新提交包含內容
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
// 包含了2個檔案的修改和1個目錄的修改
> git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
git hash-object
命令可以用於計算檔案的雜湊值 -w 表示把將物件寫入到 git 資料庫中
--stdin 表示從標準輸入讀取內容
git cat-file
命令可以根據傳入雜湊值取出 git 儲存的物件 -p 自動判斷內容的型別
一次提交的資料結構可以用下圖來概括:
Git 包檔案
可能有的小夥伴透過上述方式在自己專案中嘗試時,發現在。git/objects/下找不到對應檔案,這是什麼原因呢?
可能真的不是操作出了問題,而是 Git 進行了壓縮操作。
Git 最初儲存物件時使用的時"鬆散(loose)"物件格式,即儲存在。git/objects/下。
但是,Git 會時不時(或者當你手動執行git gc
命令後)地將這些物件打包成一個稱為“包檔案(packfile)”的二進位制檔案(儲存在。git/objects/pack),以節省空間和提高效率。
Git 引用
引用類似於指標,除了 HEAD 儲存在。git/HEAD 以外,其他指標儲存在。git/refs 目錄下
- 分支
- HEAD:一種特殊的指標,用於指向目前所在的 commit,。git/HEAD 檔案裡儲存的就是引用的 commit 的雜湊值
- 標籤(輕量標籤)
可以看出,所謂的引用只是一個記錄了 commit 雜湊值的檔案,非常的輕量,這也是為什麼分支/標籤的建立、刪除速度能這麼快的原因。