首页/开发/Git 与协作速查/子模块与子树的选择
进阶与大仓实践69 条

子模块与子树的选择

子模块保留对方完整仓库与独立历史,适合工具库与可复用组件;子树把代码并入本仓库目录,单仓克隆即可构建,代价是历史合并噪声与更新需要显式命令。

Git 与协作速查开发更新于 2026-10-13

子模块与子树怎么选

两者都是「把一个仓库引入另一个仓库」的方案,但机制相反:子模块(submodule)在父仓库里保存一个指向对方仓库某次提交的指针,对方仓库与历史保持独立;子树(subtree)把对方的代码以普通目录形式并进本仓库历史,克隆本仓库即可完整构建。选型的关键是「组件是否独立演进、团队是否接受多一步初始化」;拿不准时先看对方是否被多个项目共用。

基本信息

子模块本质父仓库存指针(gitlink),不保存对方文件历史
子树本质对方内容并入指定前缀目录,历史合入本仓库
关键命令git submodule update --init --recursive;git subtree add --prefix=目录 仓库 分支
上游贡献子模块直接向对方仓库提 PR;子树用 subtree split 拆出提交再推
地址维护子模块地址记录在 .gitmodules,改地址要同步提交

对比

维度子模块子树 subtree
克隆体验需 --recurse-submodules普通克隆即可
历史独立仓库历史合入本仓库历史
更新方式更新指针 / --remotesubtree pull/push
贡献回上游直接向子模块仓库提 PR用 subtree split 生成提交
适合共享库、插件少量嵌入式代码、单仓构建

快速判断

快速判断
组件独立发版、被多仓复用 → 子模块
只想单仓构建、改动频率低 → 子树
需要严格版本对齐与审计 → 子模块 + 固定指针
两种方案都要求团队约定更新节奏;混用时更新责任不清,通常只选一种。审计场景关注「当前指向哪次提交」,这正是子模块指针与子树合并记录都能回答的问题,但查法不同。

易错点

  • 子树反复 pull 会把对方历史逐步带入,产生大量合并提交;用 --squash 精简更新,并接受拆分提交时的额外成本。
  • 子模块在克隆与 CI 里都要额外初始化(--recurse-submodules / submodule update --init),新人容易漏;写进 README 与 CI 模板。
  • 子模块内容更新后忘记在父仓库提交新指针,CI 拉到的仍是旧版本;更新完立即提交指针并复核。
  • 子树 split 依赖正确的合并点记录,历史处理不当会拆出全量差异;split 前拉全历史并核对提交清单。
  • 改动 .gitmodules 地址后没提交该文件,其他人仍指向旧地址;地址变更与指针提交放在同一个提交里。

数据来源与参考

  • Git 官方文档:git-submodule
  • Git 官方文档:git-subtree
  • Pro Git 2nd Edition · 7.11 子模块
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