Git内部原理之深入解析Git的引用和包文件
生活随笔
收集整理的這篇文章主要介紹了
Git内部原理之深入解析Git的引用和包文件
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
一、Git 分支本質(zhì)
- 如果對倉庫中從一個提交(比如 1a410e)開始往前的歷史感興趣,那么可以運行 git log 1a410e 這樣的命令來顯示歷史,不過需要記得 1a410e 是查看歷史的起點提交。如果我們有一個文件來保存 SHA-1 值,而該文件有一個簡單的名字, 然后用這個名字指針來替代原始的 SHA-1 值的話會更加簡單。
- 在 Git 中,這種簡單的名字被稱為“引用(references,或簡寫為 refs)”,可以在 .git/refs 目錄下找到這類含有 SHA-1 值的文件。在目前的項目中,這個目錄沒有包含任何文件,但它包含了一個簡單的目錄結(jié)構(gòu):
- 若要創(chuàng)建一個新引用來幫助記憶最新提交所在的位置,從技術(shù)上講只需簡單地做如下操作:
- 現(xiàn)在,就可以在 Git 命令中使用這個剛創(chuàng)建的新引用來代替 SHA-1 值:
- 但是,不提倡直接編輯引用文件,如果想更新某個引用,Git 提供了一個更加安全的命令 update-ref 來完成此事:
- 這基本就是 Git 分支的本質(zhì):一個指向某一系列提交之首的指針或引用,若想在第二個提交上創(chuàng)建一個分支,可以這么做:
- 這個分支將只包含從第二個提交開始往前追溯的記錄:
- 至此,我們的 Git 數(shù)據(jù)庫從概念上看起來像這樣:
- 當(dāng)運行類似于 git branch 這樣的命令時,Git 實際上會運行 update-ref 命令,取得當(dāng)前所在分支最新提交對應(yīng)的 SHA-1 值,并將其加入想要創(chuàng)建的任何新引用中。
二、HEAD 引用
- 現(xiàn)在的問題是,當(dāng)執(zhí)行 git branch 時,Git 如何知道最新提交的 SHA-1 值呢? 答案是 HEAD 文件。
- HEAD 文件通常是一個符號引用(symbolic reference),指向目前所在的分支。所謂符號引用,表示它是一個指向其他引用的指針。
- 然而在某些罕見的情況下,HEAD 文件可能會包含一個 git 對象的 SHA-1 值。當(dāng)在檢出一個標(biāo)簽、提交或遠(yuǎn)程分支,讓倉庫變成 “分離 HEAD”狀態(tài)時,就會出現(xiàn)這種情況。
- 如果查看 HEAD 文件的內(nèi)容,通常會看到類似這樣的內(nèi)容:
- 如果執(zhí)行 git checkout test,Git 會像這樣更新 HEAD 文件:
- 當(dāng)執(zhí)行 git commit 時,該命令會創(chuàng)建一個提交對象,并用 HEAD 文件中那個引用所指向的 SHA-1 值設(shè)置其父提交字段。
- 也可以手動編輯該文件,然而同樣存在一個更安全的命令來完成此事:git symbolic-ref,借助此命令來查看 HEAD 引用對應(yīng)的值:
- 同樣可以設(shè)置 HEAD 引用的值:
- 不能把符號引用設(shè)置為一個不符合引用規(guī)范的值:
三、標(biāo)簽引用
- 我們知道, Git 有三種主要的對象類型(數(shù)據(jù)對象、樹對象和提交對象,具體請參考:Git內(nèi)部原理之深入解析Git對象),然而實際上還有第四種:標(biāo)簽對象(tag object), 它非常類似于一個提交對象,包含一個標(biāo)簽創(chuàng)建者信息、一個日期、一段注釋信息,以及一個指針。主要的區(qū)別在于,標(biāo)簽對象通常指向一個提交對象,而不是一個樹對象,像是一個永不移動的分支引用,永遠(yuǎn)指向同一個提交對象,只不過給這個提交對象加上一個更友好的名字罷了。
- 正如 Git之深入解析本地倉庫的基本操作·倉庫的獲取更新和提交歷史的查看撤銷以及標(biāo)簽別名的使用 中所討論的那樣,存在兩種類型的標(biāo)簽:附注標(biāo)簽和輕量標(biāo)簽,可以像這樣創(chuàng)建一個輕量標(biāo)簽:
- 這就是輕量標(biāo)簽的全部內(nèi)容,一個固定的引用。 然而,一個附注標(biāo)簽則更復(fù)雜一些,若要創(chuàng)建一個附注標(biāo)簽,Git 會創(chuàng)建一個標(biāo)簽對象,并記錄一個引用來指向該標(biāo)簽對象,而不是直接指向提交對象??梢酝ㄟ^創(chuàng)建一個附注標(biāo)簽來驗證這個過程(使用 -a 選項):
- 下面是上述過程所建標(biāo)簽對象的 SHA-1 值:
- 現(xiàn)在對該 SHA-1 值運行 git cat-file -p 命令:
- 不難注意到,object 條目指向打了標(biāo)簽的那個提交對象的 SHA-1 值。另外,標(biāo)簽對象并非必須指向某個提交對象,可以對任意類型的 Git 對象打標(biāo)簽。例如,在 Git 源碼中,項目維護者將它們的 GPG 公鑰添加為一個數(shù)據(jù)對象,然后對這個對象打了一個標(biāo)簽,可以克隆一個 Git 版本庫,然后通過執(zhí)行下面的命令來在這個版本庫中查看上述公鑰:
- Linux 內(nèi)核版本庫同樣有一個不指向提交對象的標(biāo)簽對象,首個被創(chuàng)建的標(biāo)簽對象所指向的是最初被引入版本庫的那份內(nèi)核源碼所對應(yīng)的樹對象。
四、遠(yuǎn)程引用
- 現(xiàn)在將看到的第三種引用類型是遠(yuǎn)程引用(remote reference),如果添加了一個遠(yuǎn)程版本庫并對其執(zhí)行過推送操作,Git 會記錄下最近一次推送操作時每一個分支所對應(yīng)的值,并保存在 refs/remotes 目錄下。例如,可以添加一個叫做 origin 的遠(yuǎn)程版本庫,然后把 master 分支推送上去:
- 此時,如果查看 refs/remotes/origin/master 文件,可以發(fā)現(xiàn) origin 遠(yuǎn)程版本庫的 master 分支所對應(yīng)的 SHA-1 值,就是最近一次與服務(wù)器通信時本地 master 分支所對應(yīng)的 SHA-1 值:
- 遠(yuǎn)程引用和分支(位于 refs/heads 目錄下的引用)之間最主要的區(qū)別在于,遠(yuǎn)程引用是只讀的。雖然可以 git checkout 到某個遠(yuǎn)程引用,但是 Git 并不會將 HEAD 引用指向該遠(yuǎn)程引用。因此,永遠(yuǎn)不能通過 commit 命令來更新遠(yuǎn)程引用,Git 將這些遠(yuǎn)程引用作為記錄遠(yuǎn)程服務(wù)器上各分支最后已知位置狀態(tài)的書簽來管理。
五、包文件
- 如果跟著做完了上文的所有步驟,那么現(xiàn)在應(yīng)該有了一個測試用的 Git 倉庫, 其中包含 11 個對象:四個數(shù)據(jù)對象,三個樹對象,三個提交對象和一個標(biāo)簽對象:
- Git 使用 zlib 壓縮這些文件的內(nèi)容,而且并沒有存儲太多東西,所以上文中的文件一共只占用了 925 字節(jié)。接下來,我們添加一些大文件到倉庫中,以此展示 Git 的一個很有趣的功能。為了便于演示,要把之前在 Grit 庫中用到過的 repo.rb 文件添加進(jìn)來,如下所示,這是一個大小約為 22K 的源代碼文件:
- 如果查看生成的樹對象,可以看到 repo.rb 文件對應(yīng)的數(shù)據(jù)對象的 SHA-1 值:
- 接下來可以使用 git cat-file 命令查看這個對象有多大:
- 現(xiàn)在,稍微修改這個文件,然后看看會發(fā)生什么:
- 查看這個最新的提交生成的樹對象,可以看到一些有趣的東西:
- repo.rb 對應(yīng)一個與之前完全不同的數(shù)據(jù)對象,這意味著,雖然只是在一個 400 行的文件后面加入一行新內(nèi)容,Git 也會用一個全新的對象來存儲新的文件內(nèi)容:
- 磁盤上現(xiàn)在有兩個幾乎完全相同、大小均為 22K 的對象(每個都被壓縮到大約 7K),如果 Git 只完整保存其中一個,再保存另一個對象與之前版本的差異內(nèi)容,豈不更好?
- 事實上 Git 可以那樣做,最初向磁盤中存儲對象時所使用的格式被稱為“松散(loose)”對象格式。但是,Git 會時不時地將多個這些對象打包成一個稱為“包文件(packfile)”的二進(jìn)制文件,以節(jié)省空間和提高效率,當(dāng)版本庫中有太多的松散對象,或者手動執(zhí)行 git gc 命令,或者向遠(yuǎn)程服務(wù)器執(zhí)行推送時,Git 都會這樣做。要看到打包過程,可以手動執(zhí)行 git gc 命令讓 Git 對對象進(jìn)行打包:
- 這個時候再查看 objects 目錄,會發(fā)現(xiàn)大部分的對象都不見了,與此同時出現(xiàn)了一對新文件:
- 仍保留著的幾個對象是未被任何提交記錄引用的數(shù)據(jù)對象,在此例中是之前創(chuàng)建的 “what is up, doc?” 和 “test content” 這兩個示例數(shù)據(jù)對象,因為從沒將它們添加至任何提交記錄中,所以 Git 認(rèn)為它們是懸空(dangling)的,不會將它們打包進(jìn)新生成的包文件中。
- 剩下的文件是新創(chuàng)建的包文件和一個索引,包文件包含了剛才從文件系統(tǒng)中移除的所有對象的內(nèi)容,索引文件包含了包文件的偏移信息,我們通過索引文件就可以快速定位任意一個指定對象。有意思的是運行 gc 命令前磁盤上的對象大小約為 15K,而這個新生成的包文件大小僅有 7K,通過打包對象減少了一半的磁盤占用空間。
- Git 是如何做到這點的呢? Git 打包對象時,會查找命名及大小相近的文件,并只保存文件不同版本之間的差異內(nèi)容??梢圆榭窗募?#xff0c;觀察它是如何節(jié)省空間的,git verify-pack 這個底層命令可以就可以查看已打包的內(nèi)容:
- 此處,033b4 這個數(shù)據(jù)對象(即 repo.rb 文件的第一個版本)引用了數(shù)據(jù)對象 b042a,即該文件的第二個版本,命令輸出內(nèi)容的第三列顯示的是各個對象在包文件中的大小,可以看到 b042a 占用了 22K 空間,而 033b4 僅占用 9 字節(jié)。同樣有趣的地方在于,第二個版本完整保存了文件內(nèi)容,而原始的版本反而是以差異方式保存的,這是因為大部分情況下需要快速訪問文件的最新版本。
- 最妙之處是可以隨時重新打包,Git 時常會自動對倉庫進(jìn)行重新打包以節(jié)省空間。當(dāng)然也可以隨時手動執(zhí)行 git gc 命令來這么做。
總結(jié)
以上是生活随笔為你收集整理的Git内部原理之深入解析Git的引用和包文件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Git内部原理之深入解析Git对象
- 下一篇: Git内部原理之深入解析传输协议