Git Submodule 使用与避坑
一个仓库要引用另一个仓库的代码,又不想把源码拷进来。submodule 是 Git 自带方案:主仓库只记录"指向子仓库哪个 commit"。
它很实用,也很容易让人抓狂。大部分痛苦来自一个事实:
主仓库记录的是子模块的 commit 指针,不是"最新版"。
一、添加
git submodule add https://xxx/utils.git libs/utils
git commit -m "chore: 引入 utils 子模块"
会生成 .gitmodules:
[submodule "libs/utils"]
path = libs/utils
url = https://xxx/utils.git
二、克隆含子模块的仓库
直接 git clone 后,libs/utils 是空目录。必须:
git submodule update --init --recursive
或者一步到位:
git clone --recurse-submodules https://xxx/main.git
忘了这一步就编译,是子模块第一大坑——报错信息完全看不出根因。
三、更新子模块
cd libs/utils
git switch main
git pull
cd ../..
git add libs/utils # 记下新的 commit 指针
git commit -m "chore: 升级 utils 到最新"
子模块里 checkout 了新版本,主仓库不会自动记录,必须再 git add 那个目录。
四、批量操作
git submodule foreach 'git status' # 每个子模块里执行
git submodule update --init --recursive # 对齐到主仓库记录的版本
git submodule update --remote --merge # 各子模块拉到远端最新
--remote 的行为取决于 .gitmodules 里的 branch 配置,没配就是远端默认分支。
五、彻底删除
三步,一步都不能少:
git submodule deinit -f libs/utils
git rm -f libs/utils
rm -rf .git/modules/libs/utils
git commit -m "chore: 移除 utils 子模块"
漏最后一步,以后重新添加同名子模块会报"已存在"。
六、常见坑
- CI 里不拉子模块:流水线要加
--recurse-submodules,或单独跑git submodule update --init --recursive - HTTPS 与 SSH 混用:
.gitmodules写死 HTTPS,机器上只配了 SSH key 会 403 - 私有子模块权限:CI token 要有子仓库读权限,否则卡在 clone
- 改代码改到子模块里忘了推送:主仓库指向一个别人拉不到的 commit
- 分支切换后子模块状态不对:
git submodule update对齐一下就好
七、什么时候别用
如果两个仓库几乎总是一起改、发版节奏一样,考虑合并成一个仓库(monorepo)或改用包管理(npm / Maven / Go module)。
子模块真正适合的场景是:第三方或者独立演进的组件,版本需要被精确锁定。