概述
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
目錄,並創建了兩個空目錄:info
和pack
。
接下來演示向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 所做的實質工作。
內部指針如下圖所示: