一篇文章看懂Git的內(nèi)部存儲(chǔ)結(jié)構(gòu)
Git是一個(gè)開(kāi)源的分布式版本控制系統(tǒng),目前大多數(shù)團(tuán)隊(duì)都采用Git作為團(tuán)隊(duì)內(nèi)部的版本控制系統(tǒng),并且在DevOps的整體流程中,版本控制是其中重要的一環(huán),在“基礎(chǔ)設(shè)施即代碼”的理念下,不止是系統(tǒng)源代碼,系統(tǒng)配置納入版本管理,數(shù)據(jù)和環(huán)境配置也要納入版本管理,可以說(shuō),版本管理使得研發(fā)流程可復(fù)用、標(biāo)準(zhǔn)化、自動(dòng)化,進(jìn)而提高了研發(fā)效率。
大多數(shù)人在使用git時(shí),大多數(shù)都是使用IDE封裝好的GUI界面操作,少量使用者也是git add,git commit,git pull幾個(gè)常用的命令。對(duì)于git的用戶來(lái)說(shuō),這些都足夠了。如果你是一個(gè)DevOps開(kāi)發(fā)者或是想成為Git的深度用戶,知道這些是遠(yuǎn)遠(yuǎn)不夠的。下面就讓我們了解一下Git背后的邏輯。
第一、Git的三個(gè)區(qū)+一個(gè)遠(yuǎn)程倉(cāng)庫(kù)

這個(gè)圖好多人都應(yīng)該見(jiàn)過(guò)。里面也是一些經(jīng)常使用的命令。主要包含幾個(gè)部分:
①Remote:遠(yuǎn)程倉(cāng)庫(kù),像github就是一個(gè)遠(yuǎn)程倉(cāng)庫(kù)。
②Repository:本地倉(cāng)庫(kù),通過(guò)git clone將遠(yuǎn)程倉(cāng)庫(kù)的代碼下載到本地。代碼庫(kù)的元數(shù)據(jù)信息在根目錄下的.git目錄下。
③Workspace:工作空間,就是我們寫(xiě)代碼的目錄。
④Index:暫存區(qū),指的是.git目錄下的index文件。
在平時(shí)寫(xiě)完代碼后執(zhí)行g(shù)it add 就是將變更的內(nèi)容從工作空間提交到暫存區(qū),git commit就是將暫存區(qū)的內(nèi)容提交到本地代碼庫(kù)里,git push 就是將本地代碼庫(kù)的變更提交到遠(yuǎn)程倉(cāng)庫(kù),這時(shí)其他人就能通過(guò)pull 將你的變更下載到工作空間。
第二、git的內(nèi)部存儲(chǔ)結(jié)構(gòu)
新建一個(gè)gittest目錄,然后執(zhí)行g(shù)it init就會(huì)初始化一個(gè)本地倉(cāng)庫(kù)。打開(kāi).git目錄文件列表如下:

hooks:是存儲(chǔ)git鉤子的目錄,鉤子是在特定事件發(fā)生時(shí)觸發(fā)的腳本。比如:提交之前,提交之后。
info:是存儲(chǔ)git信息的目錄,比如排除特定后綴的文件.
objects:是存儲(chǔ)git各種對(duì)象及內(nèi)容的對(duì)象庫(kù),包含正常的和壓縮后的。
refs:是存儲(chǔ)git各種引用的目錄,包含分支、遠(yuǎn)程分支和標(biāo)簽。
config:是代碼庫(kù)級(jí)別的配置文件。
HEAD:是代碼庫(kù)當(dāng)前指向的分支,這里為master。
一、新建README.md文件
新建README.md文件,并輸入內(nèi)容“This is test file!”

git add README.md 將工作空間的變更提交到暫存區(qū)。
.git/index文件會(huì)被修改,通過(guò)git ls-files --stage 查看暫存區(qū)的內(nèi)容,可以看到README.md文件有修改,通過(guò)git cat-file -p SHA-1查看文件內(nèi)容。

