深入淺出git(一)——資料模型
自2005年誕生以來,git 已經在開源世界中大受歡迎,我們中的許多人也在我們的工作崗位上使用它。 它是一個很棒的VCS工具,具有很多優點,但易於學習並不是其中之一。 對於 git 如果只會死記硬背命令那麼要不了多久你就會忘記,然後一而再而三的背誦,無疑讓人很受打擊,在我看來,熟悉使用 git 甚至開始喜歡它的唯一方法是瞭解它如何在內部工作。
git 命令只是對資料儲存的抽象,如果不瞭解 git 的工作原理,無論我們在筆記中記憶或儲存了多少 git 命令或技巧我們仍然會對git的使用感到困惑.而git則是通過抽象的命令來暴露它的資料結構的使用方法.
所以這邊文章我們更多的要關注 git 的內部關係-資料模型,當然這篇文章不會涉及到 git 的原始碼.
準備工作
初始化倉庫
為了講解資料模型,我們首先要在自己的工作目錄下初始化一個空的 git 倉庫
git init
git會告知我們已經在當前的目錄下建立了一個 .git 目錄,我們來看看這個 .git 長什麼樣子.
$ tree .git/ .git |-- HEAD |-- config |-- description |-- hooks ||-- applypatch-msg.sample ||-- commit-msg.sample ||-- fsmonitor-watchman.sample ||-- post-update.sample ||-- pre-applypatch.sample ||-- pre-commit.sample ||-- pre-push.sample ||-- pre-rebase.sample ||-- pre-receive.sample ||-- prepare-commit-msg.sample ||-- update.sample |-- info ||-- exclude |-- objects ||-- info ||-- pack |-- refs |-- heads |-- tags 8 directories, 15 files
其中一些檔案和目錄是不是看著有些熟悉,現在我們主要還是看 objects
這個目錄,現在它是空的,但是一會兒我們就會改變它.
提交檔案
首先我們建立一個 Main.java
檔案
touch Main.java
然後輸入一部分內容
public class Main { public static void main(String[] args) { System.out.println("Hello World"); } }
然後以同樣的方式在準備一個README.md檔案
touch README.md
向檔案中輸入以下內容
this is my first java project!
現在add並且commit他們到倉庫
git add . git commit -m 'Initial Commit'
模型的建立
現在看上去沒啥特殊的,現在我們回過頭來在看看 .git/objects
目錄下已經存在了一些子資料夾以及檔案了
.git/objects |-- 84 |-- 705622ee44f2afbb21087ca7d81fda01fccded |-- 95 |-- fc1236534b6f73930367f02895467040f47d4a |-- b0 |-- 81e51f448387e72a3e3551ba8610eedc172e60 |-- f1 |-- a8b89f50a2fd8287578daa2b0374adf3cad8aa |-- info |-- pack 6 directories, 4 files
需要注意的是在你的電腦上目錄和檔名稱和我這裡是不一樣的.
blob object的建立
在 .git/objects
下我們注意到每個目錄的名稱只有2個字元長度,Git為每個物件生成一個40個字元的校驗和(SHA-1)雜湊,該校驗和的前兩個字元用作目錄名,另外38個字元用作檔案(物件)名。
當我們提交一些檔案時,git建立的第一類物件是 blob object ,在我們的例子中是兩個,每一個 blob object 對應我們提交的每一個檔案:

blob-object
blob object包含檔案的快照以及擁有檔案校驗和.
tree object的建立
git建立的另外一種物件是 tree object
,在我們的例子中只有一個,它包含我們專案中所有檔案的列表,其中包含分配給它們的blob object的指標(這就是git如何將檔案與blob object相關聯)

tree-object
commit object的建立
最後git還建立了一個commit object,該物件具有指向它的tree object的指標(以及一些其他資訊)

