[Git]Git內部原理 - Git對象

概述

Git 最初是一套面向版本控制系統的工具集,而不是一個完整的、用戶友好的版本控制系統,所以它還包含了一部分用於完成底層工作的命令。這些命令被設計成能以 UNIX 命令行的風格連接在一起,抑或藉由腳本調用,來完成工作。 這部分命令一般被稱作“底層(plumbing)”命令,而那些更友好的命令則被稱作“高層(porcelain)”命令。平時用戶接觸最多的是高層命令。

從底層命令瞭解Git工作機制

git目錄說明

衆所周知的是,當在一個新目錄或已有目錄執行 git init 時,Git 會創建一個 .git 目錄。
在這裏插入圖片描述
其中目錄結構爲:

$ ll
total 7
-rw-r--r-- 1 YoungLu 197121 130  1月 19 16:09 config
-rw-r--r-- 1 YoungLu 197121  73  1月 19 16:09 description
-rw-r--r-- 1 YoungLu 197121  23  1月 19 16:09 HEAD
drwxr-xr-x 1 YoungLu 197121   0  1月 19 16:09 hooks/
drwxr-xr-x 1 YoungLu 197121   0  1月 19 16:09 info/
drwxr-xr-x 1 YoungLu 197121   0  1月 19 16:09 objects/
drwxr-xr-x 1 YoungLu 197121   0  1月 19 16:09 refs/

實際使用中,這個目錄中的文件還更多:
在這裏插入圖片描述
但是在init後的.git目錄下,只有如下文件:
說明:

  • config:包含項目特有的配置選項。
  • description:文件僅供 GitWeb 程序使用,我們無需關心。
  • HEAD:指示目前被檢出的分支。
  • hooks:包含客戶端或服務端的鉤子腳本(hook scripts)
  • info:包含一個全局性排除(global exclude)文件,用以放置那些不希望被記錄在 .gitignore 文件中的忽略模式(ignored patterns)。
  • objects:存儲所有數據內容
  • refs:存儲指向數據(分支)的提交對象的指針
    補充說明:
  • index:文件保存暫存區信息。

git對象

Git 是一個內容尋址文件系統。這意味着,Git 的核心部分是一個簡單的鍵值對數據庫(key-value data store)。

hash-object

可以通過底層命令 hash-object 來演示上述效果——該命令可將任意數據保存於 .git 目錄,並返回相應的鍵值。

YoungLu@DESKTOP-MVPAQ15 MINGW64 /d/gittest/test (master)
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack

YoungLu@DESKTOP-MVPAQ15 MINGW64 /d/gittest/test (master)
$ find .git/objects -type f

git初始化了objects目錄,並創建了兩個空目錄:infopack
接下來演示向git數據庫存入一些文檔:

$ echo 'hello world' | git hash-object -w --stdin
3b18e512dba79e4c8300dd08aeb37f8e728b8dad

-w 選項指示 hash-object 命令存儲數據對象;若不指定此選項,則該命令僅返回對應的鍵值。 --stdin 選項則指示該命令從標準輸入讀取內容;若不指定此選項,則須在命令尾部給出待存儲文件的路徑。 該命令輸出一個長度爲 40 個字符的校驗和。 這是一個 SHA-1 哈希值——一個將待存儲的數據外加一個頭部信息(header)一起做 SHA-1 校驗運算而得的校驗和。
現在object中被創建了一個目錄,其中有一個18e512dba79e4c8300dd08aeb37f8e728b8dad文件:

$ find .git/objects -type f
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad

校驗和的前兩個字符用於命名子目錄,餘下的 38 個字符則用作文件名。

cat-file

cat-file 命令從 Git 那裏取回數據。

$ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
hello world

版本控制系統1.0

接下來演示版本控制的工作原理。

(1)創建一個文件test.txt,並寫入"one piece",同時將其存入數據庫:

$ echo 'one piece' > test.txt

在這裏插入圖片描述
這裏之所以有個LF will be replace by CRLF,是坑爹的windows系統的問題,有興趣的可以去搜索瞭解下。

(2)將"two piece"寫入test.txt,同時將其存入數據庫:

$ echo 'two piece' > test.txt

在這裏插入圖片描述

(3)查看object中的文件,其中:3b/18e512dba79e4c8300dd08aeb37f8e728b8dad是之前寫“hello world產生的”。

$ find .git/objects -type f
.git/objects/05/f3b2d301d4c16d942e471e0f6abdaa6c0f000d
.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
.git/objects/8c/c930120a302b8c1d3550f8dce41fa1b664d0ad

