fix: 所有问题都建 Issue、合并失败重试、push 与 PR 事件分流
1. 所有问题都建 Issue(原先只有阻断级才建) 建 Issue 的判据从 blocking 改为 findings:任何等级的问题都创建/更新 Issue, 等级体现在标题 [OCR][medium] 与标签 medium 上,阻断项在正文标注 「(阻断合并)」。低等级问题不再丢失,也不会挡住合并。 无任何发现时才关闭该分支的 Issue。 2. 合并失败自动重试 Gitea 在算完 PR 可合并性之前会返回 405 Please try again later, 原先直接放弃,晋级随机失败。现在对 405/409/5xx 按指数退避重试 4 次; 权限不足、真实冲突等永久失败立即放弃并往 PR 留言说明。 PR 已被合并(405 already merged)视为成功,幂等收尾。 3. push 与 PR 事件分流(本次新发现的 bug) 合并路径原先按「是否找到关联 PR」判断,于是一个残留的 test→prd PR 会让后续 push 被当成 PR 事件,走错分支并跳过晋级,日志还会给出 「PR targets prd, which is not a checked branch」这种与实际不符的原因。 现在按事件类型决定:trigger 以 pull_request 开头才走合并 PR 路径, push/manual 一律走晋级分支。关联 PR 仅用于评论归属。 验证: - 直接 push test → 建出 issue #4([OCR][medium],标签 code-review,medium) - 合并重试与幂等分支的判定表全部通过 - 事件分流判定表:push/manual → promote,pull_request.* → merge PR
This commit is contained in:
@@ -345,6 +345,44 @@ export class GiteaClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gitea answers 405 "Please try again later" while it is still computing a pull
|
||||
* request's mergeability, and 409 for transient lock contention. Both clear up
|
||||
* on their own, so a merge is retried on those before giving up.
|
||||
*/
|
||||
export function isTransientMergeError(err) {
|
||||
if (!err) return false;
|
||||
const msg = String(err.message || "");
|
||||
// Already merged is a final state, not something to retry.
|
||||
if (/already merged/i.test(msg)) return false;
|
||||
if (err.status === 405 || err.status === 409) return true;
|
||||
if (err.status === 500 || err.status === 502 || err.status === 503 || err.status === 504) return true;
|
||||
return /try again later|please try again|mergeability|not ready/i.test(msg);
|
||||
}
|
||||
|
||||
/** True when a merge failed only because the pull request is already merged. */
|
||||
export function isAlreadyMerged(err) {
|
||||
return /already merged/i.test(String(err?.message || ""));
|
||||
}
|
||||
|
||||
/** Retry `fn` while it throws a transient error, with exponential backoff. */
|
||||
export async function retryTransient(fn, { attempts = 4, baseDelayMs = 1500, onRetry } = {}) {
|
||||
let delay = baseDelayMs;
|
||||
let lastErr;
|
||||
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
lastErr = err;
|
||||
if (!isTransientMergeError(err) || attempt === attempts) throw err;
|
||||
onRetry?.(attempt, err);
|
||||
await new Promise((r) => setTimeout(r, delay));
|
||||
delay = Math.min(delay * 2, 12000);
|
||||
}
|
||||
}
|
||||
throw lastErr;
|
||||
}
|
||||
|
||||
/** Derive the repository web URL from an API base URL. */
|
||||
export function webBaseUrl(apiBaseUrl) {
|
||||
return normalizeBaseUrl(apiBaseUrl);
|
||||
|
||||
Reference in New Issue
Block a user