四種數據類型
實際上Git基于數據類型的不同,把對象分為四種:數據對象、樹對象、提交對象、標簽對象。Git文件系統的設計思路與linux文件系統相似,即將文件的內容與文件的屬性分開存儲,文件內容以“裝滿字節的袋子”存儲在文件系統中,文件名、所有者、權限等文件屬性信息則另外開辟區域進行存儲。在Git中,數據對象相當于文件內容,樹對象相當于文件目錄樹,提交對象則是對文件系統的快照,標簽對象則是對提交信息的引用。
下面我們分別對每種對象進行說明(建議大家操作的時候觀察一下.git/objects目錄的變化)
數據對象
數據對象是文件的內容,不包括文件名、權限等信息。Git會根據文件內容計算出一個hash值,以hash值作為文件索引存儲在Git文件系統中。由于相同的文件內容的hash值是一樣的,因此Git將同樣內容的文件只會存儲一次。git hash-object可以用來計算文件內容的hash值,并將生成的數據對象存儲到Git文件系統中:
$ echo 'version 1' | git hash-object -w --stdin
83baae61804e65cc73a7201a7252750c76066a30
$ echo 'version 2' | git hash-object -w --stdin
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
$ echo 'new file' | git hash-object -w --stdin
fa49b077972391ad58037050f2a75f74e3671e92
上面示例中,-w表示將數據對象寫入到Git文件系統中,如果不加這個選項,那么只計算文件的hash值而不寫入;–stdin表示從標準輸入中獲取文件內容,當然也可以指定一個文件路徑代替此選項。上面講數據對象寫入到Git文件系統中,那如何讀取數據對象呢?git cat-file
可以用來實現所有Git對象的讀取。
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30
version 1
$ git cat-file -t 83baae61804e65cc73a7201a7252750c76066a30
blob
上面示例中,-p表示查看Git對象的內容,-t表示查看Git對象的類型。
我們能夠對Git文件系統中的數據對象進行讀寫。但是,我們需要記住每一個數據對象的hash值,才能訪問到Git文件系統中的任意數據對象,這顯然是不現實的。數據對象只是解決了文件內容存儲的問題,而文件名的存儲則需要通過樹對象來解決。
樹對象
樹對象是文件目錄樹,記錄了文件獲取目錄的名稱、類型、模式信息。使用git update-index可以為數據對象指定名稱和模式,然后使用git write-tree將樹對象寫入到Git文件系統中:
$ git update-index --add --cacheinfo 100644 83baae61804e65cc73a7201a7252750c76066a30 test.txt
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
–add表示新增文件名,如果第一次添加某一文件名,必須使用此選項;–cacheinfo <mode> <object> <path>是要添加的數據對象的模式、hash值和路徑,<path>意味著為數據對象不僅可以指定單純的文件名,也可以使用路徑。另外要注意的是,使用git update-index添加完文件后,一定要使用git write-tree寫入到Git文件系統中,否則只會存在于index區域。此時的效果類似于執行了git add
命令。
樹對象仍然可以使用git cat-file查看:
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
tree
樹對象解決了文件名的問題,而且,由于我們是分階段提交樹對象的,樹對象可以看做是開發階段源代碼目錄樹的一次次快照,因此我們可以是用樹對象作為源代碼版本管理。但是,這里仍然有問題需要解決,即我們需要記住每個樹對象的hash值,才能找到個階段的源代碼文件目錄樹。在源代碼版本控制中,我們還需要知道誰提交了代碼、什么時候提交的、提交的說明信息等,接下來的提交對象就是為了解決這個問題的。
提交對象
提交對象是用來保存提交的作者、時間、說明這些信息的,可以使用git commit-tree來將提交對象寫入到Git文件系統中:
$ echo 'first commit' | git commit-tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
db1d6f137952f2b24e3c85724ebd7528587a067a
上面commit-tree除了要指定提交的樹對象,也要提供提交說明,至于提交的作者和時間,則是根據環境變量自動生成,并不需要指定。這里需要提醒一點的是,讀者在測試時,得到的提交對象hash值一般和這里不一樣,這是因為提交的作者和時間是因人而異的。此時相當于執行了git commit
提交對象的查看,也是使用git cat-file:
$ git cat-file -p d555a9dfc304e74a9557a5d92dc0807c20765104
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author defwang <wangdefu@100tal.com> 1607259906 +0800
committer defwang <wangdefu@100tal.com> 1607259906 +0800first commit$ git cat-file -t bb4de00acf2b259e767d8321e0fa3c865dae780e
commit
上面是屬于首次提交,那么接下來的提交還需要指定使用-p指定父提交對象,這樣代碼版本才能成為一條時間線:
$ git update-index --add --cacheinfo 100644 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test2.txt
$ git write-tree
e6cebf205657fb2046b5f38877bbfa8c3ae2d05c
$ echo 'second commit' | git commit-tree e6cebf205657fb2046b5f38877bbfa8c3ae2d05c -p d555a9dfc304e74a9557a5d92dc0807c20765104
bb4de00acf2b259e767d8321e0fa3c865dae780e
使用git cat-file查看一下新的提交對象,可以看到相比于第一次提交,多了parent部分:
$ git cat-file -p bb4de00acf2b259e767d8321e0fa3c865dae780e
tree e6cebf205657fb2046b5f38877bbfa8c3ae2d05c
parent d555a9dfc304e74a9557a5d92dc0807c20765104
author defwang <wangdefu@100tal.com> 1607260467 +0800
committer defwang <wangdefu@100tal.com> 1607260467 +0800second commit
使用git log可以查看整個提交歷史:
$ git log --stat bb4de00acf2b259e767d8321e0fa3c865dae780e
commit bb4de00acf2b259e767d8321e0fa3c865dae780e
Author: defwang <wangdefu@100tal.com>
Date: Sun Dec 6 21:14:27 2020 +0800second committest2.txt | 1 +1 file changed, 1 insertion(+)commit d555a9dfc304e74a9557a5d92dc0807c20765104
Author: defwang <wangdefu@100tal.com>
Date: Sun Dec 6 21:05:06 2020 +0800first committest.txt | 1 +1 file changed, 1 insertion(+)
有時候我們為了方便記憶某一次提交,會對該提交打一個tag進行標記,這就產生了標簽對象
標簽對象
$ git tag -a v1.0.0 bb4de00acf2b259e767d8321e0fa3c865dae780e -m "test tag"
當執行上面命令之后,.git目錄下的refs目錄中就會多一個文件
refs/tags
└── v1.0.0
文件內容為為40位的hash碼, 同樣我們使用git cat-file
讀取該hash值
$ git cat-file -p 4599faa3ffb8042b85fce6f80cad2633b4ee01ff
object bb4de00acf2b259e767d8321e0fa3c865dae780e
type commit
tag v1.0.0
tagger defwang <wangdefu@100tal.com> 1607262079 +0800test tag
git cat-file -t 4599faa3ffb8042b85fce6f80cad2633b4ee01ff
tag
可見該對象的類型為tag
至此,我們已經知道了git的四種數據類型,blob、tree、commit、tag
Git中的數據對象解決了數據存儲的問題,樹對象解決了文件名存儲問題,提交對象解決了提交信息的存儲問題,標簽對象則是對提交對象進行更形象的命名,從Git設計中可以看出,Linus對一個源代碼版本控制系統做了很好的抽象和解耦,每種對象解決的問題都很明確,相比于使用一種數據結構,無疑更靈活和更易維護。
?