子模块解决什么问题
子模块(submodule)把另一个 Git 仓库以固定提交的形式嵌入当前仓库:父仓库只保存子模块的 URL、检出路径,以及一个指向具体提交的 gitlink 记录,文件内容仍存放在子模块自己的仓库里。它适合「依赖的代码由其他团队或上游维护、但必须锁定到具体版本」的场景,代价是父子仓库要分别提交、分别推送。
基本信息
| 适用版本 | Git 1.6.5 起支持,Windows/Linux/macOS 行为一致 |
|---|---|
| 引入命令 | git submodule add <仓库URL> [<路径>] |
| 父仓库记录什么 | .gitmodules(URL 与路径)+ gitlink(子模块提交哈希) |
| 内容何时拉取 | clone 时加 --recurse-submodules,或事后 init 加 update |
| 权限要求 | 对子模块远端至少读权限;推送指针需本地写权限 |
典型使用场景
- 跨团队共享的内部库:版本必须锁定,不允许随手跟进上游 HEAD
- 第三方依赖需要少量本地改动:Fork 后以子模块引入,改动可追溯
- 文档站点、主题、脚本片段在多个项目间复用同一份仓库
- 审计要求把「依赖的具体提交」写进父仓库历史
- 构建需要离线可复现:固定提交而不是浮动分支
最小示例
最小示例
git submodule add https://example.com/libs/common.git vendor/common # 引入并立即克隆
# 父仓库根目录执行,省略路径时默认取仓库名
git status # 会看到 .gitmodules 与 vendor/common 两项
cd vendor/common && git log --oneline -1 # 确认子模块停在哪个提交
cd ../.. && git commit -m 'chore: 引入 common 子模块' # 提交的是指针不是内容
git clone --recurse-submodules https://example.com/app.git # 克隆时一次拉全
git submodule status --recursive # 核对各子模块提交,行首 - 表示未初始化与常见替代方案对比
| 方案 | 依赖记录方式 | 更适合什么情况 |
|---|---|---|
| 子模块 | 父仓库记录提交哈希 | 代码由外部维护且必须锁定版本 |
| 包管理器 | 依赖清单加锁文件 | 语言生态内有成熟包管理 |
| vendoring | 依赖源码直接入库 | 要求单仓库交付、可接受复制 |
| monorepo | 同仓库多目录 | 团队能统一发布节奏 |
父仓库记录的是子模块的一个具体提交,子模块上游更新不会自动进入父仓库:要在子模块内 fetch 并 checkout 到目标提交,再回父仓库 git add 该路径并提交指针变更。
子模块远端地址变更后,老克隆不会自动跟随:需同步 .gitmodules(git submodule sync)并重新 update,否则拉取仍指向旧地址。