Skip to content

子模块

概述

Git 子模块(Submodule)允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。这使你能够将外部项目或库作为独立仓库管理,同时在主项目中引用特定版本。

什么是子模块

子模块是 Git 仓库中包含另一个 Git 仓库的方式:

  • 子模块保持独立的版本控制
  • 主项目记录子模块的特定提交引用
  • 子模块可以在多个项目间共享

子模块的特点

  • 独立仓库:子模块有自己的 .git 目录
  • 版本锁定:主项目记录子模块的特定提交
  • 嵌套支持:子模块可以包含自己的子模块
  • 远程引用:子模块指向独立的远程仓库

添加子模块

基本添加

bash
# 添加子模块
git submodule add <repository-url> <path>

# 示例:添加第三方库
git submodule add https://github.com/user/library.git libs/library

# 添加到特定分支
git submodule add -b main https://github.com/user/library.git libs/library

# 添加指定版本的子模块
git submodule add https://github.com/user/library.git libs/library
cd libs/library
git checkout v1.2.0
cd ../..
git add libs/library
git commit -m "添加 library v1.2.0 作为子模块"

添加时的配置

bash
# 指定分支
git submodule add -b stable https://github.com/user/library.git libs/library

# 指定名称(用于 .gitmodules 中的标识)
git submodule add --name my-lib https://github.com/user/library.git libs/library

.gitmodules 文件

添加子模块后,会在项目根目录创建 .gitmodules 文件:

ini
[submodule "libs/library"]
    path = libs/library
    url = https://github.com/user/library.git
    branch = main

查看子模块状态

bash
# 查看子模块状态
git submodule status

# 输出示例:
# abc1234 libs/library (v1.2.0)
# -abc1234 表示子模块未初始化
# +abc1234 表示子模块有更新
#  abc1234 表示子模块正常

# 查看详细信息
git submodule summary

# 查看子模块的远程 URL
git config --file .gitmodules --get-regexp path

更新子模块

初始化和更新

bash
# 克隆包含子模块的仓库后
git submodule init
git submodule update

# 或者一步完成
git submodule update --init

# 同时更新嵌套子模块
git submodule update --init --recursive

克隆时自动初始化

bash
# 克隆时自动初始化子模块
git clone --recursive <repository-url>

# 或者克隆后再初始化
git clone <repository-url>
cd repository
git submodule update --init --recursive

更新子模块到最新版本

bash
# 更新子模块到远程最新提交
git submodule update --remote

# 更新特定子模块
git submodule update --remote libs/library

# 更新到特定分支的最新提交
git submodule update --remote --merge

# 更新并变基
git submodule update --remote --rebase

手动更新子模块

bash
# 进入子模块目录
cd libs/library

# 获取最新代码
git fetch origin

# 切换到需要的版本
git checkout v1.3.0

# 返回主项目
cd ../..

# 提交子模块更新
git add libs/library
git commit -m "更新 library 到 v1.3.0"

批量更新

bash
# 更新所有子模块
git submodule foreach git pull origin main

# 对所有子模块执行命令
git submodule foreach 'git checkout main && git pull'

# 检查所有子模块的状态
git submodule foreach git status

删除子模块

删除步骤

bash
# 1. 反初始化子模块
git submodule deinit -f libs/library

# 2. 从 .git/modules 中删除
rm -rf .git/modules/libs/library

# 3. 从 .gitmodules 和索引中删除
git rm -f libs/library

# 4. 提交更改
git commit -m "移除 library 子模块"

# 5. 删除 .gitmodules(如果没有其他子模块)
rm .gitmodules
git add .gitmodules
git commit -m "删除 .gitmodules 文件"

使用 git rm 删除

bash
# Git 1.8.3+ 可以一步删除
git rm libs/library
git commit -m "移除 library 子模块"

# 同时清理 .git/modules
git submodule deinit -f libs/library
rm -rf .git/modules/libs/library

子模块工作流程

场景一:开发者在子模块中工作

bash
# 1. 进入子模块
cd libs/library

# 2. 创建功能分支
git checkout -b feature/new-function

# 3. 进行修改
vim src/library.js

# 4. 提交修改
git add .
git commit -m "添加新功能"

# 5. 推送到子模块远程
git push origin feature/new-function

