PR/MR 评审与 CODEOWNERS
一次“已经批准”的变更为何仍不可信
某个 PR 在上午获得批准,下午作者为了解决冲突又推送一次提交,合并者看到绿色审批标记便直接合入。故障复盘时才发现,评审者看过的是旧差异,新提交修改了认证分支,却没有重新触发有效审批。这里失败的不是代码评论技巧,而是仓库把“某人曾经点过批准”误当成“当前候选提交已经具备完整证据”。
Pull Request(PR)和 Merge Request(MR)都是托管平台建立在 Git 分支、提交差异与权限之上的协作对象。作者解释变更,评审者对某个 head SHA 的差异给出 Comment、Approve 或 Request changes,检查系统为同一候选提交报告结果,服务端规则再决定这些证据是否足以合并。CODEOWNERS 解决的是路径由谁负责,必需审批与必需检查解决的是证据是否齐备,陈旧审批策略和最新推送审批解决的是证据是否仍对应当前版本,绕过名单则决定谁能在例外时跳过规则。
真正的门禁必须在服务端拒绝不满足条件的合并和直推。PR 模板、口头约定、机器人评论都只能帮助协作,不能替代分支规则或 ruleset。项目排期与绩效指标也不应混入这条判断链,否则团队很容易为了缩短合并时间而弱化必要审查。
先建立可验证的评审现场
创建一个不含生产代码和真实数据的练习仓库,默认分支设为 main。至少准备三个身份或等价角色:作者能创建功能分支,评审者能给出有效审批,规则管理员能配置保护规则。若只有一个账号,可以熟悉界面,却无法证明“作者不能自批”“普通成员不能绕过”等权限边界。
开发机需要 Git。GitHub 示例还使用可选的 GitHub CLI;不安装 CLI 也可在网页完成同一流程。
git --version
git remote -v
git status --short
gh auth statusgh auth status 只用于确认当前登录的主机和账号,不要把令牌输出、诊断日志或凭据文件贴进 PR。企业代理、SSO 或自托管平台环境先完成平台登录,再进行隔离仓库实验。
开始前约定两个责任边界:代码作者负责解释变更和完成反馈,评审者负责核对风险与验证证据;最终合并者负责确认“当前被批准的提交”仍是即将合并的提交。三者可以由两个人承担,但不能让作者的自述代替独立审批。
平台能力决定门禁能否成立
GitHub Review 提供评论、批准和请求修改三种正式状态;仓库规则可要求有效审批、在新提交改变差异后撤销陈旧批准,也可要求最近一次可评审推送由最后推送者之外的人批准。具体语义见 Pull request reviews 与 ruleset 可用规则。全量撤销旧批准更保守;“最后推送者之外的人批准”减少重复审查,但前提是团队接受此前审批继续有效的风险。
GitHub 从 PR 的 base branch 读取第一个有效 CODEOWNERS,搜索顺序是 .github/、仓库根目录、docs/;owner 必须具备显式写权限。自动请求 owner 与强制 owner 批准是两个能力,只有保护规则或 ruleset 要求 Code Owner review 时,批准才成为合并硬门槛,详见 GitHub Code owners。
GitLab Free 的批准可作为协作信号,但必需审批属于 Premium 或 Ultimate 能力;Code Owner 强制审批还要求目标分支受保护。允许直接 push 的身份可以绕过 MR 审批链,因此 Merge request approvals、Code Owners 与 Protected branches 必须一起配置。GitLab 对新提交是否重置审批有独立设置,并用 diff 的 patch-id 辅助判断是否真正改变差异,见 approval settings。
平台、套餐、自托管版本和组织策略会改变强制边界。配置页显示 CODEOWNERS 命中,只能证明路由有效;用普通成员直推失败、旧审批在新差异后失效、缺少 owner 批准时合并被拒绝,才能证明门禁成立。可选的 GitHub CLI 只是网页操作的另一个入口,gh pr 可以创建、评审和读取请求状态,但不会替代服务端规则。
安装与首次启用
PR/MR 是托管平台能力,开发机无需安装评审服务。最小启用动作是:仓库允许创建变更请求,目标分支存在,作者能推送一个非目标分支,评审者能读取差异。可选 CLI 只是网页操作的另一个入口,不会替代服务端规则。
先创建一条极小变更:
git switch main
git pull --ff-only
git switch -c docs/review-gate-demo
printf "review gate demo\n" > review-gate-demo.txt
git add review-gate-demo.txt
git commit -m "docs: add review gate demo"
git push -u origin docs/review-gate-demoGitHub 可以用下面的命令创建 Draft PR;其他平台在网页选择同一 source branch 和 target branch 即可:
gh pr create \
--base main \
--head docs/review-gate-demo \
--draft \
--title "docs: verify review gate" \
--body "Purpose: verify reviewer routing, stale approvals, and merge blocking."预期结果是平台返回一个变更请求 URL,目标为 main,源分支为练习分支,状态为 Draft。Draft 用来表明作者尚未交付评审,不应被当作“先占编号、评审者自行判断是否能看”的模糊状态。准备好后执行 gh pr ready 或在网页标记 Ready for review。
实验完成后不要立即删除分支。先保留 PR/MR 时间线作为验证证据,确认规则结果后再关闭练习请求并删除远端分支。
先固定变更请求的输入
好的 PR/MR 描述不是复述提交标题,而是给评审者建立验证上下文。团队模板至少应要求作者写清:为什么改、改了哪些边界、怎样验证、失败时怎样回退、哪些风险没有覆盖。模板可以放在平台支持的仓库目录中,但字段名称应贴合项目,不要复制一份几十项的万能表单。
## Why
描述要解决的问题和不做的范围。
## Change
列出行为变化、配置变化和兼容性边界。
## Verification
给出可重复命令、预期结果和失败结果。
## Risk and rollback
说明影响面、观测入口以及撤销方式。描述是人类审查入口,不是安全门禁。作者勾选“测试通过”不会自动产生可信检查结果;真正需要阻断的检查必须由受控系统以当前提交为输入,并由保护规则消费稳定的检查名称。
用 CODEOWNERS 表达路径责任
以 GitHub 语法为例,下面的抽象规则把默认责任、接口契约、数据库变更和所有权文件本身分开:
# .github/CODEOWNERS
* @your-org/maintainers
/api/ @your-org/api-owners
/database/migrations/ @your-org/database-owners
/.github/CODEOWNERS @your-org/repository-admins规则按路径匹配,不理解业务调用关系。一个 api/ 改动可能同时影响数据库、客户端和部署清单,CODEOWNERS 只能找到被改文件的 owner,不能自动推导所有下游风险。跨目录影响仍需作者在描述中声明,并由评审者追加相应领域人员。
GitHub 使用最后一个匹配规则,路径区分大小写,不支持 ! 否定模式。多个 owner 若要共同成为同一规则的候选人,应放在同一行;但在 GitHub 上,同一行多个 owner 默认表示任一 owner 的有效批准即可满足该路径的 Code Owner 要求,不表示每个人都必须批准。需要双人或跨职能审批时,还要叠加审批数量或独立规则。
GitLab 的文件位置搜索顺序、section、多审批语法和 owner 资格与 GitHub 不完全相同。跨平台仓库可以共享简单路径思想,不能假设复杂语法可移植。迁移平台时必须用一个实际文件变更重新验证 owner 请求和阻断状态。
把评审证据变成服务端门禁
一个稳健的最小基线通常包含:必须通过 PR/MR 合入、至少一名合格评审者批准、敏感路径需要 owner 批准、作者不能自批、未解决讨论阻止合并、关键检查通过、新提交使相关旧审批失效、禁止普通成员直接 push 或强推目标分支。
这些条件之间是“同时满足”,不是互相替代:
新提交失效策略不能只看“提交数量”。真正的问题是审批绑定了哪个差异版本。若平台允许保留未受影响 owner 的审批,也应验证平台怎样判断“未受影响”;安全敏感仓库更适合采用保守策略,在代码发生变化后重新审批。
正反实验:批准后继续推送
实验目标不是完成一次合并,而是证明审批对旧差异有效、对新差异不再自动有效。以下以支持陈旧审批失效的仓库为例。
管理员对 main 启用“必须通过变更请求”“至少一项有效审批”“新提交撤销陈旧审批”,并禁止普通成员直推。将 review-gate-demo.txt 匹配到一个有效 owner;确认 owner 或团队具备平台要求的仓库权限。作者把 Draft 标记为 Ready,评审者检查当前差异后批准。
观察合并框:若没有其他门禁,审批项应显示满足。作者继续修改同一文件并推送新提交。
printf "second revision\n" >> review-gate-demo.txt
git add review-gate-demo.txt
git commit -m "docs: revise review gate demo"
git push再读取状态:
gh pr view --json headRefOid,reviewDecision,latestReviews,statusCheckRollup
gh pr checks预期结果是 headRefOid 已变化,旧批准被撤销或不再满足当前合并条件,reviewDecision 回到需要评审的状态,合并按钮继续阻断。评审者重新检查新差异并批准后,审批门槛才恢复满足。
用普通成员尝试直接推送目标分支,预期被远端拒绝。不要使用管理员账号代替这一步,因为管理员或特定角色可能拥有绕过能力。关闭未合并的练习请求并清理分支:
gh pr close --delete-branch
git switch main
git pull --ff-only
git branch -D docs/review-gate-demo如果团队使用 GitLab,应显式启用相应 approval reset 设置,并确认目标 protected branch 的 Allowed to push and merge 不允许普通开发者直推。若平台或套餐不支持强制审批,验证结论必须写成“协作提示有效但不能形成硬门禁”,再由团队决定升级能力或使用另一个服务端控制点。
让一次评审围绕可理解的变化展开
把重构、格式化、依赖升级和行为变化塞进同一 PR,会同时放大认知负担和 owner 范围。接入项目时,先按可独立验证和回滚的行为边界拆分变更,再设置 reviewer。行数不是唯一标准:一次很小的权限改动可能风险很高,一次机械生成文件更新可能行数很大但审查策略明确。
提交前用本地 Git 检查评审输入:
git fetch origin
git diff --stat origin/main...HEAD
git diff --name-status origin/main...HEAD
git log --oneline origin/main..HEAD... 比较的是两个分支从共同祖先到当前分支的变化,更接近 PR/MR 展示的逻辑。作者应先自行检查无关格式化、生成物、调试日志和敏感文件,再请求他人投入评审时间。
固定检查契约而不是固定流水线实现
保护规则消费的是检查结论和名称,例如 test、lint 或 security-policy。流水线可由不同工具实现,但规则侧需要稳定契约。重命名 Job、替换应用或改变触发条件前,先核对保护规则;否则会出现旧检查永远等待,或新检查运行了却没有被门禁消费。
不要把所有检查都设为必需。必需检查应满足三个条件:对合并风险有直接判断价值、在所有目标 PR/MR 场景都会可靠产生、失败有明确 owner 和修复路径。偶发的外部服务检查若直接阻断,会把供应商可用性变成仓库可写性。
选择合并策略
Merge commit 保留分支拓扑,适合需要看到一组提交边界的仓库;Squash merge 把一次 PR/MR 收敛成一个主线提交,便于回滚和保持主线简洁;Rebase merge 保留每个提交但重写到主线之上,要求提交本身已经整理好。团队应选择一种默认策略并说明例外,不要让每个作者按个人偏好制造不同历史。
无论哪种策略,合并前都要确认最终 head SHA。自动合并或合并队列适合高并发仓库,因为它把“检查通过时的提交”和“真正进入目标分支的候选结果”重新对齐;低并发仓库可以先使用 up-to-date 检查,但要接受频繁同步和重复验证的成本。
作者查看差异和状态:
gh pr diff
gh pr view --json url,baseRefName,headRefOid,reviewDecision,mergeStateStatus,statusCheckRollup
gh pr checks --watch评审者提交正式评审,而不是只留一条普通评论:
gh pr review <number> --approve --body "Verified scope and test evidence."
gh pr review <number> --request-changes --body "Please add rollback verification."普通评论适合提问和非阻断建议;批准表示当前差异满足评审职责;请求修改表示存在必须解决的问题。评审者应明确哪些意见阻断,避免作者从语气猜测门槛。
作者推送修改后,应回复每条讨论、标明对应提交,并重新请求评审。解决 conversation 只表示对话已处理,不等于评审者认同修复;若平台允许作者自行 resolve,团队仍应依靠重新审批和当前差异检查建立证据。
合并时可以绑定预期 head SHA,降低审批后分支又被改动的竞态:
gh pr merge <number> --squash --match-head-commit <expected-head-sha> --delete-branch若仓库使用 merge queue,按平台流程入队,不要用管理员参数跳过队列。--admin 的存在是应急能力,不是解决门禁失败的日常捷径。
CODEOWNERS 没有请求任何人
修改了预期目录,PR/MR 仍没有 owner,或 owner 只显示为普通文本。
先确认目标分支上的 CODEOWNERS 位置、大小写和实际匹配规则,再检查用户或团队是否具备平台要求的仓库权限。GitHub 应查看 base branch 中 .github/、根目录、docs/ 的搜索顺序;GitLab 可在文件页面查看语法和资格提示。
文件放在源分支但 base branch 尚无该规则、后置规则覆盖前置规则、路径大小写错误、团队不可见或没有写权限,都是常见原因。
先把最小规则合入目标分支,给 owner 正确权限,再用只改一个明确匹配文件的新请求复测。不要通过把所有人设为全局 owner 掩盖匹配错误。
Ready for review 后,目标 owner 被自动请求;启用 owner 强制审批时,其他非 owner 的批准不能单独解除该路径门禁。
有人批准了,合并仍显示 Review required
时间线里存在 Approve,合并框仍要求评审。
检查批准者是否是有效 approver、是否满足 Code Owner 路径、审批是否对应当前 head SHA,以及是否还需要更多独立审批。
只读用户的批准可能不计入门槛;一个普通 reviewer 不能替代 owner;新提交可能已撤销旧审批;多个规则也可能同时生效。
根据合并框列出的缺口重新请求正确人员,不要让管理员直接合并。若规则意外叠加,转到仓库规则页核对目标分支的全部有效规则。
重新批准后 reviewDecision 变为满足,且当前 headRefOid 与评审者看到的版本一致。
推送新提交后旧批准仍然有效
作者在批准后修改了代码,合并仍可继续。
查看仓库是否启用陈旧审批失效或平台对应设置,确认新提交确实改变了代码差异,而不只是无影响的元数据操作。
规则未启用、目标分支没有命中保护范围、平台采用差异感知的保留策略,或当前套餐不支持所需强制能力。
在规则中启用新提交后的审批重置;对关键仓库采用更保守策略。若能力不可用,就记录无法强制的风险,并用受控合并角色补偿,而不是声称已有硬门禁。
按前面的批准后推送实验追加同文件内容,确认旧批准消失或不再满足合并。
必需检查一直 Expected 或 Pending
代码和审批都完成,检查名称却长期等待。
核对保护规则中登记的检查名称、流水线实际产生的名称、事件触发条件和当前 head SHA。
Job 改名、路径过滤导致工作流不运行、Fork 场景不触发受信任任务,或规则引用了已经下线的应用。
恢复稳定检查名称,或先在替代检查稳定产生后变更门禁。不要通过删除所有必需检查解决单个名称漂移。
新建普通 PR/MR,检查能从 Pending 进入成功或失败,并且失败时确实阻止合并。
普通成员可以直接推送目标分支
没有 PR/MR、审批和检查,提交仍进入 main。
使用普通成员账号测试,不使用管理员、维护者、机器人或部署密钥;检查平台的 push 权限和绕过名单。
只配置了审批规则,没有限制 direct push;GitLab 的 Allowed to push and merge 未显式设为 No one;某个应用拥有过宽绕过权。
关闭普通成员直推,把自动化改为最小权限的专用身份,并限定其可操作分支。确需应急绕过时要求走 PR/MR 并留下原因。
普通成员 push 被远端拒绝,而通过满足门禁的 PR/MR 可以合并。
讨论已解决但问题没有修复
所有 conversation 显示 resolved,代码仍保留评审指出的问题。
比较评论所在旧 diff 与当前 diff,检查对应修改提交和重新审批记录。
作者误把 resolve 当作修复,评论因代码移动变为 outdated,或团队只统计未解决讨论数量。
阻断问题使用正式 Request changes;修复后由评审者核对当前差异并重新批准。团队约定谁有权关闭安全和数据变更讨论。
问题对应测试或检查失败可复现,修复后转绿;当前 head 取得新的有效批准。
网页评审通常经过 HTTPS,Git push 可能使用 SSH 或 HTTPS。企业代理下“网页能打开但 push 失败”并不矛盾:浏览器代理、Git 代理和 SSH 路径是三套入口。先分别验证平台页面、git fetch 和 CLI API,不要在仓库配置里写入带账号密码的代理 URL。
CLI 令牌只授予完成 PR/MR 操作所需的仓库权限。作者令牌不需要管理规则;评审机器人不应拥有管理员绕过;合并应用只访问明确仓库。凭证进入系统密钥库或 CI Secret,不写进 remote URL、PR 描述、终端截图和示例日志。
Fork PR/MR 的 source branch 属于贡献者信任域。允许 maintainer edit 会让维护者能改该分支,但不会让不可信代码自动获得上游 Secret。评审门禁只消费检查结果;怎样安全运行 Fork 代码属于 CI/CD 安全边界,不能用“已经有 review”替代执行隔离。
评审者权限也遵循最小化:能评论不等于能给出有效批准,能批准不等于应能修改规则,能合并不等于应能绕过规则。离职或转组时同时回收团队成员资格、owner 资格、机器人授权和待处理审批,避免历史身份继续成为有效门槛参与者。
让 owner 对领域负责,而不是对每一行负责
CODEOWNERS 适合表达“谁有资格判断这个路径的风险”,不适合把每个文件分给一个人。规则过细会频繁失效,规则过粗会让核心团队成为所有请求的瓶颈。优先按稳定领域边界设置团队 owner,并为每个关键领域保留至少两名可用成员。
owner 团队应有明确职责:维护匹配规则,约定评审 SLA,培养替补,定期检查无 owner 路径和僵尸团队。CODEOWNERS 本身必须被受控 owner 保护,否则有写权限的作者可以先把自己从规则中移除,再提交敏感变更。
把评审质量落到证据
有效评审至少看四类证据:行为差异是否符合目标,自动验证是否覆盖关键路径,权限与数据边界是否变化,失败后是否可恢复。不同变更需要不同深度。文案修正可以一人快速批准;认证、迁移、加密和发布控制变更通常需要领域 owner 与独立风险 owner。
不要用评论数、审批速度或 PR 数量衡量个人产出。这些指标会诱导拆碎无意义请求和快速盖章。团队更应追踪被门禁阻止的问题类型、审批后再次修改的比例、长期无人响应的 owner 路径,以及绕过事件是否都有复盘。
为例外设置有期限的路径
紧急修复仍应留下变更请求、当前 head、批准或绕过原因和后续验证。若平台支持“仅通过 PR 绕过”,优先使用它,保留差异与时间线;不要直接给应急人员永久直推权。
例外结束后立即撤销临时权限,并在约定时间内补齐测试、文档和复盘。没有到期时间和 owner 的例外会逐渐变成默认通道。
审批对应的是旧提交
最危险的错觉是“这个 PR 已经有人看过”。评审证据实际对应某个 head SHA 或差异版本。批准后 force push、自动修复、冲突解决和合并主线都可能改变内容。判断标准应是合并时 head SHA 是否仍受当前审批与检查覆盖;修复手段是启用陈旧审批失效、限制 head branch 写入者,并在自动合并时绑定候选提交。
所有权规则可以被变更本身削弱
若作者能在同一次变更中修改 CODEOWNERS 并删除敏感目录 owner,平台可能按 base branch 或当前规则产生不同结果。最小防线是保护所有权文件本身,并由仓库管理员团队审批。更高风险仓库还应把基础规则放到组织级控制面,避免单仓库管理员静默降低门槛。
机器人既是协作者也是绕过主体
依赖更新、格式化和发布机器人需要创建分支或 PR,但通常不需要直接写主线。给机器人管理员权限能快速消除失败,却把一个令牌变成全仓绕过通道。应把机器人限制为创建分支和变更请求,让普通检查与审批继续生效;确需 bypass 的应用单独列名、限定模式并监控每次使用。
大 PR 让审批形式化
当差异跨越多个领域、包含生成物或持续被追加提交,评审者很难证明自己看过最终状态。判断不只看行数,还看领域数量、二进制或生成文件比例、提交后变化频率和验证耗时。解决方案是按可回滚行为拆分,先合入无行为变化的准备工作,再合入功能;无法拆分的迁移则增加设计说明、分阶段检查和专门 owner。
门禁可用性会反向影响交付
必需检查、单一 owner 和外部审批服务都可能成为仓库停摆点。高风险门禁不能因此取消,而要设计冗余:owner 团队至少两人,检查有明确维护者和超时策略,平台故障有受控例外流程。取舍标准是失败时默认阻断的安全收益,是否高于对交付连续性的影响,并且例外是否可审计、可撤销。
已明确变更请求的 base branch、head branch 和最终 head SHA。作者、有效评审者、规则管理员和合并者的权限边界已验证。PR/MR 模板要求说明范围、验证、风险和回退,不把勾选项当作机器证据。
CODEOWNERS 位于目标平台支持的位置,路径匹配和 owner 资格已用真实小改动验证。CODEOWNERS 文件自身有 owner,普通作者不能静默降低所有权门槛。目标分支要求通过 PR/MR,普通成员不能直推或强推。
必需审批、owner 审批、讨论解决和检查结果同时形成服务端门禁。已执行“批准后再推一条代码提交”的实验,旧审批失效并阻止合并。必需检查名称稳定,所有预期场景都会产生结果,失败有明确 owner。
合并策略、自动合并或合并队列边界已统一,合并前会确认候选提交。机器人、Fork 和临时应急身份没有获得无边界主线写入权。例外有原因、审批人、到期时间、审计记录和权限回收动作。
练习 PR/MR 和分支已在保留验证证据后清理。示例中没有真实账号、邮箱、仓库地址、代理凭证或访问令牌。
