一个团队从 Git Flow 迁到 trunk-based:所有人直接往主干提交、靠特性开关控制发布。三个月后他们说"更顺了"——而同期线上事故多了两起,都是"开关组合没测到"。
trunk-based 不是"更先进的模型",而是"把成本从"合并冲突"转移到"开关矩阵""。这个"更容易被看见"解释了"迁移的动机往往是"我被合并冲突烦透了",而"开关矩阵"是一项新增的、看不见的成本——它在第一次"开关组合事故"时才会被记起。
而这两项成本都很真实,只是其中一项更容易被看见。
1. 四种分支模型的成本位置
| 模型 | 主要成本 |
|---|---|
| Git Flow(多长期分支) | 合并冲突、集成延迟 |
| GitHub Flow(短分支 + PR) | 评审等待、集成频率 |
| trunk-based(直提主干) | 特性开关矩阵、纪律要求 |
| 发布分支模型 | 双向合入(主干↔发布) |
第三项的"开关矩阵"是最容易被忽略的:N 个开关就有 2^N 种组合,而测试通常只覆盖"全开"与"全关"。这个"2^N"不是理论担忧:三个开关就是 8 种组合,而"开关之间的依赖"会让其中一部分非法——于是"组合空间"既大又有约束,靠人工枚举不可行。
第一项的"痛感"有个"放大机制":分支越久,改动越多,冲突越多,而"解冲突"需要"重新理解两边"——于是"合并那天"要花一整天。
第一项的"集成延迟"很直观:分支开得越久,合并越痛——而痛感在"合并那天"集中爆发。
2. trunk-based 的三个前提
| 前提 | 缺了会怎样 |
|---|---|
| 特性开关(可运行时关闭) | 未完成代码进主干,影响发布 |
| 自动化测试与 CI | 直提主干的风险没人兜 |
| 小步提交(< 1 天) | 大改动直提,回滚困难 |
第一项是核心:**"未完成的代码"必须"对用户不可见"**第一项的"隐含要求"是"开关必须能"按环境/按用户"控制":如果只有"改配置重启"这一种方式,那"未完成代码"仍会影响发布。
,否则"主干永远可发布"这条假设不成立。
第二项是安全网:直提主干意味着"每次提交都可能进生产",于是"测试与构建"必须是自动的、快的。
第三项的"判据"可以换成"能否在一天内回滚":如果一次提交包含"三天的工作",那"回滚"就是"丢掉三天"——于是没人敢回滚。
第三项的"1 天"是经验值:超过一天的改动,回滚粒度就太粗。
3. 开关矩阵的三种风险
| 风险 | 表现 |
|---|---|
| 组合未测 | 只有全开/全关被覆盖 |
| 开关长期不清理 | 代码里 dozens of if (flag) |
| 开关依赖顺序 | 打开 A 必须先打开 B |
第一项就是开头那两起事故:两个开关的组合"A 开 B 关"从未被测试第一项的"修法"是"列出所有"会被同时使用的组合",只测这些"——而"会被同时使用"由"业务场景"决定,不是"穷举"。
,而它在某次灰度里恰好出现。
第二项的"识别"是"数代码里 if (flag) 的数量":超过 20 处,说明"开关没在清理"——而它同时意味着"组合空间"已经失控。
第二项的成本会累积:每个开关都是一条"永久分支",于是"代码里有两条路径",而只有一条常被走。
// 开关台账(每条必须有"到期")
flags:
new-checkout:
created: 2026-09-01
expires: 2026-12-01 # 到期必须"转正或删除"
states: [off, on]
tested_combos: [[off], [on]] # 明确"测了哪些组合"
fast-search:
depends_on: [new-checkout] # 依赖关系必须写
"热修"是所有模型都要处理的一条路径,而它最能暴露模型缺陷:trunk-based 下热修就是"一个 commit + 立即发",而分支模型下热修要"合回主干 + 合到发布"两处。
// 两种模型的热修路径(同一场景:线上 bug 需 1 小时内修)
trunk-based:建 commit → CI → 直接发 → 完成(一条路径)
分支模型: 从 release 建 hotfix → 修 → 合 release → 再合 main → 完成(两条路径,易漏)
// 常见事故:只合了 release,忘了合 main → 下次发布"旧 bug 复活"
4. GitHub Flow 的隐性成本
| 成本 | 说明 |
|---|---|
| 评审等待 | PR 平均等 1–2 天 |
| 分支寿命 | "等评审"期间主干已前进 |
| 集成频率 | 每天合并不是"持续集成" |
第二项会让"短分支"变长:分支开了三天,主干合了 20 个 PR第二项的"量化"是"分支寿命中位数":超过 2 天,"短分支"已经在退化成"小 Git Flow"——而"冲突"会按期到来。
——于是"合并时冲突"照旧发生。
所以"GitHub Flow 的成败取决于评审速度":评审慢,短分支就退化成"小 Git Flow"。
"开关矩阵"可以只测"业务上会出现的组合",而这需要一张"组合清单":
// 组合清单(三条规则,控制规模)
1 每个开关与"默认关"组合必测(基线)
2 有依赖关系的开关:只测"合法组合"(A 开必须先 B 开)
3 线上会同时打开的组合:全测(通常 ≤ 5 组)
// 结果:从 2^N 收敛到 5–8 组,可人工维护
5. 怎么选:三个判据
| 判据 | 指向 |
|---|---|
| 发布频率(每天多次 vs 每周) | 高频 → trunk-based |
| 团队规模与信任 | 小团队/高信任 → trunk-based |
| 测试自动化程度 | 弱 → 需要分支隔离 |
第三项的"门槛"可以量化:CI 从提交到结果 < 10 分钟、主干构建成功率 > 95%。达不到这两条,直提主干的"每天多次发布"就只是口号。
第三项最硬:如果没有"提交即验证"的能力,直提主干就是"把风险交给运气"。
第一项是"收益"侧:一天发多次的团队,分支模型必须支持"随时可发";而"每周发一次"的团队,分支的隔离价值更大。
"分支寿命"与"评审等待"是一对因果,需要一起看:
// 两个数(按周统计)
分支寿命中位数: 首次提交 → 合并 (目标 < 2 天)
PR 等待评审中位数: 提交 → 第一次评审 (目标 < 4 小时)
// 若"等待评审"占"分支寿命"的 70% → 问题是评审产能,不是模型
6. 排错:五种"分支模型带来的问题"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 合并永远很痛 | 分支太长 | 缩短分支寿命 |
| 主干不可发布 | 未完成代码直提 | 加开关 |
| 开关越积越多 | 无到期机制 | 台账 + 定期清理 |
| 发布分支双向合入混乱 | 无"主干优先"规则 | 定义合入方向 |
| 评审积压 | 评审能力不足 | 加人/拆小 PR |
第四项在"发布分支"模型里最典型:热修要合回主干、主干又要合到发布——两条方向,任一条漏了就"下次发布回退 bug"。
最后一个"回滚"视角:trunk-based 的回滚单元是"一个提交",而分支模型的回滚单元是"一次合并"。
// 两种回滚粒度
trunk-based:revert 一个 commit → 影响"一次小改动"(精确但次数多)
分支模型:回滚整个 release → 影响"一批改动"(粗糙但一次搞定)
// 判据:你的团队更常"回滚一处"还是"回滚一批"
7. 边界:什么情况不必换模型
| 场景 | 为什么 | 做法 |
|---|---|---|
| 单人项目 | 无合并冲突 | 随意 |
| 发布频率低(每月) | 分支隔离更省 | Git Flow |
| 强合规(每次发布需审计) | 发布分支是证据 | 保留发布分支 |
| 大型单体(测试慢) | 直提主干风险高 | 短分支 + 严格 CI |
判断口径一句话:"合并成本"与"隔离收益"哪个更大。最后一条与"迁移"有关的边界:不要一次切换。可以先"缩短分支寿命(3 天 → 1 天)"与"建开关台账",等 CI 达到门槛再"直提主干"——中途任何一步都可能暴露"前置不足"。
合并成本高(长期分支、多人改动重叠),用 trunk-based;隔离收益大(发布需审计、测试慢),用分支。
8. 自测
- 四种分支模型的成本分别落在哪里?为什么"开关矩阵"最容易被忽略?
- trunk-based 的三个前提是什么?缺"小步提交"会造成什么?
- 为什么"两个开关的组合"会出事?2^N 的组合该怎么测?
- GitHub Flow 的成本为什么取决于"评审速度"?
- 三个判据里哪一个最硬?为什么"测试自动化弱"不该直提主干?
- 判断"该不该换模型"的那个问题是什么?