『現學現忘』Git物件 — 16、Tree物件詳解

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

1、Tree物件介紹

接下來要探討的 Git 物件型別是樹物件(tree object),它能解決檔名儲存的問題。tree物件可以儲存檔名,也允許我們將多個檔案組織到一起。

Git以一種類似於UNIX檔案系統的方式儲存內容,但做了一些簡化。所有內容均以樹(tree)物件和資料(blob )物件的形式儲存,其中樹物件對應了UNIX中的目錄項,資料物件blob則大致上對應了檔案中的內容。

一個樹物件可以包含一條或多條記錄(tree物件和blob 物件),每條記錄含有一個指向blob 物件或者子tree物件的SHA-1指標,以及相應的模式、型別、檔名資訊。

如下圖:

# 檔案模式、物件型別、物件的SHA-1指標、檔名
100644 blob fa49b077972391ad58037050f2a75f74e3671e92    new.txt

Tree物件儲存方式如下圖所示

image

2、Tree物件說明

(1)初始化一個新的本地版本庫

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月 11 14:50 ./
drwxr-xr-x 1 L 197121 0  4月 10 20:23 ../
drwxr-xr-x 1 L 197121 0  4月 11 14:50 .git/

(2)建立一個樹物件(重點)

1)新建一個檔案,然後把檔案提交到本地版本庫。

例如:新建檔案test.txt,檔案內容version 1

# 建立檔案
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ echo "version 1" >> test.txt

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll
total 1
-rw-r--r-- 1 L 197121 10  4月 11 14:57 test.txt

# 檢視檔案內容
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ cat test.txt
version 1

2)把test.txt檔案,提交到本地版本庫。

# 1.test.txt檔案提交到本地版本庫
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git hash-object -w ./test.txt
83baae61804e65cc73a7201a7252750c76066a30

# 2.檢視Git資料庫內容,可以看到新增了一個blob物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30

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

# 3.檢視blob物件物件內容
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30
version 1

以上就和我們講blob物件的操作一樣。

此時test.txt檔案被管理在Git本地版本庫中。

3)建立一個樹物件。

通常Git是根據暫存區或者索引檔案index來建立tree物件,因此要把檔案儲存到暫存區進並建立index檔案。

提示1:

index檔案在.git目錄中,最新初始化的Git本地倉庫是沒有index檔案,只有新增過一次資料到暫存區之後,才會在.git目錄中自動生成index檔案。

新初始化的.git目錄內容如下:是沒有index檔案的。

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll .git/
total 7
-rw-r--r-- 1 L 197121 130  4月 11 14:50 config
-rw-r--r-- 1 L 197121  73  4月 11 14:50 description
-rw-r--r-- 1 L 197121  23  4月 11 14:50 HEAD
drwxr-xr-x 1 L 197121   0  4月 11 14:50 hooks/
drwxr-xr-x 1 L 197121   0  4月 11 14:50 info/
drwxr-xr-x 1 L 197121   0  4月 11 14:59 objects/
drwxr-xr-x 1 L 197121   0  4月 11 14:50 refs/

提示2:

可以通過git ls-files命令檢視暫存區的檔案資訊。

引數資訊如下,括號中簡寫:

  • --cached(-c): 檢視暫存區中檔案。git ls-files命令預設執行此選項。
  • --midified(-m):檢視修改的檔案。
  • --delete(-d):檢視刪除過的檔案。
  • --other(-o) :檢視沒有被Git跟蹤的檔案。
  • --stage(-s):顯示mode以及檔案對應的Blob物件,進而我們可以獲取暫存區中對應檔案裡面的內容。

例如:git ls-files -c或者git ls-files --cached (其他命令同理)

我們常用git ls-files -s命令檢視暫存區的檔案資訊。

接下來,我們可以通過底層命令:update-indexwrite-treeread-tree等命令,輕鬆建立自己的tree物件。

# 1.檢視暫存區當前狀態,可以看到沒有任何顯示
# 說明暫存區沒有儲存任何檔案
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git ls-files -s

