Appearance
子模块(Submodule)
Git 子模块(Submodule)允许你在一个 Git 仓库中嵌套另一个独立的 Git 仓库,适合管理项目依赖或组合多个相关项目。
什么是子模块及适用场景
子模块是将一个外部仓库作为子目录纳入当前项目,并固定在某个特定的提交上。
适用场景:
- 项目依赖另一个内部共享库(不想发布到 npm/pip 等)
- 多个项目共用同一份工具代码
- 需要锁定依赖的特定版本
- 组合多个独立项目(Monorepo 的早期替代方案)
不适用场景:
- 已有成熟的包管理系统(npm、pip、Maven 等)时
- 对依赖更新频率要求高时
- 团队 Git 经验不足时(子模块有一定学习成本)
git submodule add 添加子模块
bash
# 添加子模块
git submodule add https://github.com/user/library.git
# 添加到指定目录
git submodule add https://github.com/user/library.git libs/library
# 指定分支(默认跟踪默认分支)
git submodule add -b develop https://github.com/user/library.git添加后的变化:
bash
# 1. 创建子模块目录
ls libs/library/
# 2. 创建 .gitmodules 文件(记录子模块配置)
cat .gitmodules
# [submodule "libs/library"]
# path = libs/library
# url = https://github.com/user/library.git
# 3. 在主仓库暂存区有两项改动
git status
# Changes to be committed:
# new file: .gitmodules
# new file: libs/library
# 4. 提交这些改动
git add .gitmodules libs/library
git commit -m "chore: 添加 library 子模块"主仓库如何追踪子模块:
主仓库不追踪子模块的文件内容,而是追踪子模块指向的提交 SHA-1。
bash
git ls-tree HEAD libs/library
# 160000 commit abc123... libs/library
# (160000 模式 + commit 类型 = 子模块)git submodule init / update
克隆包含子模块的仓库后,子模块目录是空的,需要初始化和更新:
bash
# 方式1:分两步初始化
git submodule init # 注册子模块(基于 .gitmodules)
git submodule update # 克隆子模块内容(到主仓库记录的提交)
# 方式2:一步完成
git submodule update --init
# 递归处理(子模块中还有子模块)
git submodule update --init --recursive克隆含子模块的仓库
bash
# 方式1:克隆时自动初始化子模块
git clone --recurse-submodules https://github.com/user/repo.git
git clone --recursive https://github.com/user/repo.git # 旧语法
# 方式2:克隆后手动初始化
git clone https://github.com/user/repo.git
cd repo
git submodule update --init --recursive更新子模块版本
bash
# 进入子模块目录
cd libs/library
# 拉取最新代码
git pull origin main
# 回到主仓库
cd ../..
# 查看子模块状态(新指向的提交)
git status
# modified: libs/library (new commits)
# 提交更新(记录新的 SHA-1)
git add libs/library
git commit -m "chore: 升级 library 子模块到最新版"批量更新所有子模块:
bash
# 将所有子模块更新到各自的最新远程提交
git submodule update --remote
# 更新特定子模块
git submodule update --remote libs/library查看子模块状态
bash
# 查看所有子模块状态
git submodule status
# -abc123 libs/library (未初始化,SHA-1 前有 -)
# def456 libs/library (正常)
# +ghi789 libs/library (子模块内有新提交,SHA-1 前有 +)
# Uabc123 libs/library (有合并冲突,SHA-1 前有 U)
# 递归查看
git submodule status --recursive在所有子模块上执行命令
bash
# 在所有子模块上执行 git pull
git submodule foreach git pull origin main
# 在所有子模块上执行任意命令
git submodule foreach 'git status'
git submodule foreach 'git log --oneline -3'删除子模块
删除子模块需要几个步骤:
bash
# 1. 从 .gitmodules 中移除
git submodule deinit libs/library
# 2. 从 Git 中移除
git rm libs/library
# 3. 提交改动
git commit -m "chore: 移除 library 子模块"
# 4. 清理残留文件(可选)
rm -rf .git/modules/libs/library子模块 vs Subtree 对比
| 对比项 | Submodule | Subtree |
|---|---|---|
| 学习曲线 | 较高 | 中等 |
| 历史隔离 | 完全独立 | 合并在一起 |
| 更新方式 | 需要手动更新 SHA-1 | 直接合并 |
| 克隆复杂度 | 需要 --recurse-submodules | 克隆即包含 |
| 贡献到子模块 | 进入目录直接操作 | 需要 git subtree push |
| 磁盘占用 | 独立的 .git 目录 | 共享一个 .git |
| 适合场景 | 明确依赖版本控制 | 偶尔合并的外部代码 |
实际操作示例
bash
# 完整示例:在前端项目中添加 UI 组件库子模块
git submodule add git@github.com:company/ui-components.git src/ui
# 提交
git commit -m "chore: 添加内部 UI 组件库"
# 推送(包括 .gitmodules 的更改)
git push
# 另一位开发者拉取并初始化
git pull
git submodule update --init总结
| 操作 | 命令 |
|---|---|
| 添加子模块 | git submodule add <url> [path] |
| 初始化(克隆后) | git submodule update --init --recursive |
| 克隆时初始化 | git clone --recurse-submodules <url> |
| 更新子模块 | git submodule update --remote |
| 查看状态 | git submodule status |
| 对所有子模块执行 | git submodule foreach <command> |
| 删除子模块 | git submodule deinit + git rm |
子模块是管理复杂项目依赖的一种方式,但它有一定的使用复杂度。在选择子模块之前,先考虑是否有更简单的方案(如包管理器)。