『現學現忘』Git物件 — 15、blob物件詳解

繁華似錦Fighting發表於2022-04-25

Git 是一套內容定址檔案系統。什麼意思呢?

就是Git的核心部分是一個簡單的鍵值資料庫(key-value data store)。你可以向該資料庫插入任意型別的內容,並會返回一個鍵值,通過該鍵值可以在任何時候再取出該內容。

(一)Git物件的存放目錄

Git中物件都儲存在本地版本庫的.git/objects 目錄(即:物件資料庫)中。

首先初使化一個趕緊的Git倉庫:

# 建立一個本地的git倉庫git_learning
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository
$ mkdir git_learning

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository
$ cd git_learning/

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning
$ git init
Initialized empty Git repository in J:/git-repository/git_learning/.git/

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll -a
total 8
drwxr-xr-x 1 L 197121 0  4月 10 20:24 ./
drwxr-xr-x 1 L 197121 0  4月 10 20:23 ../
drwxr-xr-x 1 L 197121 0  4月 10 20:24 .git/

確認 objects 目錄是預設初始狀態:

# 檢視.git/objects/目錄
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll -a .git/objects/
total 4
drwxr-xr-x 1 L 197121 0  4月 10 20:24 ./
drwxr-xr-x 1 L 197121 0  4月 10 20:24 ../
drwxr-xr-x 1 L 197121 0  4月 10 20:24 info/
drwxr-xr-x 1 L 197121 0  4月 10 20:24 pack/

# 檢視info目錄和pack目錄中的內容
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll -a .git/objects/info/
total 0
drwxr-xr-x 1 L 197121 0  4月 10 20:24 ./
drwxr-xr-x 1 L 197121 0  4月 10 20:24 ../

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll -a .git/objects/pack/
total 0
drwxr-xr-x 1 L 197121 0  4月 10 20:24 ./
drwxr-xr-x 1 L 197121 0  4月 10 20:24 ../

從上可以看到Git初始化一個本地版本庫的時候,就已經初始化了objects目錄,並在其中建立了packinfo子目錄,但是沒有其他常規檔案,packinfo子目錄中也沒有檔案。我們只關注objects目錄下除了infopack目錄之外的變化。

(二)Git中物件型別

Git中物件型別有四種:blob(塊)物件,tree(目錄樹)物件,commit(提交)物件和tag(標籤)物件,這四種原子物件構成了Git高層資料結構的基礎。

(三)blob物件

1、blob物件說明

(1)blob物件定義

blob物件有叫資料物件。

blob物件是用來儲存文字內容的。即把一個文字檔案的內容,作為一個blob物件儲存在Git系統中。

翻譯:

  • Git中blob物件就是對應檔案系統中的檔案,確切的說是檔案的內容,包含
    鍵:一個hash值和校驗值的組合,
    值:檔案的內容。
  • 比較特殊的是:blob物件只存內容,不存檔名,檔名在tree物件中儲存。

blob物件儲存方式如下圖

image

(2)blob物件說明

通過底層命令 git hash-object 來演示,該命令可將任意資料儲存於 .git/objects 目錄(即 物件資料庫)中,並返回指向該資料物件的唯一的鍵。

1)建立一個新的資料物件,並將它手動存入你的新Git資料庫中:

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ echo 'git object test content' | git hash-object -w --stdin
cb2eb834126f53952590c448f14fece6cbb1bff3

說明:這是在Git中以最簡單的形式儲存資料到Git版本庫中,git hash-object命令會接受你傳給它的東西,而它只會返回,可以儲存在Git倉庫中資料物件的唯一的鍵。

命令含義如下:

  • git hash-object:Git底層命令,可以根據傳入的文字內容返回表示這些內容的鍵值。
  • git object test content:為文字檔案中的內容。
  • -w選項:表示hash-object命令將資料物件儲存到Git資料庫中;若不指定此選項,則該命令僅返回對應的鍵(也就是那串Hash值)
  • --stdin 選項:表示該命令從標準輸入(比如鍵盤)讀取內容,若不指定此選項,則須在命令尾部給出待儲存檔案的路徑。例如:git hash-object -w 檔案路徑

