合并请求门禁设计:自动化检查清单如何平衡速度与质量
2026/7/15 21:53:27 网站建设 项目流程

合并请求门禁设计:自动化检查清单如何平衡速度与质量

一、MR 评审走了 3 天,80% 的时间在等 CI 通过

一个典型 MR 的生命周期:提交代码 → CI 跑 45 分钟(lint + unit test + build + security scan)→ 找到评审人 → 评审提修改意见 → 改一轮代码 → CI 再跑 45 分钟 → 再一轮评审 → 合并。如果有 3 个 MR 在排队,开发者一天就过去了。

门禁(Merge Request Gate)的目的是在合并前自动检查这些事项,减少人工评审的负担。但如果门禁门槛太低,质量没保障;如果门禁太高,等待时间太长。门禁设计就是在这两个极端之间找平衡。

二、门禁的分层设计

graph TD A["Push 代码 / 创建 MR"] --> B["Layer 1: 快速门禁<br/>(< 5 min)"] B --> B1["Lint 检查"] B --> B2["Type Check (tsc --noEmit)"] B --> B3["Commit Message Check"] B --> B4["文件大小检查"] B --> C{"Layer 1 全部通过?"} C -->|"否"| D["立即反馈<br/>不消耗评审资源"] C -->|"是"| E["Layer 2: 标准门禁<br/>(< 20 min)"] E --> E1["Unit Test"] E --> E2["Build 检查"] E --> E3["依赖审计"] E --> E4["Simple Security Scan"] E --> F{"Layer 2 通过?"} F -->|"否"| D F -->|"是"| G["Layer 3: 全面门禁<br/>(< 60 min)"] G --> G1["Integration Test"] G --> G2["E2E Test"] G --> G3["Full Security Scan"] G --> G4["Performance Regression"] G --> H{"Layer 3 通过?"} H -->|"否"| I["高优先级修复<br/>可请求绕过 + 事后补齐"] H -->|"是"| J["MR 可合并<br/>人工评审"] style D fill:#FF6B6B,color:#fff style I fill:#F5A623,color:#000 style J fill:#50B86C,color:#fff

三层门禁的核心思路:把检查按"速度"和"覆盖率"分层。Layer 1 在 5 分钟内给开发者即时反馈,Layer 2 在 20 分钟内验证核心功能,Layer 3 在 60 分钟内做全面验证。

门禁的分类策略

分层超时是否阻塞合并典型检查
L1 快速5 min硬阻塞Lint, Type Check, Commit Msg
L2 标准20 min硬阻塞Unit Test, Build, Dep Audit
L3 全面60 min软阻塞(可绕过)Integration, E2E, Security, Perf

"软阻塞"的意思是:失败后 MR 不能合并,但如果紧急修复且开发者确认风险可控,可以有条件绕过(标明skip-gate: Layer3, reason: security-scan-known-false-positive)。

三、门禁系统实现

CI 配置

# .github/workflows/mr-gates.yml name: MR Gates on: pull_request: types: [opened, synchronize, reopened] jobs: # ===== Layer 1: 快速门禁 (< 5 min) ===== fast-gates: name: "L1 Fast Gates" runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v4 - name: Lint run: npm run lint -- --max-warnings=0 - name: Type Check run: npx tsc --noEmit - name: Commit Message Check uses: wagoid/commitlint-github-action@v5 with: configFile: commitlint.config.js # ===== Layer 2: 标准门禁 (< 20 min) ===== standard-gates: name: "L2 Standard Gates" runs-on: ubuntu-latest timeout-minutes: 20 needs: [fast-gates] # 必须 L1 通过 steps: - uses: actions/checkout@v4 - name: Setup uses: actions/setup-node@v4 with: { node-version: 20 } - name: Install Dependencies run: npm ci - name: Unit Tests run: npm test -- --coverage --maxWorkers=4 - name: Build Check run: npm run build - name: Dependency Audit run: npm audit --audit-level=high # ===== Layer 3: 全面门禁 (< 60 min) ===== full-gates: name: "L3 Full Gates" runs-on: ubuntu-latest timeout-minutes: 60 needs: [standard-gates] steps: - uses: actions/checkout@v4 - name: Integration Tests run: npm run test:integration - name: E2E Tests (Critical Paths) run: npm run test:e2e:smoke - name: Security Scan uses: aquasecurity/trivy-action@master with: scan-type: 'fs' severity: 'CRITICAL,HIGH' - name: Bundle Size Check run: npm run size-check

门禁状态管理

