01、存儲方式
Git 從核心上來看不過是簡單地存儲鍵值對(key-value)。它允許插入任意類型的內容,並會返回一個鍵值,通過該鍵值可以在任何時候再取出該內容。
Git 存儲數據內容的方式,為每份內容生成一個文件,取得該內容與頭信息的 SHA-1 校驗和,創建以該校驗和前兩個字符為名稱的子目錄,並以 (校驗和) 剩下 38 個字符為文件命名 (保存至子目錄下)。
寫入對象
$ echo 'version 2' > test.txt $ git hash-object -w test.txt 取回對象 $ git cat-file -p d670460b4b4aece5915caf5c68d12f560a9fe3e4 返回對象的類型 $ git cat-file -t 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
02、Git 對象
數據對象 blob object
樹對象 tree object
解決文件名的保存問題,允許我們將多個文件組織到一起,Git 以一種類似於 UNIX 文件系統的方式存儲內容,但作了些許簡化。
所有內容均以樹對象和數據對象的形式存儲,其中樹對象對應了 UNIX 中的目錄項,數據對象則大致上對應了 inodes 或文件內容。 一個樹對象包含了一條或多條樹對象記錄(tree entry),每條記錄含有一個指向數據對象或者子樹對象的 SHA-1 指針,以及相應的模式、類型、文件名信息。
$ git cat-file -p master^{tree}
100644 blob a906cb2a4a904a152e80877d4088654daad0c859 README 100644 blob 8f94139338f9404f26296befa88755fc2598c289 Rakefile 040000 tree 99f1a6d12cb4b6f19c8655fca46c3ecf317074e0 lib (這是一個文件夾)
你可以自己創建 tree 。通常 Git 根據你的暫存區域或 index 來創建並寫入一個 tree 。因此要創建一個 tree 對象的話首先要通過將一些文件暫存從而創建一個 index 。
$ git write-tree $ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
commit 對象
commit 對象有格式很簡單:指明了該時間點項目快照的頂層樹對象、作者/提交者信息(從 Git 設置的 user.name 和 user.email中獲得)以及當前時間戳、一個空行,以及提交注釋信息。
每一個 commit 對象都指向了你創建的樹對象快照。
當你執行git add, git commit, Git其實就是保存修改了的文件的 blob,更新索引,創建 tree 對象,最后創建 commit 對象,這些 commit 對象指向了頂層 tree 對象以及先前的 commit 對象。
這三類 Git 對象 ── blob,tree 以及 commit ── 都各自以文件的方式保存在 .git/objects 目錄下。
03、對象存儲
Git是如何存儲對象的?
構造文件頭
對象類型+空格+數據內容的長度+空字節(null byte)
>> header = "blob #{content.length}\0" => "blob 16\000"
計算SHA-1 校驗和
Git 將文件頭與原始數據內容拼接起來,並計算拼接后的新內容的 SHA-1 校驗和:
>> store = header + content
=> "blob 16\000what is up, doc?" >> require 'digest/sha1' => true >> sha1 = Digest::SHA1.hexdigest(store) => "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
用 zlib 對數據內容進行壓縮
在 Ruby 中可以用 zlib 庫來實現。首先需要導入該庫,然后用 Zlib::Deflate.deflate() 對數據進行壓縮:
>> require 'zlib'
=> true >> zlib_content = Zlib::Deflate.deflate(store) => "x\234K\312\311OR04c(\317H,Q\310,V(-\320QH\311O\266\a\000_\034\a\235"
將用 zlib 壓縮后的內容寫入磁盤
需要指定保存對象的路徑 (SHA-1 值的頭兩個字符作為子目錄名稱,剩余 38 個字符作為文件名保存至該子目錄中)。
>> path = '.git/objects/' + sha1[0,2] + '/' + sha1[2,38] => ".git/objects/bd/9dbf5aae1a3862dd1526723246b20206e5fc37" >> require 'fileutils' => true >> FileUtils.mkdir_p(File.dirname(path)) => ".git/objects/bd" >> File.open(path, 'w') { |f| f.write zlib_content } => 32
所有的 Git 對象都以這種方式存儲,惟一的區別是類型不同 ── 除了字符串 blob,文件頭起始內容還可以是 commit 或 tree 。不過雖然 blob 幾乎可以是任意內容,commit 和 tree 的數據卻是有固定格式的。