此命令輸出一個長度為40個字元的校驗和,這是一個SHA-1雜湊值,如上cb2eb834126f53952590c448f14fece6cbb1bff3。該值是檔案原內容加上特定頭部資訊拼接起來,做雜湊計算得到的數值。(只要文字內容相同,計算出的結果都是一樣的)

2)在計算機中檢視本地版本庫.git/objects 目錄中的變化。

可以看到.git/objects 目錄中多了一個cb資料夾,如下:

image

進入cb資料夾,可以看到有一個檔案,如下:

image

好,我們就看到這裡就行,這樣看比較複雜,還是回到Git Bash中檢視。

3)看本地版本庫.git/objects 目錄中的變化。

# 只看檔案可以加 -type f選項引數
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects
.git/objects
.git/objects/cb
.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3
.git/objects/info
.git/objects/pack

我們可以看到cb目錄和cb目錄中的2eb834126f53952590c448f14fece6cbb1bff3檔案。

這就是Git中一個blob物件的儲存。

(3)blob物件儲存的方式

Git物件的定址使用40位的16進位制數表示,也就是SHA-1雜湊碼,例如:cb2eb834126f53952590c448f14fece6cbb1bff3

為了管理方便,在檔案系統中前兩位作為.git/objects/ 子目錄的名字,後38為作為檔名字。

如下:

# 檢視objects目錄中的檔案,一個檔案對應一個Git物件(資料內容)
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3

# 輸出資料物件的hash鍵,不儲存該資料物件。
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ echo 'git object test content' | git hash-object --stdin
cb2eb834126f53952590c448f14fece6cbb1bff3

我們可以看到兩個Hash串是一樣的。

提示:你可能感覺用40位作為Git物件的定址ID,可能會存在不同的內容但是雜湊碼相同的情況,你的感覺是正確的,但是這種情況出現的概率肯定可以忽略不計了。

(4)檢視blob物件內容

我們先用cat命令直接讀取上面檔案,看看是什麼情況,如下圖:

image

可以看到顯示的內容是一片亂碼。

我們需要根據Hash鍵讀取資料,使用命令git cat-file -p 鍵

-p選項可指示該命令自動判斷內容的型別,併為我們使用友好的格式顯示內容。

如下:

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -p cb2eb834126f53952590c448f14fece6cbb1bff3
git object test content

提示:

cat命令直接讀取Git物件檔案,為什麼是亂碼資訊?

檔案內容是先通過 zlib 壓縮,然後將 zlib 壓縮後的內容寫入磁碟檔案(SHA-1 前兩個字元作為子目錄名稱,後 38 個字元作為子目錄檔案的名稱)

(5)檢視Git物件的型別

通過git cat-file -t 鍵命令,可以檢視.git/objects 目錄中Git物件的型別

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -t cb2eb834126f53952590c448f14fece6cbb1bff3
blob

這裡也說明,我們之前儲存的Git物件是一個blob物件。

(6)Git管理檔案

至此,你已經掌握瞭如何向 Git 中存入內容,以及如何將它們取出。

我們同樣可以將這些操作應用於檔案中的內容。 例如,可以對一個檔案進行簡單的版本控制。

1)首先,建立一個檔案。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ echo "hello-git.txt v1" > hello-git.txt

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll
total 1
-rw-r--r-- 1 L 197121 17  4月 10 23:17 hello-git.txt

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ cat hello-git.txt
hello-git.txt v1

此時檔案還有被Git管理。

2)將hello-git.txt檔案存入Git資料庫。

# 加入版本庫,生成blob物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git hash-object -w ./hello-git.txt
a620c95d3001e1f64cecfc6715f9750cc7bbbf98

3)檢視Git資料庫內容。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98
.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3

可以看到有多了一個a6子目錄,就說明有新增了一個物件。

