Skip to content

子模块(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 对比

对比项SubmoduleSubtree
学习曲线较高中等
历史隔离完全独立合并在一起
更新方式需要手动更新 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

子模块是管理复杂项目依赖的一种方式,但它有一定的使用复杂度。在选择子模块之前,先考虑是否有更简单的方案(如包管理器)。