(4)版本回溯
在這裏插入圖片描述
看,這裏通過cat-file獲取了文件的內容,那是不是通過>命令就可以回溯了呢?沒錯!
在這裏插入圖片描述
但是記住文件的每一個版本所對應的 SHA-1 值並不現實;另外,這個簡單的版本控制系統中文件名並沒有保存,我們僅僅保存了文件內容。

版本控制系統2.0

接下來要探討的對象類型是樹對象(tree object),它能解決文件名保存的問題,也允許我們將多個文件組織到一起。 Git 以一種類似於 UNIX 文件系統的方式存儲內容,但作了些許簡化。 所有內容均以樹對象和數據對象的形式存儲,其中樹對象對應了 UNIX 中的目錄項,數據對象則大致上對應了 inodes 或文件內容。 一個樹對象包含了一條或多條樹對象記錄(tree entry),每條記錄含有一個指向數據對象或者子樹對象的 SHA-1 指針,以及相應的模式、類型、文件名信息。 例如,某項目當前對應的最新樹對象可能是這樣的:
在這裏插入圖片描述
master^{tree} 語法表示 master 分支上最新的提交所指向的樹對象。
對於目錄,並不是一個數據對象(blob),而是一個指針,指向另一個樹對象:
在這裏插入圖片描述
這是官網給出的概念圖:
在這裏插入圖片描述
接下來創建一個樹:

$ echo "version 1" > test.txt
$ git hash-object -w test.txt
warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
83baae61804e65cc73a7201a7252750c76066a30
$ echo "version 2" > test.txt
$ git hash-object -w test.txt
warning: LF will be replaced by CRLF in test.txt.
The file will have its original line endings in your working directory
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
文件名 文件內容 sha-1值
test.txt version 1 83baae61804e65cc73a7201a7252750c76066a30
test.txt version 2 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a

通常,Git 根據某一時刻暫存區(即 index 區域,下同)所表示的狀態創建並記錄一個對應的樹對象,如此重複便可依次記錄(某個時間段內)一系列的樹對象。 因此,爲創建一個樹對象,首先需要通過暫存一些文件來創建一個暫存區。 可以通過底層命令 update-index 爲一個單獨文件——我們的 test.txt 文件的首個版本——創建一個暫存區。 利用該命令,可以把 test.txt 文件的首個版本人爲地加入一個新的暫存區。 必須爲上述命令指定 --add 選項,因爲此前該文件並不在暫存區中(我們甚至都還沒來得及創建一個暫存區呢);同樣必需的還有 --cacheinfo 選項,因爲將要添加的文件位於 Git 數據庫中,而不是位於當前目錄下。 同時,需要指定文件模式、SHA-1 與文件名:

$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt

接下來通過write-tree命令將暫存區寫入一個樹對象:

$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30    test.txt
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree

接下來創建一個新的樹:

$ echo "new file" > new.txt

$ git update-index --cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt

$ git update-index --add new.txt
warning: LF will be replaced by CRLF in new.txt.
The file will have its original line endings in your working directory

$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341

$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92    new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a    test.txt

接下來將第一個樹對象加入第二個:

$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579

$ git write-tree
3c4e9cd789d88d8d89c1073707c3585e41b0e614

$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92    new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a    test.txt

可以認爲 Git 內部存儲着的用於表示上述結構的數據是這樣的:
在這裏插入圖片描述
現在有三個樹對象,分別代表了我們想要跟蹤的不同項目快照。
可以通過調用 commit-tree 命令創建一個提交對象,爲此需要指定一個樹對象的 SHA-1 值,以及該提交的父提交對象(如果有的話)。

搭一個歷史提交樹

第一次提交
我們從之前創建的第一個樹對象開始:

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

查看:

$ git cat-file -p fdf4fc3
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author YoungLu <[email protected]> 1579501467 +0800
committer YoungLu <[email protected]> 1579501467 +0800

first commit

第二次提交

$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
cac0cab538b970a37ea1e769cbbde608743bc96d

第三次提交

$ echo 'third commit'  | git commit-tree 3c4e9c -p cac0cab
1a410efbd13591db07496601ebc7a059dd55cfe9

接下來,可以通過以下命令查看提交的歷史信息:

$ git log --stat 1a410e
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Author: Scott Chacon <[email protected]>
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 <[email protected]>
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 <[email protected]>
Date:   Fri May 22 18:09:34 2009 -0700

    first commit

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

這也就是git add 和 git commit 命令時, Git 所做的實質工作。
內部指針如下圖所示:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章