跳转到内容

Git 子模块 (Submodules)

有时你的项目需要依赖另一个 Git 仓库的代码(例如一个通用的工具库)。虽然可以使用包管理器(如 npm, maven),但如果你想直接在源码层面进行依赖管理,或者该库不是公开包,Git Submodule 就是标准解决方案。

子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。它能让你将另一个仓库克隆到自己的项目中,同时保持提交的独立。

假设你想把 libs/utils 库添加到当前项目的 third-party/utils 目录下。

Terminal window
# 语法:git submodule add <仓库URL> [路径]
git submodule add https://github.com/example/utils.git third-party/utils

执行后,Git 会做两件事:

  1. .gitmodules 文件中添加子模块的映射信息。
  2. 将该子模块的当前 commit hash 记录在父仓库中。

当你克隆一个含有子模块的项目时,默认情况下,子模块目录是空的。

方法 A:克隆时一步到位(推荐)

Section titled “方法 A:克隆时一步到位(推荐)”

使用 --recursive 参数:

Terminal window
git clone --recursive https://github.com/my-org/main-project.git

如果你已经克隆了父仓库,但忘记了加 --recursive,你需要运行两个命令:

Terminal window
# 初始化本地配置文件
git submodule init
# 从该项目中抓取所有数据并检出父项目中列出的合适的提交
git submodule update

通常可以将它们合并为一个命令:

Terminal window
git submodule update --init --recursive

进入子模块目录,它就是一个标准的 Git 仓库。

Terminal window
cd third-party/utils
git status

关键点:默认情况下,子模块处于 Detached HEAD(游离指针)状态。它指向的是父仓库记录的特定 Commit,而不是某个分支。

如果上游仓库(utils)更新了,你想在你的项目中同步这些更新:

  1. 手动更新

    Terminal window
    cd third-party/utils
    git fetch
    git checkout main # 切换到分支
    git pull
  2. 在父目录更新: 如果你只是想更新到子模块远程分支的最新状态:

    Terminal window
    git submodule update --remote

更新完成后,记得在父仓库提交这次变更:

Terminal window
cd ../.. # 回到父仓库根目录
git add third-party/utils
git commit -m "chore: update utils submodule to latest version"

在旧版 Git 中删除子模块非常繁琐。在现代 Git 版本中,步骤已简化,但仍需小心:

Terminal window
# 1. 卸载子模块(Git 1.8.3+,加 -f 可强制卸载有本地修改的子模块)
git submodule deinit -f -- third-party/utils
# 2. 从 git 索引和 .gitmodules 中移除
git rm -f third-party/utils
  1. 删除残留的 .git/modules 目录(可选,为了彻底清理):

Bash (Mac/Linux/Git Bash)

Terminal window
rm -rf .git/modules/third-party/utils

PowerShell (Windows)

Terminal window
Remove-Item -Recurse -Force .git/modules/third-party/utils

最后,提交这次删除操作:

Terminal window
git commit -m "chore: remove utils submodule"

子模块很强大,但也增加了工作流的复杂度。在决定使用之前,请考虑:

  • 是否可以用包管理器(npm/pip)代替?
  • 团队成员是否熟悉 git submodule 命令?

如果必须使用,请记住口诀:克隆加递归 (--recursive),修改先推子 (push submodule first)