10.2 Git 對象

2018-02-24 15:22 更新

Git 對象

Git 是一個內容尋址文件系統(tǒng)。 看起來很酷, 但這是什么意思呢? 這意味著,Git 的核心部分是一個簡單的鍵值對數(shù)據(jù)庫(key-value data store)。 你可以向該數(shù)據(jù)庫插入任意類型的內容,它會返回一個鍵值,通過該鍵值可以在任意時刻再次檢索(retrieve)該內容。 可以通過底層命令?hash-object?來演示上述效果——該命令可將任意數(shù)據(jù)保存于?.git?目錄,并返回相應的鍵值。 首先,我們需要初始化一個新的 Git 版本庫,并確認?objects?目錄為空:

$ git init test
Initialized empty Git repository in /tmp/test/.git/
$ cd test
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f

可以看到 Git 對?objects?目錄進行了初始化,并創(chuàng)建了?pack?和?info?子目錄,但均為空。 接著,往 Git 數(shù)據(jù)庫存入一些文本:

$ echo 'test content' | git hash-object -w --stdin
d670460b4b4aece5915caf5c68d12f560a9fe3e4

-w?選項指示?hash-object?命令存儲數(shù)據(jù)對象;若不指定此選項,則該命令僅返回對應的鍵值。--stdin?選項則指示該命令從標準輸入讀取內容;若不指定此選項,則須在命令尾部給出待存儲文件的路徑。 該命令輸出一個長度為 40 個字符的校驗和。 這是一個 SHA-1 哈希值——一個將待存儲的數(shù)據(jù)外加一個頭部信息(header)一起做 SHA-1 校驗運算而得的校驗和。后文會簡要討論該頭部信息。 現(xiàn)在我們可以查看 Git 是如何存儲數(shù)據(jù)的:

$ find .git/objects -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

可以在?objects?目錄下看到一個文件。 這就是開始時 Git 存儲內容的方式——一個文件對應一條內容,以該內容加上特定頭部信息一起的 SHA-1 校驗和為文件命名。 校驗和的前兩個字符用于命名子目錄,余下的 38 個字符則用作文件名。

可以通過?cat-file?命令從 Git 那里取回數(shù)據(jù)。 這個命令簡直就是一把剖析 Git 對象的瑞士軍刀。 為?cat-file?指定?-p?選項可指示該命令自動判斷內容的類型,并為我們顯示格式友好的內容:

$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
test content

至此,你已經(jīng)掌握了如何向 Git 中存入內容,以及如何將它們取出。 我們同樣可以將這些操作應用于文件中的內容。 例如,可以對一個文件進行簡單的版本控制。 首先,創(chuàng)建一個新文件并將其內容存入數(shù)據(jù)庫:

$ echo 'version 1' > test.txt
$ git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30

接著,向文件里寫入新內容,并再次將其存入數(shù)據(jù)庫:

$ echo 'version 2' > test.txt
$ git hash-object -w test.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a

數(shù)據(jù)庫記錄下了該文件的兩個不同版本,當然之前我們存入的第一條內容也還在:

$ find .git/objects -type f
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

現(xiàn)在可以把文件內容恢復到第一個版本:

$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
$ cat test.txt
version 1

或者第二個版本:

$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
$ cat test.txt
version 2

然而,記住文件的每一個版本所對應的 SHA-1 值并不現(xiàn)實;另一個問題是,在這個(簡單的版本控制)系統(tǒng)中,文件名并沒有被保存——我們僅保存了文件的內容。 上述類型的對象我們稱之為數(shù)據(jù)對象(blob object)。 利用?cat-file -t?命令,可以讓 Git 告訴我們其內部存儲的任何對象類型,只要給定該對象的 SHA-1 值:

$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
blob

樹對象

接下來要探討的對象類型是樹對象(tree object),它能解決文件名保存的問題,也允許我們將多個文件組織到一起。 Git 以一種類似于 UNIX 文件系統(tǒng)的方式存儲內容,但作了些許簡化。 所有內容均以樹對象和數(shù)據(jù)對象的形式存儲,其中樹對象對應了 UNIX 中的目錄項,數(shù)據(jù)對象則大致上對應了 inodes 或文件內容。 一個樹對象包含了一條或多條樹對象記錄(tree entry),每條記錄含有一個指向數(shù)據(jù)對象或者子樹對象的 SHA-1 指針,以及相應的模式、類型、文件名信息。 例如,某項目當前對應的最新樹對象可能是這樣的:

