子模块在父仓库中存储的是指向特定commit的指针,而非分支名或代码内容。它通过.gitmodules记录url,通过tree对象保存sha哈希值,实现快照式引用;执行git submodule add仅声明路径与url,不自动拉取代码,需显式运行git submodule update --init --recursive才能检出内容;切换分支或更新子模块后,必须在父仓库中git add子模块路径并commit,才能更新其commit hash记录,否则协作者拉取的仍是旧版本。

Git 子模块和主仓库分支之间没有自动同步关系——子模块记录的是某个固定 commit,不是分支名,所以 git checkout main 或 git pull 不会自动更新子模块内容。
子模块到底存的是什么?
子模块在父仓库中只保存一个指向特定 commit 的指针(即 .gitmodules 中的 URL + git ls-tree HEAD path/to/submodule 输出的 SHA),它不关心子模块仓库当前在哪个分支、有没有新提交。换句话说:子模块是“快照式引用”,不是“动态分支跟踪”。
- 执行
git submodule add后,子模块目录被初始化为远程仓库的默认分支(通常是main或master)的最新 commit,但这个 commit 会被立刻记录进父仓库的 tree 对象中 - 后续你在子模块里
git checkout dev或git pull,只要没在父仓库中git add并git commit新的 commit hash,父仓库就“不知道”你改了子模块 -
git status在父仓库中显示子模块为 “modified”(带+或-),其实就是提示你:子模块工作区/索引和父仓库记录的 commit 不一致
如何让子模块跟上主仓库某分支的演进?
没有魔法命令能一键绑定子模块到父仓库的某个分支。你需要明确两种常见策略:
-
策略一(推荐):子模块始终使用固定 release commit —— 每次升级子模块时,手动进入子模块目录
git checkout v2.3.0→git submodule update --init --recursive→ 回到父目录git add path/to/submodule→git commit。这是最可控、可复现的方式 -
策略二:启用
--remote模式跟踪子模块分支 —— 运行git submodule update --remote --merge path/to/submodule(或--rebase)。这会让子模块执行git fetch origin && git merge origin/main,然后把新 commit 写回父仓库(需再git add & commit)。注意:--remote不会自动 commit 到父仓库,且容易因合并冲突中断
别误用 git submodule update --init:它只拉取父仓库已记录的 commit,不会刷新子模块的分支头。
为什么 git pull 后子模块还是旧的?
因为 git pull 只更新父仓库的代码和远程跟踪分支(如 origin/main),它完全不触碰子模块目录——除非你显式加 --recurse-submodules 参数。
- 安全做法:用
git pull --recurse-submodules,它等价于先git pull父仓库,再对每个子模块运行git submodule update --init --recursive - 危险做法:仅
git pull后直接git submodule update—— 如果子模块尚未init(比如首次克隆),该命令静默失败,目录为空,极易引发构建错误 - CI 场景必须写全:建议始终用
git submodule update --init --recursive,避免依赖git status是否干净或是否已 init 过
子模块分支切换后如何正确提交?
当你 cd path/to/submodule && git checkout feat/login 并做了修改,父仓库并不知情。要让它“看到”变化,必须两步:
- 回到父目录,
git add path/to/submodule(注意:不加斜杠,路径是子模块在父仓库中的实际目录名) git commit -m "submodule: bump path/to/submodule to feat/login tip"
此时 git ls-tree HEAD path/to/submodule 才会显示新的 commit hash。否则协作者 git clone 后执行 git submodule update,拉下来的仍是旧 commit。
最容易被忽略的点:子模块的“分支”信息(如 HEAD 指向 refs/heads/feat/login)不会被父仓库记录,只有 commit hash 是权威依据。换言之,你切分支、改代码、不 commit 到父仓库,等于什么都没发生。











