feat: 重构被审查分支的选择逻辑
push 审查 check_branches 现在只表示「要做 push 审查的分支」,不再兼作 PR 目标分支: - 勾选具体分支:这些分支的 push 会审查,通过后晋级到管理分支 - 新增「任意分支」(*):所有分支的 push 都审查 - 留空:只审 PR,不审 push PR 审查 始终覆盖「勾选的分支 + 管理分支」作为合并目标。 管理分支是最终目标,不需要出现在检查分支里也能被保护。 管理分支从候选中移除 分支选择器不再列出管理分支——它是终点,不需要再检查自己。 安全边界 「任意分支」只扩大审查范围,不会把任意分支自动合并进管理分支; 晋级仍要求分支被显式勾选(promotesToManaged)。 同时修正一处语义错误 早先 branchMatches 把空列表当成「审所有分支」,导致新建仓库在用户 还没选分支时就审查全部 push。现在空列表明确表示「只审 PR」。 测试:分支匹配矩阵重写,覆盖 push/PR/晋级/保护目标四类判定,共 20 项通过。
This commit is contained in:
+56
-16
@@ -7,7 +7,10 @@ import { test } from "node:test";
|
||||
import { rmSync } from "node:fs";
|
||||
|
||||
import { parseUnifiedDiff, addedLineNumbers, diffLineNumbers, pickAnchorLine } from "../app/lib/diff.js";
|
||||
import { branchMatches, parseRepoUrl, pullRequestMatches } from "../app/lib/review.js";
|
||||
import {
|
||||
branchMatches, parseRepoUrl, pullRequestMatches,
|
||||
promotesToManaged, isProtectedTarget, reviewsAnyPush, selectedBranches,
|
||||
} from "../app/lib/review.js";
|
||||
import { severityAtLeast, parseList } from "../app/lib/ocr.js";
|
||||
import { normalizeBaseUrl } from "../app/lib/gitea.js";
|
||||
import {
|
||||
@@ -91,23 +94,60 @@ test("pickAnchorLine falls back to context then to the start line", () => {
|
||||
assert.deepEqual(pickAnchorLine(entry, 999, 999), { line: 999, inDiff: false });
|
||||
});
|
||||
|
||||
test("branchMatches matches an explicit branch list, not globs", () => {
|
||||
assert.ok(branchMatches("main", "main"));
|
||||
assert.ok(branchMatches("refs/heads/main", "main"));
|
||||
assert.ok(branchMatches("release/1.2", "main,release/1.2"));
|
||||
assert.ok(branchMatches("dev", ""));
|
||||
assert.ok(!branchMatches("feature/x", "main,release/1.2"));
|
||||
assert.ok(!branchMatches("release/2.0", "release/*"), "globs are no longer patterns");
|
||||
test("branchMatches reviews pushes only on the listed branches", () => {
|
||||
assert.ok(branchMatches("test", "test,dev"), "listed push");
|
||||
assert.ok(branchMatches("refs/heads/test", "test,dev"), "refs/heads prefix is stripped");
|
||||
assert.ok(!branchMatches("feature/x", "test,dev"), "unlisted push");
|
||||
assert.ok(!branchMatches("prd", "test,dev"), "the managed branch is not implicitly checked");
|
||||
// An empty list means "pull requests only", not "every push".
|
||||
assert.ok(!branchMatches("anything", ""));
|
||||
// Globs were never supported and a literal `*` is now the wildcard token.
|
||||
assert.ok(!branchMatches("release/2.0", "release/*"), "globs are not patterns");
|
||||
});
|
||||
|
||||
test("pullRequestMatches keys on the target branch, not the feature branch", () => {
|
||||
// The two-stage flow: feature -> test -> prd.
|
||||
assert.ok(pullRequestMatches("fix/x", "test", "test,prd"), "PR into test");
|
||||
assert.ok(pullRequestMatches("test", "prd", "test,prd"), "promotion PR into prd");
|
||||
assert.ok(!pullRequestMatches("fix/x", "master", "test,prd"), "PR into an unprotected branch");
|
||||
assert.ok(pullRequestMatches("any", "any", ""), "empty list reviews everything");
|
||||
// Listing a long-lived branch still reviews PRs originating from it.
|
||||
assert.ok(pullRequestMatches("test", "unprotected", "test,prd"), "head branch listed");
|
||||
test("branchMatches treats the wildcard token as every branch", () => {
|
||||
assert.ok(reviewsAnyPush("*"));
|
||||
assert.ok(reviewsAnyPush("*,test"));
|
||||
assert.ok(!reviewsAnyPush("test,dev"));
|
||||
assert.ok(!reviewsAnyPush(""), "empty list means pull requests only");
|
||||
assert.ok(branchMatches("feature/whatever", "*"), "wildcard reviews any push");
|
||||
assert.ok(branchMatches("feature/whatever", "*,test"), "wildcard plus explicit branches");
|
||||
assert.deepEqual(selectedBranches("*,test"), ["test"], "wildcard is not a branch name");
|
||||
});
|
||||
|
||||
test("pullRequestMatches covers checked branches and the managed branch", () => {
|
||||
const checks = "test,dev";
|
||||
const managed = "prd";
|
||||
assert.ok(pullRequestMatches("fix/x", "prd", checks, managed), "PR into the managed branch");
|
||||
assert.ok(pullRequestMatches("fix/x", "test", checks, managed), "PR into a checked branch");
|
||||
assert.ok(pullRequestMatches("test", "prd", checks, managed), "promotion PR test -> prd");
|
||||
assert.ok(!pullRequestMatches("fix/x", "other", checks, managed), "PR into an unprotected branch");
|
||||
assert.ok(pullRequestMatches("test", "other", checks, managed), "head branch explicitly listed");
|
||||
// The managed branch is protected even though it is absent from the list.
|
||||
assert.ok(pullRequestMatches("anything", "prd", "", "prd"), "empty list still protects the managed branch");
|
||||
assert.ok(pullRequestMatches("anything", "other", "", ""), "no managed branch and empty list reviews all");
|
||||
});
|
||||
|
||||
test("promotesToManaged only promotes explicitly checked branches", () => {
|
||||
const checks = "test,dev";
|
||||
const managed = "prd";
|
||||
assert.ok(promotesToManaged("test", checks, managed), "checked branch promotes");
|
||||
assert.ok(promotesToManaged("dev", checks, managed), "checked branch promotes");
|
||||
assert.ok(!promotesToManaged("feature/x", checks, managed), "unlisted branch never promotes");
|
||||
assert.ok(!promotesToManaged("prd", checks, managed), "the managed branch has nowhere to go");
|
||||
// "任意分支" widens review but must not auto-merge arbitrary branches.
|
||||
assert.ok(!promotesToManaged("feature/x", "*,test", managed), "wildcard alone does not promote");
|
||||
assert.ok(promotesToManaged("test", "*,test", managed), "wildcard keeps explicit promotions");
|
||||
});
|
||||
|
||||
test("isProtectedTarget accepts the managed branch and checked branches", () => {
|
||||
const checks = "test,dev";
|
||||
const managed = "prd";
|
||||
assert.ok(isProtectedTarget("prd", checks, managed), "managed branch");
|
||||
assert.ok(isProtectedTarget("test", checks, managed), "checked branch");
|
||||
assert.ok(!isProtectedTarget("other", checks, managed), "unprotected branch");
|
||||
assert.ok(!isProtectedTarget("", checks, managed), "empty ref");
|
||||
assert.ok(isProtectedTarget("refs/heads/prd", checks, managed), "prefix is stripped");
|
||||
});
|
||||
|
||||
test("parseRepoUrl derives owner and name from every supported URL form", () => {
|
||||
|
||||
Reference in New Issue
Block a user