路线图

08-Git 工程化专题

星辉 2026-07-02 阅读 3 min 480 字 路线图
08-Git 工程化专题 封面

前面七章讲的都是"单仓库"场景。随着团队和项目变大,会遇到三个工程化问题:怎么组织多服务代码、怎么管大文件、怎么复用跨项目代码。本章讲这三块。

Monorepo vs Multirepo#

这是工程组织层面的根本选择——所有代码放一个仓库(Monorepo),还是每个服务一个仓库(Multirepo)。

Monorepo#

text
my-company/
├── services/
│   ├── user-service/
│   ├── order-service/
│   └── payment-service/
├── libs/
│   ├── auth-lib/
│   └── common-types/
├── infra/
│   ├── terraform/
│   └── k8s-manifests/
└── docs/

优势:

  • 原子提交:一个 PR 同时改服务和依赖库,不会出现"库改了但服务没跟上"的中间状态
  • 代码共享简单:所有服务共享同一份 libs,不用发包
  • 统一工具链:一份 lint 配置、一份 CI 模板,全员复用
  • 跨服务重构容易:IDE 全局搜索 + 改动一次性提交

劣势:

  • CI 复杂:一个仓库的 CI 要识别"这次改了哪些服务,只构建这些"
  • 权限粒度粗:所有人能看所有代码(对某些场景是问题)
  • 仓库膨胀:10 年的代码历史,clone 一次几个 GB
  • 需要工具支持:Bazel、Buck、Turborepo、Nx 这类构建工具是 Monorepo 的标配

Multirepo#

text
user-service/        ← 独立仓库
order-service/       ← 独立仓库
payment-service/     ← 独立仓库
auth-lib/            ← 独立仓库(发包给别人用)

优势:

  • CI 简单:每个仓库的 CI 只管自己
  • 权限清晰:按仓库分配权限
  • 仓库轻量:每个仓库只关心自己的代码

劣势:

  • 跨服务改动难:改一个 lib 接口,要发版、所有依赖方升级,周期长
  • 代码复用靠发包:lib 改了要发布到 npm/PyPI/Maven,下游再升级
  • 工具链漂移:每个仓库的 ESLint、tsconfig 各自演化,时间久了不一致
  • 版本地狱:服务 A 用 lib v1.2,服务 B 用 v1.3,某天要统一时发现 API 不兼容

对比#

维度MonorepoMultirepo
跨服务改动原子提交,简单多 PR + 发版 + 升级,复杂
CI 配置复杂(需要按路径触发)简单(每仓库独立)
权限管理粗(全仓库可见)细(按仓库分配)
代码复用直接引用源码靠发包
工具链一致性天然统一需要额外约束
仓库体积大(需浅克隆)小
适合规模中大型(50+ 人)小到中(< 50 人)

选型建议#

  • 5 个以下服务、小团队:Multirepo,简单直接
  • 10+ 服务、中大型团队、强工程文化:Monorepo,配合 Bazel/Nx/Turborepo
  • 混合模式:核心服务 Monorepo + 边缘服务独立仓库,是常见的折中

Google、Meta、Microsoft 内部都是 Monorepo,但他们的工具链(自研构建系统、PB 级代码仓库支持)不是普通团队能复制的。普通团队上 Monorepo 前要先评估工具链能力——没有 Bazel/Nx 这类增量构建工具,Monorepo 在 50+ 服务时会变慢到无法接受。

Git LFS#

Git 设计上不适合存大文件(>50MB 的 blob 性能开始下降)。Git LFS(Large File Storage)是补丁方案——把大文件存在 LFS 服务器,仓库里只保留指针。

工作原理#

text
工作区有大文件 video.mp4 (500MB)
        │ git add
        ▼
LFS filter 把文件内容上传到 LFS 服务器
仓库里只存一个指针文件(几百字节)
        │ git commit
        ▼
commit 里是这个指针(小)
clone 时只拉指针,需要时才拉实际内容

配置#

bash
# 安装 Git LFS
git lfs install

# 指定哪些文件用 LFS
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "/assets/big-files/*"

# 提交 .gitattributes(LFS 配置进版本控制)
git add .gitattributes
git commit -m "chore: configure Git LFS"

# 正常 add/commit,LFS 自动处理
git add design.psd
git commit -m "feat: add design file"

适用场景#

  • 设计文件(PSD、AI、Sketch)
  • 视频和音频
  • 大型数据集(机器学习训练数据)
  • 二进制依赖(游戏资源)

