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