同時(shí).git/objects下也會(huì)有新的對(duì)象,如下:

這個(gè)對(duì)象就是剛才add到暫存區(qū)的對(duì)象,在.git/objects目錄下看到一個(gè)文件。這便是Git存儲(chǔ)數(shù)據(jù)內(nèi)容的方式--為每份內(nèi)容生成一個(gè)文件,取該內(nèi)容與頭信息的SHA-1校驗(yàn)和,創(chuàng)建以該校驗(yàn)和前兩個(gè)字符為名稱得子目錄,并以校驗(yàn)和剩下38個(gè)字符為文件命名。這里并沒(méi)有顯示真實(shí)的文件名。
通過(guò)git cat-file -t SHA-1查看對(duì)象的類型。blob代表文件。

二、創(chuàng)建一個(gè)目錄web,并添加一個(gè)web.txt文件

執(zhí)行g(shù)it add web/ 添加到暫存區(qū)。
查看暫存區(qū)內(nèi)容,變成了2條記錄,存儲(chǔ)方式和上面文件一樣。

可以看出,這里只有文件內(nèi)容生成的文件,沒(méi)有為目錄生成文件。
在.git/objects下面也創(chuàng)建了相應(yīng)的目錄和文件。

三、提交內(nèi)容
到目前為止,暫存區(qū)的內(nèi)容有README.md和web/web.txt文件。下面通過(guò)git commit 將暫存區(qū)的內(nèi)容提交到本地倉(cāng)庫(kù)。

使用git log 查看本次提交信息,使用git cat-file -p查看變更內(nèi)容,當(dāng)類型為tree時(shí)表示文件夾,會(huì)顯示該文件夾下的文件或目錄列表,當(dāng)類型為blob時(shí)為文件,會(huì)顯示該文件的內(nèi)容。

因此本次commit的存儲(chǔ)模型為:

此時(shí)再查看.git/objects目錄下,有增加了幾個(gè)目錄。其中:
8d:提交的commit對(duì)象
58和ff:提交的commit對(duì)應(yīng)的tree對(duì)象和web目錄的tree對(duì)象。
a1和b3:提交的README.md文件和web/web.txt文件對(duì)應(yīng)的數(shù)據(jù)對(duì)象。

可以看出,當(dāng)我們執(zhí)行g(shù)it add 和git commit 命令時(shí),Git做的工作是將被改寫(xiě)的文件保存為數(shù)據(jù)對(duì)象、更新暫存區(qū),記錄樹(shù)對(duì)象,最后創(chuàng)建一個(gè)指明了頂層樹(shù)對(duì)象和父提交的提交對(duì)象。這三種Git對(duì)象(數(shù)據(jù)對(duì)象-blob、樹(shù)對(duì)象-tree、提交對(duì)象-commit)最后均以單獨(dú)文件的形式保存在.git/objects目錄下。
四、新建分支test
上面提到分支、遠(yuǎn)程分支和標(biāo)簽都會(huì)存儲(chǔ)在.git/refs下。

heads包含分支,tags包含標(biāo)簽。每個(gè)引用文件里都會(huì)指向一個(gè)commit,如基于master分支新建的test分支 git branch test:

使用git checkout test切換到test分支,此時(shí)HEAD的內(nèi)容為refs/heads/test,表示當(dāng)前分支為test.

在test分支下修改文件內(nèi)容:

git status查看工作空間狀態(tài),有兩個(gè)修改過(guò)的文件。

git add .將變更的文件添加到暫存區(qū),查看暫存區(qū)內(nèi)容發(fā)現(xiàn):
README.md的校驗(yàn)和從之前的a177f89--->7a51843
web/web.txt的校驗(yàn)和從之前的b31494b--->f5ebb2c
查看文件內(nèi)容也是最新修改的內(nèi)容。

.git/objects目錄下也多了2個(gè)新的目錄