注意事项#

  • LFS 服务器是单点:GitHub/GitLab 的 LFS 服务有配额和带宽限制
  • clone 不会自动拉 LFS 内容:需要 git lfs pull,浅克隆时尤其注意
  • LFS 不是免费的:GitHub LFS 配额 1GB 存储 + 1GB 带宽/月,超出要付费
  • LFS 迁移困难:历史里已经存的大文件不会自动迁到 LFS,要用 git lfs migrate

Submodule vs Subtree#

跨仓库复用代码的两种方式。

Submodule#

Submodule 把另一个仓库作为子目录嵌入当前仓库:

bash
# 添加 submodule
git submodule add https://github.com/org/shared-lib.git libs/shared

# clone 带 submodule 的仓库
git clone --recurse-submodules https://github.com/org/main-repo.git

# 已 clone 的仓库初始化 submodule
git submodule update --init --recursive

# 更新 submodule 到最新
git submodule update --remote

特点:

  • 子仓库独立,有自己的 commit 历史
  • 主仓库只存子仓库的 commit hash(指针)
  • 切分支、checkout 时 submodule 状态可能不一致,要手动同步

痛点:

  • detached HEAD:submodule 默认 checkout 到具体 commit,不在任何分支上
  • 同步复杂:主仓库切分支,submodule 不会自动跟着切
  • recursive 操作多:--recurse-submodules 参数容易忘
  • CI 复杂:CI 配置要处理 submodule 拉取

Subtree#

Subtree 把另一个仓库合并进当前仓库的子目录,不再保留独立的仓库引用:

bash
# 添加 subtree
git subtree add --prefix=libs/shared https://github.com/org/shared-lib.git main --squash

# 从上游拉取更新
git subtree pull --prefix=libs/shared https://github.com/org/shared-lib.git main --squash

# 把改动推回上游
git subtree push --prefix=libs/shared https://github.com/org/shared-lib.git feature/change

特点:

  • 子仓库的代码完全合并进主仓库,历史也合并了
  • 不需要特殊命令就能 clone——所有内容都在主仓库里
  • 主仓库会变大(合并了子仓库历史)

优势:比 submodule 简单,没有"detached HEAD"和同步问题 劣势:操作命令较慢(要拉子仓库全部历史);推回上游不直观

对比#

维度SubmoduleSubtree
子仓库独立性独立(保留引用)合并进主仓库
clone 复杂度需要 --recurse-submodules普通 clone 即可
历史保留子仓库独立历史合并进主仓库历史
同步更新简单(update --remote)较慢(subtree pull)
推回上游在子仓库内 commit + pushsubtree push,不直观
主仓库体积小(只存指针)大(合并历史)
学习曲线陡(detached HEAD 等坑)平缓

选型建议#

  • 跨团队共享、强独立演进:Submodule(但要团队理解它的状态机)
  • 同团队、轻量共享:Subtree(更简单)
  • 能 Monorepo 就别用这两个:Submodule 和 Subtree 都是在 Multirepo 下"模拟 Monorepo"的妥协方案。如果共享代码量较大,直接 Monorepo 是更优解。

实战要点#

  • Monorepo 不是"先进",Multirepo 不是"落后"。两者都是合理的工程选择,看团队规模和工具链能力。
  • Monorepo 必须配构建工具。没有 Bazel/Nx/Turborepo 的 Monorepo,CI 会变成"每次改一行,全仓库构建一遍"。
  • Git LFS 是补丁不是银弹。能在源码层面避免大文件就避免(比如把数据集放对象存储,仓库里只存 URL)。
  • Submodule 用之前三思。它的状态机复杂,团队成员都要培训才能用对。能 Subtree 就别 Submodule。
  • 混合模式很常见。核心业务 Monorepo + 基础设施代码独立仓库 + 文档站独立仓库,是很多公司的现实结构。

小结#

Git 工程化的三个专题——Monorepo/LFS/Submodule——都是"在 Git 基础模型上打补丁"。Monorepo 解决代码组织问题但需要构建工具支持;LFS 解决大文件问题但有单点和配额风险;Submodule/Subtree 解决跨仓库复用但状态机复杂。

理解这些工具的边界比会用更重要——知道什么时候不该用,比知道怎么用更值钱。第一阶段(理念与协作)到此结束,下一阶段进入持续集成 CI。