# 2.把test.txt檔案存入暫存區
# 通過git update-index命令實現
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git update-index --add --cacheinfo 100644 \
> 83baae61804e65cc73a7201a7252750c76066a30 test.txt

# 3.再次檢視暫存區當前狀態,可以看到暫存區中有一個檔案了。
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0       test.txt
# 這裡就說明了為什麼之前要把test.txt檔案先存入到本地版本庫中了,
# 因為需要檔案的hash鍵,來新增到暫存區。
### 這裡也說明了檔名和檔案hash鍵的結合。(重點)

命令說明:

  • 為建立一個樹物件,首先需要通過暫存一些檔案到暫存區。
    通過底層命令 git update-index將一個單獨檔案存入暫存區中。
  • --add 選項:因為此前該檔案並不在暫存區中,一個檔案首次新增到暫存區,需要使用--add 選項。
  • --cacheinfo 選項:因為要新增的test.txt檔案位於Git 資料庫中(上一步的操作),而不是位於當前工作目錄,所以需要--cacheinfo 選項。
  • 最後需要指定檔案模式SHA-1檔名

檔案模式說明:

  • 100644:表明這是一個普通檔案。(blob物件的檔案模式一般都為100644)
  • 100755:表示一個可執行檔案。
  • 120000:表示一個符號連結。

繼續,下面來觀察生成的樹物件::

# 4.完成上面步驟後,檢視.git目錄
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll .git/
total 8
-rw-r--r-- 1 L 197121 130  4月 11 14:50 config
-rw-r--r-- 1 L 197121  73  4月 11 14:50 description
-rw-r--r-- 1 L 197121  23  4月 11 14:50 HEAD
drwxr-xr-x 1 L 197121   0  4月 11 14:50 hooks/
-rw-r--r-- 1 L 197121 104  4月 11 15:39 index	# 出現了index檔案
drwxr-xr-x 1 L 197121   0  4月 11 14:50 info/
drwxr-xr-x 1 L 197121   0  4月 11 14:59 objects/
drwxr-xr-x 1 L 197121   0  4月 11 14:50 refs/
# 這裡提示一下,暫存區Stage可以理解成是一個簡單的索引檔案。
# 指的就是.git/index檔案。(重點)

# 5.現在先檢視一下Git資料庫內容,還是之前那一個blob物件。
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30

# 6.把暫存區中的內容提交到本地版本庫
# 換句話說就是把暫存區中存放的檔案索引(快照)提交到本地版本庫。
# 使用write-tree命令實現
# 也就是通過write-tree命令生成樹對像
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579

# 7.再次檢視Git資料庫內容,多了一個d8物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579

# 8.檢視d8物件的型別,可以看到是一個樹物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree

# 9.再次檢視暫存區當前狀態,發現暫存區的內容沒有清空
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0       test.txt
# 說明:在檢視暫存區,發現暫存區的內容沒有清空,即:暫存區內容寫到版本庫,暫存區不清空。(重點)

4)總結

以上就是在Git中,使用底層命令手動建立一個樹物件的過程。

  • 建立一個檔案,把該檔案通過git hash-object命令儲存到本地版本庫中。
  • 通過git update-index命令,把檔案儲存到暫存區中。
  • 通過git write-tree命令,把暫存區中的檔案索引資訊提交到本地版本庫,生成了一個樹物件。

(3)建立第二個檔案(重點)

1)新增new.txt檔案,並修改test.txt檔案內容。

# 1.建立new.txt檔案
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ echo "new file" > new.txt

# 2.修改test.txt檔案內容
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ echo "version 2" >> test.txt

# 3.檢視兩個檔案的內容
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ cat new.txt
new file

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ cat test.txt
version 1
version 2

# 4.檢視工作目錄中的檔案
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ ll
total 2
-rw-r--r-- 1 L 197121  9  4月 11 16:25 new.txt
-rw-r--r-- 1 L 197121 20  4月 11 16:25 test.txt

