From ab509eee0de015d1bc8146746fa3bf92b8eec089 Mon Sep 17 00:00:00 2001 From: kgod <1257628228@qq.com> Date: Sun, 20 Sep 2026 14:54:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=AF=E6=8C=81=20test=E2=86=92prd=20?= =?UTF-8?q?=E4=B8=A4=E7=BA=A7=E6=B5=81=E8=BD=AC=E7=9A=84=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E4=B8=8E=20synchronize=20=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 自动合并门槛过严 上一版要求 PR 目标必须等于 managed_branch 才自动合并, 这会让两级流转(feature -> test -> prd)的第一级永远合不进去。 改为:目标命中任一检查分支即可自动合并。 managed_branch 退化为「push 无 PR 时的对比基准」与默认值。 2. Gitea 的 PR 更新事件名是 synchronized Gitea webhook 的 payload.action 用 synchronized, 只有它的 Actions runner 才会改写成 GitHub 风格的 synchronize。 原先只认 synchronize,导致「PR 有新提交」永远不触发。 现在两种拼写都接受。 验证(真实 webhook,非手工触发): - feature -> test 开 PR:pull_request.opened 自动入队并审查 - 再推一个 commit:pull_request.synchronized 自动入队 - 无阻断问题 -> commit status success -> 自动 squash 合并,PR #4 已 merged - 向未受保护分支推送会被正确过滤 --- README.md | 17 +++++++++++++++-- app/lib/review.js | 10 +++++----- app/server.js | 8 +++++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bdae913..407c479 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ curl -fsS http://localhost:8090/api/health | 字段 | 说明 | | --- | --- | | `repo_url` | 仓库 Git 地址,`owner` / `name` 由此自动解析 | -| `managed_branch` | 唯一的管理分支,所有审查都相对它做对比,也是唯一允许自动合并的目标 | +| `managed_branch` | 主管理分支。push 事件没有对应 PR 时用它做对比基准;自动合并不限于它,任一检查分支都可作为合并目标 | | `check_branches` | 受保护的目标分支列表,逗号分隔。任何**合并进**这些分支的 PR 都会审查(功能分支不必列出),向这些分支推送也会审查 | | `review_scope` | `both` / `pr` / `push` | | `block_severity` | 阻断级别阈值,如 `critical,high`;留空则不看级别 | @@ -217,7 +217,17 @@ curl -X POST http://localhost:8090/api/review \ ## 行为说明 -**分支与合并**:`check_branches` 是**被保护的目标分支**。任何合并进这些分支的 PR 都会被审查,所以功能分支不必列进去;向这些分支直接推送也会审查。审查基准是该改动实际要合入的分支(PR 用 PR 自己的目标分支,push 用管理分支),也就是「这次改动合进去会怎样」。自动合并只作用于目标为 `managed_branch` 的 PR。 +**分支与合并**:`check_branches` 是**被保护的目标分支**。任何合并进这些分支的 PR 都会被审查,所以功能分支不必列进去;向这些分支直接推送也会审查。审查基准是该改动实际要合入的分支(PR 用 PR 自己的目标分支,push 用管理分支),也就是「这次改动合进去会怎样」。 + +自动合并作用于目标为**任一检查分支**的 PR,因此两级流转可以直接用: + +```text +feature/xxx ──PR──> test ──PR──> prd + ↑ 第一级:审查通过即自动合并 + ↑ 第二级:同样审查通过才合并 +``` + +`managed_branch` 只决定「push 事件没有对应 PR 时,拿哪个分支做对比基准」,以及新仓库的默认值。 **审查范围**:PR 事件优先用 PR 的目标分支作为基准;push 事件优先用 webhook 里的 `before`,当 `before` 是新建分支的全零值或缺失时,退回到与管理分支的 merge-base。 @@ -246,6 +256,9 @@ docker compose build # 构建镜像 **PR 提交了却没有触发审查** 先看仓库列表的 Webhook 列:显示「未配置」说明 Gitea 不会发事件,点「修复 Webhook」即可。其次确认 `check_branches` 是否包含该 PR 的**目标分支**(不是功能分支)。 +**审查通过了却没有自动合并** +看任务日志里的 `auto-merge skipped` 原因。常见几类:`auto_merge` 开关没打开;PR 目标分支不在 `check_branches` 里;存在阻断问题;审查覆盖不完整(OCR 报了 partial/警告/token 预算截断);提交状态不是 success;PR head 已变化或不可合并。 + **Webhook 投递失败,提示 `webhook can only call allowed HTTP servers`** Gitea 拦截了内网地址。按上文给 `[webhook] ALLOWED_HOST_LIST` 加上本服务地址,重启 Gitea。 diff --git a/app/lib/review.js b/app/lib/review.js index 067504d..3449f45 100644 --- a/app/lib/review.js +++ b/app/lib/review.js @@ -654,11 +654,11 @@ export class ReviewEngine { appendLog("auto-merge skipped: no pull request for this branch"); return false; } - // Only merges into the managed branch are performed; a PR targeting any - // other branch is reviewed but never auto-merged. - const managed = repo.managed_branch || repo.base_branch; - if (job.base_ref && managed && job.base_ref !== managed) { - appendLog(`auto-merge skipped: PR targets ${job.base_ref}, managed branch is ${managed}`); + // A two-stage flow (feature -> test -> prd) legitimately merges into a + // checked branch that is not the managed branch, so the gate is "is the + // target one of the branches we protect", not "is it the managed branch". + if (job.base_ref && !branchMatches(job.base_ref, repo.check_branches)) { + appendLog(`auto-merge skipped: PR targets ${job.base_ref}, which is not a checked branch`); return false; } if (reviewIncomplete) { diff --git a/app/server.js b/app/server.js index 85ea937..aaf2577 100644 --- a/app/server.js +++ b/app/server.js @@ -216,9 +216,15 @@ async function handlePush(payload, cfg) { return { queued: 1, jobId }; } +// Gitea's webhook payload uses "synchronized"; only its Actions runner rewrites +// that to the GitHub-style "synchronize", so accept every spelling. +const PR_ACTIONS = new Set([ + "opened", "reopened", "synchronize", "synchronized", "ready_for_review", +]); + async function handlePullRequest(payload, cfg) { const action = payload.action; - if (!["opened", "synchronize", "reopened", "ready_for_review"].includes(action)) { + if (!PR_ACTIONS.has(action)) { return { queued: 0, reason: `action ${action} ignored` }; } const owner = payload.repository?.owner?.username || payload.repository?.owner?.login;