使用子模組和子樹來管理 Git 專案

2020-05-23 20:14:00

使用子模組和子樹來幫助你管理多個儲存庫中共有的子專案。

如果你參與了開源專案的開發,那麼你很可能已經用了 Git 來管理你的原始碼。你可能遇到過有很多依賴和/或子專案的專案。你是如何管理它們的?

對於一個開源組織,要實現社群產品的單一來源文件和依賴管理比較棘手。文件和專案往往會碎片化和變得冗餘,這致使它們很難維護。

必要性

假設你想把單個專案作為一個儲存庫內的子專案,傳統的方法是把該專案複製到父儲存庫中,但是,如果你想要在多個父專案中使用同一個子專案呢?如果把子專案複製到所有父專案中,當有更新時,你都要在每個父專案中做修改,這是不太可行的。這會導致父專案中的冗餘和資料不一致,使更新和維護子專案變得很困難。

Git 子模組和子樹

如果你可以用一條命令把一個專案放進另一個專案中,會怎樣呢?如果你隨時可以把一個專案作為子專案新增到任意數目的專案中,並可以同步更新修改呢?Git 提供了這類問題的解決方案:Git 子模組submodule和 Git 子樹subtree。建立這些工具的目的是以更加模組化的水平來支援共用程式碼的開發工作流,旨在 Git 儲存庫原始碼管理source-code management(SCM)與它下面的子樹之間架起一座橋樑。

Cherry tree growing on a mulberry tree

生長在桑樹上的櫻桃樹

下面是本文要詳細介紹的概念的一個真實應用場景。如果你已經很熟悉樹形結構,這個模型看起來是下面這樣:

Tree with subtrees

Git 子模組是什麼?

Git 在它預設的包中提供了子模組,子模組可以把 Git 儲存庫嵌入到其他儲存庫中。確切地說,Git 子模組指向子樹中的某次提交。下面是我 Docs-test GitHub 儲存庫中的 Git 子模組的樣子:

Git submodules screenshot

資料夾@提交 Id 格式表明這個儲存庫是一個子模組,你可以直接點選資料夾進入該子樹。名為 .gitmodules 的組態檔包含所有子模組儲存庫的詳細資訊。我的儲存庫的 .gitmodules 檔案如下:

Screenshot of .gitmodules file

你可以用下面的命令在你的儲存庫中使用 Git 子模組:

克隆一個儲存庫並載入子模組

克隆一個含有子模組的儲存庫:

$ git clone --recursive <URL to Git repo>

如果你之前已經克隆了儲存庫,現在想載入它的子模組:

$ git submodule update --init

如果有巢狀的子模組:

$ git submodule update --init --recursive

下載子模組

序列地連續下載多個子模組是很枯燥的工作,所以 clonesubmodule update 會支援 --jobs (或 -j)引數:

例如,想一次下載 8 個子模組,使用:

$ git submodule update --init --recursive -j 8$ git clone --recursive --jobs 8 <URL to Git repo>

拉取子模組

在執行或構建父專案之前,你需要確保依賴的子專案都是最新的。

拉取子模組的所有修改:

$ git submodule update --remote

使用子模組建立儲存庫:

向一個父儲存庫新增子樹:

$ git submodule add <URL to Git repo>

初始化一個已存在的 Git 子模組:

$ git submodule init

你也可以通過為 submodule update 命令新增 --update 引數在子模組中建立分支和追蹤提交:

$ git submodule update --remote

更新子模組的提交

上面提到過,一個子模組就是一個指向子樹中某次提交的連結。如果你想更新子模組的提交,不要擔心。你不需要顯式地指定最新的提交。你只需要使用通用的 submodule update 命令:

$ git submodule update

就像你平時建立父儲存庫和把父儲存庫推播到 GitHub 那樣新增和提交就可以了。

從一個父儲存庫中刪除一個子模組

僅僅手動刪除一個子專案資料夾不會從父專案中移除這個子專案。想要刪除名為 childmodule 的子模組,使用:

$ git rm -f childmodule

雖然 Git 子模組看起來很容易上手,但是對於初學者來說,有一定的使用門檻。

Git 子樹是什麼?

Git 子樹 subtree,是在 Git 1.7.11 引入的,讓你可以把任何儲存庫的副本作為子目錄嵌入另一個儲存庫中。它是 Git 專案可以注入和管理專案依賴的幾種方法之一。它在常規的提交中儲存了外部依賴資訊。Git 子樹提供了整潔的整合點,因此很容易復原它們。

如果你參考 GitHub 提供的子樹教學來使用子樹,那麼無論你什麼時候新增子樹,在本地都不會看到 .gittrees 組態檔。這讓我們很難分辨哪個是子樹,因為它們看起來很像普通的資料夾,但是它們卻是子樹的副本。預設的 Git 包中不提供帶 .gittrees 組態檔的 Git 子樹版本,因此如果你想要帶 .gittrees 組態檔的 git-subtree 命令,必須從 Git 原始碼儲存庫的 /contrib/subtree 資料夾 下載 git-subtree。

你可以像克隆其他常規的儲存庫那樣克隆任何含有子樹的儲存庫,但由於在父儲存庫中有整個子樹的副本,因此克隆過程可能會持續很長時間。

你可以用下面的命令在你的儲存庫中使用 Git 子樹。

向父儲存庫中新增一個子樹

想要向父儲存庫中新增一個子樹,首先你需要執行 remote add,之後執行 subtree add 命令:

$ git remote add remote-name <URL to Git repo>$ git subtree add --prefix=folder/ remote-name <URL to Git repo> subtree-branchname

上面的命令會把整個子專案的提交歷史合併到父儲存庫。

向子樹推播修改以及從子樹拉取修改

$ git subtree push-all

或者

$ git subtree pull-all

你應該使用哪個?

任何工具都有優缺點。下面是一些可能會幫助你決定哪種最適合你的特性:

  • Git 子模組的儲存庫佔用空間更小,因為它們只是指向子專案的某次提交的連結,而 Git 子樹儲存了整個子專案及其提交歷史。
  • Git 子模組需要在伺服器中可存取,但子樹是去中心化的。
  • Git 子模組大量用於基於元件的開發,而 Git 子樹多用於基於系統的開發。

Git 子樹並不是 Git 子模組的直接可替代項。有明確的說明來指導我們該使用哪種。如果有一個歸屬於你的外部儲存庫,使用場景是向它回推程式碼,那麼就使用 Git 子模組,因為推播程式碼更容易。如果你有第三方程式碼,且不會向它推播程式碼,那麼使用 Git 子樹,因為拉取程式碼更容易。

自己嘗試使用 Git 子樹和子模組,然後在評論中留下你的使用感想。