阿里面試官:你不知道git的內(nèi)部實(shí)現(xiàn)機(jī)制?

在工作過程中我們會(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è)字符為文件命名 (保存至子目錄下)。如下圖:


image

打開00文件夾可以看到里面保存的內(nèi)容:


image

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中為:
image
可以看到,目錄作為tree存儲(chǔ),文件作為blob存儲(chǔ)

之后,我們通過git cat-file -p <id>命令可以發(fā)現(xiàn)存儲(chǔ)是樹型的,也就是對(duì)應(yīng)于git的tree對(duì)象,保存的都是指向下一個(gè)部分的索引id
如下圖,每一步都是查看的上一步中的某個(gè)id:

image


上述所說每個(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è)新的版本的文件,上一次提交指向舊的文件,修改文件的提交指向新版本的文件。

整體情況如下圖:


image

另外,Git 用 zlib 壓縮文件內(nèi)容,因此存儲(chǔ)的文件并不會(huì)占用太多空間


了解了git整體存儲(chǔ)方式之后,我們?cè)倏匆幌虑懊嫣岬降拇鎯?chǔ)指向數(shù)據(jù) (分支) 的提交對(duì)象的指針的refs目錄

refs目錄內(nèi)容如下圖:

image

首先,也是思考一個(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

image

添加上refs文件夾下的文件后,我們的Git存儲(chǔ)結(jié)構(gòu)就看起來像下圖:
image


接下來,再思考一個(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)力!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 你應(yīng)該知道的git git 起步 git 基礎(chǔ) git 是分布式,也就是說沒有中央服務(wù)器,代碼從倉(cāng)庫(kù)完整的鏡像下來...
    Lyan_2ab3閱讀 673評(píng)論 0 3
  • 朋友整理的,放這里偶爾過來看看 一、基本介紹 首先,Git作為版本控制系統(tǒng),他的原理與SVN為首的集中式版本控制系...
    allenzhan閱讀 1,098評(píng)論 0 3
  • 以下筆記主要參考gitgot,大致了解git使用和原理。 第一部分我們從個(gè)人的視角去研究如何用好Git,并且揭示G...
    carolwhite閱讀 2,512評(píng)論 0 1
  • Add & Commit git init 初始化一個(gè) Git 倉(cāng)庫(kù)(repository),即把當(dāng)前所在目錄變成...
    冬絮閱讀 5,125評(píng)論 0 9
  • 梁凱恩在《下一個(gè)奇跡就是你》中這樣一句話“啊!這是多么美好的一天,充滿著愛,熱情,效益,感恩,power.” 想想...
    帥帥的床邊故事閱讀 464評(píng)論 0 0

友情鏈接更多精彩內(nèi)容