2)將new.txt檔案和test.txt檔案的第二個版本新增到暫存區。

test.txt檔案新增到暫存區。

# 1.檢視暫存區當前檔案資訊
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0       test.txt

# 2.把test.txt檔案提交到本地版本庫
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git hash-object -w ./test.txt
0c1e7391ca4e59584f8b773ecdbbb9467eba1547

# 3.檢視Git資料庫內容,可以看到又多出一個0c物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
# 提示:因上面修改了test.txt檔案內容,在提交到版本庫,內容不一樣了hash就變了。

# 4.把修改後的test.txt檔案加入暫存區
# 因為之前提交過test.txt檔案到暫存區,所以不用加--add選項
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git update-index --cacheinfo 100644 \
> 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt

# 5.檢視暫存區當前檔案資訊
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git ls-files -s
100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0       test.txt
# 我們可以看到,暫存區的test.txt檔案被最新修改的版本覆蓋了,
# 之前是83開頭的
# 提示:暫存區是按對應檔案覆蓋的,新修改的檔案,覆蓋之前的原檔案,
# 不會覆蓋其他檔案,即暫存區不是整體覆蓋的。(重點)

new.txt檔案新增到暫存區。

# 1.new.txt檔案新增到暫存區
# 這次我們直接用一個命令把new.txt檔案從工作區直接新增到暫存區
# 說明:
# 因為是new.txt檔案是第一次新增到暫存區,所以需要--add選項
# 因為是從new.txt檔案是在工作區,所以不需要--cacheinfo選項
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git update-index --add new.txt

# 2.檢視Git資料庫內容,可以看到對了一個fa物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92

# 3.檢視暫存區當前檔案資訊,new.txt檔案已經新增到暫存區中
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git ls-files -s
100644 fa49b077972391ad58037050f2a75f74e3671e92 0       new.txt
100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0       test.txt

說明:git update-index --add 檔名完成了之前的兩步操作。

  1. new.txt檔案內容存入了Git版本庫。
  2. new.txt檔案新增到了暫存區中。

3)把暫存區的內容提交的本地版本庫。

此時工作目錄和暫存區中的檔案狀態是一樣的, 可以通過git write-tree命令提交到本地版本庫,生成樹對像了。

# 1.提交暫存區內容
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git write-tree
163b45f0a0925b0655da232ea8a4188ccec615f5

# 2.檢視Git資料庫內容,可以看到又多了一個名為16的tree物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92

L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -t 163b45f0a0925b0655da232ea8a4188ccec615f5
tree

此時Git版本庫中的5個物件,即表示了專案的2個版本。(不明白這句話?繼續往下看)

(4)將第一個樹物件加入暫存區,使其成為新的樹對

# 1.檢視暫存區當前檔案資訊
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git ls-files -s
100644 fa49b077972391ad58037050f2a75f74e3671e92 0       new.txt
100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0       test.txt

# 2.將第一個樹物件加入暫存區
# 第一個樹物件hash:d8329fc1cc938780ffdd9f94e0d364e0ea74f579
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579

# 3.再次檢視暫存區當前檔案資訊,有多了一個bak/test.txt檔案
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git ls-files -s
100644 83baae61804e65cc73a7201a7252750c76066a30 0       bak/test.txt
100644 fa49b077972391ad58037050f2a75f74e3671e92 0       new.txt
100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 0       test.txt

說明:

  • read-tree命令:可以把樹物件讀入暫存區。
  • --prefix=bak選項:將一個已有的樹物件作為子樹讀入暫存區。

接下來繼續,再提交暫存區的內容,會繼續生成一個新的tree物件在Git倉庫中。

# 把暫存區的內容生成新的tree物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git write-tree
01ab2a43b1eb150bcf00f375800727df240cf653

# 檢視新生成的物件
# 檢視tree物件的型別
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -t 01ab2a43b1eb150bcf00f375800727df240cf653
tree

