Git Submodule 使用说明:从概念到常用操作
git submodule 的作用是将一个 git 仓库,最为另一个 git 仓库的子模块
一、为什么需要 submodule
在项目开发中,经常会遇到这种情况:
A 仓库:主项目
B 仓库:公共模块、SDK、工具库、框架代码
A 项目需要使用 B 项目的代码,但 B 项目本身又是一个独立仓库,也需要单独维护、提交、打版本。
这时可以把 B 仓库作为 A 仓库的 submodule 使用。
简单理解:
submodule = 把一个 Git 仓库,挂到另一个 Git 仓库的某个目录下。
例如:
A 仓库
└── client/Assets/B 这里挂载 B 仓库
这样 A 仓库可以使用 B 仓库中的代码,但 B 仓库仍然保持自己的 Git 历史、分支和远程地址。
二、先理解最核心的一句话
使用 submodule 前,必须先理解这一点:
父仓库 A 记录的不是 B 仓库的分支,而是 B 仓库的某一个具体 commit。
例如:
B 仓库 master_sdk 分支历史:
xxxxx1 -> xxxxx2 -> xxxxx3
如果 A 仓库当前记录的是:
client/Assets/B -> xxxxx1
那么团队其他人拉取 A 仓库后,B submodule 默认会被检出到 xxxxx1。
即使 B 仓库的 master_sdk 分支已经更新到了 xxxxx3,A 仓库也不会自动使用 xxxxx3。
因为 A 仓库锁定的是:
B 仓库的某一个 commit
不是:
B 仓库的某一个分支最新代码
这就是 submodule 最容易误解的地方。
这样设计的好处是:版本稳定。
团队成员、Jenkins 打包机、线上构建环境,都会拿到 A 仓库记录的同一个 B 版本。
不会因为 B 仓库后续提交了新代码,导致 A 项目构建结果突然变化。
三、添加 submodule
进入 A 仓库根目录,执行:
git submodule add git@github.com:xxxxxx/B.git client/Assets/B
如果希望指定 B 仓库的某个分支,例如 master_sdk,执行:
git submodule add -b master_sdk git@github.com:xxxxxx/B.git client/Assets/B
执行后,A 仓库中会出现:
.gitmodules
client/Assets/B
其中 .gitmodules 是 submodule 的配置文件。
.gitmodules 内容类似:
[submodule "client/Assets/B"]
path = client/Assets/B
url = git@github.com:xxxxxx/B.git
branch = master_sdk
字段含义:
path :B 仓库挂载到 A 仓库中的路径
url :B 仓库的远程地址
branch :执行 git submodule update --remote 时,默认跟踪的 B 仓库分支
注意:
branch = master_sdk 不代表 A 仓库会自动使用 master_sdk 最新代码。
它只表示:
当执行 git submodule update --remote 时,Git 会去 master_sdk 分支获取最新 commit。
添加完成后,需要把 A 仓库的变化提交:
git add .gitmodules client/Assets/B
git commit -m "Add B submodule"
git push
到这里,别人拉取 A 仓库时,才能知道 A 仓库中有这个 submodule。
四、第一次克隆带 submodule 的仓库
如果 A 仓库已经包含 submodule,第一次克隆时推荐使用:
git clone --recurse-submodules git@github.com:xxxxxx/A.git
这条命令会:
1. 克隆 A 仓库
2. 初始化 submodule
3. 拉取 submodule 内容
4. 把 submodule 检出到 A 仓库记录的 commit
如果已经普通克隆了 A 仓库:
git clone git@github.com:xxxxxx/A.git
那么进入 A 仓库后,再执行:
git submodule update --init --recursive
这两种方式最终效果一样:
都会把 B submodule 检出到 A 仓库当前记录的 commit。
注意:
它们默认都不是把 B 仓库更新到远程分支最新 commit。
如果只是想拿到 A 仓库当前指定的 B 版本,到这里就结束。
如果还想把 B 更新到远程分支最新提交,需要继续看第六、七节。
五、已经克隆的项目,后来新增了 submodule
假设你本地已经有 A 仓库。
后来其他人给 A 仓库添加了 submodule,并提交到了远程。
你执行:
git pull
之后,还需要执行:
git submodule update --init --recursive
原因是:
git pull 只会更新 A 仓库本身,以及 A 仓库记录的 submodule commit。
它不一定会自动把 submodule 目录内容完整拉取并切换到正确 commit。
所以已经克隆过的项目,拉取最新代码后,推荐执行:
git pull
git submodule update --init --recursive
这一步的作用是:
让本地 submodule 和 A 仓库当前记录的 submodule commit 保持一致。
如果只是同步 A 仓库当前记录的 submodule 版本,到这里就结束。
如果还要把 submodule 更新到 B 仓库远程分支最新提交,需要继续执行:
六、更新 submodule 到远程分支最新提交
七、提交 submodule 指针变化
六、更新 submodule 到远程分支最新提交
先区分两个命令。
1. git submodule update
git submodule update
它的作用是:
把 submodule 检出到 A 仓库当前记录的 commit。
它不是更新到远程最新代码。
例如:
A 仓库记录的 B commit:xxxxx1
B 仓库 master_sdk 最新 commit:xxxxx3
执行:
git submodule update
B 仍然会回到 xxxxx1。
2. git submodule update --remote
如果希望 B submodule 更新到远程分支最新提交,执行:
git submodule update --remote client/Assets/B
如果 .gitmodules 中配置了:
branch = master_sdk
那么这条命令会去 B 仓库的 master_sdk 分支获取最新 commit。
如果要递归更新所有 submodule,可以执行:
git submodule update --remote --recursive
总结:
git submodule update
= 回到 A 仓库当前记录的 B commit
git submodule update --remote
= 去 B 仓库远程分支获取最新 commit
执行 git submodule update --remote 后,如果 B 更新到了新的 commit,还没有结束。
接下来必须执行第七节,把这个变化提交到 A 仓库。
七、提交 submodule 指针变化
执行:
git submodule update --remote client/Assets/B
之后,在 A 仓库中执行:
git status
可能会看到:
modified: client/Assets/B
这里的 modified 通常不是普通文件修改,而是表示:
A 仓库记录的 B submodule commit 发生了变化。
可以执行:
git diff --submodule
查看 B submodule 从哪个 commit 更新到了哪个 commit。
确认无误后,在 A 仓库中提交:
git add client/Assets/B
git commit -m "Update B submodule"
git push
这一步非常重要。
因为:
B 仓库负责保存 B 自己的代码。
A 仓库负责记录当前使用 B 的哪一个 commit。
如果你只是在本地执行了:
git submodule update --remote client/Assets/B
但没有在 A 仓库提交:
git add client/Assets/B
git commit
git push
那么其他人拉取 A 仓库时,仍然不会使用新的 B commit。
八、查看 submodule 状态
查看 submodule 当前状态:
git submodule status
示例输出:
daddf8e4fe8b122128c63d69fd73fd031269285a client/Assets/B (heads/master_sdk)
含义:
daddf8e4fe8b122128c63d69fd73fd031269285a
表示当前 B submodule 检出的 commit。
client/Assets/B
表示 submodule 在 A 仓库中的路径。
(heads/master_sdk)
表示这个 commit 对应到 B 仓库中的 master_sdk 分支引用。
注意:
即使输出中出现了分支名,A 仓库真正记录的仍然是 commit,不是分支。
也可以使用:
git status
查看 A 仓库是否存在 submodule 指针变化。
如果看到:
modified: client/Assets/B
说明本地 B submodule 的 commit 和 A 仓库当前记录的 commit 不一致。
如果这个变化需要同步给团队,就执行第七节提交。
九、submodule 的 detached HEAD 状态
进入 submodule 目录后,可能会看到 detached HEAD 状态。
这是正常现象。
原因是:
A 仓库记录的是 B 仓库的某一个 commit,而不是 B 仓库的分支。
如果你只是使用 submodule,不需要处理。
如果要在 submodule 中修改代码,需要先切换到分支:
cd client/Assets/B
git checkout master_sdk
然后再修改和提交。
十、在 submodule 中修改代码
submodule 是独立 Git 仓库,不是 A 仓库中的普通文件夹。
如果要修改 B submodule 的代码,推荐流程如下:
# 1. 进入 B submodule
cd client/Assets/B
# 2. 切换到要开发的分支
git checkout master_sdk
# 3. 修改代码后,提交并推送 B 仓库
git add .
git commit -m "Modify B module"
git push
# 4. 回到 A 仓库
cd -
# 5. 提交 A 仓库中的 submodule 指针变化
git add client/Assets/B
git commit -m "Update B submodule"
git push
记住顺序:
先提交并推送 B 仓库代码。
再提交并推送 A 仓库中的 submodule 指针变化。
如果只提交了 A 仓库,但 B 仓库的新 commit 没有 push,其他人拉取 A 仓库时,可能会找不到对应的 B commit。
也可以单独克隆一份 B 仓库开发。
B 仓库提交并推送后,再回到 A 仓库执行:
git submodule update --remote client/Assets/B
git add client/Assets/B
git commit -m "Update B submodule"
git push
十一、.gitmodules 和 .git/config 的区别
.gitmodules 是 A 仓库中的 submodule 配置文件,会被提交到远程仓库,团队成员都能拿到。
.git/config 是本地 Git 配置文件,只存在于当前本地仓库,不会提交到远程。
执行:
git submodule init
会把 .gitmodules 中的信息写入本地 .git/config。
一般团队协作时,主要关注 .gitmodules,不建议手动修改 .git/config。
如果 .gitmodules 中的 url 或 branch 发生变化,其他人拉取代码后,可以执行:
git submodule sync --recursive
git submodule update --init --recursive
用于同步本地 submodule 配置。
十二、删除 submodule
删除 submodule 不能只删除目录。
假设要删除:
client/Assets/B
推荐流程如下。
1. 取消初始化 submodule
git submodule deinit -f client/Assets/B
2. 从 A 仓库中移除 submodule
git rm -f client/Assets/B
这一步会删除工作区中的 submodule,并更新 A 仓库索引,通常也会修改 .gitmodules。
3. 清理本地残留的 submodule Git 数据
rm -rf .git/modules/client/Assets/B
注意区分:
client/Assets/B
这是 submodule 的工作目录。
.git/modules/client/Assets/B
这是 Git 在本地保存的 submodule 仓库数据。
4. 提交删除操作
git status
git commit -m "Remove submodule client/Assets/B"
git push
如果 .gitmodules 中仍然残留对应配置,需要手动删除后提交:
git add .gitmodules
git commit -m "Remove submodule config"
git push
十三、常见误区
误区一:以为 submodule 会自动跟随远程分支最新提交
不会。
A 仓库记录的是 B submodule 的具体 commit,不是 B 的分支本身。
误区二:以为 git submodule update 会更新到最新代码
默认不会。
git submodule update
表示更新到 A 仓库当前记录的 submodule commit。
更新到远程分支最新提交需要执行:
git submodule update --remote
误区三:只更新了 B,没有提交 A
如果 B 仓库有了新 commit,但 A 仓库没有提交 submodule 指针变化,其他人拉取 A 仓库后,仍然会使用旧的 B commit。
误区四:只提交了 A,没有推送 B
如果在 submodule 目录中修改了 B 仓库代码,但没有先提交并推送 B 仓库,其他人拉取 A 仓库时可能找不到对应 commit。
正确顺序是:
先提交并推送 B 仓库。
再提交并推送 A 仓库中的 submodule 指针变化。
误区五:把 submodule 当普通文件夹管理
submodule 是独立 Git 仓库,不是父仓库中的普通目录。
它有自己的分支、commit、远程地址和提交历史。
误区六:以为 git pull 后 submodule 一定已经同步
git pull 主要更新 A 仓库本身和 A 仓库记录的 submodule commit。
拉取父仓库后,建议执行:
git submodule update --init --recursive
确保本地 submodule 和 A 仓库记录的 commit 一致。
误区七:删除 submodule 时只删除目录
只执行:
rm -rf client/Assets/B
是不完整的。
应按第十二节流程清理 submodule 配置和 .git/modules 本地缓存。
十四、常用场景速查
1. 添加 submodule
git submodule add -b master_sdk git@github.com:xxxxxx/B.git client/Assets/B
git add .gitmodules client/Assets/B
git commit -m "Add B submodule"
git push
2. 第一次克隆带 submodule 的仓库
git clone --recurse-submodules git@github.com:xxxxxx/A.git
或者:
git clone git@github.com:xxxxxx/A.git
cd A
git submodule update --init --recursive
3. 拉取父仓库后同步 submodule
git pull
git submodule update --init --recursive
4. 更新 submodule 到远程分支最新提交
git submodule update --remote client/Assets/B
git status
git diff --submodule
git add client/Assets/B
git commit -m "Update B submodule"
git push
5. 在 submodule 中修改代码
cd client/Assets/B
git checkout master_sdk
git add .
git commit -m "Modify B module"
git push
cd -
git add client/Assets/B
git commit -m "Update B submodule"
git push
6. 删除 submodule
git submodule deinit -f client/Assets/B
git rm -f client/Assets/B
rm -rf .git/modules/client/Assets/B
git commit -m "Remove submodule client/Assets/B"
git push
十五、最后总结
使用 submodule 时,只要记住三件事:
1. A 仓库记录的是 B 仓库的某一个 commit。
2. git submodule update 默认是回到 A 仓库记录的 commit,不是更新到远程最新代码。
3. 如果 B 的 commit 变了,需要在 A 仓库提交 submodule 指针变化。
最常见的两条流程:
同步 A 仓库当前记录的 submodule:
git submodule update --init --recursive
更新 submodule 到远程最新提交:
git submodule update --remote client/Assets/B
git add client/Assets/B
git commit
git push



5869

被折叠的 条评论
为什么被折叠?



