feat: add --comments-file to pr review for inline comments (task 0035)
`pr review <n>` gains `--comments-file <path>`, a JSON array of inline
comments submitted with the review. Each entry is one of two exclusive
shapes: a new comment `{ path, line, body }` (mapped to `new_position`,
always the new side) or a reply `{ reply_to, body }`. A reply carries no
line or side — gitea-axi finds the target via the reviews-plus-comments
fan-out (there is no get-comment-by-id endpoint), reconstructs its anchor
from the target's own `diff_hunk`, and infers old/new side from it, so a
same-line post threads with the existing conversation. All entries map
onto the review-submission payload's `comments[]`; no new HTTP layer is
added. An unknown `reply_to` is a VALIDATION_ERROR raised before the POST,
and the submitted inline-comment count rides the action block.
The shared path-resolve-and-read behind --body-file and --comments-file is
extracted into src/flag-file.ts.
This commit was merged in pull request #42.
This commit is contained in:
55
src/diff.ts
55
src/diff.ts
@@ -1,6 +1,6 @@
|
||||
import type { GiteaClient } from "./client.js";
|
||||
import type { RepoContext } from "./context.js";
|
||||
import { classifyHttpError } from "./errors.js";
|
||||
import { axiError, classifyHttpError } from "./errors.js";
|
||||
|
||||
/** The raw-diff truncation limit, distinct from the body/comment limits. */
|
||||
export const DIFF_TRUNCATE_LIMIT = 4000;
|
||||
@@ -73,3 +73,56 @@ export function trimDiffHunk(hunk: string): string {
|
||||
}
|
||||
return [lines[0], ...lines.slice(-2)].join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* The file-line anchor of a review comment, as the side-tagged position the
|
||||
* review-submission payload wants: exactly one of `new_position` (new side) or
|
||||
* `old_position` (old side) is set.
|
||||
*/
|
||||
export interface HunkAnchor {
|
||||
new_position?: number;
|
||||
old_position?: number;
|
||||
}
|
||||
|
||||
const HUNK_HEADER = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/;
|
||||
|
||||
/**
|
||||
* Reconstruct the file-line anchor of the line a review comment's `diff_hunk`
|
||||
* belongs to. Gitea builds a comment's `diff_hunk` so it ends at the commented
|
||||
* line, so the anchor is that last body line: starting from the `@@ -old +new @@`
|
||||
* header, walk the hunk tracking old- and new-file line numbers, and read the
|
||||
* last line off. An added (`+`) or context (` `) line anchors on the new side
|
||||
* (`new_position`); a deleted (`-`) line anchors on the old side (`old_position`).
|
||||
*
|
||||
* This lets a reply post a matching inline comment that threads with its target
|
||||
* without the caller supplying any line or side — Gitea joins comments on the
|
||||
* same line into one conversation.
|
||||
*/
|
||||
export function anchorFromDiffHunk(hunk: string): HunkAnchor {
|
||||
const lines = hunk.split("\n");
|
||||
const header = HUNK_HEADER.exec(lines[0] ?? "");
|
||||
if (header === null) {
|
||||
throw axiError(
|
||||
"Gitea returned a review comment whose diff_hunk has no @@ header to anchor a reply",
|
||||
"UNKNOWN",
|
||||
);
|
||||
}
|
||||
let oldLine = Number(header[1]);
|
||||
let newLine = Number(header[2]);
|
||||
// An empty body is degenerate; default to the header's new-side start.
|
||||
let anchor: HunkAnchor = { new_position: newLine };
|
||||
for (const line of lines.slice(1)) {
|
||||
if (line.startsWith("-")) {
|
||||
anchor = { old_position: oldLine };
|
||||
oldLine += 1;
|
||||
} else if (line.startsWith("+")) {
|
||||
anchor = { new_position: newLine };
|
||||
newLine += 1;
|
||||
} else {
|
||||
anchor = { new_position: newLine };
|
||||
oldLine += 1;
|
||||
newLine += 1;
|
||||
}
|
||||
}
|
||||
return anchor;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user