Git 子模塊
1 概覽
使用 Git 管理源代碼,進行子模塊操作時,此文可作為參考(Lookup Cheat-Sheet)
2 何時使用 submodules
合適的場景:
- 子模塊代碼應獨立于其他應用者項目(container/container project)。不依賴,高內聚,同一代碼庫共享于多個應用者項目。
- 代碼規模大,若其中某個同樣大規模的代碼模塊長時間不需要更新,器皿項目無需每次拉取時都涉及該模塊時,應將該模塊獨立為子模塊。方便合作代碼貢獻者可以選擇只初始化一次,之后都不再提取(fetch)該子模塊。
- 子模塊與器皿項目應用不同的框架或語言。
與其使用 submodules,不如使用 subtrees 的場景:
- 需要讓某個模塊具有子代碼的概念,作為一個器件,依附于器皿項目當中,對其做定制化修改,而不用去動器皿項目,只操作器件。同時具備管理一套代碼庫的簡便性。
- 模塊簡單,或模塊有依賴器皿項目的代碼,則不必拆分為單獨的項目倉儲,避免冗雜的本地/遠程引用(ref)管理。所有代碼都是一套本地代碼。
| Submodules | Subtrees |
|---|---|
| 更復雜 (尤其對初學者) | 更簡單 |
| 另一個倉儲的一個提交引用鏈接 | 代碼提交會合并到器皿項目的提交歷史中 |
| 能夠單獨被訪問(存在于中心服務器中的獨立倉儲) | 去中心化(直接通過器皿項目訪問) |
| 需要更多的步驟來操作 | 克隆,拉取,推送都與之前相同(當然也有針對子樹的命令) |
| 不占用器皿項目倉儲大小 | 直接占用器皿項目倉儲容量 |
3 使用技巧及規范
3.1 為倉儲添加和配置子模塊
# 添加子模塊并跟蹤名稱為 branch_name 的分支,不加 -b 將會對后續子模塊的跟蹤產生影響
git submodule add -b branch_name URL_to_Git_repo optional_directory_path
3.1.1 一般原則
-
為子模塊設定分支。除了
git submodule add時使用-b參數設定以外,還可以通過修改 .gitmodules 文件更改。 -
設計好倉儲內子模塊的存放路徑。除了
git submodule add時通過optional_directory_name設定以外,還可以通過修改 .gitmodules 文件更改。
注:除了直接用文本編輯器修改 .gitmodules 文件以外,還可以通過命令進行修改(參考如下示例)。
# 查看現有的 submodule 屬性
git config --file=.gitmodules -l
# 修改 submodule 屬性
git config --file=.gitmodules submodule.modulename.branch branch_name
git config --file=.gitmodules submodule.modulename.path path/to/submodule
git config --file=.gitmodules submodule.modulename.url URL_to_Git_repo
3.2 從一個已經設置好子模塊的倉儲獲取子模塊
# 克隆倉儲時,同時克隆倉儲的子模塊,相當于進行了子模塊的初始化和更新
git clone --recursive URL_to_Git_repo
# 克隆倉儲后,初始化并更新子模塊,子模塊就會引用子模塊倉儲默認分支下最新一次提交
git submodule update --init
# 子模塊設置變更、子模塊變更(新增、更名、刪除等)
git pull
git submodule sync
git submodule update --init
3.2.1 對子模塊倉儲直接進行代碼編寫
# 簽出分支,使子模塊具備拉取最新修改并直接連通父倉儲推送的能力
git submodule foreach "git checkout $(git config -f $toplevel/.gitmodules submodule.$name.branch || echo main)"
# 子模塊遠程有更新,需要獲取。以下命令等同于進入子模塊目錄執行 `git pull`。如果本地有未提交的修改,均需要合并操作,有沖突無法合并則獲取會失敗。
git submodule update --remote --merge
# 推送代碼到遠程分支
git push --recurse-submodules=on-demand
3.2.2 不更改子模塊代碼
使用 detached HEAD,子模塊鏈接的是提交 commit 的哈希值 hash。無法拉取或提交代碼,只能通過更新倉儲的子模塊版本,再通過 git submodule update 命令更新子模塊。
3.3 刪除已有子模塊
# 從 .git/config 中刪除子模塊
git submodule deinit -f path/to/submodule
# 從器皿項目(父項目)的 .git/modules 目錄中刪除子模塊文件夾
rm -rf .git/modules/path/to/submodule
# 從 .gitmodules 中去掉子模塊入口并刪除子模塊所在物理路徑 path/to/submodule 下的所有文件
git rm -f path/to/submodule
3.4 常用命令解析
# 為所有子模塊執行 git 命令,-q 參數靜默模式,--recursive 循環操作所有子模塊(包括子模塊的子模塊引用)
git submodule foreach "git command"
# 更新子模塊
# --remote 從子模塊遠程分支更新(最新),不加此參數則從父倉儲的子模塊版本鏈接更新(對應提交哈希值)
# --recursive 循環操作所有子模塊(包括子模塊的子模塊引用)
# --merge,合并遠程分支的修改,有沖突需要解決沖突。不會 detach HEAD。
# --rebase,撤銷本地提交,臨時保存并保留本地修改,應用遠程修改,再將本地修改合并,合并時同樣需要解決沖突。不會 detach HEAD。
git submodule update
注:對于 --remote 而言
- remote 使用
submodule.<name>.branch=branch_name來確認分支名稱,若未設置則使用遠程倉儲設定的默認分支。再通過讀取子模塊branch.branch_name.remote=origin來確認從遠程分支獲取的地址和 HEAD,默認為 detached HEAD,對應remotes/origin/HEAD,獲取該分支最新提交。- 如果本地子模塊簽出了某個分支,則 HEAD attached 到對應分支,即
branch.branch_name.merge=refs/heads/branch_name。- 如果本地子模塊當前的 HEAD commit hash 和
submodule.<name>.branch中的值不一致的話,會導致子模塊被 detach HEAD,不再追蹤某個分支,而是映射到submodule.<name>.branch最新的提交上。以上通常存在兩種情況,第一種是 HEAD 和submodule.<name>.branch分支本身就不同。第二種是本地子模塊的提交落后于submodule.<name>.branch最新的提交。通過添加 --merge 參數可以避免 detachment。- 如果子模塊有本地提交并與遠程提交產生了沖突,需要用 --rebase 參數來解決。
- 子模塊內使用
git pull直接使用的是對應分支的 HEAD,為branch.branch_name.merge=refs/heads/branch_name。對應上述第2點,和一個簽出 branch_name 分支后 HEAD attach 到 heads/branch_name 的子模塊的 HEAD 是一致的。
3.5 常用 git 設置(推薦進行配置)
- 除了直接改 .gitconfig 文件,還可通過命令查看和更改
# 查看現有 git 設置
git config --global -l
# 修改現有 git 設置
git config --global status.submoduleSummary true
git config --global diff.submodule log
git config --global alias.spush "push --recurse-submodules=on-demand"
- 查看 submodule 提交變更
[status]
submoduleSummary = true
- diff 時可以看到 submodule 的變更項
[diff]
submodule = log
- 命令別名
[alias]
sdiff = "!git diff && git submodule foreach 'git diff'"
spull = "!git pull && git submodule sync && git submodule update --init"
smerge = "submodule update --remote --merge"
srebase = "submodule update --remote --rebase"
scheckout = "submodule foreach 'git checkout $(git config -f $toplevel/.gitmodules submodule.$name.branch || echo main) && git pull'"
spush = "push --recurse-submodules=on-demand"

浙公網安備 33010602011771號