想象一下,你在开发一个大项目(父项目),需要用到另一个独立的项目(例如一个公共库、一个UI组件库)。你不想直接复制粘贴它的代码,因为那样就无法方便地获取那个库的后续更新。
Git 子模块就是来解决这个问题的。它允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。
最重要的核心原理:
父项目不存储子模块的所有文件内容。它只存储一个“指针”,这个指针精确地指向子模块仓库的某一个特定的 Commit ID。
这就是理解所有子模块操作的关键。当你更新、切换父项目的分支时,这个“指针”会跟着改变,指向子模块在那个时间点应该使用的版本。
下面会分 3 部分讲述子模块的使用技巧,操作方式包括 Git 命令行方式 和 TortoiseGit 客户端方式
你的主项目 MyProject
需要引入一个外部库 SharedLibrary
。
添加子模块
在你的父项目 MyProject
的根目录下,执行以下命令:
# git submodule add <仓库URL> <存放路径>
git submodule add https://github.com/user/SharedLibrary.git libs/shared-library
这个命令做了三件事:
SharedLibrary
仓库到 libs/shared-library
目录。.gitmodules
文件(如果它不存在的话),用来记录子模块的信息。提交父项目的变更
运行 git status
,你会看到:
new file: .gitmodules
new file: libs/shared-library
现在,提交这个变更来正式将子模块“注册”到你的父项目中:
git commit -m "feat: Add SharedLibrary as a submodule"
git push
MyProject
的根目录空白处右键,选择 TortoiseGit -> Submodule Add… (添加子模块)。https://github.com/user/SharedLibrary.git
)。libs/shared-library
)。.gitmodules
文件和 libs/shared-library
文件夹。填写提交信息,然后 Commit & Push。你的同事需要克隆 MyProject
,或者你需要拉取包含了子模块更新的父项目。
首次克隆项目
使用 --recurse-submodules
参数,可以在克隆父项目时自动初始化并拉取所有子模块。
git clone --recurse-submodules <父项目的URL>
如果你忘记加参数,也可以分步操作:
git clone <父项目的URL>
cd MyProject
git submodule update --init --recursive
--init
: 初始化本地的 .git/config
文件,注册子模块信息。--recursive
: 如果子模块还包含了其他子模块,也一并处理。拉取父项目的更新 (可能包含子模块指针的变更)
日常工作中,你拉取了父项目的更新,发现它指向了子模块的一个新版本。
# 1. 拉取父项目的变更
git pull
# 2. 根据父项目最新的指针,更新子模块的代码
git submodule update --recursive
首次克隆项目
拉取父项目的更新
由于子模块默认处于“分离头指针”(Detached HEAD)状态,我们必须先将子模块切换到分支(主分支 or 你指定的分支)后,才能进行修改。
注:在“分离头指针”这种状态下,你依然可以提交代码,但这些提交不属于任何分支。当你试图用 git push origin HEAD 推送时,Git 懵了:
- 你 (Source): “把 HEAD 指向的这个 commit 推上去!”
- Git (Destination): “推到远程仓库 (origin) 的哪里去呢?HEAD 在远程不是一个合法的分支名。我不知道该在远程创建/更新哪个分支。”
你需要修复 SharedLibrary
子模块里的一个 Bug。
进入子模块并切换到分支
# 1. 进入子模块目录
cd libs/shared-library
# 2. 检查状态,你会看到 "HEAD detached at..."
git status
# 3. 切换到你想修改的分支(例如 main)
git checkout main
# 4. (强烈推荐) 拉取最新代码,确保你的修改基于最新版本
git pull
修改、提交并推送子模块
现在你可以像操作任何普通 Git 仓库一样操作它。
# ... 在这里修改文件 ...
git add .
git commit -m "fix: 修复了 SharedLibrary 中的某个重要 Bug"
git push origin main
至此,子模块本身的代码已经推送到它自己的远程仓库了。但父项目还不知道这个变化。
更新父项目的指针
# 1. 回到父项目根目录
cd ../..
# 2. 检查状态,你会看到子模块被标记为 "modified (new commits)"
git status
# 输出会像这样:
# modified: libs/shared-library (new commits)
# 3. 添加这个变更到暂存区
git add libs/shared-library
# 4. 提交并推送父项目的更新
git commit -m "chore: 更新 SharedLibrary 子模块以修复 Bug"
git push
进入子模块并切换到分支
libs/shared-library
)。main
分支,点击 OK。修改、提交并推送子模块
更新父项目的指针
libs/shared-library
被列为“已修改”。commit & push
,再回到父项目 commit & push
更新指针。cd
进去,然后 git checkout
到一个分支。git submodule update
来同步子模块代码。git submodule update
。希望这篇详尽的教程能帮你彻底掌握 Git 子模块的使用!