git 的 submodule 作為一個獨立的 repo, 其擁有普通 repo 全部的功能, 我們可以完全按照普通的 repo 管理命令來進(jìn)入 submodule 中進(jìn)行手動管理. 不過如果存在多個 submodule 位于同一 superproject 下時, 掌握一些 git submodule ... 命令就變得尤為重要了.
本文列出了常用的一些 git submodule 管理命令, 并舉出實際應(yīng)用中遇到的問題及解決方案.
submodule 介紹
在 git 倉庫 superproject 的目錄中使用 git submodule add https://github/HanleyLee/C 即可將 https://github/HanleyLee/C 作為一個 submodule 被 superproject 依賴與管理.
當(dāng) submodule 被修改時我們可以在 superproject 中得到通知:
-
在
submodule中添加了新文件:himg -
修改了
submodule中的內(nèi)容 (沒有new commit):himg -
在 submodule 中產(chǎn)生了
new commit:himg
submodule 特點
在 repo Test 作為 submodule 被 superproject 管理后:
- 我們?nèi)匀豢梢赃M(jìn)入
repo Test的目錄中對相關(guān)內(nèi)容進(jìn)行修改, 然后通過常用的 git 命令進(jìn)行操作. - 在
superproject下可以通過git submodule ***命令來管理其下的所有子倉庫, 使其與遠(yuǎn)程庫保持同步或推送到遠(yuǎn)程庫.
submodule 的版本如何被管理的 (大致思路)
添加 git submodule 的方法很簡單, 使用 git submodule add <repo url> 即可. 添加完之后, 在 superproject 的目錄下會產(chǎn)生一個 .gitmodule 文件, 文件的結(jié)構(gòu)如下:
[submodule "C"]
path = C
url = git@github.com:HanleyLee/C.git
[submodule "Cpp"]
path = Cpp
url = git@github.com:HanleyLee/Cpp.git
可以看到, .gitmodule 文件中標(biāo)記了每一個 submodule 的 path 與 url.
然后我們進(jìn)入 ./C:
$ cd ./C
$ ls -l
$ ls -lhia
35107110 drwxr-xr-x 9 hanley staff 288B May 7 21:15 .
35107043 drwxr-xr-x 14 hanley staff 448B May 7 20:45 ..
35107127 -rw-r--r-- 1 hanley staff 26B May 7 19:59 .git
$ cat .git
gitdir: ../.git/modules/C
我們發(fā)現(xiàn) ./C/.git 竟然是一個文件 (常規(guī) git 目錄中的 .git 是文件夾), 然后其內(nèi)容指向了另一個文件夾 (類似于指針), 我們再去到那個文件夾:
$ cd ../git/modules/C
$ ls -lhia
35107128 drwxr-xr-x 16 hanley staff 512B May 7 21:17 .
35107126 drwxr-xr-x 9 hanley staff 288B May 7 19:59 ..
35112722 -rw-r--r-- 1 hanley staff 17B May 7 21:17 COMMIT_EDITMSG
35109064 -rw-r--r-- 1 hanley staff 0B May 7 21:44 FETCH_HEAD
35109063 -rw-r--r-- 1 hanley staff 16B May 7 21:44 FETCH_LOG
35112529 -rw-r--r-- 1 hanley staff 21B May 7 20:25 HEAD
35116056 -rw-r--r-- 1 hanley staff 41B May 7 21:15 ORIG_HEAD
35114647 -rw-r--r-- 1 hanley staff 319B May 7 20:47 config
35107131 -rw-r--r-- 1 hanley staff 73B May 7 19:59 description
35107132 drwxr-xr-x 15 hanley staff 480B May 7 19:59 hooks
35116224 -rw-r--r-- 1 hanley staff 1.7K May 7 21:17 index
35107129 drwxr-xr-x 3 hanley staff 96B May 7 19:59 info
35107178 drwxr-xr-x 4 hanley staff 128B May 7 19:59 logs
35107159 drwxr-xr-x 9 hanley staff 288B May 7 21:40 objects
35107174 -rw-r--r-- 1 hanley staff 112B May 7 19:59 packed-refs
35107146 drwxr-xr-x 5 hanley staff 160B May 7 19:59 refs
我們發(fā)現(xiàn)這個文件夾才是 submodule 的真實 .git 文件夾, 我們對于 submodule 的所做的 commit 信息也都保存在這里.
submodule 常用命令
git submodule: 顯示所有submodule, 等同于git submodule status-
添加 submodule 到現(xiàn)有項目
- Run
git submodule add -b <branch> --name <name> <repo-url> <local dir> - Commit both files on the superproject
- Run
-
從當(dāng)前項目移除 submodule
git submodule deinit -f <submodule_path>rm -rf .git/modules/<submodule_path>git rm -f <submodule_path>
-
復(fù)制含 submodule 項目到本地
- Clone the superproject as usual
- Run
git submodule initto init the submodules - Run
git submodule updateto have the submodules on a detached HEAD
或者直接執(zhí)行
git clone --recurse-submodules <repo-url> git submodule init: 將本項目所依賴的submodule進(jìn)行初始化git submodule update: 將本項目所依賴的submodule更新到本地最新版本git submodule update --init: 前面兩個命令的合并git submodule update --init --recursive: 前面三個命令的合集,--recursive是為了保證即使submodule又嵌套了sub-submodule, 也可以被執(zhí)行到. 這個命令比較全面, 會經(jīng)常使用git submodule update: 更新 submodule 為superproject本次 commit 所記錄的版本 (本地版本為舊版本的話那么就與舊版本保持同步!)git submodule update --remote: 更新 submodule 為遠(yuǎn)程項目的最新版本 (更為常用!)git submodule update --remote <submodule-name>: 更新指定的 submodule 為遠(yuǎn)程的最新版本-
git push --recurse-submodules=-
check: 檢查submodule是否有提交未推送, 如果有, 則使本次提交失敗 -
on-demand: 先推送 submodule 的更新, 然后推送主項目的更新(如果 submodule 推送失敗, 那么推送任務(wù)直接終止) -
while: 所有的submodule會被依次推送到遠(yuǎn)端, 但是superproject將不會被推送 -
no: 與while相反, 只推送superproject, 不推送其他submodule
-
git pull --recurse-submodules: 拉取所有子倉庫 (fetch) 并 merge 到所跟蹤的分支上git diff --submodule: 查看 submodule 所有改變git submodule foreach '<arbitrary-command-to-run>': 對所有 submodule 執(zhí)行命令, 非常有用, 如git submodule foreach 'git checkout main'
為什么 superproject 在 git pull 之后 submodule 沒有切到最新節(jié)點?
默認(rèn)情況下, git pull 命令會遞歸地抓取子模塊的更改 (fetch), 然而, 它不會將 submodule merge 到所跟蹤的分支上. 因此我們還需要執(zhí)行 git submodule update.
如果我們想一句話解決, 那么可以使用 git pull --recurse-submodule, 這個可以在拉取完 submodule 后再將其 merge 到所跟蹤的分支上.
如果我們想讓 Git 總是以 --recurse-submodules 拉取, 可以將配置選項 submodule.recurse 設(shè)置為 true. 具體命令為 git config --global submodule.recurse true. 此選項會讓 Git 為所有支持 --recurse-submodules 的命令使用該選項 (除 clone 以外).
git pull --recurse-submodule會讓我們的pull命令遞歸地用于所有submodule, 如果你的submodule數(shù)量過多的話可能會等較長時間. 這時可以使用git pull && git submodule update --init --recursive一句話命令來只拉取更新了的submodule并更新到最新commit, 使用git config --global alias.sdiff '!'"git diff && git submodule foreach 'git diff'"為此命令設(shè)置alias
在 submodule 沒有提交commit的情況下推送 superproject 的commit
如果我們在主項目中提交并推送但并不推送子模塊上的改動, 其他嘗試檢出我們修改的人會遇到麻煩, 因為他們無法得到依賴的子模塊改動. 那些改動只存在于我們本地的拷貝中.
為了確保這不會發(fā)生, 我們可以讓 Git 在推送到主項目前檢查所有 submodule 是否已推送. git push 命令接受可以設(shè)置為 check 或 on-demand 的 --recurse-submodules 參數(shù). 如果任何提交的 submodule 改動沒有推送那么 check 選項會直接使 push 操作失敗.(此外還有 demand, while, no 選項, 參考前節(jié)命令列表進(jìn)行理解)
為了以后方便, 我們可以設(shè)置默認(rèn)檢查 git config --global push.recurseSubmodules check
為什么每次 update 后 submodule 的 HEAD 狀態(tài)變?yōu)榱?detached?
很多人用了 git submodule 后, 都發(fā)現(xiàn)每次 update 之后, submodule 中的 HEAD 都是 detached 狀態(tài)的, 即使本次 git checkout master 后, 下次更新仍然恢復(fù)原樣, 難道就沒有辦法使其固定在某個 branch 上嗎? 經(jīng)過研究, 參考 stackoverflow 的答案, 我發(fā)現(xiàn)是可以解決的.
問題的關(guān)鍵在于 .gitmodule 的配置:
[submodule "C"]
path = C
url = git@github.com:HanleyLee/C.git
update = rebase
[submodule "Cpp"]
path = Cpp
url = git@github.com:HanleyLee/Cpp.git
update = rebase
我們需要添加 update = rebase 這行, 根據(jù) git official 的說明
checkout
the commit recorded in the superproject will be checked out in the submodule on a detached HEAD.
If --force is specified, the submodule will be checked out (using git checkout --force), even if the commit specified in the index of the containing repository already matches the commit checked out in the submodule.
rebase
the current branch of the submodule will be rebased onto the commit recorded in the superproject.
merge
the commit recorded in the superproject will be merged into the current branch in the submodule.
submodule 的 update 有多種選擇, 默認(rèn)情況下是 checkout, 其會根據(jù) superproject 所記錄的 submodule 的 commit 進(jìn)行 checkout. 類似于 git checkout 4eda5fgrd, 這必然導(dǎo)致 submodule 的 HEAD 處于 detached 狀態(tài). 解決辦法就是使用 rebase(merge 也可以), 這樣當(dāng)我們對 submodule 設(shè)置了一個初始的 branch 后, 其以后都只會在這個 branch 上對遠(yuǎn)程的最新 commit 進(jìn)行 rebase, 不會導(dǎo)致 detached 狀態(tài)的產(chǎn)生.
以 submodule 的目錄為 ./C/ 為例. 具體的解決步驟如下:
$ cd C/
$ git checkout main
$ cd ..
$ git config -f .gitmodules submodule.C.update rebase
此時, 以后再使用 git submodule update 就不會有 detached 狀態(tài)的產(chǎn)生了
另外, 在
.gitmodule文件中也可以指定 branch, 這里指定的 branch 表示跟蹤的遠(yuǎn)程倉庫的分支, 如果不指定, 則默認(rèn)為跟蹤遠(yuǎn)程的HEAD所指向的branch