""" MR 门禁状态管理器 跟踪每个 MR 的门禁状态,管理绕过规则 """ from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Optional from datetime import datetime class GateStatus(Enum): PENDING = "pending" RUNNING = "running" PASSED = "passed" FAILED = "failed" BYPASSED = "bypassed" # 人工绕过 TIMEOUT = "timeout" @dataclass class GateLayer: """单个门禁层""" name: str checks: List[str] status: GateStatus = GateStatus.PENDING started_at: Optional[datetime] = None finished_at: Optional[datetime] = None bypass_reason: Optional[str] = None class MRGateManager: """ MR 门禁管理器 门禁规则: 1. L1 必须全部通过,不可绕过 2. L2 必须全部通过,不可绕过 3. L3 失败后可绕过,但需要理由 + 审批 4. 绕过记录在 MR 消息中永久保留 """ def __init__(self): self.layers = { "L1_Fast": GateLayer( name="快速门禁", checks=["lint", "type-check", "commit-message"], ), "L2_Standard": GateLayer( name="标准门禁", checks=["unit-test", "build", "dep-audit"], ), "L3_Full": GateLayer( name="全面门禁", checks=["integration-test", "e2e", "security-scan", "perf"], ), } self.bypass_history: List[Dict] = [] def can_merge(self) -> tuple[bool, str]: """ 判断当前 MR 是否可以合并 返回:(是否可以合并, 原因) """ # L1 + L2 必须通过 for layer_id in ["L1_Fast", "L2_Standard"]: layer = self.layers[layer_id] if layer.status == GateStatus.FAILED: return False, f"{layer.name} 失败,请修复后重新提交" if layer.status != GateStatus.PASSED: return False, f"{layer.name} 仍在运行中" # L3 可以 bypass l3 = self.layers["L3_Full"] if l3.status == GateStatus.FAILED: if l3.bypass_reason: return True, f"{l3.name} 已绕过 (原因: {l3.bypass_reason[:100]})" return False, f"{l3.name} 失败,如需绕过请添加 bypass 理由" if l3.status != GateStatus.PASSED: return False, f"{l3.name} 仍在运行中" return True, "所有门禁通过" def request_bypass(self, layer_id: str, reason: str, approver: str) -> bool: """ 请求绕过某个门禁层 限制: - L1/L2 不可绕过 - 需要审批人 - 理由不可为空 """ if layer_id in ["L1_Fast", "L2_Standard"]: return False # L1/L2 不可绕过 layer = self.layers.get(layer_id) if not layer: return False if not reason or len(reason.strip()) < 10: return False # 理由至少 10 个字符 layer.bypass_reason = reason layer.status = GateStatus.BYPASSED # 记录绕过历史 self.bypass_history.append({ "layer": layer_id, "reason": reason, "approver": approver, "timestamp": datetime.now().isoformat(), }) return True def get_mr_status_summary(self) -> str: """生成 MR 状态摘要(用于 MR 评论)""" lines = ["## MR 门禁状态\n"] for layer_id, layer in self.layers.items(): status_icon = { GateStatus.PASSED: "✅", GateStatus.FAILED: "❌", GateStatus.BYPASSED: "⚠️", GateStatus.RUNNING: "🔄", GateStatus.PENDING: "⏳", }.get(layer.status, "❓") lines.append(f"{status_icon} **{layer.name}**: {layer.status.value}") if layer.bypass_reason: lines.append(f" - 绕过原因: {layer.bypass_reason}") can_merge, reason = self.can_merge() if can_merge: lines.append("\n✅ MR 可以合并") else: lines.append(f"\n❌ MR 不可合并: {reason}") return "\n".join(lines)

四、门禁设计的平衡

缺点:

  1. CI 排队:如果 5 个 MR 同时推送,每个等 45 分钟的 CI,最后一个等 3 小时以上。需要 CI 并发扩容。
  2. Flaky Test 的影响:如果 E2E 测试不稳定(假阳性 10%),经常导致 MR 被误拦,开发者反而学会"无视"L3 失败。
  3. 绕过滥用:L3 的绕过功能如果缺乏监控,会被当成"失败也合"的快捷键。需要设置绕过配额(如每人每月最多 3 次)。

禁用场景:

  • 个人项目:门禁 overhead 远大于收益。
  • 紧急 Hotfix:应该支持紧急通道(skip-gate: all, reason: p0-bug)。

五、总结

MR 门禁设计的核心是分层:L1(< 5 分钟,硬阻塞)做语法/格式检查,L2(< 20 分钟,硬阻塞)做功能验证,L3(< 60 分钟,软阻塞可绕过)做全面检查。三个关键决策:哪些检查放哪层(按速度和覆盖率分配)、软阻塞的绕过规则(需要理由+审批+配额限制)、Flaky Test 的管理(假阳性率超过阈值自动降级)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询