打开网易新闻 查看精彩图片

2024年,GitHub上每天有超过3000万次代码合并。其中相当一部分,正在经历一种荒诞的等待——改个文档错别字,却要排队等后端构建、集成测试、安全扫描全部跑完。

这些检查明明和文档毫无关系。但开发者只能干瞪眼。

问题的根源很隐蔽:GitHub的强制检查(Required Checks)是静态的,而拉取请求(Pull Request,代码合并前的审查单元)是动态的。这个错配让无数团队陷入两难——要么浪费计算资源,要么降低代码保护标准。

静态规则撞上动态现实

静态规则撞上动态现实

GitHub的分支保护机制设计于一个简单假设:所有进入主分支的代码,都应该经过同一套检查。这个假设在十年前成立,当时大多数仓库结构单一,前后端代码混在一起。

现代代码库早已不同。一个典型项目可能包含:前端界面、后端API、数据库迁移脚本、文档站点、CI配置。改文档的人不该为数据库Schema变更买单,改CSS的人不需要跑机器学习模型测试。

但GitHub的强制检查列表是写死的。管理员在设置页面勾选"需要状态检查通过",然后指定一组工作流名称。这组名称对所有PR生效,无论实际改了什么文件。

团队被迫在三个坏选项里挑一个:

选项一:全量要求。所有检查对所有PR强制生效。结果是改个README要等45分钟,CPU时间白白烧掉。

选项二:降级保护。取消某些检查的强制要求,换取速度。风险是危险代码可能趁虚而入。

选项三:假装条件执行。这是最常见的折中。保持工作流在强制列表里,但用路径过滤(path filters)或文件变更检测,让不相关的任务快速退出。

代码看起来像这样:

```yaml

on:

pull_request:

打开网易新闻 查看精彩图片

paths:

- "api/**"

- "db/**"

- "auth/**"

表面聪明,实则埋雷。

执行逻辑偷走了政策决策

执行逻辑偷走了政策决策

路径过滤解决的是"这个工作流该不该运行"。但它回答不了另一个问题:"这个PR需不需要这项检查才能合并"。

这是两个完全不同的层面。前者是执行效率,后者是合并政策。GitHub把它们捆在了一起。

举个例子:一个只改docs/的PR,路径过滤让后端工作流跳过,状态显示为"跳过"或成功。分支保护看到绿色对勾,放行合并。看起来没问题?

再换一个场景:PR同时改了api/和docs/。后端工作流正常运行,但文档检查被路径过滤跳过了。如果文档检查也在强制列表里,这个PR永远合并不进去——因为工作流根本没运行,没有状态可以上报。

团队被迫在CI配置里写越来越多的壳逻辑。检测文件变更、条件设置输出、动态决定报告什么状态。YAML文件膨胀成几百行,里面藏着合并政策的暗线。

「这相当于把交通规则写进了汽车发动机的控制程序」,一位在Stripe基础设施团队工作过的工程师这样比喻,「车能跑,但换个车型就得重写规则。」

更隐蔽的代价是认知负担。新加入的开发者看到绿色的CI通过,以为所有检查都跑了。实际上可能只是壳逻辑判断"这次不用跑"。调试失败时,需要同时理解GitHub的分支保护、工作流的触发条件、以及藏在shell脚本里的决策树。

分离执行与政策:一个更干净的模型

分离执行与政策:一个更干净的模型

打开网易新闻 查看精彩图片

理想状态应该很直观:同一个保护分支,不同PR可以有不同的强制检查清单。

改文档的PR:文档lint、拼写检查、快速审批路径。

改核心API的PR:单元测试、集成测试、安全扫描、两名资深审查者批准。

两者互不影响。系统根据PR实际内容,动态计算需要满足的条件。

这需要把"什么检查要跑"和"什么检查必须通过"拆开。前者是执行层,后者是政策层。GitHub目前只提供了前者。

一些团队开始自建解决方案。GitHub Apps监听pull_request事件,分析变更文件,然后动态推送一个聚合状态——比如叫"MergeGuard"——到分支保护。真正的检查分散在各个工作流,由App决定哪些结果对这个PR算数。

这个模型有个诱人之处:它把政策逻辑集中到一个地方。不再需要在十几个YAML文件里同步路径过滤规则。变更范围分析、检查要求映射、例外情况处理,都在App里完成。

但自建也有代价。维护一个高可用的GitHub App、处理Webhook投递失败、应对API速率限制、保证状态计算的确定性。小团队往往望而却步。

市场上也出现了商业产品填补这个空白。2023年成立的Mergify、更早的Trunk.io,都提供了基于变更内容的动态合并条件。它们的核心卖点不是功能更多,而是"让GitHub原生应该支持的东西变得可用"。

为什么GitHub迟迟不动

为什么GitHub迟迟不动

这不是技术难题。计算PR的变更范围、匹配规则、动态调整强制检查列表,工程实现并不复杂。

更可能的障碍是产品哲学。GitHub的分支保护设计强调简单和可预测:管理员看一眼设置页面,就能知道什么能进主分支。动态规则引入了不确定性——同一个分支,不同PR的门槛不同,审计追踪也更复杂。

但现状的"简单"是假象。它把复杂性推给了用户,让用户用壳逻辑和YAML技巧模拟本该原生支持的功能。

GitHub在2023年推出了"规则集"(Rulesets)功能,部分回应了这个需求。规则集允许更细粒度的分支保护,可以按分支模式、用户身份、甚至提交消息设置不同规则。但PR内容感知仍然缺失。

一个值得注意的对比是GitLab。它的合并请求批准规则支持基于代码变更路径的条件,原生区分"执行"和"要求"。GitHub的企业客户里,不乏因为这个差异而迁移或考虑迁移的团队。

竞争压力可能最终推动改变。但在那之前,3000万开发者还在继续写那些"假装条件执行"的YAML文件。

你的团队是怎么处理这个问题的——全量硬等、降级冒险,还是已经自建了动态规则系统?