程式碼託管從業者 Git 指南
阿新 • • 發佈:2021-01-26
>本文作者:李博文 - CODING 後端開發工程師
## 前言
六七年前,我機緣巧合進入了程式碼託管行業,做過基於 Git 支援 SVN 客戶端接入、Git 程式碼託管平臺分散式、Git 程式碼託管讀寫分離、Git 程式碼託管高可用等工作,所幸學到了一些知識,積累了一些經驗,本次分享我的一點經驗之談,希望對即將進入或者已在程式碼託管行業的朋友有所幫助。
## Git 的發展歷史
### 版本控制系統的發展歷史
版本控制系統歷史悠久,最早的開源的版本控制系統可以追溯到幾乎與 C 語言同時誕生的 [Source Code Control System (SCCS)](https://en.wikipedia.org/wiki/Source_Code_Control_System),作者**Marc J. Rochkind**來自著名的貝爾實驗室,他於 1973 年釋出了 SCCS 的初始版本。SCCS 的壽命悠久,直到 2007 年再沒有人維護而終結。SCCS 本質上是一種 **Local Only** 版本控制系統,如今網路快速發展,無法跟上時代的腳步只能消亡,同類型的 RCS 雖然維護至今,也鮮有人問津。
![](https://img2020.cnblogs.com/blog/1594319/202101/1594319-20210126140549161-1724865003.png)
1986 年誕生的 [CVS](https://en.wikipedia.org/wiki/Concurrent_Versions_System) 是一款真正的自由軟體,使用 GPL 協議釋出。一個有趣的事實是:CVS 是 RCS 的前端,也就是說 CVS 將 RCS 從 **Local Only** 變成了 **Client-Server** 版本控制系統。隨著 2000 年 [Apache Subversion](https://en.wikipedia.org/wiki/Apache_Subversion) 誕生,CVS 的市場快速萎縮,到了 2008 年 CVS 不再維護,集中式版本控制系統漸漸也只剩 Subversion 在維護了。
最早的分散式版本控制系統是 1992 年誕生的 [Sun WorkShop TeamWare](https://en.wikipedia.org/wiki/Sun_WorkShop_TeamWare),但它並沒有發展的很好。從 2000 年到 2007 年,分散式版本控制系統如雨後春筍一樣冒了出來,2005 年誕生的 [Git](https://en.wikipedia.org/wiki/Git) 和 [Mercurial](https://en.wikipedia.org/wiki/Mercurial) 幸運流傳開來,時至今日,Git 終於在版本控制領域獨佔鰲頭。
### Git 的發展史
2005 年,開發 BitKeeper 的商業公司結束與 Linux 核心開源社群的合作關係,他們收回了 Linux 核心社群免費使用 BitKeeper 的權利。Linus Torvalds 花了十天時間編寫了 Git 的第一個版本,Git 的故事由此展開。
Git 原本只能在 Linux 上執行,隨著開源社群的參與,逐漸能在各個平臺上執行。在 Windows 上,最初有兩個方案,一個是讓 Git 在 Cygwin 的環境下編譯,Cygwin 是 Windows 上的 POSIX 相容層,但缺陷是需要帶一大堆 DLL。另一個方案是 msysgit,基於 MSYS 執行時,MYSY 是更小的 POSIX 相容環境。到了 2015 年,msysgit 不再維護,主要開發者基於 MSYS2 環境推出了 [Git for Windows](https://github.com/git-for-windows/git),而 MSYS2 的核心執行時基於 Cygwin 進行了定製。值得一提的是,在 Git for Windows 中,Git 命令並不是基於 MSYS2 執行時,而是原生的 Windows 程式,到今天我們已經可以使用 Visual C++ 編譯 Git 原始碼了,Git for Windows 的維護者 [Johannes Schindelin](https://github.com/dscho) 加入微軟後,在 Windows 上使用 Git 的體驗也越來越好。
2008 年 11 月 [Shawn O. Pearce](https://github.com/spearce) 寫下了 [libgit2](https://github.com/libgit2/libgit2) 的第一個提交;2009 年 9 月,Shawn 寫下了 [JGit](https://github.com/eclipse/jgit) 的第一個提交。Libgit2/jgit 被程式碼託管平臺,Git 客戶端廣泛使用,比如 GitHub 使用 libgit2 的 Ruby 繫結 [rugged](https://github.com/libgit2/rugged) 提供頁面讀寫儲存庫能力。遺憾的是 Shawn 已經離開這個世界兩年多了。
再來回顧 Git 的一些大事件:2008 年 GitHub 誕生,是最成功的程式碼託管平臺,幾乎以一己之力帶來了 Git 的繁榮;2008 年 BitBucket 誕生,最初 BitBucket 還支援 Mercurial,到了 2020 年已不再支援;2011 年 GitLab 誕生,而國內的 Gitee 也是基於 GitLab 發展而來的;2014 年 CODING 成立,國內國外程式碼託管平臺百花齊放;2018 年,微軟花費 75 億美元收購 GitHub,大家才猛然發現,基於 Git 的程式碼託管平臺已經有了這樣大的價值。
Git 是一個充滿活力的版本控制系統,每一年,Git 的開發者們都在將他們新的知識、經驗實踐到 Git 中。2018 年 5 月,在谷歌工作的 Git 開發者們釋出了 Git Wire Protocol,這解決了 Git 協議中最低效的部分;到了 2020 年 10 月,Git 實驗性地支援 SHA256 雜湊演算法,在 SHA1 被破解幾年後,我們終於可以在 Git 中嘗試淘汰 SHA1 了。
Git 的發展必然會擠佔其他版本控制系統份額,隨著 Git 越來越流行,更多的專案也從其他的版本控制系統遷移到 Git 上來:
+ 編譯器基礎設施 LLVM 從 SVN 遷移到 Git
+ FreeBSD 從 SVN 遷移到 Git
+ GCC(仍處於遷移過程中)從 SVN 遷移到 Git
+ Windows 原始碼(已經遷移到 Git,使用 VFS for Git 技術)
+ VIM 遷移到 GitHub
+ OpenJDK 從 Mercurial 遷移到 Git
2016 年,Git 誕生11年之後,[BitKeeper](https://github.com/bitkeeper-scm/bitkeeper) 宣佈採用 Apache 2.0 許可協議開源,如果再回到 2005 年,BitKeeper 又會做出怎樣的抉擇呢?
## Git 的儲存原理
對於程式碼託管從業人員來說,只瞭解 Git 的使用並不足以參與程式碼託管平臺服務開發和架構優化等工作,所以瞭解 Git 的一些原理非常必要。
### Git 的目錄結構
首先需要了解 Git 儲存庫的目錄結構,Git 儲存庫分為常規儲存庫和 Bare (裸)儲存庫,普通使用者從遠端克隆下來的儲存庫,或者本地初始化的儲存庫大多是常規儲存庫,這類儲存庫和特定的工作區相關聯;另一類是沒有工作區的儲存庫,就是裸儲存庫,在程式碼託管平臺的伺服器上,儲存庫幾乎都是以裸儲存庫的方式儲存的。對於常規儲存庫而言,其儲存庫真正的路徑是工作區根目錄下的 `.git` 資料夾,或者 `.git` 檔案指向的目錄,後者通常用於 Git 子模組。
知道了 Git 儲存庫的位置,就可以檢視儲存庫的目錄結構,下面是一個檢視儲存庫的截圖。
![](https://img2020.cnblogs.com/blog/1594319/202101/1594319-20210126140608634-1286019936.png)
不同的目錄具備不同的作用,大致如下:
| 路徑 | 屬性 | 作用 | 備註 |
| ----------- | ---- | ---------------------------------------- | ----------------------------------------------------------- |
| HEAD | `R` | 儲存當前檢出的引用或者提交 ID | 在遠端伺服器上用於展示預設分支 |
| config | `R` | 儲存庫配置 | 儲存庫配置優先順序高於使用者配置,使用者配置優先順序高於系統配置 |
| branches | `D` | `deprecated` | |
| description | `R` | `depracated` | |
| hooks | `D` | Git 鉤子目錄,包括服務端鉤子和客戶端鉤子 | 當設定了 `core.hooksPath` 時,則會從設定的鉤子目錄查詢鉤子 |
| info | `D` | 儲存庫資訊 | dump 協議依賴,但目前 dump 協議已無人問津 |
| objects | `D` | 儲存庫物件儲存目錄 | |
| refs | `D` | 儲存庫引用儲存目錄 | |
| packed-refs | `R` | 儲存庫打包引用儲存檔案 | 該檔案可能不存在,執行 `git pack-refs` 或者 `git gc` 後出現 |
在這些目錄或者檔案中,最重要的是 `objects` 和 `refs` ,只需要兩個目錄的資料就可以重建儲存庫了。在 `objects` 目錄下,Git 物件可能以鬆散物件也可能以打包物件的形式儲存:
| 路徑 | 描述 |
| ------------------------------ | -------------------------------------------------------- |
| `objects/[0-9a-f][0-9a-f]` | 鬆散物件儲存目錄,最多有 256 個這樣的子目錄 |
| `objects/pack` | 打包物件目錄,除了打包物件,還有打包物件索引,多包索引等 |
| `objects/info` | 儲存儲存庫擴充套件資訊 |
| `objects/info/packs` | 啞協議依賴 |
| `objects/info/alternates` | 儲存庫物件借用技術 |
| `objects/info/http-alternates` | 儲存庫物件借用,用於 HTTP fetch |
Git 在實現其複雜功能的時候還會建立一些其他目錄,更詳細的細節可以查閱:[Git Repository Layout](https://github.com/git/git/blob/master/Documentation/gitrepository-layout.txt)。
### Git 物件的儲存
Git 的物件可以按照鬆散物件的格式儲存,也可以按照打包物件的格式儲存,使用者將檔案納入版本控制時,Git 會將檔案的型別標記為 `blob`,將檔案長度和 `\x00` 以及檔案內容合併在一起計算 SHA1 雜湊值後,使用 Deflate 壓縮,儲存到儲存庫的 objects 目錄下,路徑匹配正則為 `objects\/[0-9a-f]{2}\/[0-9a-f]{38}$`,當然如果使用 SHA256 則應該匹配 `objects\/[0-9a-f]{2}\/[0-9a-f]{62}$`,鬆散物件的空間佈局如下:
![](https://img2020.cnblogs.com/blog/1594319/202101/1594319-20210126140632541-1943321336.png)
Git 使用的 Deflate 是 [Phil Katz](https://en.wikipedia.org/wiki/Phil_Katz) 為 PKZIP 建立的壓縮演算法,也是使用最廣泛的壓縮演算法之一,其變體 GZIP 也被廣泛用於 POSIX 檔案壓縮和 HTTP 壓縮。Git 命令列,libgit2 目前依賴 zlib 提供 deflate 演算法,jgit 則使用 Java 提供的 deflate 實現,Golang 則在 `compress/zlib` 包中提供 `deflate` 支援,但演算法實現在 `compress/flate`,嚴格來說 Git 使用的是 deflate 的 zlib 包裝,比如我們使用 zlib 建立 zip 壓縮包時會使用 `-15` 作為 `WindowBits`,而在建立 GZIP 時會使用 `31` 作為 `WindowBits`,在 Git 中,則會使用 `15` 作為 `WindowBits`。
在 Git 中,除了有 `blob` 物件,還有 `commit` ,`tag`,以及 `tree` ,`commit` 物件儲存了使用者的提交資訊,`tree` 顧名思義,儲存的是目錄結構。下面是一個 commit 物件的內容:
```text
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Scott