强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Git 完全指南 / 10 - 子模块:submodule、subtree、monorepo 策略

第十章:子模块与多仓库管理

当项目依赖其他 Git 仓库时,子模块和子树提供了不同的管理方案。


10.1 Git Submodule

子模块允许你将一个 Git 仓库作为另一个仓库的子目录,同时保持独立的版本历史。

10.1.1 添加子模块

# 添加子模块
$ git submodule add https://github.com/lib/library.git libs/library

# 指定分支
$ git submodule add -b main https://github.com/lib/library.git libs/library

# 结果:创建 .gitmodules 文件和 libs/library 目录
$ cat .gitmodules
[submodule "libs/library"]
    path = libs/library
    url = https://github.com/lib/library.git
    branch = main

10.1.2 克隆含子模块的仓库

# 方法 1:克隆后初始化子模块
$ git clone https://github.com/user/project.git
$ cd project
$ git submodule init
$ git submodule update

# 方法 2:一步完成
$ git clone --recursive https://github.com/user/project.git

# 方法 3:克隆后递归初始化
$ git clone https://github.com/user/project.git
$ cd project
$ git submodule update --init --recursive

10.1.3 更新子模块

# 更新所有子模块到最新提交
$ git submodule update --remote

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

# 更新并合并
$ git submodule update --remote --merge

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

10.1.4 子模块日常工作流

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

# 子模块有独立的 Git 仓库
$ git status
$ git checkout main
$ git pull

# 回到主仓库
$ cd ../..

# 查看子模块状态
$ git submodule status
 abc1234 libs/library (heads/main)

# 提交子模块变更
$ git add libs/library
$ git commit -m "Update library submodule to latest"

10.1.5 删除子模块

# 1. 从 .gitmodules 删除配置
$ git config -f .gitmodules --remove-section submodule.libs/library

# 2. 从暂存区删除
$ git rm --cached libs/library

# 3. 删除子模块目录
$ rm -rf libs/library
$ rm -rf .git/modules/libs/library

# 4. 提交变更
$ git commit -m "Remove library submodule"

10.2 Git Subtree

Subtree 将子仓库的代码直接合并到主仓库中,不需要额外的 .gitmodules 文件。

10.2.1 添加子树

# 添加子树(前缀目录)
$ git subtree add --prefix=libs/library https://github.com/lib/library.git main --squash

# --squash 表示压缩子仓库历史为一个提交

10.2.2 更新子树

# 拉取子树最新代码
$ git subtree pull --prefix=libs/library https://github.com/lib/library.git main --squash

# 推送本地修改到上游仓库
$ git subtree push --prefix=libs/library https://github.com/lib/library.git main

10.2.3 分割子目录为独立仓库

# 将某个子目录的历史分离为独立分支
$ git subtree split --prefix=libs/library --branch=library-split

# 推送到独立仓库
$ git push https://github.com/lib/library.git library-split:main

10.3 Submodule vs Subtree 对比

特性 Submodule Subtree
代码存储 独立仓库,只存引用 直接合并到主仓库
克隆方式 需要 --recursive 无需额外操作
更新方式 进入子目录手动更新 subtree pull
历史记录 保留完整子仓库历史 可选择压缩历史
推送修改 在子模块目录推送 subtree push
删除难度 较复杂 简单
适用场景 大型依赖库 小型共享代码
学习曲线 较陡 较平缓

10.4 Monorepo vs Polyrepo

架构对比

策略 说明 代表项目
Monorepo 所有代码在一个仓库 Google、Facebook、Babel
Polyrepo 每个服务/库独立仓库 大多数开源项目
混合模式 核心代码 Monorepo + 外部依赖 Polyrepo 很多企业项目

Monorepo 管理工具

# Git Sparse Checkout(稀疏检出,只检出部分目录)
$ git clone --no-checkout https://github.com/org/monorepo.git
$ cd monorepo
$ git sparse-checkout init --cone
$ git sparse-checkout set services/api libs/shared
$ git checkout main

# 或使用 Git 2.25+ 的简化方式
$ git clone --filter=blob:none --sparse https://github.com/org/monorepo.git
$ cd monorepo
$ git sparse-checkout set services/api libs/shared

仓库对比表

考量维度 Monorepo Polyrepo
代码共享 直接引用 通过包管理器
原子提交 ✅ 跨项目原子提交 ❌ 需要协调
CI/CD 需要增量构建 每个仓库独立
权限控制 粗粒度 细粒度
仓库大小 可能很大 相对较小
开发体验 统一工具链 独立技术栈
代码复用 容易 需要发布包

10.5 常见问题

子模块 detached HEAD

# 子模块默认处于 detached HEAD 状态
$ cd libs/library
$ git status
HEAD detached at abc1234

# 切换到分支解决
$ git checkout main
$ git pull

子模块冲突解决

# 更新子模块时遇到冲突
$ git submodule update --remote --merge
CONFLICT (content): Merge conflict in libs/library

# 进入子模块解决冲突
$ cd libs/library
$ git merge --abort    # 或手动解决冲突
$ git checkout main    # 切换到正确分支
$ cd ../..
$ git add libs/library
$ git commit -m "Resolve submodule conflict"

业务场景

场景 推荐方案
引用外部库且需要固定版本 Submodule
共享内部工具库 Subtree 或私有包管理
微服务单体仓库 Monorepo + Sparse Checkout
大型单体应用 Monorepo
多团队协作 Monorepo + 代码所有权 (CODEOWNERS)

扩展阅读


🔗 上一章09 - 标签管理 | 下一章11 - 变基进阶