fix: dual-review round-2 — partial diffs cap the verdict, bounded fallback chain, URL-safe paths
Codex adversarial review of the fallback chain found 1 P1 + 3 P2:
- P1: head-only and incomplete per-commit fallbacks could present a SUBSET
diff as a full passing review. fetchDiff now returns {diff, partial, note};
review.js caps partial reviews at 'warn' (same contract as truncation) and
labels the status 'partial review (<reason>)'.
- getRepo default-branch lookup swallows only 404; auth/5xx propagate.
- per-commit reconstruction dedupes + validates shas (40-hex) and hard-caps
at 20 requests; capped/short coverage marks the result partial; the
all-sources-failed message is bounded to 1500 chars.
- gitea.js URL-encodes every webhook-supplied path segment (owner/repo/ref/
sha) — branch names with '/' or '#' can no longer distort request paths.
Tests: 61 pass (2 new: sha cap/dedupe, getRepo 401 propagation).
Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
+9
-5
@@ -1,5 +1,9 @@
|
||||
// Minimal Gitea API client (fetch-based, no deps).
|
||||
|
||||
// URL path segment from webhook/API-supplied values (owner, repo, refs,
|
||||
// shas): never trust them as URL-safe — branch names can carry '/', '#', '?'.
|
||||
const seg = (s) => encodeURIComponent(String(s));
|
||||
|
||||
export class GiteaClient {
|
||||
constructor({ baseUrl, token, timeoutMs = 30000 }) {
|
||||
this.baseUrl = baseUrl.replace(/\/$/, '');
|
||||
@@ -28,17 +32,17 @@ export class GiteaClient {
|
||||
|
||||
// PR diff
|
||||
getPrDiff(owner, repo, index) {
|
||||
return this.req('GET', `/api/v1/repos/${owner}/${repo}/pulls/${index}.diff`, { raw: true });
|
||||
return this.req('GET', `/api/v1/repos/${seg(owner)}/${seg(repo)}/pulls/${seg(index)}.diff`, { raw: true });
|
||||
}
|
||||
|
||||
// Single-commit diff
|
||||
getCommitDiff(owner, repo, sha) {
|
||||
return this.req('GET', `/api/v1/repos/${owner}/${repo}/git/commits/${sha}.diff`, { raw: true });
|
||||
return this.req('GET', `/api/v1/repos/${seg(owner)}/${seg(repo)}/git/commits/${seg(sha)}.diff`, { raw: true });
|
||||
}
|
||||
|
||||
// Compare (commit list between two shas)
|
||||
compare(owner, repo, before, after) {
|
||||
return this.req('GET', `/api/v1/repos/${owner}/${repo}/compare/${before}...${after}`);
|
||||
return this.req('GET', `/api/v1/repos/${seg(owner)}/${seg(repo)}/compare/${seg(before)}...${seg(after)}`);
|
||||
}
|
||||
|
||||
// Full-range diff for a push (base...head) in ONE request. Gitea 1.27's API
|
||||
@@ -48,12 +52,12 @@ export class GiteaClient {
|
||||
// answers 404 even for valid refs. Callers must be ready to fall back to
|
||||
// the API-based sources below (fetchDiff in review.js does).
|
||||
getCompareDiff(owner, repo, before, after) {
|
||||
return this.req('GET', `/${owner}/${repo}/compare/${before}...${after}.diff`, { raw: true });
|
||||
return this.req('GET', `/${seg(owner)}/${seg(repo)}/compare/${seg(before)}...${seg(after)}.diff`, { raw: true });
|
||||
}
|
||||
|
||||
// Repo metadata (used for default_branch when the job doesn't carry it)
|
||||
getRepo(owner, repo) {
|
||||
return this.req('GET', `/api/v1/repos/${owner}/${repo}`);
|
||||
return this.req('GET', `/api/v1/repos/${seg(owner)}/${seg(repo)}`);
|
||||
}
|
||||
|
||||
// Add a collaborator (requires owner/admin token). 204 on success.
|
||||
|
||||
Reference in New Issue
Block a user