feat: 仓库导入、分支模型重构与审查历史摘要
仓库接入 - 新增「从 Gitea 导入」:用全局 Token 列出可见仓库,一键建配置 - 新增仓库只填 Git 地址,自动解析 owner/name,支持 https/ssh/scp 写法 - 新增「测试连接」按钮,保存前即可校验地址并拉取分支 分支模型 - 由单一 base_branch + glob 改为「一个管理分支 + 多个检查分支」 - 管理分支是唯一合并目标;检查分支全部纳入监控 - 支持从任意分支克隆创建管理分支 - 审查基准改为管理分支的 merge-base;仅目标为管理分支的 PR 才自动合并 - 旧库自动迁移:base_branch 播种 managed_branch,branch_patterns 展开为检查分支 审查历史 - 新增 review_records 表与「审查历史」页 - 记录触发来源、PR 链接、审查范围、阻断阈值、发布方式、自动合并设置、 排除路径、LLM 模型、token 消耗、耗时、需求背景与全部审查意见 - 详情弹窗一览,支持按仓库过滤 AI 摘要 - 新增独立摘要模块(app/lib/summary.js),与代码审查提示词分离 - 专用提示词输出固定四节、500 字内的中文记录:结论/范围/问题/要点 - 与代码审查共用全局 LLM 设置;摘要失败不影响审查与合并,可单条重跑 修复 - 摘要改用内置 fetch:运行镜像没有 curl,原先 spawn curl 必然 ENOENT - 去掉 blob:none 部分克隆并把凭据写入 .git/config: 惰性取 blob 不会带上 per-command extraHeader,私有库会报 could not read Username UI - 审查背景改为多行文本域(可滚动) - 分支改为可点选列表,管理分支高亮 - 仓库表格展示管理分支与检查分支
This commit is contained in:
+30
-9
@@ -7,7 +7,7 @@ import { test } from "node:test";
|
||||
import { rmSync } from "node:fs";
|
||||
|
||||
import { parseUnifiedDiff, addedLineNumbers, diffLineNumbers, pickAnchorLine } from "../app/lib/diff.js";
|
||||
import { branchMatches } from "../app/lib/review.js";
|
||||
import { branchMatches, parseRepoUrl } from "../app/lib/review.js";
|
||||
import { severityAtLeast, parseList } from "../app/lib/ocr.js";
|
||||
import { normalizeBaseUrl } from "../app/lib/gitea.js";
|
||||
import {
|
||||
@@ -91,14 +91,34 @@ test("pickAnchorLine falls back to context then to the start line", () => {
|
||||
assert.deepEqual(pickAnchorLine(entry, 999, 999), { line: 999, inDiff: false });
|
||||
});
|
||||
|
||||
test("branchMatches honours glob patterns and ref prefixes", () => {
|
||||
assert.ok(branchMatches("main", "*"));
|
||||
assert.ok(branchMatches("main", ""));
|
||||
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", "release/*"));
|
||||
assert.ok(!branchMatches("feature/x", "release/*"));
|
||||
assert.ok(branchMatches("feature/x", "main, feature/*"));
|
||||
assert.ok(branchMatches("a/b/c", "a/**"));
|
||||
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("parseRepoUrl derives owner and name from every supported URL form", () => {
|
||||
const cases = [
|
||||
["http://192.168.31.51/kgod/myrepo.git", "kgod", "myrepo"],
|
||||
["http://192.168.31.51/kgod/myrepo", "kgod", "myrepo"],
|
||||
["https://git.example.com/group/sub/repo.git", "group/sub", "repo"],
|
||||
["ssh://git@git.example.com:2222/kgod/myrepo.git", "kgod", "myrepo"],
|
||||
["git@git.example.com:kgod/myrepo.git", "kgod", "myrepo"],
|
||||
];
|
||||
for (const [url, owner, name] of cases) {
|
||||
const parsed = parseRepoUrl(url);
|
||||
assert.equal(parsed.owner, owner, url);
|
||||
assert.equal(parsed.name, name, url);
|
||||
}
|
||||
});
|
||||
|
||||
test("parseRepoUrl rejects unusable input", () => {
|
||||
assert.throws(() => parseRepoUrl(""), /Git 地址/);
|
||||
assert.throws(() => parseRepoUrl("not a url"), /无法识别|缺少/);
|
||||
assert.throws(() => parseRepoUrl("http://host/onlyowner"), /缺少/);
|
||||
});
|
||||
|
||||
test("severityAtLeast compares known severities and rejects unknown ones", () => {
|
||||
@@ -185,7 +205,8 @@ test("repository defaults and branch state round-trip", () => {
|
||||
const { db, cleanup } = tempDb("repo");
|
||||
try {
|
||||
const repo = upsertRepository(db, { owner: "kgod", name: "demo" });
|
||||
assert.equal(repo.base_branch, "main");
|
||||
assert.equal(repo.managed_branch, "main");
|
||||
assert.equal(repo.check_branches, "main");
|
||||
assert.equal(repo.block_severity, "critical,high");
|
||||
assert.equal(repo.review_scope, "both");
|
||||
assert.equal(repo.create_issue, 1);
|
||||
|
||||
Reference in New Issue
Block a user