单人维护者 PR 合并仪式
维护者何时可以直接合并自己的拉取请求,以及在分支保护要求一次批准并对管理员强制执行时所用的仪式。
在项目分支保护下自行合并 PR 的规范参考。
main上当前生效的保护要求 0 个批准评审,并让enforce_admins保持关闭,如分支保护页面所记录。因此,维护者直接合并自己的拉取请求。本页中的临时切换仪式仅在保护要求一次批准并对管理员强制执行时适用。
1. 死锁
main 上当前生效的保护带有:
required_pull_request_reviews.required_approving_review_count: 0enforce_admins: false
在这些设置下不存在死锁。维护者的拉取请求在其必需的状态检查为绿色、其评审对话均已解决之后,使用 gh pr merge <pr-number> --squash --delete-branch 合并。
只有当保护同时带有以下两项更严格的设置时,才会存在死锁:
required_pull_request_reviews.required_approving_review_count: 1enforce_admins: true
这些规则对多维护者场景是正确的(每次变更都通过经同行评审的 PR 抵达 main;管理员不被豁免)。它们会让单人维护者陷入死锁:GitHub 的策略不允许对你自己创建的 PR 进行自我批准,而 enforce_admins=true 会阻止 gh pr merge --admin 对评审批准要求的覆盖。在 enforce_admins: false 下,--admin 会绕过批准要求,无需任何切换,但它也会绕过处于红色状态的必需检查,因此在使用它之前先确认这些检查为绿色。
存在三条解决路径:
| 路径 | 何时使用 |
|---|---|
永久设置 required_approving_review_count: 0(当前生效的设置) | 仅在仓库将无限期保持单人维护时采用;会逆转保护的评审意图 |
| 临时切换仪式 | 当保护在 enforce_admins=true 下要求一次批准时,用于单人维护者自合并;在合并窗口之外保持保护不变式 |
| 配置一个具有写入权限的 CI 机器人身份 | 仅在即将引入多位维护者时采用;机器人批准会削弱保护的人工评审意图 |
本仓库目前使用永久零批准路径。只要保护要求一次批准并对管理员强制执行,临时切换仪式就是规范路径。
2. 临时切换仪式
该仪式的不变式:仪式开始时的保护状态等于仪式结束时的保护状态。放松只存在于合并窗口之内。
手动形式:
# 1. Open the PR, push branch, wait for CI green.
gh pr create --base main --head <feature-branch> --title "..." --body "..."
gh pr checks <pr-number> # poll until all green
# 2. Relax approver-count to 0.
gh api repos/<owner>/<repo>/branches/main/protection/required_pull_request_reviews \
-X PATCH -F required_approving_review_count=0
# 3. Squash-merge with admin override (admin override now succeeds because the
# review-approval requirement is the only block enforce_admins refuses).
gh pr merge <pr-number> --squash --delete-branch --admin
# 4. Restore approver-count to 1.
gh api repos/<owner>/<repo>/branches/main/protection/required_pull_request_reviews \
-X PATCH -F required_approving_review_count=1封装形式:参见 scripts/dev/admin_merge.py,这是一个原子化的 Python 封装器,它在单次非交互序列中执行步骤 2 → 3 → 4,并在步骤 3 失败时通过 try / finally 提供回滚安全保障。
python scripts/dev/admin_merge.py <pr-number>3. 不变式
- 切换窗口最小化。 步骤 2 → 3 → 4 在单次非交互序列中执行。保护仅在合并 API 调用持续期间处于放松状态——在每次观察到的运行中都不到一秒。
- 恢复是无条件的。 即使步骤 3 失败,步骤 4 也会运行(封装器的
try/finally子句确保在任何退出路径上都会恢复)。一次失败的合并尝试不会让保护停留在放松状态。 - 在
enforce_admins开启期间,CI 门禁全程保留。required_status_checks不受切换影响;在enforce_admins: true下,合并仍要求已配置的状态检查上下文为绿色。在enforce_admins: false下,步骤 3 的--admin合并也会绕过处于红色状态的必需检查,因此不要在该状态下运行此仪式。 - 审计轨迹可见。
gh pr merge --admin注记出现在 PR 的合并元数据中;保护 API 的切换以维护者的身份出现在仓库的审计日志中。
4. 何时不应使用该仪式
当保护要求 0 个批准评审或让 enforce_admins 保持关闭时,该仪式没有必要:按照第 1 节所述直接合并。
该仪式用于维护者自己的 PR 的自合并。当有同行评审者可用时(一位拥有写入权限的贡献者),标准的 gh pr review --approve + gh pr merge --squash --delete-branch 路径才是正确的,该仪式则没有必要。
如果仓库引入了第二位维护者,则退役该仪式:标准的 PR 评审流程无需任何切换即可恢复保护的人工评审意图。
5. 审计一次过往运行
每次仪式调用都会落在三个持久的表面上,操作者可以在事后检查它们:
- PR 的合并元数据。
gh pr view <pr-number> --json mergedBy,mergedAt,mergeCommit返回合并执行者、合并时间戳以及 squash 提交的 SHA。mergedBy执行者与运行步骤 3 的维护者身份一致。 - 仓库的审计日志。
gh api /repos/<owner>/<repo>/actions/runs --jq '.workflow_runs[] | select(.event=="push")'将合并后的 CI 运行与合并提交相关联;仓库的保护规则审计日志(通过 Settings → Branches 界面查看gh api /repos/<owner>/<repo>/branches/main/protection的历史记录)显示步骤 2 与步骤 4 的切换包围着合并窗口。 - 维护者的 git 历史。
git log main --first-parent --oneline列出main上的每一个 squash 合并提交,提交主题中带有对应的 PR 编号(依据gh pr merge --squash的默认行为,附加(#<pr-number>)后缀),使操作者能够将任何过往的落地追溯到其 PR 与仪式调用。
当 scripts/dev/admin_merge.py 为入口点时,其非交互日志(stderr)会依次列出三个保护 API 调用与合并 API 调用;将其 stderr 通过管道传给 tee(2>&1 | tee),即可在不依赖 GitHub 侧日志保留期的情况下,捕获每次运行的审计片段。