fix(robomp): thread commit_id/head_sha/patch through proxy backend
Follow-up fix round for 71d608aae6 (validate PR review comment
anchors against the diff before submitting). The validation path
broke in several places the original commit did not cover:
- GitHubBackend.submit_review gained no commit_id parameter, so
submitting through GitHubProxyClient raised a production
TypeError on a path the validation code depended on.
- PullRequestInfo lacked head_sha, so the Forgejo commit_id
fallback raised AttributeError; it is now parsed in
_pr_from_payload and carried through the proxy round-trip.
- _pr_file_from dropped the file patch, silently no-oping anchor
validation for anything routed through the proxy; the patch is
now forwarded.
- The hunk parser treated any +++/--- line as a file header,
desyncing line counters when added/removed content began with
those prefixes; file headers are now recognized only before
the first hunk.
- Reworded the comment to "Forgejo only" to match the actual
backend behavior.
Adds tests for forgejo commit_id fetch, fallback double-failure,
empty-patch fail-open, LEFT-side anchoring, proxy commit_id
validation, and file-creation hunk boundaries.
This commit is contained in:
@@ -100,6 +100,7 @@ class GitHubBackend(Protocol):
|
||||
body: str,
|
||||
event: str,
|
||||
comments: list[Mapping[str, Any]],
|
||||
commit_id: str | None = None,
|
||||
) -> PullRequestReviewInfo: ...
|
||||
|
||||
async def add_assignees(self, repo: str, number: int, assignees: list[str]) -> None: ...
|
||||
|
||||
@@ -69,6 +69,7 @@ class PullRequestInfo:
|
||||
head_repo: str = ""
|
||||
title: str = ""
|
||||
body: str = ""
|
||||
head_sha: str = ""
|
||||
|
||||
|
||||
@dataclass(slots=True, frozen=True)
|
||||
@@ -592,7 +593,7 @@ class GitHubClient:
|
||||
def _review_comments_payload(self, comments: list[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
||||
"""Adapt canonical host-tool comment shape to the wire schema for this platform.
|
||||
|
||||
GitHub keeps line/side/start_line/start_side; Forgejo/Gitea only reads
|
||||
GitHub keeps line/side/start_line/start_side; Forgejo only reads
|
||||
path/body/new_position (+old_position), so github-only keys are dropped
|
||||
and `line` is mapped to `new_position` for RIGHT-side comments or
|
||||
`old_position` for LEFT-side (removed-line) comments.
|
||||
@@ -798,6 +799,7 @@ def _pr_from_payload(repo: str, data: Mapping[str, Any]) -> PullRequestInfo:
|
||||
head_repo=str(head_repo.get("full_name") or "") if isinstance(head_repo, Mapping) else "",
|
||||
title=str(data.get("title") or ""),
|
||||
body=str(data.get("body") or ""),
|
||||
head_sha=str(head.get("sha") or "") if isinstance(head, Mapping) else "",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1760,9 +1760,15 @@ def _diff_anchorable_lines(patch: str) -> tuple[frozenset[int], frozenset[int]]:
|
||||
old_line = int(m.group(1))
|
||||
new_line = int(m.group(2))
|
||||
continue
|
||||
if raw.startswith("+++") or raw.startswith("---") or raw.startswith("\\"):
|
||||
if raw.startswith("\\"):
|
||||
continue
|
||||
if new_line is None or old_line is None:
|
||||
# `+++`/`---` are file headers only before the first hunk. Inside a
|
||||
# hunk a diff line's *content* may start with `++` (added) or `--`
|
||||
# (removed), and those lines must advance the counters.
|
||||
in_hunk = new_line is not None and old_line is not None
|
||||
if raw.startswith(("+++", "---")) and not in_hunk:
|
||||
continue
|
||||
if not in_hunk:
|
||||
continue
|
||||
if raw.startswith("+"):
|
||||
right.add(new_line)
|
||||
@@ -1778,9 +1784,7 @@ def _diff_anchorable_lines(patch: str) -> tuple[frozenset[int], frozenset[int]]:
|
||||
return frozenset(right), frozenset(left)
|
||||
|
||||
|
||||
def _filter_anchorable_comments(
|
||||
staged: list[Any], files: list[PullRequestFileInfo]
|
||||
) -> tuple[list[Any], list[Any]]:
|
||||
def _filter_anchorable_comments(staged: list[Any], files: list[PullRequestFileInfo]) -> tuple[list[Any], list[Any]]:
|
||||
"""Partition staged comments into (anchorable, dropped) by diff hunk membership."""
|
||||
by_path = {f.path: f for f in files}
|
||||
cache: dict[str, tuple[frozenset[int], frozenset[int]]] = {}
|
||||
|
||||
@@ -671,6 +671,8 @@ def create_proxy_app(settings: Settings) -> FastAPI:
|
||||
body = _require_str(data.get("body"), "body")
|
||||
event = str(data.get("event") or "COMMENT")
|
||||
comments = _require_review_comments(data.get("comments"))
|
||||
commit_id_raw = data.get("commit_id")
|
||||
commit_id = commit_id_raw if isinstance(commit_id_raw, str) and commit_id_raw else None
|
||||
github: GitHubClient = request.app.state.github
|
||||
try:
|
||||
review = await github.submit_pr_review(
|
||||
@@ -679,6 +681,7 @@ def create_proxy_app(settings: Settings) -> FastAPI:
|
||||
body=body,
|
||||
event=event,
|
||||
comments=comments,
|
||||
commit_id=commit_id,
|
||||
)
|
||||
except GitHubError as exc:
|
||||
return _gh_error_response(exc)
|
||||
|
||||
@@ -334,17 +334,21 @@ class GitHubProxyClient:
|
||||
body: str,
|
||||
event: str,
|
||||
comments: list[Mapping[str, Any]],
|
||||
commit_id: str | None = None,
|
||||
) -> PullRequestReviewInfo:
|
||||
json_body: dict[str, Any] = {
|
||||
"repo": repo,
|
||||
"pr_number": pr_number,
|
||||
"body": body,
|
||||
"event": event,
|
||||
"comments": comments,
|
||||
}
|
||||
if commit_id:
|
||||
json_body["commit_id"] = commit_id
|
||||
data = await self._request(
|
||||
"POST",
|
||||
"/gh/v1/submit_pr_review",
|
||||
json_body={
|
||||
"repo": repo,
|
||||
"pr_number": pr_number,
|
||||
"body": body,
|
||||
"event": event,
|
||||
"comments": comments,
|
||||
},
|
||||
json_body=json_body,
|
||||
)
|
||||
return _pr_review_from(data)
|
||||
|
||||
@@ -602,6 +606,7 @@ def _pr_file_from(data: Any) -> PullRequestFileInfo:
|
||||
status=str(data.get("status") or ""),
|
||||
additions=int(data.get("additions") or 0),
|
||||
deletions=int(data.get("deletions") or 0),
|
||||
patch=str(data.get("patch") or ""),
|
||||
)
|
||||
|
||||
|
||||
@@ -619,6 +624,7 @@ def _pr_from(data: Any) -> PullRequestInfo:
|
||||
head_repo=str(data.get("head_repo") or ""),
|
||||
title=str(data.get("title") or ""),
|
||||
body=str(data.get("body") or ""),
|
||||
head_sha=str(data.get("head_sha") or ""),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user