git commit 將暫存區(qū)的內(nèi)容提交到本地倉(cāng)庫(kù)。和上面一樣,此時(shí)也會(huì)創(chuàng)建提交對(duì)象,樹(shù)對(duì)象,通過(guò)查看不同的對(duì)象,最后都能查看到具體的數(shù)據(jù)對(duì)象的最新內(nèi)容。

同時(shí)在.git/objects下也會(huì)創(chuàng)建提交對(duì)象c7,樹(shù)對(duì)象cf和樹(shù)對(duì)象a3

五、分支合并沖突
在master分支修改web/web.txt,將內(nèi)容改為“this is a old web.txt”,以便產(chǎn)生沖突。
git add 添加到暫存區(qū),查看文件內(nèi)為新修改的內(nèi)容。

git commit添加到本地倉(cāng)庫(kù),同時(shí)生成提交對(duì)象,樹(shù)對(duì)象。

執(zhí)行g(shù)it merge test進(jìn)行分支合并,web/web.txt出現(xiàn)沖突。

分支合并后,master分支的commit由之前的 8da82ba變?yōu)閏75be2f。
暫存區(qū)中的README.md的校驗(yàn)和也由master分支的 a177f89變成7a51843。
而web/web.txt變成了3個(gè)文件,b3是master當(dāng)前commit的父commit的校驗(yàn)和,18是master最新提交的校驗(yàn)和,f5是test分支上的校驗(yàn)和。

解決沖突,兩行內(nèi)容都保留,結(jié)果如下:

git add 添加到暫存區(qū),查看暫存區(qū)內(nèi)容,README.md是test分支上的,web/web.txt是解決完沖突新生成的校驗(yàn)和。

也就是說(shuō),分支合并會(huì)修改當(dāng)前分支的commit信息以及暫存區(qū)的內(nèi)容,當(dāng)解決完沖突后,將沖突文件git add 添加到暫存區(qū),然后git commit 將合并后的內(nèi)容提交到本地倉(cāng)庫(kù)。


也就是說(shuō),合并完沖突后,只有將更新后的內(nèi)容commit,在生成的提交對(duì)象上才能找到新加的內(nèi)容。
這里回答下遇到的一個(gè)問(wèn)題:
問(wèn)題:我當(dāng)前分支是test分支,當(dāng)從master分支合并過(guò)來(lái)后沖突了,我解決完沖突,只提交了這個(gè)文件,其他文件不用提,因?yàn)槠渌募膬?nèi)容已經(jīng)在master分支上了,當(dāng)我從test分支再合并回master分支后,也應(yīng)該沒(méi)有問(wèn)題。
解釋:根據(jù)上面的分析,每個(gè)commit都是一個(gè)提交對(duì)象,這個(gè)提交對(duì)象關(guān)聯(lián)一個(gè)樹(shù)對(duì)象,樹(shù)對(duì)象會(huì)包含最新的數(shù)據(jù)對(duì)象。當(dāng)從master分支合并后,master的commit提交對(duì)象包含的樹(shù)對(duì)象和數(shù)據(jù)對(duì)象,會(huì)作為新的文件內(nèi)容存放到test分支的暫存區(qū),此時(shí)有2種選擇:
①,當(dāng)在test分支commit時(shí),這次生成的提交對(duì)象才會(huì)包含master過(guò)來(lái)的文件內(nèi)容。
②,如果此時(shí)放棄提交(手動(dòng)checkout當(dāng)前分支最新的),那么當(dāng)前commit提交對(duì)象不包含master的文件內(nèi)容,當(dāng)test分支合并回master時(shí),也會(huì)將test分支的文件內(nèi)容存放到master分支的暫存區(qū),當(dāng)push到遠(yuǎn)程倉(cāng)庫(kù)時(shí),也就是test分支的數(shù)據(jù)對(duì)象的內(nèi)容,因此其他人拿到的文件內(nèi)容就會(huì)丟失。