Blob Object
- 10.1中說啦,git本質是一個內容可尋址的系統,所以Git的核心就是一個 key-value 鍵值對應的存儲商店。你存入任何類型的內容,它都會返回一個key來讓你可以重復取回存儲的內容。
- 我們使用
hash-object
函數來演示這個基本的存儲內容的功能。
- 存儲命令行中的“test content”內容
//依次執行下面的命令
//---------------------------------------------------------------------------------
//在test文件夾中初始化git倉庫
$ git init test
//輸出,在哪個目錄創建 .git 目錄
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下沒有任何文件
//---------------------------------------------------------------------------------
/*
存儲內容到數據庫中,--stdin告訴命令從標準輸入中讀取內容,也就是'test content'
并將存儲的內容的SHA-1值返回,該值由40個字符組成。
*/
$ echo 'test content' | git hash-object -w --stdin
/*
返回的SHA-1,由于SHA-1由內容計算而來,而hash-object命令也只保存內容
所以這個值每個電腦返回的都一樣,我的跟書上的也一樣
*/
d670460b4b4aece5915caf5c68d12f560a9fe3e4
//再次查找".git/objects"目錄下的文件
$ find .git/objects -type f
/*
輸出文件,可以看到是上面哪個值的前兩個字母“d6”變成目錄
剩余的38個字符作為文件名,存儲啦一個文件。
這是git存儲內容的最基本方式,每片內容都存為一個文件。
并且用其內容的SHA-1 checksum值來命名。
*/
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
//---------------------------------------------------------------------------------
/*
將文件內容重新讀取出來。-p參數就是打印內容
*/
$ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4
/*
輸出的內容就是剛剛存儲的內容 “test content”
*/
test content
通過上面的操作,學到啥啦?
1. git把每一片內容都存為一個文件。文件名由存儲的內容計算出的SHA-1值的后38位字符命名,且文件存放在.git/objects/d6/
文件夾下,這個d6
取自SHA-1值的前兩位。
- 存儲test.txt文件中的
version 1
version 2
內容
/*
依次執行下面的命令
*/
//---------------------------------------------------------------------------------
/*
在test.txt文件中寫入“version 1”
*/
$ echo 'version 1' > test.txt
/*
將test.txt文件中的內容“version 1”以文件的形式
,存儲到".git/objects"文件夾中。
并輸出所存儲內容的SHA-1值
*/
$ git hash-object -w test.txt
/*
“version 1”的SHA-1值
*/
83baae61804e65cc73a7201a7252750c76066a30
//---------------------------------------------------------------------------------
/*
在test.txt文件中寫入“version 2”
*/
$ echo 'version 2' > test.txt
/*
將test.txt文件中的內容“version 2”以文件的形式
,存儲到".git/objects"文件夾中。
并輸出所存儲內容的SHA-1值
*/
$ git hash-object -w test.txt
/*
“version 2”的SHA-1值
*/
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
//---------------------------------------------------------------------------------
/*
查找 “.git/objects” 目錄下的所有文件
*/
$ find .git/objects -type f
/*
輸出顯示,有三個文件,分別代表存儲的三個內容。
就是"test content" "version 1" "version 2"
*/
.git/objects/1f/7a7a472abf3dd9643fd615f6da379c4acb3e3a
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
//---------------------------------------------------------------------------------
/*
用“.git/objects”目錄下存儲內容的文件,改變test.txt文件的內容
最簡單的版本控制,將test.txt還原為“version 1”
*/
$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30 > test.txt
/*
查看test.txt的文件內容
*/
$ cat test.txt
/*
內容已經還原為“version 1”
*/
version 1
//---------------------------------------------------------------------------------
/*
用“.git/objects”目錄下存儲內容的文件,改變test.txt文件的內容
最簡單的版本控制,將test.txt還原為“version 2”
*/
$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a > test.txt
/*
查看test.txt的文件內容
*/
$ cat test.txt
/*
內容又變成"version 2"
*/
version 2
//---------------------------------------------------------------------------------
/*
查看“.git/objects”目錄下存儲內容的文件的object類型
*/
$ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
/*
類型為blob
*/
blob
通過上面的操作,得到啥?
1. 使用git hash-object
只是將文件內容保存到數據庫中(.git/objects),不保存文件名。這種只保存內容的object類型,叫做blob
Tree Objects
- Tree Objects解決啦存儲文件名的問題,并且允許你存儲一組文件。
- Git中所有的
內容
都被存儲為tree objects
和blob objects
- 單個
tree object
包含一個或多個tree條目
,tree條目
就是指下圖中的箭頭,而每個tree條目
包含一個SHA-1
值,指向一個blob object
或sub tree object
,并且包含mode
、type
、SHA-1
、filename
等信息。
圖一:tree對象的基本結構 - 代碼說話,下面的代碼舉例演示的就是圖一。
/*
依次執行下面的命令
*/
//---------------------------------------------------------------------------------
/*
” master^{tree}“ 代表`master`分支上的最后一個‘commit’所指向的那個‘tree object’對象
所以該命令就是打印輸出 這個‘tree object’對象的內容。
*/
$ git cat-file -p master^{tree}
/*
所以,這個‘tree’指向兩個‘blob’,并指向一個‘sub tree’,名字是'lib'
下面四列屬性分別是:
*/
//mode type SHA-1 filename
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README
100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile
040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib
//---------------------------------------------------------------------------------
/*
繼續通過‘SHA-1’值來查看lib這個tree object‘的內容,
如下所示,’lib’指向’simplegit.rb‘這個'blob'類型的對象。
*/
$ git cat-file -p 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0
100644 blob 47c6340d6459e05787f644c2447d2595f5d3a54b simplegit.rb
- 創建
tree object
- Git通常通過
staging area
(即index
)的狀態來創建一系列tree object
。所以先要通過將文件添加到staging area
來生成index
,才能創建tree object
。
/*
依次執行下面的命令
*/
//---------------------------------------------------------------------------------
/*
將‘version 1’的test.txt文件添加到‘staging area’
參數解釋:
1.‘git update-index’:該命令來將文件添加到'Staging Area'(即更新'index')
2.‘--add’ :因為需要添加的文件不存在于‘Staging Area’,所以需要添加該參數
3.'--cacheinfo' :因為要添加的‘version 1’的test.txt不在'working tree'中,所以使用這個參數
,后面依次是 mode object(輸入對象的'SHA-1') filename
這個SHA-1值就是上面代碼中得到的。上面自己找。
mode只有下面三種是對git中的文件(blobs)是有效的,別的都是表示目錄或子模塊:
1.100644:代表正常文件
2.100755:代表可執行文件
3.120000:代表一個象征性鏈接
*/
$ git update-index --add --cacheinfo 100644 \
83baae61804e65cc73a7201a7252750c76066a30 test.txt
//---------------------------------------------------------------------------------
/*
創建tree object
*/
$ git write-tree
/*
輸出的是生成的tree object對象的'SHA-1'值
*/
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
/*
查看tree object的內容
*/
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
/*
輸出結果顯示,該tree obejct指向
第一版本的test.txt文件的內容“version 1”存儲在數據庫中的‘blob’對象。
注意:‘blob’只保存文件的內容
*/
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
/*
用命令行確定tree object的類型
*/
$ git cat-file -t d8329fc1cc938780ffdd9f94e0d364e0ea74f579
/*
輸出結果顯示類型為 ‘tree’
*/
tree
//---------------------------------------------------------------------------------
/*
新建new.txt并添加到staging area,將‘version 2’的test.txt文件添加到staging area
,替換掉之前添加的內容為“version 1”的test.txt
*/
/*
新建new.txt
*/
$ echo 'new file' > new.txt
/*
將'version 2'添加到staging area中的test.txt中,替換‘version 1’
*/
$ git update-index --cacheinfo 100644 \
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
$ git update-index test.txt
/*
將new.txt添加到staging area
*/
$ git update-index --add new.txt
//---------------------------------------------------------------------------------
/*
從改變啦狀態的staging area新建新的tree object
*/
$ git write-tree
/*
輸出新建的tree object的SHA-1值。
*/
0155eb4229851634a0f03eb265b69f5a2d56f341
/*
查看新建的tree object的內容
*/
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
/*
從結果可以看到,這個tree object指向new.txt文件和“version 2”的test.txt文件,也就是兩個blob
*/
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
//---------------------------------------------------------------------------------
/*
`git read-tree`:將已經創建的tree object讀取到staging area中。
`--prefix=bak` :該選項表示將讀取的tree object作為當前
已經存在于staging area中的tree object對象的“子tree”
*/
$ git read-tree --prefix=bak d8329fc1cc938780ffdd9f94e0d364e0ea74f579
/*
上一步讀取操作后,staging area已經改變,新建新的tree object
*/
$ git write-tree
/*
輸出新建的tree object的SHA-1值
*/
3c4e9cd789d88d8d89c1073707c3585e41b0e614
/*
查看新建的tree object的內容,可以看到指向兩個blob,新增啦一個bak,是tree類型,就是上一步讀取的那個tree object.
*/
$ git cat-file -p 3c4e9cd789d88d8d89c1073707c3585e41b0e614
040000 tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579 bak
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
從上面的操作中,得到啥結論?
-
tree object
可以包含多個條目,每個條目可以指向blob
,也可以指向tree
-
tree object
中的每行信息都存儲啦mode
、type
、SHA-1
、filename
四個屬性 -
tree object
是根據staging area
的狀態來創建的,其狀態改變,新建tree object
,如果狀態不變,那么新建的tree object
與原來的相同(感覺應該更本沒新建) -
tree object
和blob object
都存儲在.git/objects
文件夾中 - 上面操作最后得到的
tree object
的結構就如下面圖二所示
圖二:最終tree object的結構圖
Commit Objects
-
snapshots
==root tree object
,一個頂層的tree object
相當于整個工程的一個快照
上面的一系列操作產生啦三個tree object
,分別表示工程的三個snapshots
快照,我們可以用這三個tree object
來讓工程恢復到某一狀態。
但是有一個問題,想要使用這三個tree object
,我們得記住三個SHA-1
值。 -
commit object
存儲啦tree
、author
、committer
等信息 - 跟著代碼走
//---------------------------------------------------------------------------------
/*
`git commit-tree`命令創建`commit object`對象,需要指定需要指向的`tree object`的`SHA-1`值(如`d8329f`)。
'first commit` 作為`commit object`的信息。
*/
$ echo 'first commit' | git commit-tree d8329f
/*
輸出新建的`commit object`對象的`SHA-1`值
注意:輸出的值與書本不一樣,因為`commit object`中存儲的時間、提交者等信息不同。
*/
fdf4fc3344e67ab068f836878b6c4951e3b15f3d
/*
查看生成的`commit object`的內容
*/
$ git cat-file -p fdf4fc3
/*
輸出結果,顯示啦`commit`指向的`tree`的`SHA-1`值,
還有author和committer、時間等信息。
*/
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott Chacon <schacon@gmail.com> 1243040974 -0700
committer Scott Chacon <schacon@gmail.com> 1243040974 -0700
/*
最后輸出啦`commit`的`Message`信息。
*/
first commit
//---------------------------------------------------------------------------------
/*
`git commit-tree`命令創建`commit object`對象
`-p fdf4fc3` : 用于提供當前`commit`的`parent`,是上一個commit的`SHA-1`值
`second commit` 的parent是`first commit`
*/
$ echo 'second commit' | git commit-tree 0155eb -p fdf4fc3
/*
輸出`commit object`的`SHA-1`值
*/
cac0cab538b970a37ea1e769cbbde608743bc96d
/*
`git commit-tree`命令創建`commit object`對象
`-p cac0cab` : 用于提供當前`commit`的`parent`,是上一個commit的`SHA-1`值
third commit 的parent是`second commit`
*/
$ echo 'third commit' | git commit-tree 3c4e9c -p cac0cab
/*
輸出新建的`commit object`的`SHA-1`值。
*/
1a410efbd13591db07496601ebc7a059dd55cfe9
/*
`git log`命令查看'third commit'第三個commit的信息,自己看書去吧,
就是列出啦從'first commit'到'third commit'的各種文件變化和信息。
*/
git log --stat 1a410e
- 上面的操作,在不使用任何前端命令(就是更加友好的用于版本控制的那些常見命令)的情況下,用low-level的操作創建啦一個
Git history
。 - 上面的操作其實就是當你使用
git add
和git commit
命令時,Git進行的本質上的操作。所以可以作以下理解:-
git add
== 使用git update-index
命令將文件添加到staging area
,然后使用git write-tree
來根據staging area
的狀態創建從頂層到底層的tree object
。 -
git commit
== 使用git commit-tree
命令創建commit object
對象,指向當前staging area
生成的top level的tree object
,并且也同時指向上一個commit object
對象,如果是初始的commit
,那就不指向上一個commit
啦,根本沒有呀。
-
- 使用
SHA-1
時,可以用短的,只用前6位字符。 -
blob
、tree
、commit
這三個主要的git object最初都是作為單獨的文件存儲在.git/object目錄下的。 -
在進行啦上面一系列操作后,整個test倉庫中的對象就如下圖所示。
圖三:倉庫中所有可以到達的對象圖
Object Storage
- 上面一直用到
SHA-1
值,那么SHA-1
值是怎么來的?下面用Ruby腳本語言來演示一下Git存儲“what is up, doc?”這個字符串的過程,以及SHA-1
值的產生。
//---------------------------------------------------------------------------------
/*
在Terminal中輸入`irb`來啟動 `交互式ruby模式`
*/
$ irb
/*
輸入content = "what is up, doc?",相當于產生一個變量名為content的字符串。
*/
>> content = "what is up, doc?"
/*
會打印一下字符串內容。
*/
=> "what is up, doc?"
//---------------------------------------------------------------------------------
/*
新建一個變量名為header的字符串。
Git會產生一個header,header由對象類型開始,這里是`blob`,然后加一個空格,再加上要存儲內容的長度
最后加上一個 空字符
*/
>> header = "blob #{content.length}\0"
/*
會打印一下字符串內容。
*/
=> "blob 16\u0000"
//---------------------------------------------------------------------------------
/*
Git 會鏈接header 和 content(就是要存儲的內容),并通過鏈接后的內容計算出
一個SHA-1 checksum。
在Ruby中,計算SHA-1需要導入SHA-1的庫,用require命令導入庫。
然后用Digest::SHA1.hexdigest()計算SHA-1值
*/
/*
把header 和 content鏈接成一個字符串store
*/
>> store = header + content
/*
會打印一下字符串內容。blob 16\u0000 和 what is up, doc? 鏈接成一個字符串啦。
*/
=> "blob 16\u0000what is up, doc?"
/*
導入需要的庫
*/
>> require 'digest/sha1'
=> true
/*
調用Digest::SHA1.hexdigest()方法并傳入store作為參數
*/
>> sha1 = Digest::SHA1.hexdigest(store)
/*
打印出剛剛計算出的SHA-1值。
*/
=> "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
//---------------------------------------------------------------------------------
/*
Git 會使用`zlib`庫來壓縮要存儲的內容,在Ruby可以導入zlib,并調用Zlib::Deflate.deflate(store)方法來壓縮內容。
*/
/*
用require命令請求導入zlib庫
*/
>> require 'zlib'
=> true
/*
調用Zlib::Deflate.deflate()并傳入要壓縮的內容store作為參數
并且將壓縮后的內容存儲到zlib_content這個變量中。
*/
>> zlib_content = Zlib::Deflate.deflate(store)
/*
會打印一下壓縮后的內容。
*/
=> "x\x9CK\xCA\xC9OR04c(\xCFH,Q\xC8,V(-\xD0QH\xC9O\xB6\a\x00_\x1C\a\x9D"
//---------------------------------------------------------------------------------
/*
之前已經看到過,blob對象存儲的文件夾是:
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
用SHA-1值的前兩位作為文件夾名,用后38位作為blob對象的文件名。
*/
>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38]
/*
同樣打印,所以這個文件夾就是本次存儲的路徑
*/
=> ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37"
/*
用require命令請求導入fileutils庫,用于創建文件夾
*/
>> require 'fileutils'
=> true
/*
調用mkdir_p方法,創建文件夾
*/
>> FileUtils.mkdir_p(File.dirname(path))
/*
打印結果就是創建好的文件存儲路徑啦。
*/
=> ".git/objects/bd"
/*
調用open打開文件,調用嗯write,傳入zlib_content進行寫入保存文件。
*/
>> File.open(path, 'w') { |f| f.write zlib_content }
=> 32
- 上面的操作就是Git存儲一個
blob
的全過程,tree
和commit
對象的存儲跟這個一樣,唯一的不同就是header
里的type改成tree、commit,那么從這些操作中得到啥結論呢?- blob對象的內容不僅僅是要存儲的文件內容,還在前面加上啦一個
header
。
所以blob的完整內容為:header
+content
,而且還會對完整內容進行壓縮。 - blob對象的SHA-1值是通過其完整內容(
header
+content
)計算出來的。
- blob對象的內容不僅僅是要存儲的文件內容,還在前面加上啦一個