git 的 submodule
作為一個獨立的 repo
, 其擁有普通 repo
全部的功能, 我們可以完全按照普通的 repo
管理命令來進入 submodule
中進行手動管理. 不過如果存在多個 submodule
位于同一 superproject
下時, 掌握一些 git submodule ...
命令就變得尤為重要了.
本文列出了常用的一些 git submodule
管理命令, 并舉出實際應用中遇到的問題及解決方案.
submodule 介紹
在 git 倉庫 superproject
的目錄中使用 git submodule add https://github/HanleyLee/C
即可將 https://github/HanleyLee/C 作為一個 submodule
被 superproject
依賴與管理.
當 submodule
被修改時我們可以在 superproject
中得到通知:
-
在
submodule
中添加了新文件:himg -
修改了
submodule
中的內(nèi)容 (沒有new commit
):himg -
在 submodule 中產(chǎn)生了
new commit
:himg
submodule 特點
在 repo Test
作為 submodule
被 superproject
管理后:
- 我們?nèi)匀豢梢赃M入
repo Test
的目錄中對相關內(nèi)容進行修改, 然后通過常用的 git 命令進行操作. - 在
superproject
下可以通過git submodule ***
命令來管理其下的所有子倉庫, 使其與遠程庫保持同步或推送到遠程庫.
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
文件中標記了每一個 submodule
的 path
與 url
.
然后我們進入 ./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
-
從當前項目移除 submodule
git submodule deinit -f <submodule_path>
rm -rf .git/modules/<submodule_path>
git rm -f <submodule_path>
-
復制含 submodule 項目到本地
- Clone the superproject as usual
- Run
git submodule init
to init the submodules - Run
git submodule update
to have the submodules on a detached HEAD
或者直接執(zhí)行
git clone --recurse-submodules <repo-url>
git submodule init
: 將本項目所依賴的submodule
進行初始化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 為遠程項目的最新版本 (更為常用!)git submodule update --remote <submodule-name>
: 更新指定的 submodule 為遠程的最新版本-
git push --recurse-submodules=
-
check
: 檢查submodule
是否有提交未推送, 如果有, 則使本次提交失敗 -
on-demand
: 先推送 submodule 的更新, 然后推送主項目的更新(如果 submodule 推送失敗, 那么推送任務直接終止) -
while
: 所有的submodule
會被依次推送到遠端, 但是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é)點?
默認情況下, git pull 命令會遞歸地抓取子模塊的更改 (fetch), 然而, 它不會將 submodule merge 到所跟蹤的分支上. 因此我們還需要執(zhí)行 git submodule update
.
如果我們想一句話解決, 那么可以使用 git pull --recurse-submodule
, 這個可以在拉取完 submodule 后再將其 merge 到所跟蹤的分支上.
如果我們想讓 Git 總是以 --recurse-submodules
拉取, 可以將配置選項 submodule.recurse
設置為 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'"
為此命令設置alias
在 submodule
沒有提交commit
的情況下推送 superproject
的commit
如果我們在主項目中提交并推送但并不推送子模塊上的改動, 其他嘗試檢出我們修改的人會遇到麻煩, 因為他們無法得到依賴的子模塊改動. 那些改動只存在于我們本地的拷貝中.
為了確保這不會發(fā)生, 我們可以讓 Git 在推送到主項目前檢查所有 submodule
是否已推送. git push
命令接受可以設置為 check
或 on-demand
的 --recurse-submodules
參數(shù). 如果任何提交的 submodule
改動沒有推送那么 check
選項會直接使 push 操作失敗.(此外還有 demand
, while
, no
選項, 參考前節(jié)命令列表進行理解)
為了以后方便, 我們可以設置默認檢查 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
后, 下次更新仍然恢復原樣, 難道就沒有辦法使其固定在某個 branch
上嗎? 經(jīng)過研究, 參考 stackoverflow 的答案, 我發(fā)現(xià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 有多種選擇, 默認情況下是 checkout
, 其會根據(jù) superproject
所記錄的 submodule
的 commit
進行 checkout. 類似于 git checkout 4eda5fgrd
, 這必然導致 submodule
的 HEAD
處于 detached
狀態(tài). 解決辦法就是使用 rebase
(merge
也可以), 這樣當我們對 submodule
設置了一個初始的 branch
后, 其以后都只會在這個 branch 上對遠程的最新 commit
進行 rebase
, 不會導致 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 表示跟蹤的遠程倉庫的分支, 如果不指定, 則默認為跟蹤遠程的HEAD
所指向的branch