git submodule 三个真实踩坑: detached HEAD、commit 不一致、CI 认证失败

git submodule 三个真实踩坑封面图

# git submodule 三个真实踩坑: detached HEAD、commit 不一致、CI 认证失败

git submodule 是管理多仓库依赖的标准工具,但在团队协作中它有三个高频踩坑点,每一个都让人浪费数小时排查。本文给出真实场景、具体错误信息和可复制步骤。

问题一:切换分支后 submodule 变成 detached HEAD

场景:本地切到新功能分支后,submodule 目录的文件全空了,或者提示 HEAD detached at abc1234

根因:父仓库记录 submodule 要 checkout 的 commit ID,切换父仓库分支时,submodule 的 HEAD 指向了旧 commit,而不是任何分支。

实战案例

# 在父仓库 feature/login 分支上,submodule 是 ui-components
git checkout feature/login
cd ui-components && git log --oneline -3
# abc1234 (HEAD detached)

# 同一个 submodule 在 main 分支上正常指向 dev 分支
git checkout main
cd ui-components && git log --oneline -3
# bcd5678 dev

解法:用 git submodule update --init --recursive 强制同步 submodule 到父仓库记录的 commit。

git checkout feature/login
git submodule update --init --recursive
cd ui-components && git log --oneline -3
# abc1234 (HEAD detached at abc1234) —— 现在正确了

验证命令

git submodule status  # 显示 +abc1234 表示未同步,空白表示已同步

问题二:子仓库 commit 与父仓库 index 不一致(submodule dirty)

场景: submodule 目录里有未提交的修改,但 git status 显示 nothing to commit,push 时 CI 却报错说 submodule commit 不匹配。

根因:父仓库的 index 记录了 submodule 的某个 commit hash,但 submodule 工作区发生了本地修改(dirty),两者不一致。git submodule update 只拉取父仓库记录的那个 commit,不做 merge,所以本地修改被保留,但父仓库不知道。

实战案例

cd ui-components
git checkout dev
echo "// 本地调试注释" >> src/utils.js
git status
# nothing to commit (working tree clean)

# 父仓库认为 submodule 指向 bcd5678
git submodule status
#  bcd5678 ui-components (clean)

# 但实际文件已经被改过
cd ../ && git diff ui-components
# -Subproject commit bcd5678
# +Subproject commit bcd5678-dirty

解法:先确认 submodule 是否有本地修改,然后决定是提交还是丢弃。

# 方案A:提交本地修改到 submodule
cd ui-components
git add src/utils.js
git commit -m "temp debug comment"
cd ..
git add ui-components
git commit -m "update submodule"

# 方案B:丢弃本地修改
git submodule update --force ui-components

验证命令

git submodule status --recursive
# 绿色 = 已同步,黄色 + hash = 有未提交修改

问题三:CI 里 submodule 认证失败(GitHub Actions)

场景:本地 git submodule update --init 正常,CI 里却报 git@github.com: Permission denied (public key)Authentication failed

根因:本地有 SSH key 或 GitHub credential helper,但 Actions runner 的 checkout action 默认用 HTTPS + deploy token,不是 SSH key。

实战案例(GitHub Actions workflow):

- uses: actions/checkout@v4
  with:
    submodules: true
    token: ${{ secrets.GITHUB_TOKEN }}

错误日志:

fatal: could not read Username for 'https://github.com': No such device or address

解法:给 actions/checkout 显式传 ssh-key 或者改用 HTTPS + submodules: 'recursive' + persist-credentials: true

# 方案A:用 GitHub App 安装 token(推荐,比 SSH key 稳定)
    1. uses: actions/checkout@v4
with: submodules: true token: ${{ secrets.GH_APP_INSTALLATION_TOKEN }} # 方案B:手动 SSH key(PAT 有过期时间)
    1. uses: actions/checkout@v4
with: submodules: true ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }} ssh-known-hosts: github.com

持久化凭证(防止 token 过期后 submodule 拉不到)

# 在 CI job 里持久化 git credential
git config --global credential.helper store
echo "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com" >> ~/.git-credentials

验证命令(本地测试 CI 等效环境):

# 用 HTTPS + token 模拟 CI 环境
GIT_ASKPASS=true git submodule update --init --recursive
# 如果需要密码说明认证没配好

现在你可以做什么

  1. 检查当前 submodule 状态git submodule status --recursive,绿色正常,黄色 + hash 表示有未同步修改
  2. 切分支后统一 submodule:切换父仓库分支后,跑 git submodule update --init --recursive
  3. CI 里加上 submodule 认证:在 actions/checkout 里加 submodules: truetoken: secrets.GH_APP_INSTALLATION_TOKEN
  4. 遇到 detached HEAD:直接 git submodule update --init 即可修复,不要在 submodule 里做 git checkout 分支操作
© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享