子模块“漂移”源于父仓库记录的commit ID与子模块实际检出状态不一致,根本原因是未在.gitmodules中显式配置branch字段并配合git submodule update --remote使用:添加时需git submodule add -b branch_url path,已有子模块则用git config -f .gitmodules submodule.<name>.branch branch设置,否则--remote将默认拉取main分支而非目标分支。

子模块“漂移”不是代码出错,而是父仓库记录的 commit ID 和子模块实际检出状态不一致——根本原因是没按分支指针(branch 配置)+ git submodule update --remote 组合使用。
子模块的 branch 配置必须显式写入 .gitmodules
默认 git submodule add 不写分支,子模块只会固定到某个 commit,后续 git submodule update --remote 仍默认拉 main(或 master),哪怕远程已有 develop 分支更新。要让子模块真正“跟踪分支”,必须在 .gitmodules 中声明 branch 字段:
- 添加时带
-b:例如git submodule add -b develop https://github.com/team/lib.git libs/lib,会自动写入branch = develop - 已有子模块补配置:进入父仓库根目录,执行
git config -f .gitmodules submodule.libs/lib.branch develop,再git add .gitmodules && git commit - 手动编辑
.gitmodules也行,但务必保证格式严格:字段名小写、等号前后无空格、路径与 submodule 名一致
漏掉这一步,--remote 就是盲人摸象——它不知道该跟谁,只能 fallback 到默认分支。
git submodule update --remote 的行为取决于 branch 配置是否存在
这个命令不是“拉最新代码”那么简单。它实际分两步:先 fetch 远程对应分支的最新提交,再将子模块检出到那个 commit。但前提是:.gitmodules 里有 branch 字段,且该分支在子模块远程仓库中真实存在。
- 有
branch = develop→ fetch origin/develop → 检出最新 commit → 父仓库显示 modified: libs/lib - 没
branch配置 → fetch origin/main → 检出 main 最新 commit → 即使子模块开发都在 develop 上,也会跳过那些提交 - 分支名拼错(比如写成
devel)→ fetch 失败 → 报错fatal: couldn't find remote ref refs/heads/devel
执行后一定要检查子模块目录是否真到了目标分支的 HEAD:进 libs/lib 目录运行 git status,输出应为 On branch develop,而不是 HEAD detached at abc1234。
更新后必须 git add 子模块路径才能固化指针
git submodule update --remote 只改子模块本地状态,父仓库的索引(index)仍指向旧 commit。此时 git status 会显示 modified: libs/lib,这是正确信号,不是错误。
- 直接
git add libs/lib→ 把新 commit ID 写入暂存区 →git commit固化指针 - 误操作:在子模块内
git checkout develop && git pull后,忘了回父目录git add→ 父仓库指针未更新,下次 clone 仍是旧版本 - 更危险的操作:在子模块里
git commit新功能后,直接git add libs/lib→ 父仓库指针指向一个尚未 push 到远程的本地 commit,协作时别人无法检出
指针是否锁定,只看父仓库的 commit 记录里 libs/lib 对应的 SHA-1 是否和子模块当前 git rev-parse HEAD 一致。不一致,就是漂移。
sync 命令不是万能的,它不解决分支跟踪问题
git submodule sync 只同步 .git/config 里的 URL(比如远程地址变了),对 branch 配置、commit ID、检出状态完全无影响。很多人以为 run 一遍 sync 就能“对齐”,结果发现子模块还是 detached、还是拉不到 develop 分支——因为 sync 根本不碰分支逻辑。
- 需要 sync 的典型场景:子模块远程仓库迁移到新域名,
.gitmodules已更新 URL,但本地.git/config还是旧地址 → 此时git submodule sync才有用 - 想让子模块保持在某分支最新?靠
update --remote+add,不是 sync - 嵌套子模块(recursive)下,
sync --recursive也只是逐层同步 URL,同样不管分支
分支指针的锁定,本质是一次配置(.gitmodules)、一次拉取(--remote)、一次提交(add + commit)的闭环。少任何一环,都留着漂移的缝隙。


