# 檢視tree物件的內容,也就是記錄暫存區的內容。
# 可以看到該tree物件,包含了兩個blob物件和一個tree物件。
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ git cat-file -p 01ab2a43b1eb150bcf00f375800727df240cf653
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579    bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92    new.txt
100644 blob 0c1e7391ca4e59584f8b773ecdbbb9467eba1547    test.txt


# 檢視當前Git倉庫中的物件
L@DESKTOP-T2AI2SU MINGW64 /j/git-repository/git_learning (master)
$ find .git/objects -type f
.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92

到這裡我們的演示就完成了,請看下面的總結。

3、總結

(1)分析每個樹物件的儲存結構

我們可以先檢視一下Git本地庫中的物件,如下

.git/objects/01/ab2a43b1eb150bcf00f375800727df240cf653 # 第三個tree樹物件
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547 # test.txt第二個版本(blob物件)
.git/objects/16/3b45f0a0925b0655da232ea8a4188ccec615f5 # 第二個tree樹物件
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt第一個版本(blob物件)
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # 第一個tree樹物件
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt第一個版本(blob物件)

我們接下來用三個圖,描述一下三個樹物件的結構關係。

第一個樹物件結構如下圖

image

第二個樹物件結構如下圖

image

第三個樹物件結構如下圖

image

也可以換Git物件型別表示:

image

(2)blob物件和tree物件(重點)

從上圖我們可以分析出:

  • blob物件代表檔案一次次的版本。
  • tree物件代表專案的一次次的版本。

這就是我前面2-(3)描述過的Git版本庫中的5個物件,即表示了專案的2個版本。

(就先這樣理解)

(3)總結(重點)

暫存區的概念和相關理解:

  1. 所謂的暫存區Stage只是一個簡單的索引檔案而已。指的是是 .git/index檔案。
  2. 暫存區這個索引檔案裡面包含的是檔案的目錄樹,像一個虛擬的工作區,在這個虛擬工作區的目錄樹中,記錄了檔名、檔案的時間戳、檔案長度、檔案型別以及最重要的SHA-1值,檔案的內容並沒有儲存在其中,所以說它像一個虛擬的工作區。
    即:暫存區,也就是.git/index檔案中存放的是檔案內容的索引(快照),也可以是tree物件的索引。
  3. 索引指向的是.git/objects/目錄下的檔案(Git物件)。
  4. Git通過暫存區的檔案索引資訊來建立tree物件的。
  5. tree物件可以使檔案內容和檔名進行關聯。
  6. 一個樹物件可以包含一條或多條記錄(tree物件和blob 物件)。
  7. 暫存區內容寫到版本庫中後,暫存區索引內容不清空。
  8. 暫存區中的檔案內容索引,是按對應檔案覆蓋的,也就是修改一個檔案內容,新增到快取區,只會把對應的檔案覆蓋,其他檔案不會被覆蓋,即:暫存區不是整體覆蓋的。

暫存區的作用:除非是繞過暫存區直接提交,否則Git想把修改提交上去,就必須將修改存入暫存區最後才能commit。每次提交的是暫存區所對應的檔案快照。

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

4、問題

現在有三個樹物件(因為執行了三次write-tree命令),分別代表了我們想要跟蹤專案的三次快照。然而問題依舊:若想重用這些快照,你必須記住這三個樹物件的SHA-1雜湊值。

並且,你也完全不知道是誰儲存了這些快照,在什麼時刻儲存的,以及為什麼儲存這些快照。

而以上這些,提交物件commit object為你儲存了這些基本資訊。

5、本文用到的命令總結

Git底層命令:

  • git update-index --add:把檔案索引(快照)存入暫存區中。
  • git write-tree:將當前暫存區的索引內容同步到一個樹物件中。
  • git ls-files -s:檢視暫存區的檔案資訊。
  • git read-tree --prefix=bak:將一個已存在的樹物件新增到暫存區。
  • git cat-file -t 鍵:檢視Git物件的型別。
  • git cat-file -p 鍵:檢視Git物件的內容。

參考:

相關文章