面向 Visual Studio 开发者的 Git 内部源代码
在我撰寫(xiě)的 Git DevOps 文章 (msdn.com/magazine/mt767697) 中,我介紹了 Git 版本控制系統(tǒng) (VCS) 與可能已經(jīng)很熟悉的集中式 VCS 的區(qū)別。然后,我演示了如何在 Visual Studio 中使用 Git 工具完成一些 Git 任務(wù)。在本文中,我將匯總 Git 在新發(fā)布的 Visual Studio 2017 IDE 中的運(yùn)作方式的相關(guān)變化,并介紹 Git 存儲(chǔ)庫(kù)在文件系統(tǒng)中的實(shí)現(xiàn)方式。之后,我將探究數(shù)據(jù)存儲(chǔ)的拓?fù)浜透鞣N存儲(chǔ)對(duì)象的結(jié)構(gòu)和內(nèi)容。最后,我將對(duì) Git 分支進(jìn)行低級(jí)別解釋,以闡明我的觀(guān)點(diǎn),即希望大家能夠理解我將在近期發(fā)表的文章中介紹的更高級(jí) Git 操作。
注意: 在本文中,我沒(méi)有使用服務(wù)器或遠(yuǎn)程方案。我探究的是純本地方案,可以在安裝了 Visual Studio 2017 和 Git for Windows (G4W) 的任何一臺(tái) Windows 計(jì)算機(jī)(已連接或未連接 Internet/網(wǎng)絡(luò)連接)上進(jìn)行操作。本文介紹了 Git 內(nèi)部源代碼。閱讀本文的前提是,熟悉 Visual Studio Git 工具以及 Git 基本操作和概念。
Visual Studio、Git 和你
Git 不僅是指包含版本控制數(shù)據(jù)存儲(chǔ)的存儲(chǔ)庫(kù),也是指處理管理命令的引擎: 底層命令執(zhí)行低級(jí)別操作;高層命令以類(lèi)似于宏的方式捆綁底層命令,從而宏觀(guān)調(diào)用,簡(jiǎn)化操作。掌握 Git 后,你會(huì)發(fā)現(xiàn)一些任務(wù)需要用到這些命令(我將在本文中使用其中一些命令),并且需要使用命令行接口 (CLI) 來(lái)調(diào)用這些命令。遺憾的是,Visual Studio 2017 不再安裝 Git CLI,因?yàn)樗褂玫男?Git 引擎 MinGit 并不提供 Git CLI。與 G4W 2.10 一同發(fā)布的 MinGit(“最簡(jiǎn) Git”)是可移植的簡(jiǎn)化功能集 API,專(zhuān)為需要與 Git 存儲(chǔ)庫(kù)進(jìn)行交互的 Windows 應(yīng)用程序而設(shè)計(jì)。G4W 乃至 MinGit 都是官方 Git 開(kāi)放源代碼項(xiàng)目的分支。也就是說(shuō),它們都繼承了官方 Git 修補(bǔ)程序和更新程序(只要已發(fā)布),并確保 Visual Studio 可以執(zhí)行同樣的操作。
若要訪(fǎng)問(wèn) Git CLI(并繼續(xù)和我一起操作),建議安裝完整的 G4W 程序包。雖然有其他 Git CLI/GUI 工具選項(xiàng),但 G4W(作為 MinGit 的官方父級(jí)引擎)是明智之選,特別是因?yàn)樗c MinGit 共享配置文件。若要獲取最新的 G4W 安裝程序,請(qǐng)?jiān)L問(wèn)官方網(wǎng)站來(lái)源 (git-scm.com) 的“下載”部分。運(yùn)行安裝程序,并選中“Git Bash Here”復(fù)選框(創(chuàng)建 Git 命令提示符窗口)和“Git GUI Here”復(fù)選框(創(chuàng)建 Git GUI 窗口)。這樣一來(lái),便可以在 Windows 資源管理器中輕松右鍵單擊文件夾,然后對(duì)當(dāng)前文件夾選擇這兩個(gè)選項(xiàng)之一(Git Bash 中的“Bash”是指 Bourne Again Shell,在 G4W 的 Unix shell 中表示 Git CLI)。下一步,選擇“通過(guò) Windows 命令提示符使用 Git”,這會(huì)將環(huán)境配置為可以在 Visual Studio 程序包管理器控制臺(tái) (Windows PowerShell) 或命令提示符窗口中輕松運(yùn)行 Git 命令。
如果使用我在本文中建議的選項(xiàng)安裝 G4W(見(jiàn)圖 1),通信路徑將會(huì)在與 Git 存儲(chǔ)庫(kù)通信時(shí)生效: Visual Studio 2017 使用 MinGit API,而 PowerShell 和命令提示符會(huì)話(huà)則使用 G4W CLI,這是與 Git 存儲(chǔ)庫(kù)通信的不同通信路徑。雖然 MinGit 和 G4W 是不同的通信終結(jié)點(diǎn),但都派生自官方 Git 源代碼,并共享配置文件。請(qǐng)注意,發(fā)出的高層命令會(huì)先被轉(zhuǎn)換成底層命令,然后再由 CLI 進(jìn)行處理。重要的是了解 Git 專(zhuān)家通常會(huì)(有時(shí)完全依靠)向 CLI 發(fā)出裸機(jī) Git 底層命令,因?yàn)檫@樣做是管理、查詢(xún)和更新 Git 存儲(chǔ)庫(kù)的最直接方法,也是最低級(jí)別方法。與低級(jí)別的底層命令相比,Visual Studio IDE 公開(kāi)的更高級(jí)別高層命令和 Git 操作也可以更新 Git 存儲(chǔ)庫(kù),但具體方式始終不太清楚,特別是因?yàn)楦邔用罱?jīng)常接受在調(diào)用時(shí)行為發(fā)生變化的選項(xiàng)。我得出的結(jié)論是,熟悉 Git 底層命令對(duì)充分利用 Git 功能必不可少。正因?yàn)榇?#xff0c;我強(qiáng)烈建議同時(shí)安裝 G4W 和 Visual Studio 2017。(若要詳細(xì)了解 Git 底層命令和高層命令,請(qǐng)?jiān)L問(wèn)?git-scm.com/docs。)
?
圖 1:MinGit API 和 Git for Windows 命令行接口的往來(lái)通信路徑
低級(jí)別 Git
Visual Studio 開(kāi)發(fā)者在遷移到 Git 時(shí)很自然就會(huì)試圖利用 VCS 的現(xiàn)有知識(shí),如 Team Foundation Server (TFS)。用于描述這兩個(gè)系統(tǒng)中的操作的術(shù)語(yǔ)和概念(如簽出/簽入代碼,合并、分支等)的確存在重疊。不過(guò),如果因此假設(shè)類(lèi)似詞匯指代的基礎(chǔ)操作也類(lèi)似,是十足錯(cuò)誤和危險(xiǎn)的想法。這是因?yàn)榉稚⑹?Git VCS 存儲(chǔ)和跟蹤文件的方式,以及其實(shí)現(xiàn)熟悉的版本控制功能的方式是根本不同的。簡(jiǎn)單來(lái)說(shuō),在遷移到 Git 時(shí),最佳做法可能是完全忘掉關(guān)于集中式 VCS 所掌握的一切知識(shí),然后重新開(kāi)始學(xué)習(xí)。
在處理受 Git 源代碼管理的 Visual Studio 項(xiàng)目時(shí),典型的編輯/分段處理/提交流程如下: 根據(jù)需要,在項(xiàng)目中添加、編輯和刪除(以下統(tǒng)稱(chēng)為“更改”)文件。完成后,先對(duì)部分或所有這些更改進(jìn)行分段處理,然后再將文件提交到存儲(chǔ)庫(kù)中。提交后,這些更改就會(huì)變成存儲(chǔ)庫(kù)完整透明的修訂記錄的一部分?,F(xiàn)在,讓我們來(lái)了解一下 Git 是如何在內(nèi)部管理此流程的每一步的。
有向無(wú)環(huán)圖:在后臺(tái),每次提交最終成為 Git 托管的有向無(wú)環(huán)圖(圖論用語(yǔ)為“DAG”)上的頂點(diǎn)(節(jié)點(diǎn))。DAG 代表 Git 存儲(chǔ)庫(kù),每個(gè)頂點(diǎn)代表稱(chēng)為“提交對(duì)象”的數(shù)據(jù)元素(見(jiàn)圖 2)。DAG 中的頂點(diǎn)與稱(chēng)為“邊”的線(xiàn)相連;按照慣例,將 DAG 邊繪制為箭頭,這樣可以表示父/子關(guān)系(頭指向父頂點(diǎn);尾指向子頂點(diǎn))。原始頂點(diǎn)表示存儲(chǔ)庫(kù)的首次提交;終端頂點(diǎn)沒(méi)有子頂點(diǎn)。DAG 邊表示所連每個(gè)頂點(diǎn)之間的確切父子關(guān)系。由于 Git 提交對(duì)象(簡(jiǎn)稱(chēng)為“提交”)表示為頂點(diǎn),因此 Git 可以利用 DAG 結(jié)構(gòu),對(duì)所有提交之間的父子關(guān)系進(jìn)行建模,這樣 Git 便能夠生成從任意一次提交向后追溯到存儲(chǔ)庫(kù)初始提交的修訂記錄。此外,與線(xiàn)性圖不同的是,DAG 支持分支(一個(gè)父頂點(diǎn)有多個(gè)子頂點(diǎn))和合并(一個(gè)子頂點(diǎn)有多個(gè)父頂點(diǎn))。每當(dāng)提交對(duì)象生成一個(gè)新的子頂點(diǎn),便會(huì)生成 Git 分支;每當(dāng)多個(gè)提交對(duì)象合并成一個(gè)子頂點(diǎn)時(shí),便會(huì)發(fā)生合并。
?
圖 2:顯示頂點(diǎn)、邊、頭、尾、原始頂點(diǎn)和終端頂點(diǎn)的有向無(wú)環(huán)圖;3 個(gè)分支(A、B 和 C);2 個(gè)分支事件(在 A4 處);1 個(gè)合并事件(B3 和 A5 在 A6 處合并)
我已經(jīng)非常詳盡地介紹了 DAG 及其相關(guān)術(shù)語(yǔ),因?yàn)榇祟?lèi)知識(shí)是了解高級(jí) Git 操作的先決條件,掌握這些知識(shí)的具體方式往往為管理 Git DAG 上的頂點(diǎn)。此外,DAG 有助于直觀(guān)呈現(xiàn) Git 存儲(chǔ)庫(kù),廣泛用于教學(xué)資料、演示和 Git GUI 工具。
Git 對(duì)象概覽:到目前為止,我只提到了 Git 提交對(duì)象。不過(guò),實(shí)際上,Git 在存儲(chǔ)庫(kù)中存儲(chǔ)以下四種不同類(lèi)型的對(duì)象:提交、樹(shù)、blob 和標(biāo)記。若要調(diào)查以上每種類(lèi)型,請(qǐng)啟動(dòng) Visual Studio(我使用的是 Visual Studio 2017,但支持 Git 的舊版的運(yùn)作方式也類(lèi)似),然后使用“文件 | 新建項(xiàng)目”新建一個(gè)控制臺(tái)應(yīng)用程序。命名項(xiàng)目,選中“新建 Git 存儲(chǔ)庫(kù)”復(fù)選框,然后單擊“確定”。(如果之前沒(méi)有在 Visual Studio 中配置過(guò) Git,將會(huì)看到“Git 用戶(hù)信息”對(duì)話(huà)框。如果看到,請(qǐng)指定你的姓名和電子郵件地址,每次提交時(shí)此類(lèi)信息都會(huì)寫(xiě)入 Git 存儲(chǔ)庫(kù)。此外,若要對(duì)計(jì)算機(jī)上的每個(gè) Git 存儲(chǔ)庫(kù)使用此類(lèi)信息,請(qǐng)選中“設(shè)置全局 .gitconfig”復(fù)選框。)
完成后,打開(kāi)“解決方案資源管理器”窗口(見(jiàn)圖 3 中的標(biāo)記 1)??梢钥吹?#xff0c;文件旁邊顯示有淡藍(lán)色鎖形圖標(biāo),盡管我還沒(méi)有進(jìn)行過(guò)提交! (此示例表明,Visual Studio 有時(shí)可能會(huì)對(duì)存儲(chǔ)庫(kù)執(zhí)行非預(yù)期操作。) 若要確切了解 Visual Studio 執(zhí)行的操作,請(qǐng)查看當(dāng)前分支的修訂記錄。
?
圖 3:新建的 Visual Studio 項(xiàng)目及其 Git 存儲(chǔ)庫(kù)修訂記錄報(bào)告
Git 將默認(rèn)分支命名為“master”,并使之成為當(dāng)前分支。Visual Studio 在狀態(tài)欄的右邊緣顯示當(dāng)前分支的名稱(chēng)(標(biāo)記 2)。當(dāng)前分支表示 DAG 上將成為下一次提交的父級(jí)的提交對(duì)象(稍后將會(huì)詳細(xì)介紹分支)。若要查看當(dāng)前分支的提交修訂記錄,請(qǐng)單擊主分支標(biāo)簽(標(biāo)記 2),然后選擇菜單中的“查看修訂記錄”(標(biāo)記 3)。
隨即出現(xiàn)的“修訂記錄 - 主分支”窗口在多列中顯示信息。左側(cè)(標(biāo)記4)是 DAG 上的兩個(gè)頂點(diǎn);每個(gè)頂點(diǎn)均經(jīng)過(guò)圖形處理,在 Git DAG 上表示一次提交。“ID”、“作者”、“日期”和“消息”列(標(biāo)記 5)顯示每次提交的詳細(xì)信息。主分支的 HEAD 以深紅色指針(標(biāo)記 6)表示,我將在本文快結(jié)束時(shí)全面講解這其中的含義。此 HEAD 標(biāo)記了當(dāng)提交在 DAG 中添加了新頂點(diǎn)后下一個(gè)邊箭頭的頭位置。
報(bào)告顯示 Visual Studio 進(jìn)行了兩次提交,每次提交都有自己的提交 ID(標(biāo)記 7)。第一次(最早)提交由 ID a759f283 進(jìn)行唯一標(biāo)識(shí);第二次提交則由 bfeb0957 進(jìn)行唯一標(biāo)識(shí)。這些值截取自包含 40 個(gè)字符的完整十六進(jìn)制安全哈希算法 1 (SHA-1)。SHA-1 是一種加密哈希函數(shù),旨在通過(guò)獲取消息(如提交數(shù)據(jù))并創(chuàng)建消息摘要(即完整的 SHA-1 哈希值,如提交 ID)來(lái)檢測(cè)是否有損壞。簡(jiǎn)單來(lái)說(shuō),SHA-1 哈希算法的行為不僅類(lèi)似于校驗(yàn)和,還類(lèi)似于 GUID,因?yàn)橛写蠹s 1.46 x 1048 個(gè)唯一組合。與其他許多 Git 工具一樣,Visual Studio 僅顯示完整值的前 8 個(gè)字符,因?yàn)橛?43 億個(gè)惟一值,足以在日常工作中避免沖突發(fā)生。若要查看完整的 SHA-1 值,請(qǐng)將鼠標(biāo)懸停在“修訂記錄報(bào)告”(標(biāo)記 8)中的行之上。
雖然“查看修訂記錄報(bào)告”的消息列會(huì)指明每個(gè)提交的聲明用途(由提交者在提交過(guò)程中提供),但畢竟只是注釋而已。若要查看提交的實(shí)際更改,請(qǐng)右鍵單擊列表中的行,然后選擇“查看提交詳細(xì)信息”(見(jiàn)圖 4)。
?
圖 4:存儲(chǔ)庫(kù)前兩次提交的提交詳細(xì)信息
第一次提交(標(biāo)記 1)包含兩個(gè)更改:.gitignore 和 .gitattributes(我在上一篇文章中介紹過(guò)這些文件)。? 每個(gè)文件旁邊的“[添加]”表明文件是被添加到存儲(chǔ)庫(kù)中。第二次提交(標(biāo)記 2)不僅顯示添加了 5 個(gè)文件,還將父提交對(duì)象的 ID 顯示為可單擊鏈接。若要將完整的 SHA-1 值復(fù)制到剪貼板中,只需單擊“操作”菜單,然后選擇“復(fù)制提交 ID”即可。
在文件系統(tǒng)中實(shí)現(xiàn) Git 存儲(chǔ)庫(kù):若要查看 Git 如何在存儲(chǔ)庫(kù)中存儲(chǔ)這些文件,請(qǐng)右鍵單擊解決方案資源管理器中的解決方案(而不是項(xiàng)目),然后選擇文件資源管理器中的“打開(kāi)文件夾”。在解決方案的根目錄下,可以看到 .git 隱藏文件夾(如果看不到 .git,請(qǐng)單擊文件資源管理器“視圖”菜單中的“已隱藏項(xiàng)”)。.git 文件夾是項(xiàng)目的 Git 存儲(chǔ)庫(kù)。它的 objects 文件夾定義了 DAG: 所有 DAG 頂點(diǎn)以及所有頂點(diǎn)之間的全部父子關(guān)系都是通過(guò)文件進(jìn)行編碼,這些文件表示存儲(chǔ)庫(kù)中從原始頂點(diǎn)開(kāi)始的每次提交(再次見(jiàn)圖 2)。.git 文件夾的 HEAD 文件和 refs 文件夾定義了分支。讓我們來(lái)深入了解一下這些 .git 項(xiàng)。
探索 Git 對(duì)象
.git\objects 文件夾存儲(chǔ)所有類(lèi)型的 Git 對(duì)象:提交(對(duì)于提交)、樹(shù)(對(duì)于文件夾)、blob(對(duì)于二進(jìn)制文件)和標(biāo)記(易記的提交對(duì)象別名)。
提交對(duì)象:現(xiàn)在,是時(shí)候啟動(dòng) Git CLI 了??梢允褂贸S玫娜我夤ぞ?#xff08;Git Bash、PowerShell 或命令窗口)。我將使用 PowerShell。首先,轉(zhuǎn)到解決方案根目錄的 .git\objects 文件夾,然后列出其內(nèi)容(圖 5 中的標(biāo)記 1)??梢钥吹?#xff0c;它包含許多以?xún)蓚€(gè)字符的十六進(jìn)制值命名的文件夾。為了避免超出操作系統(tǒng)允許的文件夾內(nèi)含文件數(shù)量,Git 將從所有 40 個(gè)字節(jié)的 SHA-1 值中刪除的前兩個(gè)字符用作文件夾名稱(chēng),然后使用剩下的 38 個(gè)字符作為要存儲(chǔ)的對(duì)象的文件名。舉例來(lái)說(shuō),我項(xiàng)目中第一次提交的提交 ID 為 a759f283,因此對(duì)象所在文件夾的名稱(chēng)為 a7(ID 的前兩個(gè)字符)。與預(yù)期一樣,當(dāng)我打開(kāi)此文件夾時(shí),看到了名為 59f283 的文件。請(qǐng)注意,這些以十六進(jìn)制命名的文件夾中存儲(chǔ)的所有文件都是 Git 對(duì)象。為節(jié)省空間,Git 使用 zlib 壓縮對(duì)象存儲(chǔ)中的文件。由于這種壓縮會(huì)生成二進(jìn)制文件,因此無(wú)法使用文本編輯器來(lái)查看這些文件。相反,需要調(diào)用 Git 命令,從而正確解壓縮 Git 對(duì)象數(shù)據(jù),并使用能夠理解的格式來(lái)呈現(xiàn)數(shù)據(jù)。
?
圖 5:使用 Git 命令行接口探索 Git 對(duì)象
我已知道文件 59f283 包含一個(gè)提交對(duì)象,因?yàn)檫@是提交 ID。但有時(shí)會(huì)在 objects 文件夾中看到不知道是什么的文件。Git 提供 cat-file 底層命令來(lái)報(bào)告對(duì)象類(lèi)型以及所含內(nèi)容(標(biāo)記 3)。若要獲取類(lèi)型,請(qǐng)?jiān)谡{(diào)用命令時(shí)指定 -t(類(lèi)型)選項(xiàng),以及 Git 對(duì)象文件名的幾個(gè)惟一字符:
git cat-file -t a759f2
在我的系統(tǒng)中,此命令報(bào)告的值為“commit”,表明以 a759f2 開(kāi)頭的文件包含提交對(duì)象。雖然僅指定 SHA-1 哈希值的前 5 個(gè)字符通常就足夠了,但也可以根據(jù)需要提供任意數(shù)量的字符(不要忘記添加文件夾名稱(chēng)中的兩個(gè)字符)。使用 -p(優(yōu)質(zhì)打印)選項(xiàng)發(fā)出同一命令后,Git 會(huì)從提交對(duì)象提取信息,然后以清晰明了的格式呈現(xiàn)這些信息(標(biāo)記 4)。
提交對(duì)象包含以下屬性: 父提交 ID、樹(shù) ID、作者姓名、作者電子郵件地址、作者提交時(shí)間戳、提交者姓名、提交者電子郵件地址、提交者提交時(shí)間戳和提交消息(存儲(chǔ)庫(kù)中的第一次提交不顯示父提交 ID)。每個(gè)提交對(duì)象的 SHA-1 都是根據(jù)這些提交對(duì)象屬性中包含的所有值計(jì)算得出,這實(shí)際上保證了每個(gè)提交對(duì)象都有一個(gè)惟一提交 ID。
樹(shù)和 blob 對(duì)象:請(qǐng)注意,盡管提交對(duì)象包含提交的相關(guān)信息,但并不包含任何文件或文件夾。相反,包含的是指向 Git 樹(shù)對(duì)象的樹(shù) ID(也是 SHA-1 值)。樹(shù)對(duì)象和其他所有 Git 對(duì)象都存儲(chǔ)在 .git\objects 文件夾中。
圖 6?展示了每個(gè)提交對(duì)象包含的根樹(shù)對(duì)象。根樹(shù)對(duì)象進(jìn)而根據(jù)需要映射到 blob 對(duì)象(接下來(lái)我將介紹)和其他樹(shù)對(duì)象。
圖 6:直觀(guān)呈現(xiàn)表示提交的 Git 對(duì)象
由于我的項(xiàng)目中的第二次提交(提交 ID 為 bfeb09)包括文件和文件夾(見(jiàn)上面的圖 4),因此我將用它來(lái)說(shuō)明樹(shù)對(duì)象的工作方式。圖 7 中的標(biāo)記 1?展示了 cat?file??p bfeb09 輸出。這一次,請(qǐng)注意,其中包含可正確引用第一個(gè)提交對(duì)象的 SHA-1 值的父屬性。(請(qǐng)注意,此為提交對(duì)象的父引用,以便 Git 能夠構(gòu)造和維護(hù)提交 DAG。)
圖 7:使用 Git CLI 探索樹(shù)對(duì)象詳細(xì)信息
根樹(shù)對(duì)象進(jìn)而根據(jù)需要映射到 blob 對(duì)象(使用 zlib 壓縮的文件)和其他樹(shù)對(duì)象。
提交 bfeb09 包含 ID 為 ca853d 的樹(shù)屬性。圖 7 中的標(biāo)記 2?展示了 cat-file -p ca853d 輸出。每個(gè)樹(shù)對(duì)象包含與對(duì)象的 POSIX 權(quán)限掩碼(040000 = 目錄、100644 = 常規(guī)不可執(zhí)行文件、100664 = 常規(guī)不可執(zhí)行組可寫(xiě)文件、100755 = 常規(guī)可執(zhí)行文件、120000 = 符號(hào)鏈接和 160000 = Gitlink)對(duì)應(yīng)的權(quán)限屬性、類(lèi)型(樹(shù)或 blob)、SHA-1(對(duì)于樹(shù)或 blob)和名稱(chēng)。名稱(chēng)是文件夾名稱(chēng)(對(duì)于樹(shù)對(duì)象)或文件名(對(duì)于 blob 對(duì)象)。觀(guān)察發(fā)現(xiàn),此樹(shù)對(duì)象由 3 個(gè) blob 對(duì)象和另一個(gè)樹(shù)對(duì)象組成。可以看到,這 3 個(gè) blob 分別指的是文件 .gitattributes、.gitignore 和 DemoConsole.sln,而樹(shù)指的是文件夾 DemoConsoleApp(圖 7 中的標(biāo)記 3)。盡管樹(shù)對(duì)象 ca853d 與項(xiàng)目的第二次提交相關(guān)聯(lián),但它的前兩個(gè) blob 表示第一次提交時(shí)添加的文件 .gitattributes 和 .gitignore(見(jiàn)圖 4 中的標(biāo)記 1)! 這些文件之所以會(huì)出現(xiàn)在第二次提交的樹(shù)中是因?yàn)?#xff0c;每次提交表示的是上一個(gè)提交對(duì)象,以及當(dāng)前提交對(duì)象捕獲的更改。若要更深入地“遍歷樹(shù)”,請(qǐng)參閱圖 7 中的標(biāo)記 3,其中展示了 cat-file -p a763da 輸出,包含另外 3 個(gè) blob(App.config、emoConsoleApp.csproj 和 Program.cs)和另一個(gè)樹(shù)(文件夾屬性)。
blob 對(duì)象也是直接使用 zlib 進(jìn)行壓縮的文件。如果未壓縮的文件包含文本,可以使用相同的 cat-file 命令和 blob ID 提取 blob 的全部?jī)?nèi)容(圖 7 中的標(biāo)記 5)。由于 blob 對(duì)象表示的是文件,因此 Git 使用 SHA-1 blob ID來(lái)確定文件是否自上次提交后發(fā)生變化;還使用 SHA-1 值對(duì)存儲(chǔ)庫(kù)中的任意兩次提交進(jìn)行差異對(duì)比。
標(biāo)記對(duì)象:鑒于 SHA-1 值的加密字母數(shù)字性,溝通起來(lái)可能有點(diǎn)難。使用標(biāo)記對(duì)象,可以為任何提交、樹(shù)或 blob 對(duì)象分配易記名稱(chēng),盡管最常見(jiàn)的做法是只標(biāo)記提交對(duì)象。標(biāo)記對(duì)象的類(lèi)型分為以下兩種:輕量級(jí)和注釋。這兩種類(lèi)型的對(duì)象都作為 .git\refs\tags 文件夾中的文件顯示(其中,標(biāo)記名稱(chēng)就是文件名)。輕量級(jí)標(biāo)記文件的內(nèi)容是現(xiàn)有提交、樹(shù)或 blob 對(duì)象的 SHA-1。注釋標(biāo)記文件的內(nèi)容是與其他所有 Git 對(duì)象一同存儲(chǔ)在 .git\objects 文件夾中的標(biāo)記對(duì)象的 SHA-1。若要查看標(biāo)記對(duì)象的內(nèi)容,可以使用相同的 cat-file -p 命令??梢钥吹綐?biāo)記對(duì)象的 SHA-1 值,以及對(duì)象類(lèi)型、標(biāo)記作者、日期時(shí)間和標(biāo)記消息。在 Visual Studio 中,可以通過(guò)許多種方法來(lái)標(biāo)記提交。一種方法是單擊“提交詳細(xì)信息”窗口(見(jiàn)上面圖 3 中的標(biāo)記 3)中的“創(chuàng)建標(biāo)記”鏈接?!疤峤辉敿?xì)信息”窗口(見(jiàn)上面圖 3 中的標(biāo)記 3)和“查看修訂記錄報(bào)告”(見(jiàn)上面圖 3 中的標(biāo)記 9)中顯示了標(biāo)記名稱(chēng)。
向存儲(chǔ)庫(kù)中的對(duì)象應(yīng)用存儲(chǔ)優(yōu)化時(shí),Git 會(huì)在 .git\objects 文件夾中填充信息和包文件夾。我將在近期發(fā)表的文章中更全面地介紹這些文件夾和 Git 文件存儲(chǔ)優(yōu)化。
了解這 4 種類(lèi)型的 Git 對(duì)象后,我發(fā)現(xiàn)可以將 Git 稱(chēng)為“內(nèi)容可尋址的文件系統(tǒng)”,因?yàn)槿我鈹?shù)量文件和文件夾中的任何類(lèi)型內(nèi)容都可以簡(jiǎn)化成一個(gè) SHA-1 值。稍后,可以使用相應(yīng)的 SHA-1 值,準(zhǔn)確可靠地重新創(chuàng)建同一內(nèi)容。從另一個(gè)角度來(lái)說(shuō),在慣常的密鑰索引驅(qū)動(dòng)查找表的高級(jí)實(shí)現(xiàn)中,SHA-1 是鍵,內(nèi)容是值。此外,如果文件內(nèi)容在兩次提交之間沒(méi)有發(fā)生變化,Git 可以節(jié)省開(kāi)支,因?yàn)槲窗l(fā)生變化的文件生成的 SHA-1 值相同。也就是說(shuō),提交對(duì)象可以引用上一次提交使用的相同 SHA-1 blob 或樹(shù) ID 值,而無(wú)需新建任何對(duì)象,即無(wú)需新建文件副本!
分支
必須先了解 Git 是如何在內(nèi)部定義分支的,才能真正理解什么是 Git 分支??偟膩?lái)說(shuō),這歸結(jié)為理解以下兩個(gè)關(guān)鍵詞的用途:頭和 HEAD。
第一個(gè)關(guān)鍵詞“頭”(英文為全部字母小寫(xiě))是 Git 為每個(gè)新建的提交對(duì)象維護(hù)的引用。為了闡明具體工作方式,圖 8?展示了多個(gè)提交和分支操作。對(duì)于提交 01,Git 為存儲(chǔ)庫(kù)創(chuàng)建了第一個(gè)頭引用,并將其默認(rèn)命名為“master”(master 是沒(méi)有任何特殊含義的任意名稱(chēng),只是一個(gè)默認(rèn)名稱(chēng)而已,Git 團(tuán)隊(duì)經(jīng)常會(huì)重命名此引用)。新建頭引用后,Git 會(huì)在 ref\heads 文件夾中創(chuàng)建一個(gè)文本文件,并將新提交對(duì)象的完整 SHA?1 置于此文件中。對(duì)于提交 01,也就是說(shuō),Git 會(huì)創(chuàng)建一個(gè)名為“master”的文件,并將提交對(duì)象 A1 的 SHA-1 置于此文件中。對(duì)于提交 02,Git 會(huì)刪除舊 SHA-1 值,并將其替換成 A2 的完整 SHA-1 提交 ID,從而更新 heads 文件夾中的 master 頭文件。Git 會(huì)對(duì)提交 03 執(zhí)行相同操作: 它會(huì)將 heads 文件夾中的 master 頭文件更新為包含 A3 的完整提交 ID。
圖 8:2 個(gè)頭好過(guò) 1 個(gè)頭:Git 在 heads 文件夾中維護(hù)各種文件以及一個(gè) HEAD 文件
大家可能已經(jīng)猜到,heads 文件夾中的 master 文件就是它指向的提交對(duì)象的分支名稱(chēng)。奇怪的是,分支名稱(chēng)也許最初指向一個(gè)提交對(duì)象,而不是一系列提交對(duì)象(我很快就會(huì)詳細(xì)介紹這一特定概念)。
請(qǐng)觀(guān)察圖 8?中的“創(chuàng)建分支和簽出文件”部分。其中,用戶(hù)在 Visual Studio 中為打印預(yù)覽功能新建了一個(gè)分支。用戶(hù)將此分支命名為 feat_print_preview,讓其以 master 為依據(jù),然后在團(tuán)隊(duì)資源管理器的“從選定項(xiàng)創(chuàng)建本地分支”窗格中選中了“簽出分支”復(fù)選框。選中此復(fù)選框即指示 Git 要讓新分支成為當(dāng)前分支(我很快就會(huì)對(duì)此進(jìn)行解釋)。在后臺(tái),Git 在 heads 文件夾中新建了一個(gè) feat_print_preview 頭文件,并將提交對(duì)象 A3 的 SHA-1 值置于其中。也就是說(shuō),現(xiàn)在 heads 文件夾中包含以下兩個(gè)文件:master 和 feat_print_preview。這兩個(gè)文件都指向 A3。
在提交 04 中,Git 需要做出一項(xiàng)決定: 通常情況下,它會(huì)更新 heads 文件夾中文件引用的 SHA-1 值。而現(xiàn)在,此文件夾中有兩個(gè)文件引用,該更新哪個(gè)文件引用呢? 此時(shí),HEAD 就派上用場(chǎng)了。HEAD(所有字母大寫(xiě))是 .git 文件夾根目錄下的一個(gè)文件,指向 heads 文件夾中的頭(英文為全部字母小寫(xiě))文件。(請(qǐng)注意,“HEAD”實(shí)際上就是一個(gè)一直被命名為 HEAD 的文件,而“頭”文件則沒(méi)有特定的名稱(chēng)。) 頭文件 HEAD 包含將分配為下一個(gè)提交對(duì)象的父 ID 的提交 ID。實(shí)際上,HEAD 標(biāo)記的是 Git 當(dāng)前在 DAG 上的位置。也就是說(shuō),可能有很多頭,但始終只有一個(gè) HEAD。
再回到圖 8,提交 01 顯示 HEAD 指向 master 頭文件,進(jìn)而指向 A1(也就是說(shuō),master 頭文件包含提交對(duì)象 A1 的 SHA-1)。在提交 02 中,Git 不需要對(duì) HEAD 文件執(zhí)行任何操作,因?yàn)?HEAD 已經(jīng)指向文件 master。提交 03 同上。不過(guò),在“創(chuàng)建和簽出新分支”步驟中,用戶(hù)創(chuàng)建了一個(gè)分支,并通過(guò)選中“簽出分支”復(fù)選框簽出了分支文件。作為響應(yīng),Git 將 HEAD 更新為指向 feat_print_preview 頭文件,而不是 master。(如果用戶(hù)沒(méi)有選中“簽出分支”復(fù)選框,那么 HEAD 會(huì)繼續(xù)指向 master。)
了解 HEAD 后,現(xiàn)在可以看到提交 04 不再需要 Git 做出任何決定: Git 只需檢查 HEAD 的值,發(fā)現(xiàn)它指向的是 feat_print_preview 頭文件。然后,便確定必須將 feat_print_preview 頭文件中的 SHA-1 更新為包含 B1 的提交 ID。
在“簽出分支”步驟中,用戶(hù)訪(fǎng)問(wèn)了團(tuán)隊(duì)資源管理器的“分支”窗格,右鍵單擊了“主分支”,然后選擇了“簽出”。作為響應(yīng),Git 簽出提交 A3 的文件,并將 HEAD 文件更新為指向 master 頭文件。
此時(shí),應(yīng)該非常清楚為什么 Git 中的分支操作如此高效快速: 新建分支可以歸結(jié)為創(chuàng)建一個(gè)文本文件(頭文件)和更新另一個(gè)文本文件 (HEAD)。切換分支只涉及更新一個(gè)文本文件 (HEAD),造成的性能影響通常很小,因?yàn)楣ぷ髂夸浿械奈募菑拇鎯?chǔ)庫(kù)進(jìn)行更新。
請(qǐng)注意,提交對(duì)象不包含任何分支信息! 實(shí)際上,僅通過(guò) HEAD 文件和 heads 文件夾中用作引用的各種文件來(lái)維護(hù)分支。然而,使用 Git 的開(kāi)發(fā)者在談到分支或引用分支時(shí),口語(yǔ)上通常指的是源自 master 或新組建的分支的一系列提交對(duì)象。上面的圖 2?展示了許多開(kāi)發(fā)者可以確定的三個(gè)分支: A、B 和 C。分支 A 從 A1 一直到 A6。A4 處的分支活動(dòng)生成了兩個(gè)新分支: B1 和 C1。因此,可以將從 B1 一直到 B3 的提交稱(chēng)為分支 B,而將從 C1 一直到 C2 的提交稱(chēng)為分支 C。
這里得出的結(jié)論是,不要忘記 Git 分支的形式定義: 就是指向提交對(duì)象的指針。此外,Git 維護(hù)所有分支(稱(chēng)為“頭”)的分支指針和當(dāng)前分支(稱(chēng)為“HEAD”)的一個(gè)分支指針。
Jonathan Waldman?是一名 Microsoft 認(rèn)證專(zhuān)家,專(zhuān)攻軟件工效學(xué),從 Microsoft 技術(shù)誕生之際便一直研究這些技術(shù)。Waldman 是 Pluralsight 技術(shù)團(tuán)隊(duì)的成員,目前負(fù)責(zé)機(jī)構(gòu)和私企軟件開(kāi)發(fā)項(xiàng)目。可以通過(guò)?jonathan.waldman@live.com?與他聯(lián)系。
原文地址:https://msdn.microsoft.com/en-us/magazine/mt809117
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的面向 Visual Studio 开发者的 Git 内部源代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: asp.net core新特性(1):T
- 下一篇: 再谈消息队列技术
