這一章會(huì)詳細(xì)講解 Git 如何物理存儲(chǔ)各對(duì)象。
所有的對(duì)象都以 SHA 值為索引用 gzip 格式壓縮存儲(chǔ),每個(gè)對(duì)象都包含了對(duì)象類型,大小和內(nèi)容。
Git 中存在兩種對(duì)象 - 松散對(duì)象(loose object)和打包對(duì)象(packed object)。
松散對(duì)象是一種比較簡(jiǎn)單格式。它就是磁盤上的一個(gè)存儲(chǔ)壓縮數(shù)據(jù)的文件。每一個(gè)對(duì)象都被寫入一個(gè)單獨(dú)文件中。
如果你對(duì)象的 SHA 值是 ab04d884140f7b0cf8bbf86d6883869f16a46f65,那么對(duì)應(yīng)的文件會(huì)被存儲(chǔ)在:
GIT_DIR/objects/ab/04d884140f7b0cf8bbf86d6883869f16a46f65
Git 使用 SHA 值的前兩個(gè)字符作為子目錄名字,所以一個(gè)目錄中永遠(yuǎn)不會(huì)包含過多的對(duì)象。文件名則是余下的 38 個(gè)字符。
可以用下面的 Ruby 代碼說明對(duì)象數(shù)據(jù)是如何存儲(chǔ)的:
def put_raw_object(content, type)
size = content.length.to_s
header = "#{type} #{size}\0" # type(space)size(null byte)
store = header + content
sha1 = Digest::SHA1.hexdigest(store)
path = @git_dir + '/' + sha1[0...2] + '/' + sha1[2..40]
if !File.exists?(path)
content = Zlib::Deflate.deflate(store)
FileUtils.mkdir_p(@directory+'/'+sha1[0...2])
File.open(path, 'w') do |f|
f.write content
end
end
return sha1
end
另外一種對(duì)象存儲(chǔ)方式是使用打包文件(packfile)。由于 Git 把每個(gè)文件的每個(gè)版本都作為一個(gè)單獨(dú)的對(duì)象,它的效率可能會(huì)十分的低。設(shè)想一下在一個(gè)數(shù)千行的文件中改動(dòng)一行,Git 會(huì)把修改后的文件整個(gè)存儲(chǔ)下來,很浪費(fèi)空間。
Git 使用打包文件(packfile)去節(jié)省空間。在這個(gè)格式中,Git 只會(huì)保存第二個(gè)文件中改變了的部分,然后用一個(gè)指針指向相似的那個(gè)文件(譯注:即第一個(gè)文件)。
對(duì)象通常是以松散格式寫到磁盤上,因?yàn)檫@個(gè)格式的訪問代價(jià)比較低。然后,你最終會(huì)需要把對(duì)象存放到打包格式中去節(jié)省磁盤空間 - 這個(gè)工作可以通過 git gc 來完成。它使用一個(gè)相當(dāng)復(fù)雜的啟發(fā)式算法去決定哪些文件是最相似的,然后基于此分析去計(jì)算差異??梢源嬖诙鄠€(gè)打包文件,在必要情況下,它們可被解包(git unpack-objects)成為松散對(duì)象或者重新打包(git repack)。
Git 會(huì)為每一個(gè)打包文件創(chuàng)建一個(gè)較小的索引文件。索引文件中包含了對(duì)象在打包文件中的偏移,以便于通過 SHA 值來快速找到特定的對(duì)象。
打包文件的實(shí)現(xiàn)細(xì)節(jié)會(huì)在稍后的‘打包文件’(Packfile)一章中講述。