git submodule

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值