來源:公眾號Hollis
作者:generalthink
原文:http://generalthink.github.io/2019/01/09/understanding-git-data-model/
自 2005 年誕生以來,git 已經在開源世界中大受歡迎,我們中的許多人也在我們的工作崗位上使用它。
它是一個很棒的 VCS 工具,具有很多優點,但易於學習並不是其中之一。
對於 git 如果只會死記硬背命令那麼要不了多久你就會忘記, 然後一而再而三的背誦, 無疑讓人很受打擊, 在我看來,熟悉使用 git 甚至開始喜歡它的唯一方法是了解它如何在內部工作。
git 命令只是對數據存儲的抽象, 如果不了解 git 的工作原理,無論我們在筆記中記憶或存儲了多少 git 命令或技巧我們仍然會對 git 的使用感到困惑. 而 git 則是通過抽象的命令來暴露它的數據結構的使用方法.
所以這邊文章我們更多的要關注 git 的內部關係 - 數據模型。
1
準備工作
初始化倉庫
為了講解數據模型, 我們首先要在自己的工作目錄下初始化一個空的 git 倉庫
git init
git 會告知我們已經在當前的目錄下創建了一個. git 目錄, 我們來看看這個. git 長什麼樣子.
其中一些文件和目錄是不是看著有些熟悉, 現在我們主要還是看objects這個目錄, 現在它是空的, 但是一會兒我們就會改變它.
提交文件
首先我們創建一個Main.java文件
touch Main.java
然後輸入一部分內容
然後以同樣的方式在準備一個 README.md 文件
touch README.md
向文件中輸入以下內容
this is my first java project!
現在 add 並且 commit 他們到倉庫
git add .
git commit -m 'Initial Commit'
2
模型的創建
現在看上去沒啥特殊的, 現在我們回過頭來在看看.git/objects目錄下已經存在了一些子文件夾以及文件了
需要注意的是在你的電腦上目錄和文件名稱和我這裡是不一樣的.
blob object 的創建
在.git/objects下我們注意到每個目錄的名稱只有 2 個字符長度, Git 為每個對象生成一個 40 個字符的校驗和(SHA-1)哈希,該校驗和的前兩個字符用作目錄名,另外 38 個字符用作文件(對象)名。
當我們提交一些文件時, git 創建的第一類對象是blob object, 在我們的例子中是兩個, 每一個blob object對應我們提交的每一個文件:
blob object 包含文件的快照以及擁有文件校驗和.
tree object 的創建
git 創建的另外一種對象是tree object, 在我們的例子中只有一個, 它包含我們項目中所有文件的列表, 其中包含分配給它們的 blob object 的指針 (這就是 git 如何將文件與 blob object 相關聯)
commit object 的創建
最後 git 還創建了一個 commit object, 該對象具有指向它的 tree object 的指針 (以及一些其他信息)
這個時候在來看以下 objects 目錄下的結構就清晰多了
3
驗證模型的準確性
上面畫出了模型圖, 但是你以為我這個模型是自己猜的嗎? 我又是如何確定哪個是 blob object? 哪個是 tree object? 哪個是 commit object 的呢? 接下來就是見證奇蹟的時刻了.
使用git log命令我們可以查看我們的提交歷史
commit f1a8b89f50a2fd8287578daa2b0374adf3cad8aa (HEAD -> master)
Author: zhu.yang
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.yang1546913526 +0800
committer zhu.yang1546913526 +0800
Initial Commit
從上面可以看到 commit 指向 tree object 並且我們可以使用git ls-tree命令來檢查下其中的內容
git ls-tree 95fc1236534b6f73930367f02895467040f47d4a
正如我們說預料的一樣, 其中包含了指向 blob object 的文件列表
100644 blob 84705622ee44f2afbb21087ca7d81fda01fccded Main.java
100644 blob b081e51f448387e72a3e3551ba8610eedc172e60 README.md
如果想要查看 Main.java 中的內容則使用cat-file命令即可
git cat-file blob 84705622ee44f2afbb21087ca7d81fda01fccded
我們可以看到其中返回了 Main.java 文件的內容
上面就是當我們創建並提交了一些文件的時候就會發生的事情. 同時也驗證了我們模型的準確性.
修改文件時模型的改變
現在我們修改一下 main.java 然後重新提交一下
正如我們看到的一樣, git 以快照的方式為Main.java新建了一個 blob object, 由於README.md沒有被修改, 因此不會為其創建新的 blob object. 而且git 會重用現有的 blob object.
現在,當 git 創建一個 tree object 時,分配給Main.java的 blob 指針會被更新,並且分配給README.md的 blob 指針將保持與前一個提交樹中的相同。
在最後, git 創建一個 commit object 並指向它的 tree 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
然後提交, 最後可以看到如下的數據模型
Git 使用 blob object 以及 tree object 來重現項目的文件夾結構. 到這裡我相信你肯定對 git 的數據模型有了較為深入的了解, 它真的是很簡單, 我相信基於它再去學習 Git 一定會是事半功倍.
4
總結