commit object
)
這個時候在來看以下objects目錄下的結構就清晰多了
.git/objects |-- 84 |-- 705622ee44f2afbb21087ca7d81fda01fccded |-- 95 |-- fc1236534b6f73930367f02895467040f47d4a |-- b0 |-- 81e51f448387e72a3e3551ba8610eedc172e60 |-- f1 |-- a8b89f50a2fd8287578daa2b0374adf3cad8aa |-- info |-- pack
驗證模型的準確性
上面畫出了模型圖,但是你以為我這個模型是自己猜的嗎?我又是如何確定哪個是blob object?哪個是tree object?哪個是commit object的呢?接下來就是見證奇蹟的時刻了.
使用 git log
命令我們可以檢視我們的提交歷史
commit f1a8b89f50a2fd8287578daa2b0374adf3cad8aa (HEAD -> master) Author: zhu.yang <[email protected]> Date:Tue Jan 8 10:12:06 2019 +0800 Initial Commit
根據我們前面說的命名約定,我們可以在objects中發現 f1a8b89f50a2fd8287578daa2b0374adf3cad8aa
這個物件.
想要檢視檔案內容我們不能簡單的使用 cat
命令,因為這些不是純文字檔案,但是好在git給我們提供了一個cat-file命令
git cat-file commit f1a8b89f50a2fd8287578daa2b0374adf3cad8aa
可以通過它獲取到commit object中的內容
tree 95fc1236534b6f73930367f02895467040f47d4a author zhu.yang <[email protected]> 1546913526 +0800 committer zhu.yang <[email protected]> 1546913526 +0800 Initial Commit
從上面可以看到commit指向tree object並且我們可以使用 git ls-tree
命令來檢查下其中的內容
git ls-tree 95fc1236534b6f73930367f02895467040f47d4a
正如我們說預料的一樣,其中包含了指向blob object的檔案列表
100644 blob 84705622ee44f2afbb21087ca7d81fda01fccdedMain.java 100644 blob b081e51f448387e72a3e3551ba8610eedc172e60README.md
如果想要檢視Main.java中的內容則使用 cat-file
命令即可
git cat-file blob 84705622ee44f2afbb21087ca7d81fda01fccded
我們可以看到其中返回了Main.java檔案的內容
public class Main { public static void main(String[] args) { System.out.println("Hello World"); } }
上面就是當我們建立並提交了一些檔案的時候就會發生的事情.同時也驗證了我們模型的準確性.
修改檔案時模型的改變
現在我們修改一下main.java然後重新提交一下

新增blob object
正如我們看到的一樣,git以快照的方式為 Main.java
新建了一個blob object,由於 README.md
沒有被修改,因此不會為其建立新的blob object.而且 git會重用現有的blob object.
現在,當git建立一個tree object時,分配給 Main.java
的blob指標會被更新,並且分配給 README.md
的blob指標將保持與前一個提交樹中的相同。

新增tree object
)
在最後,git建立一個commit object並指向它的tree object.同時還有一個指向它的父提交物件的指標(每個提交除了第一個提交至少還有一個父提交)

新增commit object
到現在為止我們已經知道了git是如何處理檔案的新增以及編輯的,唯一還遺留的就是如何處理刪除了,我們先刪除Main.java:

刪除檔案
請注意上圖中紅色的連線,我們發現刪除同樣也是非常簡單,只需要刪除tree object指向blob object的指標即可.在這種情況下我們在新的提交中刪除了Main.java,因此我們的提交的樹物件不再具有指向表示Main.java的blob object的指標.
模型對資料夾的處理
我們提供的這個資料模型還有一個附加功能-tree object是可以被巢狀的(它們可以指向其他樹物件),你可以這樣想:每個blob object代表一個檔案,每個樹物件代表一個目錄,所以如果我們有巢狀目錄,我們就有巢狀的tree object.
由於上面的圖已經是提交多次結果畫出來的了,再在上面的基礎上畫結構就不是那麼清晰了,這次我重新初始化一個倉庫來演示,現在該倉庫下存在存在的資料如下:
|-- README.md `-- app `-- user.json
然後提交,最後可以看到如下的資料模型

tree object巢狀g
Git使用blob object以及tree object來重現專案的資料夾結構.到這裡我相信你肯定對git的資料模型有了較為深入的瞭解,它真的是很簡單,我相信基於它再去學習Git一定會是事半功倍.
總結
- 建立一個提交的時候git會新增blob object,tree object,commit object並會形成鏈路圖
- 巢狀的tree object用來表示資料夾
- git從複用blob object
- 除了第一個提交之外,每一個提交都有一個父提交