跳到主要内容

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)。

子模块真正适合的场景是:第三方或者独立演进的组件,版本需要被精确锁定。