Git 子模組可讓您將一個 Git 儲存庫嵌套在另一個 Git 儲存庫中,同時為每個儲存庫維護獨立的版本控制。它們對於管理共用程式庫、第三方相依性或需要精確提交等級控制的組態集合非常有用。然而,子模組具有複雜性,需要了解其底層機制。
了解 Git 子模組
子模組是從父存儲庫到另一個存儲庫中的特定提交的引用。 Git 將其作為樹物件儲存在父儲存庫的索引中,子模組路徑到儲存庫 URL 的對應位於父儲存庫根目錄下的 .gitmodules 檔案中。
# .gitmodules
[submodule "lib/shared"]
path = lib/shared
url = https://github.com/org/shared-library.git
當您複製包含子模組的儲存庫時,子模組目錄將顯示為空,直到您初始化並取得子模組內容為止。父儲存庫不直接儲存子模組文件,僅儲存提交引用。這意味著父模組和子模組仍然是獨立的項目,可以按不同的時間表發展。
基本操作
新增子模組很簡單,但您會經常使用以下幾個操作:
# Add a submodule
git submodule add https://github.com/example/lib.git lib/example
# Clone a repository with all submodules
git clone --recurse-submodules https://github.com/org/parent.git
# Initialize submodules in an existing clone
git submodule init
git submodule update
# Update submodules to their latest remote commits
git submodule update --remote
預設情況下,父儲存庫將子模組固定到其目前提交,並且在特定提交簽出的子模組進入分離的 HEAD 狀態。這意味著父模組不會自動追蹤子模組內的變更。您必須在父儲存庫中明確提交子模組變更以記錄新的提交引用。
使用子模組分支
對於活躍的開發工作流程,您可以配置子模組來追蹤特定分支而不是固定提交:
git submodule set-branch --branch main lib/shared
git submodule update --remote
當您在子模組內進行變更時,工作流程需要幾個步驟:
cd lib/shared
git checkout -b feature/new-feature
# make changes, commit, and push
git push origin feature/new-feature
cd ../..
git add lib/shared
git commit -m "Update shared library to latest"
與 HEAD 的分離常常讓新用戶感到困惑。一種實用的模式是使用 git submodule foreach 跨所有子模組執行指令:
git submodule foreach 'git checkout main && git pull'
持續集成/持續交付集成
配置 CI 管道來處理子模組需要明確的步驟,因為大多數 CI 環境預設不會取得子模組。
| CI平台 | 配置 |
|---|---|
| GitHub 操作 | actions/checkout@v4 與 submodules: true |
| 亞搏體育appGitLab CI | 佔位符_0 |
| 詹金斯 | 檢查 SCM 配置中的子模組 |
# GitHub Actions example
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.PAT_TOKEN }}
私有子模組儲存庫需要身份驗證。使用具有儲存庫存取權限的個人存取權杖 (PAT),並將其作為機密儲存在 CI 平台中。對於緩存,請考慮從以 .gitmodules 檔案哈希為鍵的快取中恢復子模組目錄,以加快 CI 運行速度。
替代方案比較
子模組並不總是正確的選擇。根據您的專案需求考慮這些替代方案:
| 方法 | 何時使用 | 關鍵權衡 |
|---|---|---|
| Git 子模組 | 獨立的倉庫,不同的團隊 | 複雜性、獨立性 |
| Git 子樹 | 單一儲存庫,偶爾同步 | 歷史複製 |
| 莫諾雷波 | 緊密耦合的專案 | 擴展挑戰 |
| 套件管理器 | 語言層級依賴 | 版本發布開銷 |
Git 子樹將外部儲存庫合併為儲存庫中的子目錄。與子模組不同,所有檔案在克隆後都會立即出現,無需額外的步驟。但是,子樹操作會重寫歷史記錄並可能使您的儲存庫膨脹。 Nx、Turborepo 和 pnpm 工作區等 Monorepo 工具在 JavaScript 和 TypeScript 專案中越來越受歡迎,它們提供依賴項管理和建置編排,而無需使用複雜的子模組。
常見陷阱
幾個反覆出現的問題困擾著子模組使用者。分離的 HEAD 混淆是最常見的 - 永遠記住,位於子模組內意味著您位於分離的 HEAD 上,除非您明確檢出分支。刪除子模組需要多個指令:git submodule deinit、git rm,以及手動清理 .gitmodules 和 .git/config。嵌套子模組(子模組內的子模組)會增加指數級的複雜性,除非絕對必要,否則應避免使用。
Windows 和 Unix 系統之間會出現特定於平台的路徑問題。為了防止問題,請在 .gitmodules 路徑中使用正斜線並避免僅區分大小寫的名稱差異。對於淺克隆以節省空間,請使用 git clone --recurse-submodules --shallow-submodules。
先進技術
git submodule foreach 指令對於自動化來說非常強大:
git submodule foreach 'git stash || true'
git submodule foreach 'git fetch origin && git checkout origin/main'
對於非常大的子模組,稀疏檢出限制了所獲得的內容:
git submodule update --init --depth 1
cd lib/large
git sparse-checkout set src/
這些技術有助於管理大型專案中子模組的複雜性。透過掛鉤或腳本自動同步子模組可確保團隊成員無需手動幹預即可保持同步。
結論
Git 子模組是管理嵌套儲存庫的強大工具,但它們需要紀律和理解。掌握基本操作,正確配置CI,並了解常見陷阱。對於許多專案來說,子樹、monorepo 或套件管理器可能是更簡單的選擇。選擇與您團隊的工作流程和專案結構相符的方法。

