打开一个 GitHub 上的 PR,往下拉到合并按钮那一块,能看到的提示五花八门:Some checks were not successful、Review required、This branch is out-of-date with the base branch、This branch has conflicts that must be resolved、Merging is blocked,有的仓库还有 Merge when ready、This branch was successfully deployed。每一条后面都能展开出一串小勾小叉。
可是在 twwy.net 这个仓库里,这一块一直很干净。拿最近合并的 PR #157 看,它的 head 提交上一个 check 都没有,commit status 也是 0 条,合并按钮永远是绿的,点下去就合进去了。
于是问题就来了:这些提示是谁放上去的?哪些只是提醒,哪些真能把合并按钮锁住?检查失败了还能不能合?它们之间有没有先后,谁要等谁?本文顺着一个 PR 从打开到合并以后的顺序,一道一道看过去。为了不只照着文档讲,我在 twwy.net 里拉了一个临时分支 lab/pr-gates-base 当作“主干”,放了一个实验用的 workflow,开了四个指向它的测试 PR(#159–#162),文中的提交哈希和日志都出自这次实验。
一、检查从哪里来
现象
测试 PR #159 只改了一个文本文件,以 draft 状态打开。几秒钟后,它的 head 提交 e796c53 上出现了 6 个 check run 和 1 个 commit status。实验 workflow 里一共写了 6 个 job,其中 after-merge 和 on-push 两个因为 if 条件不满足没有运行,但它们照样出现在列表里,结论是 skipped。
原因:两套上报接口
GitHub 上的“检查”其实来自两套接口1。
commit status 是老接口。 任何拿到仓库写权限令牌的系统,都可以对某个提交 POST 一条 {state, context, description},context 就是它在列表里显示的名字。Jenkins、早年的 Travis 和很多部署平台用的都是它。实验里我用 curl 模拟了一个外部 CI,发了一条 lab/outside-ci。
check run 是新接口。 只有 GitHub App 能写,能带更丰富的输出(摘要、注解、重跑按钮)。GitHub Actions 本身就是一个 App,workflow 里每个 job 都会生成一个 check run,名字就是 job 名,归在同一个 check suite 下。
两套接口有一个共同点:结果都挂在提交上,而不是挂在 PR 上。 PR 页面只是去找“我的 head 提交上有哪些检查”,然后汇总显示。所以同一个提交出现在两个 PR 里,两个 PR 会看到同一组检查。
既然检查只是挂在提交上的一组报告,那检查失败了,PR 还能不能合并?
二、检查为什么拦不住合并
现象
测试 PR #160 往分支里加了一个叫 FAIL 的文件,实验 workflow 的 test job 看到它就退出 1。检查变红以后,API 里这个 PR 的 mergeable_state 是 unstable。然后我直接调用合并接口,返回 Pull Request successfully merged,合并提交 77f0799 进了 lab/pr-gates-base。
原因:报告和关卡是两回事
unstable 的意思是“有检查没通过,但它们都不是必需的”2。GitHub 默认把所有检查都当作参考信息,合并框会把失败显示成红叉,按钮却照样能点。twwy.net 的 PR 一直能直接合并,原因也在这里:仓库里没有任何规则,检查本来也一条都没有。
要让检查真正拦住合并,得在分支规则里把它列为必需检查(required status checks)。分支规则有两种写法,分支保护规则和 rulesets,第六节再细说它们的区别,这里只看“必需检查”这一条开关怎么工作3。
按名字匹配。 规则里填的是检查的名字,比如 test。GitHub 拿这个名字去 head 提交上找同名的 check run 或 commit status。如果同名的 check 和 status 都存在,两个都得通过4。还可以把某个名字钉到指定的 App 上,这样别的来源发来的同名状态不算数,免得谁随手 curl 一条 success 就过关。
必须在最新提交上。 旧提交上的绿色不算。每推一次新提交,必需检查都要在新提交上重新报告。
没报告就一直等。 这一条最容易踩坑。如果 workflow 写了 paths: 过滤,这次改动没碰到那些路径,整个 workflow 就不会运行,对应的必需检查就一直显示 Waiting for status to be reported,PR 永远合不了。反过来,workflow 运行了、但某个 job 被 if 条件跳过,这个 job 的结论是 skipped,GitHub 按通过处理。图 1 里的 after-merge 和 on-push 就是这种情况。
下表把同一组检查结果在“没有规则”和“列为必需”两种情况下的结果放在一起。
head 提交上 test 的状态 | 没有规则(twwy.net 现状) | 规则要求 test 必须通过 |
|---|---|---|
| success | 可合并(clean) | 可合并 |
| failure | 红叉,但可合并(unstable,#160 实测) | 不可合并(blocked) |
| 还在跑 | 可合并(unstable) | 不可合并,等结果 |
job 被 if 跳过(skipped) | 可合并 | 算通过 |
| workflow 被路径过滤,没运行 | 可合并 | 一直等待,永远合不了 |
| 只在旧提交上通过 | 可合并 | 不算数,要在新提交上重跑 |
“可合并”指检查这一项不拦;评审、冲突等其他条件另算。
规则里点名的是“head 提交上的检查”。可是检查跑的时候,跑的真是 head 提交吗?
三、检查跑的是哪个提交
现象
#159 的 info job 打印了几个环境变量:
event=pull_request action=openedGITHUB_SHA=bef7b38723f09a4ef78fb74d9845f29b0ece00a5GITHUB_REF=refs/pull/159/mergepr_head_sha=e796c534c1291c1193570d0a3a00f7ddfbb4d347GITHUB_SHA 不是 head 提交 e796c53。test job 里 actions/checkout 拉下来的提交是:
bef7b38 Merge e796c534c1291c1193570d0a3a00f7ddfbb4d347 into 236b111289f78077360070b6d102c59b5787202c原因:测试合并提交
PR 一打开,GitHub 就在后台把 head 合进 base 试一次,生成一个测试合并提交,放在隐藏的引用 refs/pull/N/merge 上,同时用 refs/pull/N/head 指向 head 提交。pull_request 事件触发的 workflow 跑的是这个测试合并提交5,测的是“如果现在合并,主干会变成什么样”,而不只是分支本身。跑完以后,结果又挂回 head 提交,也就是图 1 里那样。
这也解释了为什么 PR 里改 workflow 文件会立刻生效:测试 PR #162 把 workflow 里的 WORKFLOW_VERSION=v1-base 改成了 v2-head,它的 info job 打印出来的就是 v2-head,因为 workflow 文件也是从测试合并提交里读的。
冲突时会怎样
测试合并提交需要能合并才生成得出来。测试 PR #161 新建了 lab-shared.txt,开 PR 时检查正常跑完。随后我在“主干”上也新建了一个内容不同的 lab-shared.txt(bb3a2c9),再往 #161 推了一个新提交 2035e4d。结果:
- 这次推送没有触发任何 workflow,新 head 提交上 check run 是 0 个;
mergeable_state先是unknown,几秒后变成dirty。
文档里写得很明白:PR 有合并冲突时,pull_request 事件不会运行 workflow5。没有测试合并提交,也就没有东西可测。冲突是唯一一个不需要任何规则、永远拦住合并按钮的卡点,而且它还会让检查停下来,如果规则里配了必需检查,合并框会同时显示“有冲突”和“检查在等待”。解决冲突、推上新提交以后,检查才会重新开始。
测试合并提交是拿“当时的”主干算的。如果检查跑完以后主干又往前走了,这个绿色还作数吗?
四、主干前进以后
现象
这是实验里最有意思的一段。时间线如下:
- 01:24:31,#159 以 draft 打开,检查在
bef7b38(e796c53合进236b111)上通过。 - 01:25:21,带
FAIL文件的 #160 合进了主干,主干变成77f0799。 - 01:25:31,把 #159 转为 ready for review。这触发了一次新运行,但日志里的
GITHUB_SHA仍然是bef7b38,检查又通过了一次。 - 01:26:00,往 #159 推了一个新提交
8be4454。这次 checkout 出来的是aeaa0d5 Merge 8be4454 into 77f0799,里面带着 #160 合进来的FAIL文件,test失败。
#159 自己什么都没做错,它的改动只是一个文本文件。但在第 2 步到第 4 步之间,它挂着一个绿色的检查结果,而那个结果测的是一个已经不存在的主干。如果这时有人点了合并,主干上就会出现一个本该被检查拦下的问题。第 3 步还说明,测试合并提交是后台异步重算的,事件到来时不一定已经更新(这是根据日志推断的,文档没有写重算的时机)。
第一次尝试:要求分支与主干同步
必需检查的规则里有一个勾选项 Require branches to be up to date before merging,也就是文档里说的 strict 模式3。开了它,只要 PR 的分支落后于主干,合并框就显示 This branch is out-of-date with the base branch,状态是 behind,必须先点 Update branch,把主干合进分支(或 rebase),检查在新的组合上重跑,通过后才能合。
这能保证合进去的每一个提交都在“当时的主干”上测过。代价是串行化:主干每合进一个 PR,其他所有打开的 PR 都会变成 behind,都要更新、重跑、再等。PR 一多,大家就在抢“谁先跑完谁先合”,跑完一轮发现又落后了。
解决:合并队列
合并队列(merge queue)把“更新分支、重跑、合并”这件事从作者手里接了过去6。作者点的不再是 Merge,而是 Merge when ready,把 PR 放进队列。GitHub 依次为队列里的 PR 生成临时分支 gh-readonly-queue/main/...:第一个是“主干 + #1”,第二个是“主干 + #1 + #2”,以此类推,每个都触发 merge_group 事件跑检查。某一组通过了,它和它前面的 PR 一起合进主干;某一组失败了,对应的 PR 被踢出队列,排在后面的组去掉它重建。
合并队列有两个容易忽略的前提。第一,workflow 必须监听 merge_group 事件,否则队列里的临时分支上永远等不到必需检查,最后超时出队;外部 CI 则要对 gh-readonly-queue/ 前缀的分支推送做出响应。第二,它的可用范围比较窄:组织所有的公开仓库,或者 Enterprise Cloud 组织的私有仓库7。twwy.net 是个人账户下的私有仓库,用不了。
队列之外还有一个轻量的帮手,auto-merge:在 PR 上点 Enable auto-merge,等必需评审和检查都满足了自动合并8。它不会帮你更新分支,也不做组合测试,只是“条件满足时替你点按钮”。仓库要先在设置里打开 Allow auto-merge;我在实验里对 #159 调用开启接口,得到的是 Auto-merge is not enabled for this repository。
到这里,机器的检查讲完了。合并框里另一大类提示来自人:draft、评审请求、CODEOWNERS,它们又拦什么?
五、人的关卡:draft、评审与 CODEOWNERS
draft:不能合并,但检查照跑
很多人以为 draft PR 不会跑 CI,实验里并不是这样:#159 以 draft 打开,pull_request 事件的 action 是 opened,日志里 pr_draft=true,6 个 job 一个不少。转成 ready for review 时会再来一个 ready_for_review 事件,不过 pull_request 默认只监听 opened、synchronize、reopened 三种5,想在转 ready 时再跑一遍要在 types 里自己加上(实验 workflow 加了)。想让 draft 不跑,得在 job 上写 if: github.event.pull_request.draft == false。
draft 真正拦住的是两件事9:它不能合并,这一条不需要任何规则;它不会自动请求 code owner 评审,转 ready 时才发出请求。一个小细节:REST API 对 draft 状态的 #159 返回的 mergeable_state 是 clean 而不是 draft,判断能不能合并时要单独看 draft 字段。
评审:要几个人、要谁、什么时候作废
评审本身和检查一样,没有规则时只是展示:有人点了 Request changes,合并按钮照样能点。要让评审变成关卡,在分支规则里打开 Require a pull request before merging 和需要的批准人数3。它下面还有几个子开关,各自堵一个漏洞:
| 开关 | 不开会怎样 | 开了以后 |
|---|---|---|
| Required approvals(N 个) | 没人看也能合 | 至少 N 个有写权限的人批准 |
| Dismiss stale approvals | 批准后再推新代码,旧批准仍然有效 | 改动后旧批准作废,要重新批准 |
| Require review from Code Owners | CODEOWNERS 只负责发评审请求 | 改到谁的文件,必须有对应 owner 批准 |
| Require approval of the most recent push | 作者推完最后一下自己就能凑齐 | 最后一次推送必须由推送者以外的人批准 |
| Require conversation resolution | 评审意见没处理也能合 | 所有评审对话标记为已解决才能合 |
管理员默认可以绕过,除非同时勾上 Do not allow bypassing the above settings。
CODEOWNERS:先发请求,规则让它变成必需
CODEOWNERS 是放在 .github/、根目录或 docs/ 下的一个文件,按 gitignore 的写法把路径对应到人或团队,最后一条匹配的规则生效10。它本身只做一件事:PR 改到某些文件时,自动请求对应的人评审。只有在规则里打开了 Require review from Code Owners,这个请求才变成必须拿到的批准。
还有两点和前面的机制对得上。第一,GitHub 用的是 base 分支上的 CODEOWNERS,PR 里改 CODEOWNERS 不会影响这个 PR 自己的评审人,这和第三节 workflow 文件取自测试合并提交正好相反。第二,owner 必须对仓库有写权限,PR 作者本人不会被请求评审自己的 PR。实验里的 CODEOWNERS 把所有文件都给了 0cloud-ai,而测试 PR 的作者也是它,所以四个 PR 都没有出现评审请求。
前面反复提到“分支规则”。这些规则是写在哪里的,几套规则同时存在时听谁的?
六、规则从哪里来:分支保护与 rulesets
GitHub 有两种配置分支规则的地方,能设的项目大体相同,区别在组织方式11。
分支保护规则(branch protection rule) 是老办法:在设置里按分支名模式(如 main、release/*)建一条规则,把上面几节提到的开关勾上。一个分支同一时间只受一条分支保护规则管,多条模式都匹配时只取其中一条。
rulesets 是新办法:同一个分支可以同时被多个 ruleset 命中,它们没有优先级,而是叠加,同一条规则出现不同取值时取最严格的那个;它们也和分支保护规则叠加。ruleset 有启用状态,可以先停用而不删除;可以设置 bypass 列表,让某些角色、团队或 App 绕过;任何有读权限的人都能看到生效中的 ruleset,不用猜“为什么我合不了”。除了分支,ruleset 还能管标签,以及推送本身(比如禁止提交某些路径、超过某个大小的文件)。
谁能用。 分支保护和 rulesets 在免费计划下只能用于公开仓库,私有仓库需要个人 Pro 或组织 Team 及以上(rulesets 文档的“谁能用”一栏写的是 Team 和 Enterprise,个人 Pro 的私有仓库能否用 rulesets,我没有逐项核对)311。合并队列的门槛更高,见上一节。twwy.net 是私有仓库,目前一条规则也没有配,所以第二节里 #160 检查失败也能合。
规则管的是“合并之前”。合并框里还会出现部署的状态,合并之后也还有工作流在跑。这些和合并是什么先后关系?
七、合并前后的部署与工作流
environment:拦的是部署,不是合并
workflow 的 job 上写一行 environment: lab-preview,这个 job 就成了一次“部署”:PR 页面会出现部署记录,job 能读取这个环境专属的 secret。环境不存在时,第一次运行会自动创建它12。实验里的 preview job 就是这样,没有配任何保护规则,所以它立刻运行了。
给环境加上保护规则以后(必需审批人、等待计时、只允许某些分支部署、App 提供的自定义规则),被卡住的是这个 job:它停在 waiting 状态,规则通过之前不开始,也拿不到环境的 secret。这和合并按钮没有直接关系。要让“部署成功”成为合并前提,得在分支规则里另开 Require deployments to succeed before merging,指定哪些环境必须部署成功3。常见的做法是:PR 阶段部署到 preview 或 staging,把 staging 列为合并前提;合并以后再部署 production,production 环境上挂人工审批。
合并之后:push 与 closed
合并本身也是一个事件源。#160 在 01:25:21 合入,同一秒触发了两次运行:
push:主干lab/pr-gates-base收到合并提交77f0799,监听这个分支push的 workflow 运行,GITHUB_REF=refs/heads/lab/pr-gates-base。pull_request的closed:PR 被关闭,github.event.pull_request.merged为 true。实验里的after-mergejob 打印出merge_commit_sha=77f0799、GITHUB_SHA=77f0799。只关闭不合并也会来closed,所以要用merged判断5。
两者的区别在于前者只知道“主干变了”,后者知道“是哪个 PR、谁合的、带什么标签”。部署通常挂在 push 上,PR 相关的善后(发通知、删预览环境、打版本标签)挂在 closed 上。
pull_request_target:用的是主干上的 workflow
还有一个名字很像的事件 pull_request_target。它在 base 仓库默认分支的上下文中运行5,用的是默认分支上的 workflow 文件,带有可写的令牌和 secret,所以常用来给 fork 来的 PR 打标签、发评论。实验 workflow 里也监听了它,但四个测试 PR 一次都没有触发:这个 workflow 只存在于 lab/pr-gates-base,默认分支 main 上没有。对照 #162 里 pull_request 立刻用上了 PR 自己改过的 workflow,就能看出两者为什么安全性完全不同:pull_request_target 不让 PR 改写要执行的流程,代价是它也不会自动 checkout PR 的代码,自己 checkout 时就要小心运行了不可信的代码。另外,冲突的 PR 不会触发 pull_request,却仍然会触发 pull_request_target。
八、全景:一个 PR 依次经过的卡点
把前面几节按时间顺序排起来,就是一个 PR 从打开到合并以后会遇到的全部关卡。左列是 PR 经历的阶段,中间是这一步 GitHub 自动做的事,右列是卡点,以及是谁让它成为卡点的。强调色标出的两个卡点不需要任何规则,永远生效;其余的都要在分支规则里打开。
回到 twwy.net
现在回头看开头那个问题:twwy.net 的 PR 为什么永远是一个绿色按钮?因为三件事同时成立。仓库没有 workflow,也没有接外部 CI,所以 head 提交上没有任何检查;仓库没有分支保护规则或 ruleset,所以评审、同步、部署这些开关一个都没打开;PR 都不是 draft,也没有和主干冲突。剩下能拦住它的两条永远生效的卡点,一条都没碰上。
这对一个人写的博客大多数时候没问题,但第四节那种“各自是绿的、合在一起是红的”情况,这个仓库真的遇到过:PR #136 新增文章时还没有中图法类号的要求,#137 加上了这条构建校验,两个 PR 各自都能构建,先后合并以后 main 构建失败,后来用 #138 补上。如果想让主干“合进去就一定能构建”,最小的配置是:加一个跑 npm run build 的 workflow,在规则里把它列为必需检查,并勾上与主干同步。如果账户是免费计划,这还需要仓库改为公开,或者升级到 Pro。
小结
- 检查从哪里来? check run(GitHub App,Actions 每个 job 一个)和 commit status(任何持令牌的系统),结果都挂在 head 提交上,PR 只是读取汇总。
- 检查失败为什么还能合? 没有规则时检查只是报告,状态是
unstable。只有分支规则按名字点名的必需检查才拦合并;被路径过滤没运行的 workflow 会让必需检查永远等待。 - 检查跑的是哪个提交?
pull_request跑在refs/pull/N/merge这个测试合并提交上,测的是合并后的样子;有冲突时生成不出来,workflow 也就不跑。 - 主干前进后旧结果还算吗? 不一定算。实验里 #159 的检查在旧主干上是绿的,主干合进 #160 后同一个改动变红。要求与主干同步能堵住,但会串行化;合并队列在后台为“主干加前面所有 PR”逐组测试,解决排队。
- 人的关卡拦什么? draft 永远不能合并,但检查照跑;评审和 CODEOWNERS 只有规则打开后才是必需的,几个子开关分别防止批准过期、作者自批、owner 缺席。
- 规则写在哪里? 分支保护规则一个分支只生效一条;rulesets 可以多条叠加,取最严格的值。私有仓库需要付费计划。
- 合并前后的部署和工作流? environment 规则拦的是部署 job,要让部署成为合并前提需另开规则;合并后有
push和pull_request closed + merged两个入口;pull_request_target用默认分支上的 workflow。
如果想自己复现,实验 workflow 很短:在一个临时分支上放下面这个文件,开几个指向它的 PR,分别让检查失败、制造冲突、在 PR 里改 workflow,再看 Actions 页面和合并框的变化。
name: lab-gateson: pull_request: branches: [lab/pr-gates-base] types: [opened, synchronize, reopened, ready_for_review, closed] pull_request_target: branches: [lab/pr-gates-base] push: branches: [lab/pr-gates-base]permissions: contents: read statuses: writejobs: info: if: github.event.action != 'closed' runs-on: ubuntu-latest steps: - run: | echo "event=${{ github.event_name }} action=${{ github.event.action }}" echo "GITHUB_SHA=$GITHUB_SHA GITHUB_REF=$GITHUB_REF" echo "pr_head_sha=${{ github.event.pull_request.head.sha }}" test: if: github.event_name == 'pull_request' && github.event.action != 'closed' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: git log --oneline -1 && test ! -f FAIL after-merge: if: github.event.action == 'closed' && github.event.pull_request.merged runs-on: ubuntu-latest steps: - run: echo "merged at ${{ github.event.pull_request.merge_commit_sha }}"参考资料
Footnotes
-
About status checksdocs.github.com ↗检查分两种:commit status(Statuses API)和 check run(Checks API,只有 GitHub App 能写,Actions 就是这样上报的)。https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collecting-information-about-changes-in-a-project/about-status-checks ↩
-
GraphQL enum MergeStateStatusdocs.github.com ↗BEHIND、BLOCKED、CLEAN、DIRTY、DRAFT、HAS_HOOKS、UNKNOWN、UNSTABLE;REST 的
mergeable_state是同一组值的小写。https://docs.github.com/en/graphql/reference/enums#mergestatestatus ↩ -
About protected branchesdocs.github.com ↗必需评审、必需检查(strict 要求与 base 同步)、会话解决、签名提交、线性历史、合并队列、部署成功、锁定分支、不许绕过等全部开关。https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches ↩ ↩2 ↩3 ↩4 ↩5
-
Troubleshooting required status checksdocs.github.com ↗被路径或分支过滤掉的 workflow,对应的必需检查一直 Pending;同名的 check 和 status 都要通过;必需检查要在最新提交上通过;
if跳过的 job 算 Success。https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks ↩ -
Events that trigger workflowsdocs.github.com ↗
pull_request默认类型 opened、synchronize、reopened;GITHUB_REF 为refs/pull/N/merge;PR 有冲突时pull_request不运行;pull_request_target在默认分支的上下文运行;合并后用 closed +merged判断。https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows ↩ ↩2 ↩3 ↩4 ↩5 -
Managing a merge queuedocs.github.com ↗入队的 PR 和最新 base 及前面排队的 PR 合在一起,推到
gh-readonly-queue/临时分支测试;workflow 必须监听merge_group;失败、超时、冲突就出队,后面的重建。https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue ↩ -
Pull request merge queue is now generally availablegithub.blog ↗可用范围:组织所有的公开仓库,以及 Enterprise Cloud 组织的私有仓库。https://github.blog/changelog/2023-07-12-pull-request-merge-queue-is-now-generally-available/ ↩
-
Automatically merging a pull requestdocs.github.com ↗仓库先开 Allow auto-merge;只在 PR 不能立即合并时出现;等必需评审和检查都通过后自动合并。https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request ↩
-
About pull requests(Draft pull requests 一节)docs.github.com ↗Draft pull requests cannot be merged, and code owners are not automatically requested to review them.https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests ↩
-
About code ownersdocs.github.com ↗用 base 分支上的 CODEOWNERS;最后匹配的模式优先;draft 不请求 code owner;只有开了 Require review from Code Owners 才是必需的。https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners ↩
-
About rulesetsdocs.github.com ↗多个 ruleset 可同时作用于同一分支,没有优先级,同一条规则取最严格的;和分支保护规则叠加;可设 bypass 列表和启用状态。https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets ↩ ↩2
-
Managing environments for deploymentdocs.github.com ↗必需审批人(最多 6 个)、禁止自审、等待计时、限定分支、App 自定义规则;job 引用不存在的环境会自动创建;规则通过前 job 不开始,也拿不到环境 secret。https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-deployments/managing-environments-for-deployment ↩