10.2 Git Internals - Git Objects (讀書筆記哈)

Blob Object

  1. 10.1中說啦,git本質是一個內容可尋址的系統,所以Git的核心就是一個 key-value 鍵值對應的存儲商店。你存入任何類型的內容,它都會返回一個key來讓你可以重復取回存儲的內容。
  2. 我們使用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

  1. Tree Objects解決啦存儲文件名的問題,并且允許你存儲一組文件。
  2. Git中所有的內容都被存儲為tree objectsblob objects
  3. 單個tree object包含一個或多個tree條目tree條目就是指下圖中的箭頭,而每個tree條目包含一個SHA-1值,指向一個blob objectsub tree object,并且包含modetypeSHA-1filename等信息。
    圖一:tree對象的基本結構
  4. 代碼說話,下面的代碼舉例演示的就是圖一。
/*
依次執行下面的命令
*/
//---------------------------------------------------------------------------------
/*
” 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
  1. 創建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中的每行信息都存儲啦modetypeSHA-1filename四個屬性
  • tree object是根據staging area的狀態來創建的,其狀態改變,新建tree object,如果狀態不變,那么新建的tree object與原來的相同(感覺應該更本沒新建)
  • tree objectblob object都存儲在.git/objects文件夾中
  • 上面操作最后得到的tree object的結構就如下面圖二所示
    圖二:最終tree object的結構圖

Commit Objects

  1. snapshots== root tree object,一個頂層的tree object相當于整個工程的一個快照
    上面的一系列操作產生啦三個tree object,分別表示工程的三個snapshots快照,我們可以用這三個tree object來讓工程恢復到某一狀態。
    但是有一個問題,想要使用這三個tree object,我們得記住三個SHA-1值。
  2. commit object存儲啦treeauthorcommitter等信息
  3. 跟著代碼走
//---------------------------------------------------------------------------------
/*
`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 addgit commit命令時,Git進行的本質上的操作。所以可以作以下理解:
    1. git add == 使用git update-index命令將文件添加到staging area,然后使用git write-tree來根據staging area的狀態創建從頂層到底層的tree object
    2. git commit == 使用git commit-tree命令創建commit object對象,指向當前staging area生成的top level的tree object,并且也同時指向上一個commit object對象,如果是初始的commit,那就不指向上一個commit啦,根本沒有呀。
  • 使用SHA-1時,可以用短的,只用前6位字符。
  • blobtreecommit 這三個主要的git object最初都是作為單獨的文件存儲在.git/object目錄下的。
  • 在進行啦上面一系列操作后,整個test倉庫中的對象就如下圖所示。


    圖三:倉庫中所有可以到達的對象圖

Object Storage

  1. 上面一直用到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的全過程,treecommit對象的存儲跟這個一樣,唯一的不同就是header里的type改成tree、commit,那么從這些操作中得到啥結論呢?
    1. blob對象的內容不僅僅是要存儲的文件內容,還在前面加上啦一個header
      所以blob的完整內容為: header + content,而且還會對完整內容進行壓縮。
    2. blob對象的SHA-1值是通過其完整內容(header + content)計算出來的。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容