4)檢視a6物件的內容。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -p a620c95d3001e1f64cecfc6715f9750cc7bbbf98
hello-git.txt v1

5)檢視a6物件的型別。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -t a620c95d3001e1f64cecfc6715f9750cc7bbbf98
blob

可以看到,不管是你儲存一個檔案還是儲存控制檯內容到Git中,最終儲存到Git資料庫中的都是一個blob型別的Git物件。(即:blob物件是儲存資料內容的)

(7)Git管理修改過的檔案

1)我們繼續向hello-git.txt檔案中新增內容。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ echo "hello-git.txt v2" >> hello-git.txt

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ cat hello-git.txt
hello-git.txt v1
hello-git.txt v2

2)檢視Git資料庫中的物件。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98
.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3

可以看到還是之間的兩個物件cba6,說明我們修改過的檔案不會自動的儲存到Git資料庫中。

我們還需要手動的把修改後的hello-git.txt檔案,儲存到Git資料庫中。

3)把修改後的hello-git.txt檔案新增到Git資料庫中。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git hash-object -w ./hello-git.txt
7c320a2d671f2ff177063f98343a0123432521dd

4)再次檢視Git資料庫中的物件。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/7c/320a2d671f2ff177063f98343a0123432521dd
.git/objects/a6/20c95d3001e1f64cecfc6715f9750cc7bbbf98
.git/objects/cb/2eb834126f53952590c448f14fece6cbb1bff3

我們可以看到Git資料庫中多了一個7c物件。

5)檢視7c物件儲存的內容。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -p 7c320a2d671f2ff177063f98343a0123432521dd
hello-git.txt v1
hello-git.txt v2

如上所示,我們可以看到7c物件儲存了v1v2的內容,v1內容即在a6物件中,也在7c物件中。所以對於Git來說,儲存的不是檔案內容的增量。

2、blob物件總結

  • Git的核心部分是一個簡單的鍵值資料庫(key-value data store),鍵就是文字內容的Hash,值就是文字內容。
  • blob物件都儲存在.git/objects 目錄中,子目錄+目錄中的檔名,就是40位Hash值,也就是物件的鍵值。
  • 通過這個鍵就能找到對應的內容。
  • 每個文字內容儲存到Git資料庫的時候,內容都會進行zlib 壓縮再儲存。
  • blob物件儲存的是檔案的內容,相同的內容不產生新的blob物件。
  • blob物件並沒有儲存檔名。

提示:Git物件的hash鍵,我們擷取前幾位就行,練習時物件不那麼對,就不用全部都寫,能夠表示唯一物件就行。

3、問題

我們對檔案做一次修改,儲存到Git資料庫中,都會在Git資料庫中建立一個新的blob物件。而在實際的工作中,我們需要做很多的改動,才提交一個版本,我們是否可以用一個blob物件代表整個專案的一次快照。

不能,只能代表一次儲存時,一個檔案中的內容,與之前資料內容相同時不新增Git物件,資料內容不同時再次新增blob物件。即:只要有新的內容被Git納入管理,必定有一個blob物件與之對應。

那麼還有如下問題:

  1. 記住檔案的每一個版本所對應的SHA-1值並不現實。
  2. blob物件中,檔名並沒有被儲存,僅儲存了檔案的內容。

所以,沒有檔名就沒有辦法通過檔名來讀取資料,只能用40位Hash值讀取,非常的不現實。

解決方案:樹物件。

提示:以上的操作都是在工作區和本地版本庫之間進行操,不涉及暫存區,因為我們直接儲存到了本地版本庫中。

4、本文用到的命令總結

Git底層命令:

  • git hash-object -w 檔案路徑:把工作區的一個檔案提交到本地版本庫中。
  • find .git/objects -type f:檢視Git資料庫中的物件。(Linux命令)
  • git cat-file -p 鍵:檢視該Git物件的內容。
  • git cat-file -t 鍵:檢視該Git物件的型別。

參考:

相關文章