父模块中保存了子模块的某次 commit id 以表示要使用子模块的哪个代码快照, 只要保存的 commit 和子模块的 HEAD 有差异, 父模块的 git status 就会显示出这一差异, 所以父模块所记录的子模块 commit id 信息就与父模块中其他文件一样.

有两种情况会导致保存的 commit 和子模块 HEAD 产生差异, 一是子模块的 HEAD 更新了, 二是父模块中保存的 commit id 更新了.

第一种情况的原因很多, 当这一情况发生后, 可以在父模块 git add && git commit 来更新父模块中保存的 commit id, 达到抹平差异的效果:

  1. 在子模块中做了改动并 commit (注意子模块也有自己的 .git/)
  2. 到子模块中拉取了远程仓库的最新 commit
  3. 在父模块中执行了 git submodule update —remote, 这一命令相当于 2

第二种情况是由于在父模块仓库拉取远程更新, 远程仓库中已经包含了更新的子模块的 commit id, 这是由于有人触发了第一种情况并推送到了远程仓库. 这种情况下执行 git submodule update 命令会将子模块的 HEAD 指向父模块中记录的 commit id, 当然如果子模块中没有父模块记录的 commit id 说明本地子模块代码旧了, 就会从子模块的远程仓库拉取最新代码, 最终同样抹平这一差异, 如果子模块还嵌套了其它子模块, 可以用 git submodule update --recursive 递归更新所有嵌套的子模块的 HEAD.

希望这是最短的将 git submodule 说清楚的文章.