
在工作過程中我們會(huì)不可避免的使用Git,但是你知道Git是如何存儲(chǔ)你的文件、如何保存你的提交信息嗎?
了解這些也便于我們更好的理解和記憶命令,更好的排查問題和使用Git,下面就讓我們來看一下吧~
本文主要依照官網(wǎng)的介紹根據(jù)真實(shí)項(xiàng)目中的變化總結(jié)整理而成~ 一位同學(xué)在面試阿里的時(shí)候被問到這個(gè)問題,特在此整理記錄一下~
首先,我們要明確 Git 是一個(gè)分布式版本控制系統(tǒng) 其本質(zhì)是一套內(nèi)容尋址文件系統(tǒng)
通俗點(diǎn)說,Git 從核心上來看不過是簡(jiǎn)單地存儲(chǔ)鍵值對(duì)(key-value)。它允許插入任意類型的內(nèi)容,并會(huì)返回一個(gè)鍵值,通過該鍵值可以在任何時(shí)候再取出該內(nèi)容。
ps : 下面所說的SHA-1碼 和 commit_id 是同一種
首先,Git存儲(chǔ)在本地的表現(xiàn)形式
當(dāng)你在一個(gè)新目錄或已有目錄內(nèi)執(zhí)行 git init 時(shí),Git 會(huì)創(chuàng)建一個(gè) .git 目錄,幾乎所有 Git 存儲(chǔ)和操作的內(nèi)容都位于該目錄下。如果你要備份或復(fù)制一個(gè)庫(kù),基本上將這一目錄拷貝至其他地方就可以了。如下圖:
-
info目錄保存了一份不希望在 .gitignore 文件中管理的忽略模式 (ignored patterns) 的全局可執(zhí)行文件 -
hooks目錄保存了客戶端或服務(wù)端鉤子腳本 -
config文件包含了項(xiàng)目特有的配置選項(xiàng) -
objects目錄存儲(chǔ)所有數(shù)據(jù)內(nèi)容 -
refs目錄存儲(chǔ)指向數(shù)據(jù) (分支) 的提交對(duì)象的指針 -
HEAD文件指向當(dāng)前分支 -
index文件保存了暫存區(qū)域信息
其中,HEAD 及 index 文件,objects 及 refs 目錄是 Git 的核心部分。
接下來,說一下Git的存儲(chǔ)方式
如上述所說,objects 目錄存儲(chǔ)所有數(shù)據(jù)內(nèi)容,objects 目錄下的每一個(gè)文件是Git為每份存儲(chǔ)數(shù)據(jù)內(nèi)容生成一個(gè)文件,取得該內(nèi)容與頭信息的 SHA-1 校驗(yàn)和,創(chuàng)建以該校驗(yàn)和前兩個(gè)字符為名稱的子目錄,并以 (校驗(yàn)和) 剩下 38 個(gè)字符為文件命名 (保存至子目錄下)。如下圖:
打開00文件夾可以看到里面保存的內(nèi)容:
Git 以一種類似 UNIX 文件系統(tǒng)但更簡(jiǎn)單的方式來存儲(chǔ)內(nèi)容。所有內(nèi)容以 tree 或 blob 對(duì)象存儲(chǔ),其中 tree 對(duì)象對(duì)應(yīng)于 UNIX 中的目錄,blob 對(duì)象則大致對(duì)應(yīng)于 inodes 或文件內(nèi)容。
一個(gè)單獨(dú)的 tree 對(duì)象包含一條或多條 tree 記錄,每一條記錄含有一個(gè)指向 blob 或子 tree 對(duì)象的 SHA-1 指針,并附有該對(duì)象的權(quán)限模式 (mode)、類型和文件名信息。
正如Git的每一次提交都是對(duì)代碼倉(cāng)庫(kù)的完整備份,也就是保存了一份代碼倉(cāng)庫(kù)完整的快照所說,每一個(gè)commit都是存儲(chǔ)為一個(gè)Tree,如下圖:
具體在git中為:
之后,我們通過git cat-file -p <id>命令可以發(fā)現(xiàn)存儲(chǔ)是樹型的,也就是對(duì)應(yīng)于git的tree對(duì)象,保存的都是指向下一個(gè)部分的索引id
如下圖,每一步都是查看的上一步中的某個(gè)id:
上述所說每個(gè)commit創(chuàng)建一個(gè)樹快照,那么是通過什么創(chuàng)建的呢?
這就是我們上述說的用于存儲(chǔ)暫存區(qū)信息的index文件了。
通常 Git 根據(jù)你的暫存區(qū)域或 index 來創(chuàng)建并寫入一個(gè) tree 。因此要?jiǎng)?chuàng)建一個(gè) tree 對(duì)象的話首先要通過將一些文件暫存從而創(chuàng)建一個(gè) index 。
這也是為什么commit前必須要有文件被add到暫存區(qū),如果暫存區(qū)為空,commit會(huì)報(bào)錯(cuò)停止執(zhí)行。
這個(gè)時(shí)候就有一個(gè)問題了,我們有多個(gè)快照樹,它們指向了你要跟蹤的項(xiàng)目的不同快照,其中也沒有關(guān)于誰、何時(shí)以及為何保存了這些快照的信息
此時(shí),commit對(duì)象就出場(chǎng)了~ 每次commit提交后就會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)commit 對(duì)象,這個(gè)對(duì)象就是為你保存了這些基本信息的。
一般情況下,一次commit提交就可以理解為創(chuàng)建了一個(gè)tree樹,以commit_id為根節(jié)點(diǎn)的tree,該樹包含了當(dāng)前項(xiàng)目的整體快照
當(dāng)我們使用git log命令查看提交歷史的時(shí)候,就展示了commit對(duì)象的一些基本信息,如下圖:
其中:
commit 后跟的id就是當(dāng)前commit快照的樹根節(jié)點(diǎn)id
其余的還包含作者,作者郵箱,創(chuàng)建時(shí)間等基本信息
image
Git每次commit提交會(huì)保存項(xiàng)目快照,難道是將所有的文件重新復(fù)制一份嗎?
當(dāng)然不可能,在git的文件系統(tǒng)中,是存在共用文件的。
比如有三次commit提交,產(chǎn)生了三個(gè)tree樹,它們?cè)谙蛳乱玫臅r(shí)候,如果兩個(gè)commit中的整個(gè)文件夾或者某個(gè)文件沒有改變,這兩個(gè)commit的tree會(huì)指向同一個(gè)對(duì)象。 對(duì)于兩次提交修改了的文件,則會(huì)創(chuàng)建一個(gè)該文件的一個(gè)新的版本的文件,上一次提交指向舊的文件,修改文件的提交指向新版本的文件。
整體情況如下圖:
另外,Git 用 zlib 壓縮文件內(nèi)容,因此存儲(chǔ)的文件并不會(huì)占用太多空間
了解了git整體存儲(chǔ)方式之后,我們?cè)倏匆幌虑懊嫣岬降拇鎯?chǔ)指向數(shù)據(jù) (分支) 的提交對(duì)象的指針的refs目錄
refs目錄內(nèi)容如下圖:

首先,也是思考一個(gè)問題:在項(xiàng)目開發(fā)中,有許多分支,每個(gè)分支的提交記錄都不相同,我們也不可能去記住每個(gè)commit_id,去執(zhí)行像
git log 1a410e 這樣的命令來查看完整的歷史,這樣的話你就要記得 1a410e 是你最后一次提交并且記得這個(gè)id,這樣才能在提交歷史中找到這些對(duì)象,git是怎樣的應(yīng)對(duì)這個(gè)問題的呢?
這時(shí)候,我們需要一個(gè)文件來用一個(gè)簡(jiǎn)單的名字來記錄這些 SHA-1 值,這樣就可以用這些指針而不是原來的 SHA-1 值去檢索了。在 Git 中,稱之為“引用”(references 或者 refs)。
可以在 .git/refs 目錄下面找到這些包含 SHA-1 值的文件。如下圖refs中heads文件下的文件,其中每個(gè)文件存儲(chǔ)的是與文件名同名的分支的最新提交的commit_id:
添加上refs文件夾下的文件后,我們的Git存儲(chǔ)結(jié)構(gòu)就看起來像下圖:
接下來,再思考一個(gè)問題,git是怎么標(biāo)識(shí)當(dāng)前是在什么分支,從而找到refs中對(duì)應(yīng)的映射文件獲取SHA-1值呢?
那就是前面所說的HEAD文件了,我們打開文件可以看到以下內(nèi)容:
ref: refs/heads/test_branch
這里標(biāo)識(shí)的是當(dāng)前指向的是test_branch分支,并且指定了要是用的映射文件的路徑,這樣就解決了上述問題,是不是特別簡(jiǎn)單~
上述已經(jīng)介紹了Git的三個(gè)主要類型:tree樹、commit對(duì)象、HEAD。下面我們說一下Git中另外一個(gè)重要的東西:Tag(標(biāo)簽)
Tag 對(duì)象比較簡(jiǎn)單,Tag對(duì)象非常像一個(gè) commit 對(duì)象---包含一個(gè)標(biāo)簽,一組數(shù)據(jù),一個(gè)消息和一個(gè)指針。
最主要的區(qū)別就是 Tag 對(duì)象指向一個(gè) commit 而不是一個(gè) tree。它就像是一個(gè)分支引用,但是不會(huì)變化,永遠(yuǎn)指向同一個(gè) commit,僅僅是為了提供一個(gè)更加友好的名字。
總結(jié)
好了,通過介紹了git的核心組成元素 HEAD 及 index 文件,objects 及 refs 目錄 , 你應(yīng)該會(huì)對(duì)git的存儲(chǔ)和一些機(jī)制有一個(gè)簡(jiǎn)單的整體了解,這對(duì)我們更好的理解git命令和更好的使用git是有幫助的。
希望本片文章會(huì)對(duì)大家有些許幫助~
參考:git官網(wǎng)
如果感覺這篇文章對(duì)您有所幫助,請(qǐng)點(diǎn)擊一下“喜歡”或者“關(guān)注”博主,您的喜歡和關(guān)注將是我前進(jìn)的最大動(dòng)力!