git-submodule子模块管理
# Git Submodule 子模块管理
# 什么是 Submodule?
当一个项目需要包含并引用其他独立的 Git 仓库时,可以使用 Git Submodule(子模块)。
典型场景:
- 一个 monorepo 主项目包含多个独立子项目,各子项目有各自的远程仓库和提交历史
- 项目依赖一个外部库,且希望跟踪该库的特定版本
- 多端项目(如后端 + 前端 + 小程序)各自独立仓库,但需要一个主仓库统一管理
Submodule 的本质:主仓库并不保存子模块的文件内容,只保存一个指向子模块某个 commit 的引用(指针)。主仓库的 .gitmodules 文件记录子模块的路径和远程地址。
与直接把子项目代码放进主仓库的区别:子模块保持独立的提交历史和远程仓库,主仓库只追踪"当前使用的是子模块的哪个版本"。
# 实战:主仓库管理三端子项目
以我的点餐项目 cm-food-ordering 为例,主仓库管理三个子仓库:
cm-food-ordering/ ← 主仓库
├── backend/ ← submodule(fork 自 RuoYi-Vue-Plus)
├── frontend/ ← submodule(fork 自 plus-ui)
└── uniapp/ ← submodule(独立项目)
2
3
4
# 1. 添加子模块
# 语法:git submodule add <子仓库远程地址> <本地目录名>
git submodule add https://gitee.com/dream-deeply-tyu/RuoYi-Vue-Plus.git backend
git submodule add https://gitee.com/dream-deeply-tyu/plus-ui.git frontend
git submodule add https://gitee.com/dream-deeply-tyu/cm-food-ordering-uniapp.git uniapp
2
3
4
执行后会:
- 在对应目录克隆子仓库
- 生成
.gitmodules文件(记录子模块配置) - 在主仓库暂存
.gitmodules和各子模块目录
# 2. 查看 .gitmodules 文件
[submodule "backend"]
path = backend
url = https://gitee.com/dream-deeply-tyu/RuoYi-Vue-Plus.git
[submodule "frontend"]
path = frontend
url = https://gitee.com/dream-deeply-tyu/plus-ui.git
[submodule "uniapp"]
path = uniapp
url = https://gitee.com/dream-deeply-tyu/cm-food-ordering-uniapp.git
2
3
4
5
6
7
8
9
# 3. 提交主仓库
git add .gitmodules backend frontend uniapp
git commit -m "chore: 改用 submodule 管理三端子仓库"
git push
2
3
# 克隆含子模块的项目
# 方式一:一步到位(推荐)
git clone --recurse-submodules https://gitee.com/dream-deeply-tyu/cm-food-ordering.git
--recurse-submodules 会在克隆主仓库的同时初始化并拉取所有子模块。
# 方式二:先克隆,再初始化子模块
# 先克隆主仓库(子模块目录是空的)
git clone https://gitee.com/dream-deeply-tyu/cm-food-ordering.git
cd cm-food-ordering
# 初始化并拉取所有子模块
git submodule update --init --recursive
2
3
4
5
6
如果子模块里面还嵌套了子模块,
--recursive参数会递归处理。
# 日常开发流程
# 修改子模块代码并同步到主仓库
# 1. 进入子模块目录,正常开发提交
cd backend
git add -A
git commit -m "feat: 新增点餐业务模块"
git push origin cm-food-ordering
# 2. 回到主仓库,更新 submodule 指针
cd ..
git add backend
git commit -m "chore: bump backend submodule"
git push
2
3
4
5
6
7
8
9
10
11
关键点:子模块代码改动后,主仓库会显示子模块有新提交(
modified: backend (new commits))。需要在主仓库额外提交一次,记录子模块指向的新 commit。
# 切换子模块分支
子模块默认会处于"detached HEAD"状态(指向某个具体的 commit,不在任何分支上)。要开发需要先切到分支:
cd backend
git checkout cm-food-ordering
# 之后正常开发提交
2
3
# 查看子模块状态
# 查看所有子模块的当前状态
git submodule status
# 输出示例:
# +a043dbd61c... backend (cm-food-ordering)
# 3964bb7... frontend (cm-food-ordering)
# cc35188... uniapp (cm-food-ordering)
# 前面的 + 表示子模块有未提交的改动
2
3
4
5
6
7
8
# 更新子模块到最新版本
当子模块远程仓库有新提交(比如其他协作者推送了代码),主仓库需要同步:
# 方式一:进入子模块目录拉取
cd backend
git pull origin cm-food-ordering
cd ..
git add backend
git commit -m "chore: update backend submodule"
# 方式二:在主仓库统一更新所有子模块
git submodule update --remote
git add backend frontend uniapp
git commit -m "chore: update all submodules"
2
3
4
5
6
7
8
9
10
11
git submodule update --remote会拉取每个子模块远程对应分支的最新提交,但不会自动切换子模块的 HEAD,需要再git add+git commit记录新指针。
# 删除子模块
Git 没有提供 git submodule remove 命令,需要手动操作:
# 1. 从 .git/config 中移除子模块配置
git submodule deinit -f backend
# 2. 从主仓库索引中移除
git rm -f backend
# 3. 删除 .gitmodules 中的相关条目(如果没有其他子模块,直接删文件)
# 4. 删除 .git/modules/ 下的子模块缓存
rm -rf .git/modules/backend
# 5. 提交
git commit -m "chore: 移除 backend 子模块"
2
3
4
5
6
7
8
9
10
11
12
# 子模块的注意事项
主仓库只保存指针:主仓库不存储子模块的文件内容,只记录子模块当前指向的 commit hash。克隆主仓库时必须用
--recurse-submodules或手动git submodule update --init。子模块默认是 detached HEAD:克隆后子模块不在任何分支上,开发前需要手动
git checkout <branch>。子模块改动需双重提交:在子模块内提交后,还要在主仓库提交一次指针更新。容易忘记第二步,导致主仓库记录的子模块版本是旧的。
不要在主仓库直接修改子模块文件:虽然技术上可行,但容易造成混乱。应该进入子模块目录操作。
切换主仓库分支时注意子模块状态:
git checkout主仓库分支后,子模块可能不会自动切换到对应版本,需要git submodule update。