fix: 自动安装 Webhook、修正 PR 审查范围与分支匹配语义
三个导致「提交了新 PR 但没有任何反应」的独立问题: 1. 从不创建 Webhook 服务只被动接收事件,但配置仓库时不会去 Gitea 里建 Webhook, 结果仓库永远收不到 push / PR 事件,看起来和坏掉一样。 - 保存仓库时自动创建或更新 Webhook(POST /repos 返回值带 webhook 结果) - 新增 GET/POST /api/repos/:id/webhook 查询与修复 - 仓库列表新增 Webhook 列,未配置可一键「修复 Webhook」 - 新增 CR_WEBHOOK_URL,留空则推导为「Gitea 主机名 + 本服务端口」 2. 审查范围用了错的分支做基准 resolveRange 优先取 managed_branch 而不是 job.base_ref, 于是 PR 被拿去和一个无关分支比较;当两者内容相同就报 「base and head resolve to the same commit」直接跳过。 改为优先用该改动实际要合入的分支(PR 的目标分支), 仅 push 事件回退到 managed_branch。 3. 检查分支的匹配语义反了 原先要求 PR 的 head(功能分支)出现在 check_branches 里, 而用户配置的是「要保护的目标分支」(如 prd/test), 功能分支永远不会被列出,所以 PR 一律被过滤掉。 新增 pullRequestMatches:PR 的 base 命中检查分支即审查, head 命中仍保留支持。这样「审查所有合入 prd/test 的改动」成立。 验证:PR offerpai/offerpai_h5#2 重跑通过,7 条内联评论、 2 条阻断、commit status failure、Issue #3 创建。
This commit is contained in:
+48
-6
@@ -150,6 +150,15 @@ const REPO_FIELDS = [
|
||||
|
||||
async function loadRepos() {
|
||||
state.repos = await api("/repos");
|
||||
// The webhook is what makes reviews fire at all, so its state belongs in the
|
||||
// repository row instead of hiding in Gitea's settings.
|
||||
await Promise.all(state.repos.map(async (r) => {
|
||||
try {
|
||||
r.webhook = await api(`/repos/${r.id}/webhook`);
|
||||
} catch (err) {
|
||||
r.webhook = { error: err.message };
|
||||
}
|
||||
}));
|
||||
const tbody = document.querySelector("#repos-table tbody");
|
||||
if (state.repos.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="empty">还没有配置仓库。点「从 Gitea 导入」批量选择,或「新增仓库」手动填 Git 地址。</td></tr>';
|
||||
@@ -160,6 +169,7 @@ async function loadRepos() {
|
||||
<td><code>${esc(r.managed_branch)}</code></td>
|
||||
<td>${branchTags(r.check_branches, r.managed_branch)}</td>
|
||||
<td>${esc(scopeLabel(r.review_scope))}</td>
|
||||
<td>${webhookCell(r)}</td>
|
||||
<td>${r.enabled ? '<span class="tag ok">启用</span>' : '<span class="tag muted">停用</span>'}</td>
|
||||
<td>${r.auto_merge ? `<span class="tag warn">${esc(r.merge_method)}</span>` : '<span class="tag muted">否</span>'}</td>
|
||||
<td><code>${esc(r.block_severity || "—")}</code></td>
|
||||
@@ -167,6 +177,7 @@ async function loadRepos() {
|
||||
<button data-edit="${r.id}">编辑</button>
|
||||
<button data-run="${r.id}">立即审查</button>
|
||||
<button data-records="${r.id}">历史</button>
|
||||
<button data-hook="${r.id}">修复 Webhook</button>
|
||||
<button class="danger" data-del="${r.id}">删除</button>
|
||||
</td>
|
||||
</tr>`).join("");
|
||||
@@ -174,6 +185,12 @@ async function loadRepos() {
|
||||
syncRecordFilter();
|
||||
}
|
||||
|
||||
function webhookCell(r) {
|
||||
if (r.webhook?.error) return `<span class="tag err" title="${esc(r.webhook.error)}">未知</span>`;
|
||||
if (r.webhook?.installed) return '<span class="tag ok">已配置</span>';
|
||||
return '<span class="tag err">未配置</span>';
|
||||
}
|
||||
|
||||
function shortUrl(url) {
|
||||
return String(url || "").replace(/^https?:\/\//, "");
|
||||
}
|
||||
@@ -205,6 +222,19 @@ document.querySelector("#repos-table").addEventListener("click", async (ev) => {
|
||||
document.querySelector('#record-filter').value = String(target.dataset.records);
|
||||
document.querySelector('.tab[data-tab="records"]').click();
|
||||
}
|
||||
if (target.dataset.hook) {
|
||||
target.disabled = true;
|
||||
const label = target.textContent;
|
||||
target.textContent = "处理中…";
|
||||
try {
|
||||
const res = await api(`/repos/${target.dataset.hook}/webhook`, { method: "POST" });
|
||||
setBanner(`Webhook ${res.created ? "已创建" : "已更新"}:${res.url}`, "ok");
|
||||
} finally {
|
||||
target.disabled = false;
|
||||
target.textContent = label;
|
||||
await loadRepos();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
handleError(err);
|
||||
}
|
||||
@@ -347,10 +377,21 @@ form.addEventListener("submit", async (ev) => {
|
||||
else if (input.value !== "") data[field] = input.value;
|
||||
}
|
||||
try {
|
||||
if (data.id) await api(`/repos/${data.id}`, { method: "PATCH", body: data });
|
||||
else await api("/repos", { method: "POST", body: data });
|
||||
modal.classList.add("hidden");
|
||||
setBanner("");
|
||||
if (data.id) {
|
||||
await api(`/repos/${data.id}`, { method: "PATCH", body: data });
|
||||
modal.classList.add("hidden");
|
||||
setBanner("");
|
||||
} else {
|
||||
const created = await api("/repos", { method: "POST", body: data });
|
||||
modal.classList.add("hidden");
|
||||
if (created.webhook?.error) {
|
||||
setBanner(`仓库已保存,但 Webhook 安装失败:${created.webhook.error}`);
|
||||
} else if (created.webhook) {
|
||||
setBanner(`仓库已保存,Webhook ${created.webhook.created ? "已创建" : "已更新"}。`, "ok");
|
||||
} else {
|
||||
setBanner("仓库已保存。");
|
||||
}
|
||||
}
|
||||
await loadRepos();
|
||||
} catch (err) {
|
||||
if (err.status === 401) return handleError(err);
|
||||
@@ -693,8 +734,9 @@ document.querySelector("#jobs-table").addEventListener("click", async (ev) => {
|
||||
/* -------------------------------- settings -------------------------------- */
|
||||
|
||||
const SETTINGS_FIELDS = [
|
||||
"giteaUrl", "giteaToken", "webhookSecret", "adminToken", "llmUrl", "llmToken",
|
||||
"llmModel", "llmProtocol", "llmAuthHeader", "llmExtraHeaders", "rulePath",
|
||||
"giteaUrl", "giteaToken", "webhookSecret", "adminToken", "webhookUrl",
|
||||
"llmUrl", "llmToken", "llmModel", "llmProtocol", "llmAuthHeader",
|
||||
"llmExtraHeaders", "rulePath",
|
||||
];
|
||||
|
||||
async function loadSettings() {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>仓库</th><th>管理分支</th><th>检查分支</th><th>范围</th>
|
||||
<th>启用</th><th>自动合并</th><th>阻断级别</th><th></th>
|
||||
<th>Webhook</th><th>启用</th><th>自动合并</th><th>阻断级别</th><th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
@@ -80,7 +80,8 @@
|
||||
<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>
|
||||
<p class="hint">Webhook 地址:<code id="hook-url"></code>(在仓库 Settings → Webhooks 中添加,密钥填上面这一项)</p>
|
||||
<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>
|
||||
@@ -184,7 +185,7 @@
|
||||
<button type="button" id="branch-create">克隆创建</button>
|
||||
</span>
|
||||
</label>
|
||||
<p class="hint">所有检查通过后只合并到这一个分支。</p>
|
||||
<p class="hint">所有检查通过后只合并到这一个分支;它也会出现在下面的检查分支里。</p>
|
||||
<label>检查分支(可多选)
|
||||
<span class="inline">
|
||||
<input name="check_branches" placeholder="main,release/1.0" />
|
||||
@@ -192,7 +193,7 @@
|
||||
</span>
|
||||
</label>
|
||||
<div id="branch-list" class="branch-list"></div>
|
||||
<p class="hint">这些分支都会被监控审查;只有指向管理分支的 PR 才会自动合并。</p>
|
||||
<p class="hint">填「被保护的目标分支」。任何合并进这些分支的 PR 都会审查(功能分支不用列出来);向这些分支推送也会审查。只有指向管理分支的 PR 才会自动合并。</p>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
|
||||
Reference in New Issue
Block a user