1. 引言
在大型项目中,我们经常需要在一个主仓库中引用其他独立的代码仓库。Git Submodule 正是解决这一问题的标准方案。它允许你将一个 Git 仓库作为另一个 Git 仓库的子目录进行管理,同时保持两个仓库的独立性------子仓库可以有自己的提交历史、分支和版本,而主仓库只需记录子仓库的特定提交引用即可。
本文将以 https://gitee.com/omni-cloud/omni-gov.git 作为主仓库、https://gitee.com/omni-cloud/embd-skills.git 作为子仓库为例,完整演示 Git Submodule 的常规操作全流程,涵盖添加、克隆、更新、切换分支、删除等日常高频场景。无论你是刚接触 Submodule 的新手,还是希望规范多仓库协作的开发者,本文都能为你提供清晰的操作指引。
添加 Submodule 后,主仓库的目录结构将如下所示(可使用 tree /F /A 命令查看):
bash
omni-gov/
├── .git/
├── .gitmodules # 记录子模块的映射关系
├── skills/embd-skills/ # 子仓库内容(作为子目录)
│ ├── .git/ # 子仓库的独立 Git 元数据
│ ├── src/
│ └── README.md
└── 其他主仓库文件...
2. 前置准备
在开始之前,请确保你的开发环境满足以下条件:
- Git 版本:已安装 Git 2.x 及以上版本(推荐 2.20+,对 Submodule 的支持更完善)
- 访问权限 :已配置好 SSH Key 或 HTTPS 凭据,能够访问
omni-cloud组织下的两个仓库 - 主仓库 :本地已克隆主仓库
omni-cloud/omni-gov.git,并已切换到目标分支(如main或develop) - 远程仓库 :确认
omni-cloud/embd-skills.git已存在,且你有该仓库的读取权限
提示 :如果尚未克隆主仓库,可先执行
git clone https://gitee.com/omni-cloud/omni-gov.git完成克隆。
3. 添加 Submodule
进入主仓库根目录,执行以下命令将子仓库添加为 submodule,并指定到 skills/embd-skills 目录下:
bash
git submodule add https://gitee.com/omni-cloud/embd-skills.git skills/embd-skills
如果需要将子模块添加到自定义目录(例如 skills),可在命令末尾指定路径:
bash
git submodule add https://gitee.com/omni-cloud/embd-skills.git skills/embd-skills
此时 .gitmodules 中的 path 会相应地变为 skills。
执行该命令后,Git 会完成以下操作:
- 将
embd-skills仓库克隆到主仓库的embd-skills/子目录中 - 在主仓库的
.git/config中记录子模块的 URL 信息 - 创建
.gitmodules文件,用于记录子模块的映射关系(该文件会随主仓库一起提交)
执行成功后,主仓库中会出现:
embd-skills/子目录(子仓库内容).gitmodules配置文件(记录子模块映射关系)
.gitmodules 文件内容如下:
ini
[submodule "skills/embd-skills"]
path = skills/embd-skills
url = https://gitee.com/omni-cloud/embd-skills.git
其中:
path指定子模块在主仓库中的存放路径url指定子模块的远程仓库地址
注意 :
.gitmodules文件会随主仓库提交并同步给其他协作者,因此请确保其中的 URL 是团队内所有成员都能访问的地址。
提示 :添加子模块后,主仓库的.git/config中也会记录子模块的 URL 信息,但该文件仅存在于本地,不会随仓库提交。.gitmodules才是随仓库共享的配置来源,两者需保持一致。
常见错误排查 :如果执行git submodule add时提示fatal: please make sure that the .gitmodules file is in the working tree,通常有以下几种原因:
- 当前目录不是主仓库根目录 :请先确认你位于主仓库根目录(即包含
.git/的目录),可用git rev-parse --show-toplevel查看仓库根路径,然后cd到该目录再执行。.gitmodules文件被误删或损坏 :检查根目录下是否存在.gitmodules文件,若缺失可手动创建空文件后再执行git submodule add。- 仓库未正确初始化 :确认主仓库是有效的 Git 仓库(存在
.git/目录),若是在子目录中误执行了git init,需回到正确的仓库根目录操作。
确认 .gitmodules 配置无误后,接下来需要将相关文件加入暂存区,以便提交到主仓库。
4. 添加文件
将子模块相关文件加入暂存区:
bash
git add .gitmodules skills/embd-skills
这里需要同时添加两个部分:
.gitmodules:记录子模块映射关系的配置文件skills/embd-skills/:子模块对应的 Git 指针(记录当前子模块所指向的具体提交)
提示 :
git add embd-skills添加的是子模块的引用(commit 指针),而不是子仓库内的具体文件内容。主仓库正是通过记录这个指针来锁定子模块的版本,因此需要将指针变更提交到主仓库。子仓库内部的变更需要在其自身仓库中单独提交。
补充 :如果子仓库内部有未提交的改动,主仓库中的git status会显示子模块目录为modified状态。此时需要先进入子仓库完成提交与推送,再回到主仓库重新git add embd-skills更新指针引用。
5. 提交变更
提交本次变更,并附上清晰的提交信息:
bash
git commit -m "feat: 添加 embd-skills 子模块"
提交信息建议遵循 Conventional Commits 规范,使用 feat: 前缀表明这是一次新功能引入。清晰的提交信息有助于团队成员快速理解本次变更的目的,也便于后续通过 git log 回溯历史。
提示 :如果希望将子模块的添加与主仓库的其他改动分开管理,也可以拆分为多个提交。例如先提交
.gitmodules与子模块指针,再提交主仓库的其他业务代码,这样在代码评审时更容易聚焦。
6. 推送提交(创建 PR)
将本地分支推送到远程,并创建 Pull Request:
bash
git push
推送完成后,在 GitHub 上打开主仓库页面,点击 Compare & pull request 创建 PR,等待评审与合并。如果使用的是 Gitee,则进入仓库页面后点击 Pull Request 标签,选择源分支与目标分支(如 main)后创建 PR。
提示 :如果当前不在
main分支,请先切换到目标分支再推送,例如git checkout main && git push origin main。若远程分支尚未创建,可使用git push -u origin main同时建立上游跟踪关系。
补充 :推送完成后,PR 中会展示主仓库的变更内容,包括新增的.gitmodules文件和子模块指针。评审者可以通过 PR 直观地看到子模块的引入,并在合并前确认子模块的 URL 与目标提交是否符合预期。
7. 克隆含子模块的仓库
当团队成员克隆一个包含子模块的主仓库时,子模块目录默认是空的,需要额外初始化并拉取。有两种方式:
方式一:克隆时自动初始化
bash
git clone --recurse-submodules https://gitee.com/omni-cloud/omni-gov.git
方式二:克隆后手动初始化
bash
git clone https://gitee.com/omni-cloud/omni-gov.git
cd omni-gov
git submodule init
git submodule update
提示 :
git submodule init会根据.gitmodules中的配置在本地.git/config中注册子模块,git submodule update则会拉取并检出子模块到指定提交。两条命令可以合并为git submodule update --init。
8. 更新子模块
子仓库的代码更新后,主仓库需要拉取最新的子模块提交。常规操作如下:
方式一:更新所有子模块到远程最新提交
bash
git submodule update --remote
方式二:更新指定子模块
bash
git submodule update --remote skills/embd-skills
更新完成后,主仓库中会看到子模块指针发生变化,需要重新提交:
bash
git add skills/embd-skills
git commit -m "chore: 更新 embd-skills 子模块到最新提交"
git push
提示 :
git submodule update --remote默认拉取子模块远程仓库的HEAD分支(通常是master或main)。如需指定分支,可在.gitmodules中配置branch字段,或使用--remote配合-b参数指定。
常见错误排查 :如果执行git submodule update --remote skills/embd-skills时提示fatal: Unable to find refs/remotes/origin/main revision in submodule path 'skills/embd-skills',通常有以下几种原因:
- 子模块远程仓库的默认分支不是
main:远程仓库的默认分支可能是master或其他名称。可先进入子模块目录执行git branch -r查看远程分支列表,确认实际分支名。.gitmodules中配置了branch = main,但远程仓库没有该分支 :检查.gitmodules中的branch字段是否与远程仓库实际分支一致。若不一致,使用git submodule set-branch --branch <实际分支名> skills/embd-skills修正配置。- 子模块本地未拉取远程分支引用 :可先进入子模块目录执行
git fetch origin main手动拉取,再回到主仓库重新执行git submodule update --remote skills/embd-skills。- 远程分支名与本地不一致 :如果远程分支是
master,可执行git submodule update --remote -b master skills/embd-skills指定分支更新。
9. 在子模块内部工作
子模块本身是一个独立的 Git 仓库,拥有自己完整的提交历史、分支和远程仓库。因此,你可以像操作普通仓库一样,在子模块内部进行日常开发。下面演示一个完整的开发流程:从创建功能分支、修改代码,到提交并推送。
bash
# 1. 进入子模块目录
cd skills/embd-skills
# 2. 基于当前分支创建新的功能分支
git checkout -b feature/new-skill
# 3. 修改代码、新增文件...
# 例如:编辑 src/ 下的源码,或新增一个技能定义文件
# 4. 查看变更状态,确认修改内容
git status
# 5. 将改动加入暂存区
git add .
# 6. 提交变更,附上清晰的提交信息
git commit -m "feat: 新增技能模块"
# 7. 将功能分支推送到子模块的远程仓库
git push origin feature/new-skill
这里有几个关键点需要理解:
-
子模块内部的提交与推送不会影响主仓库 。主仓库只记录子模块的提交指针(即当前检出的 commit SHA),而不会感知子模块内部具体改动了哪些文件。因此,你可以在子模块中自由地创建分支、提交代码,主仓库的
git status只会显示子模块目录为modified状态,表示其当前提交与主仓库记录的指针不一致。 -
当子模块的远程分支更新后 ,回到主仓库执行
git submodule update --remote即可拉取子模块的最新提交,并更新主仓库记录的指针。
注意 :在子模块内部切换分支或提交代码时,主仓库的
git status会显示子模块为modified状态,这是正常现象,表示子模块的当前提交与主仓库记录的指针不一致。此时无需惊慌,只需在主仓库中重新git add skills/embd-skills并提交,即可将新的指针引用固化到主仓库。
提示 :如果子模块内部有未提交的改动,主仓库中的git status会显示子模块目录为modified状态。此时需要先进入子仓库完成提交与推送,再回到主仓库重新git add skills/embd-skills更新指针引用。
10. 切换子模块分支
有时需要将子模块切换到特定分支或提交:
bash
cd skills/embd-skills
git checkout main
git pull origin main
或者直接在主仓库中指定子模块的分支:
bash
git submodule set-branch --branch main skills/embd-skills
git submodule update --remote skills/embd-skills
提示 :
git submodule set-branch会更新.gitmodules中的branch配置,并同步到本地配置。该命令需要 Git 2.22+ 版本支持。
11. 删除子模块
删除子模块需要清理多个位置,手动操作容易遗漏。推荐使用以下步骤:
bash
# 1. 从 .gitmodules 中移除子模块配置
git submodule deinit -f skills/embd-skills
# 2. 从 Git 索引中移除子模块
git rm -f skills/embd-skills
# 3. 删除子模块的本地目录(如果还存在)
rm -rf .git/modules/skills/embd-skills
执行完成后,提交变更:
bash
git add .
git commit -m "chore: 移除 skills/embd-skills 子模块"
git push
注意 :
git submodule deinit会取消注册子模块并清空其工作目录,但不会删除.gitmodules中的配置。git rm -f会同时从索引和.gitmodules中移除子模块。两者配合使用才能彻底删除。
12. 总结
通过以上步骤,我们完整覆盖了 Git Submodule 的常规操作全流程:
git submodule add添加子模块git add添加相关文件git commit提交变更git push推送并创建 PRgit clone --recurse-submodules克隆含子模块的仓库git submodule update --remote更新子模块- 在子模块内部进行独立开发
git submodule deinit+git rm删除子模块
掌握 Submodule 的使用,可以让多仓库协作更加清晰、可控。当子仓库需要更新时,只需在主仓库中执行 git submodule update --remote 拉取最新提交,再提交新的指针引用即可。希望本文能帮助你顺利上手 Git Submodule,在多仓库项目中游刃有余。
延伸 :除了上述常规操作,日常维护中还会用到
git submodule status查看子模块当前状态、git submodule foreach在所有子模块中批量执行命令等高级用法。当团队成员克隆主仓库后,执行git submodule update --init即可快速还原子模块环境,避免手动逐个克隆的麻烦。