Files
kgod 216bb51c92 feat: 重构被审查分支的选择逻辑
push 审查
  check_branches 现在只表示「要做 push 审查的分支」,不再兼作 PR 目标分支:
  - 勾选具体分支:这些分支的 push 会审查,通过后晋级到管理分支
  - 新增「任意分支」(*):所有分支的 push 都审查
  - 留空:只审 PR,不审 push

PR 审查
  始终覆盖「勾选的分支 + 管理分支」作为合并目标。
  管理分支是最终目标,不需要出现在检查分支里也能被保护。

管理分支从候选中移除
  分支选择器不再列出管理分支——它是终点,不需要再检查自己。

安全边界
  「任意分支」只扩大审查范围,不会把任意分支自动合并进管理分支;
  晋级仍要求分支被显式勾选(promotesToManaged)。

同时修正一处语义错误
  早先 branchMatches 把空列表当成「审所有分支」,导致新建仓库在用户
  还没选分支时就审查全部 push。现在空列表明确表示「只审 PR」。

测试:分支匹配矩阵重写,覆盖 push/PR/晋级/保护目标四类判定,共 20 项通过。
2026-09-21 17:12:57 +08:00

283 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Gitea 代码审查</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Ctext y='14' font-size='14'%3E%F0%9F%94%8D%3C/text%3E%3C/svg%3E" />
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<header>
<h1>Gitea 代码审查</h1>
<div class="right">
<div class="status" id="status">加载中…</div>
<button class="linkish hidden" id="sign-out">清除访问 Token</button>
</div>
</header>
<nav>
<button class="tab active" data-tab="repos">仓库</button>
<button class="tab" data-tab="records">审查历史</button>
<button class="tab" data-tab="jobs">任务</button>
<button class="tab" data-tab="settings">设置</button>
</nav>
<main>
<div id="banner" class="banner hidden"></div>
<section id="tab-repos" class="panel active">
<div class="row">
<h2>已配置仓库</h2>
<div class="actions">
<button id="import-repo">从 Gitea 导入</button>
<button class="primary" id="add-repo">新增仓库</button>
</div>
</div>
<table id="repos-table">
<thead>
<tr>
<th>仓库</th><th>管理分支</th><th>检查分支</th><th>范围</th>
<th>Webhook</th><th>启用</th><th>自动合并</th><th>阻断级别</th><th></th>
</tr>
</thead>
<tbody></tbody>
</table>
</section>
<section id="tab-records" class="panel">
<div class="row">
<h2>审查历史摘要</h2>
<div class="actions">
<select id="record-filter"><option value="">全部仓库</option></select>
<button id="refresh-records">刷新</button>
</div>
</div>
<div id="records-list" class="records"></div>
</section>
<section id="tab-jobs" class="panel">
<div class="row">
<h2>审查任务</h2>
<button id="refresh-jobs">刷新</button>
</div>
<table id="jobs-table">
<thead>
<tr><th>#</th><th>仓库</th><th>分支</th><th>提交</th><th>状态</th>
<th>意见</th><th>阻断</th><th>合并</th><th>开始</th><th></th></tr>
</thead>
<tbody></tbody>
</table>
<pre id="job-log" class="log hidden"></pre>
</section>
<section id="tab-settings" class="panel">
<h2>全局设置</h2>
<form id="settings-form">
<fieldset>
<legend>Gitea</legend>
<label>API 地址 <input name="giteaUrl" placeholder="http://192.168.31.51" /></label>
<label>管理员 Token <input name="giteaToken" type="password" placeholder="留空表示不修改" /></label>
<label>Webhook 密钥 <input name="webhookSecret" type="password" placeholder="留空表示不修改" /></label>
<label>后台访问 Token <input name="adminToken" type="password" placeholder="留空表示不修改" /></label>
<label>Webhook 回调地址 <input name="webhookUrl" placeholder="留空自动推导为 <Gitea 主机>:<本服务端口>" /></label>
<p class="hint">保存仓库时会自动在 Gitea 里创建/更新 Webhook,地址用上面这一项,密钥用 Webhook 密钥。<code id="hook-url"></code></p>
</fieldset>
<fieldset>
<legend>LLM(代码审查与历史摘要共用)</legend>
<label>接口地址 <input name="llmUrl" placeholder="https://api.openai.com/v1" /></label>
<label>API Key <input name="llmToken" type="password" placeholder="留空表示不修改" /></label>
<label>模型 <input name="llmModel" placeholder="gpt-5.6-sol" /></label>
<label>协议 <input name="llmProtocol" placeholder="openai / anthropic" /></label>
<label>自定义认证头 <input name="llmAuthHeader" placeholder="留空使用默认" /></label>
<label>附加请求头 <input name="llmExtraHeaders" placeholder="K=V,K=V" /></label>
<label>审查规则文件 <input name="rulePath" placeholder="/etc/gitea-codereview/rule.json" /></label>
</fieldset>
<div class="actions">
<button type="submit">保存</button>
<button type="button" id="test-gitea">测试 Gitea</button>
<button type="button" id="test-llm">测试 LLM</button>
</div>
<pre id="settings-result" class="log hidden"></pre>
</form>
</section>
</main>
<div id="token-gate" class="modal hidden">
<div class="modal-body narrow">
<h2>需要访问 Token</h2>
<p class="hint">该服务设置了 <code>CR_ADMIN_TOKEN</code>,请输入后才能管理仓库与任务。</p>
<form id="token-form">
<label>访问 Token
<input name="token" type="password" autocomplete="current-password" required />
</label>
<div class="actions">
<button type="submit">进入</button>
</div>
<pre id="token-error" class="log hidden"></pre>
</form>
</div>
</div>
<div id="import-modal" class="modal hidden">
<div class="modal-body">
<h2>从 Gitea 导入仓库</h2>
<p class="hint">使用全局 Token 拉取当前账号可见的仓库,点击即可创建监控配置。</p>
<input id="import-search" placeholder="按仓库名过滤…" />
<div id="import-list" class="import-list"></div>
<div class="actions">
<button type="button" id="import-close">关闭</button>
</div>
</div>
</div>
<div id="manual-modal" class="modal hidden">
<div class="modal-body narrow">
<h2>立即审查</h2>
<form id="manual-form">
<input type="hidden" name="repo_id" />
<p class="hint" id="manual-repo"></p>
<label>分支 <input name="ref" placeholder="main" required /></label>
<label>提交 SHA <input name="sha" placeholder="40 位 commit hash" required /></label>
<label>基准分支 <input name="base_ref" placeholder="管理分支" /></label>
<label>关联 Pull Request 编号(可选) <input name="pr_number" type="number" min="1" /></label>
<div class="actions">
<button type="submit">加入队列</button>
<button type="button" id="manual-close">取消</button>
</div>
<pre id="manual-result" class="log hidden"></pre>
</form>
</div>
</div>
<div id="record-modal" class="modal hidden">
<div class="modal-body">
<h2 id="record-title">审查记录</h2>
<div id="record-body"></div>
<div class="actions">
<button type="button" id="record-resummarise">重新生成摘要</button>
<button type="button" id="record-close">关闭</button>
</div>
</div>
</div>
<div id="modal" class="modal hidden">
<div class="modal-body">
<h2 id="modal-title">仓库配置</h2>
<form id="repo-form">
<input type="hidden" name="id" />
<fieldset>
<legend>仓库</legend>
<label>Git 地址
<span class="inline">
<input name="repo_url" placeholder="http://192.168.31.51/kgod/myrepo.git" required />
<button type="button" id="repo-test">测试连接</button>
</span>
</label>
<p class="hint" id="repo-parsed"></p>
</fieldset>
<fieldset>
<legend>分支</legend>
<label>管理分支(合并目标)
<span class="inline">
<input name="managed_branch" placeholder="main" />
<button type="button" id="branch-create">克隆创建</button>
</span>
</label>
<p class="hint">最终目标分支。它是 PR 审查的默认合并目标,但不需要出现在下面的检查分支里。</p>
<label>检查分支(push 审查;可多选)
<span class="inline">
<input name="check_branches" placeholder="留空表示只审查 PR;* 表示任意分支" />
<button type="button" id="branch-reload">加载分支</button>
</span>
</label>
<div id="branch-list" class="branch-list"></div>
<p class="hint">选「任意分支」则所有分支的 push 都审查,也可只勾选指定分支。勾选的分支 push 通过后会自动晋级到管理分支;「任意分支」只扩大审查范围,不会自动合并任意分支。</p>
<p class="hint">PR 审查始终覆盖「勾选的分支 + 管理分支」作为合并目标,与管理分支是否列在这里无关。</p>
</fieldset>
<fieldset>
<legend>审查</legend>
<div class="grid">
<label>审查范围
<select name="review_scope">
<option value="both">Push 与 PR 都审查</option>
<option value="pr">仅 Pull Request</option>
<option value="push">仅分支 Push</option>
</select>
</label>
<label>发布方式
<select name="publish_mode">
<option value="inline">PR 内联评论</option>
<option value="issue-only">仅 Issue</option>
</select>
</label>
<label>阻断级别 <input name="block_severity" placeholder="critical,high" /></label>
<label>阻断类别 <input name="block_categories" placeholder="security" /></label>
<label>Issue 标签 <input name="issue_labels" placeholder="code-review" /></label>
<label>最大意见数 <input name="max_comments" type="number" min="1" max="200" /></label>
<label>并发数 <input name="concurrency" type="number" min="1" max="16" /></label>
<label>排除路径 <input name="excludes" placeholder="**/dist/**,**/*.lock" /></label>
<label>仓库专用 Gitea Token <input name="gitea_token" type="password" placeholder="留空使用全局" /></label>
<label>仓库专用 LLM Key <input name="llm_token" type="password" placeholder="留空使用全局" /></label>
<label>LLM 模型 <input name="llm_model" placeholder="留空使用全局" /></label>
</div>
<label>审查背景 / 需求
<textarea name="background_template" rows="6" placeholder="补充业务上下文、本次需求说明;会作为审查背景传给模型,并记录到审查历史。"></textarea>
</label>
</fieldset>
<div class="checks">
<label><input type="checkbox" name="enabled" /> 启用</label>
<label><input type="checkbox" name="create_issue" /> 发现问题时创建 Issue</label>
<label><input type="checkbox" name="auto_merge" /> 无阻断问题时自动合并</label>
<label><input type="checkbox" name="delete_branch" /> 合并后删除分支</label>
<label><input type="checkbox" name="fail_on_findings" /> 有意见即判定失败</label>
</div>
<div class="grid">
<label>自动合并方式
<select name="auto_merge_mode">
<option value="when_checks_succeed">等待检查通过后合并</option>
<option value="immediate">立即合并</option>
</select>
</label>
<label>合并方式
<select name="merge_method">
<option value="squash">squash</option>
<option value="merge">merge</option>
<option value="rebase">rebase</option>
<option value="rebase-merge">rebase-merge</option>
<option value="fast-forward-only">fast-forward-only</option>
</select>
</label>
</div>
<div class="actions">
<button type="submit">保存</button>
<button type="button" id="modal-close">取消</button>
</div>
<pre id="repo-result" class="log hidden"></pre>
</form>
</div>
</div>
<div id="clone-modal" class="modal hidden">
<div class="modal-body narrow">
<h2>创建管理分支</h2>
<form id="clone-form">
<label>新分支名 <input name="new_branch" placeholder="main" required /></label>
<label>从哪个分支复制
<select name="from_branch" id="clone-source"></select>
</label>
<div class="actions">
<button type="submit">创建</button>
<button type="button" id="clone-close">取消</button>
</div>
<pre id="clone-result" class="log hidden"></pre>
</form>
</div>
</div>
<script src="/app.js"></script>
</body>
</html>