# 6. 返回主项目
cd ../..

# 7. 更新主项目引用
git add libs/library
git commit -m "更新 library 子模块"

场景二:团队协作

bash
# 开发者 A:更新子模块
cd libs/library
git checkout main
git pull origin main
cd ../..
git add libs/library
git commit -m "更新子模块"
git push

# 开发者 B:获取更新
git pull
git submodule update --init --recursive

场景三:切换分支时处理子模块

bash
# 切换分支时子模块可能不匹配
git checkout feature-branch

# 更新子模块到正确版本
git submodule update --init --recursive

# 或者使用钩子自动更新
# 在 .git/hooks/post-checkout 中添加:
# git submodule update --init --recursive

子模块高级操作

子模块的分支管理

bash
# 在子模块中创建分支
cd libs/library
git checkout -b custom-branch

# 主项目记录子模块的分支
git config -f .gitmodules submodule.libs/library.branch custom-branch

# 更新时使用配置的分支
git submodule update --remote libs/library

子模块的标签管理

bash
# 锁定子模块到特定标签
cd libs/library
git checkout v1.2.0
cd ../..
git add libs/library
git commit -m "锁定 library 到 v1.2.0"

子模块的冲突解决

bash
# 当子模块引用冲突时
# 查看冲突
git status

# 选择一个版本
git checkout --ours libs/library    # 使用当前版本
git checkout --theirs libs/library  # 使用合并进来的版本

# 或者手动更新
cd libs/library
git checkout <commit-hash>
cd ../..
git add libs/library

子模块常见问题

问题一:子模块显示为空目录

bash
# 原因:子模块未初始化
git submodule init
git submodule update

问题二:子模块处于游离 HEAD 状态

bash
# 这是正常行为,子模块默认检出特定提交
# 如果需要在子模块中工作
cd libs/library
git checkout main
git pull

问题三:子模块更新后主项目报错

bash
# 子模块更新后需要提交主项目的引用变更
git add libs/library
git commit -m "更新子模块引用"

问题四:克隆项目后子模块为空

bash
# 使用 --recursive 选项
git clone --recursive <url>

# 或者克隆后初始化
git submodule update --init --recursive

子模块配置

配置选项

bash
# 设置子模块更新策略
git config -f .gitmodules submodule.libs/library.update checkout

# 更新策略选项:
# checkout - 检出配置的提交(默认)
# rebase - 变基到当前分支
# merge - 合并远程更新
# none - 不自动更新

# 设置子模块获取 URL
git config -f .gitmodules submodule.libs/library.url https://github.com/user/library.git

# 设置子模块的分支
git config -f .gitmodules submodule.libs/library.branch main

全局配置

bash
# 设置全局子模块更新行为
git config --global submodule.recurse true

# 设置子模块的递归行为
git config --global status.submoduleSummary true

子模块最佳实践

1. 使用标签锁定版本

bash
# 在子模块中使用标签
cd libs/library
git checkout v1.2.0
cd ../..
git add libs/library
git commit -m "使用 library v1.2.0"

2. 文档化子模块

在 README 中记录子模块信息:

markdown
## 子模块

本项目使用以下子模块:

- `libs/library` - 核心库 (v1.2.0)
- `plugins/auth` - 认证模块 (v2.0.0)

初始化命令:
git submodule update --init --recursive

3. 使用脚本管理子模块

bash
#!/bin/bash
# update-submodules.sh

echo "更新所有子模块..."
git submodule foreach 'git checkout main && git pull origin main'

echo "提交更新..."
git add .
git commit -m "更新子模块到最新版本"

4. CI/CD 中处理子模块

yaml
# GitHub Actions 示例
steps:
  - uses: actions/checkout@v3
    with:
      submodules: recursive

总结

操作命令说明
添加git submodule add <url> <path>添加子模块
初始化git submodule init初始化子模块
更新git submodule update更新子模块
更新到最新git submodule update --remote更新到远程最新
删除git submodule deinit -f <path>删除子模块
状态git submodule status查看状态
批量操作git submodule foreach <cmd>对所有子模块执行命令

使用建议

  • 使用标签锁定子模块版本
  • 文档化子模块的使用方法
  • CI/CD 中自动初始化子模块
  • 定期更新子模块并测试