$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859      README
100644 blob 8f94139338f9404f26296befa88755fc2598c289      Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0      lib

master^{tree}?語法表示?master?分支上最新的提交所指向的樹對象。 請注意,lib?子目錄(所對應的那條樹對象記錄)并不是一個數(shù)據(jù)對象,而是一個指針,其指向的是另一個樹對象:

$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b      simplegit.rb

從概念上講,Git 內部存儲的數(shù)據(jù)有點像這樣:

Figure 10-2.?當前 Git 的數(shù)據(jù)內容結構。

提交對象

現(xiàn)在有三個樹對象,分別代表了我們想要跟蹤的不同項目快照。然而問題依舊:若想重用這些快照,你必須記住所有三個 SHA-1 哈希值。 并且,你也完全不知道是誰保存了這些快照,在什么時刻保存的,以及為什么保存這些快照。 而以上這些,正是提交對象(commit object)能為你保存的基本信息。

可以通過調用?commit-tree?命令創(chuàng)建一個提交對象,為此需要指定一個樹對象的 SHA-1 值,以及該提交的父提交對象(如果有的話)。 我們從之前創(chuàng)建的第一個樹對象開始:

$ echo 'first commit' | git commit-tree d8329f
fdf4fc3344e67ab068f836878b6c4951e3b15f3d

現(xiàn)在可以通過?cat-file?命令查看這個新提交對象:

$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700

first commit

提交對象的格式很簡單:它先指定一個頂層樹對象,代表當前項目快照;然后是作者/提交者信息(依據(jù)你的?user.name?和?user.email?配置來設定,外加一個時間戳);留空一行,最后是提交注釋。

接著,我們將創(chuàng)建另兩個提交對象,它們分別引用各自的上一個提交(作為其父提交對象):

$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d
$ echo 'third commit'  | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9

這三個提交對象分別指向之前創(chuàng)建的三個樹對象快照中的一個。 現(xiàn)在,如果對最后一個提交的 SHA-1 值運行?git log?命令,會出乎意料的發(fā)現(xiàn),你已有一個貨真價實的、可由?git log?查看的 Git 提交歷史了:

$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:15:24 2009 -0700

    third commit

 bak/test.txt | 1 +
 1 file changed, 1 insertion(+)

commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:14:29 2009 -0700

    second commit

 new.txt  | 1 +
 test.txt | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:09:34 2009 -0700

    first commit

 test.txt | 1 +
 1 file changed, 1 insertion(+)

太神奇了: 就在剛才,你沒有借助任何上層命令,僅憑幾個底層操作便完成了一個 Git 提交歷史的創(chuàng)建。 這就是每次我們運行?git add?和?git commit?命令時, Git 所做的實質工作——將被改寫的文件保存為數(shù)據(jù)對象,更新暫存區(qū),記錄樹對象,最后創(chuàng)建一個指明了頂層樹對象和父提交的提交對象。 這三種主要的 Git 對象——數(shù)據(jù)對象、樹對象、提交對象——最初均以單獨文件的形式保存在.git/objects?目錄下。 下面列出了目前示例目錄內的所有對象,輔以各自所保存內容的注釋:

$ find .git/objects -type f
.git/objects/01/55eb4229851634a0f03eb265b69f5a2d56f341 # tree 2
.git/objects/1a/410efbd13591db07496601ebc7a059dd55cfe9 # commit 3
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a # test.txt v2
.git/objects/3c/4e9cd789d88d8d89c1073707c3585e41b0e614 # tree 3
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30 # test.txt v1
.git/objects/ca/c0cab538b970a37ea1e769cbbde608743bc96d # commit 2
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4 # 'test content'
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579 # tree 1
.git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 # new.txt
.git/objects/fd/f4fc3344e67ab068f836878b6c4951e3b15f3d # commit 1

如果跟蹤所有的內部指針,將得到一個類似下面的對象關系圖:

以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號