Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
@@ -0,0 +1,234 @@
#!/usr/bin/env python3
"""Fetch GitHub PR review comments and output JSON for insert_code_review_comments.
Requires: gh CLI (authenticated), git.
Must be run from within a git repository whose current branch has an open PR.
Prints JSON to stdout matching the insert_code_review_comments tool schema.
"""
import json
import os
import subprocess
import sys
from trim_diff_hunk import trim_diff_hunk, line_in_hunk, last_reachable_line
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def run_command(args, error_msg="Command failed"):
"""Run a command and return stdout. Exits on failure."""
result = subprocess.run(
args,
capture_output=True,
text=True,
encoding="utf-8",
env={**os.environ, "GH_PAGER": ""},
)
if result.returncode != 0:
print(f"{error_msg}: {result.stderr.strip()}", file=sys.stderr)
sys.exit(1)
return result.stdout
def run_gh_api(endpoint):
"""Run ``gh api --paginate`` and return a list of JSON objects.
Handles the case where ``gh`` outputs multiple concatenated JSON arrays
(one per page).
"""
text = run_command(
["gh", "api", endpoint, "--paginate"],
error_msg=f"gh api {endpoint} failed",
).strip()
if not text:
return []
items = []
decoder = json.JSONDecoder()
pos = 0
try:
while pos < len(text):
while pos < len(text) and text[pos] in " \t\n\r":
pos += 1
if pos >= len(text):
break
obj, end = decoder.raw_decode(text, pos)
items.extend(obj if isinstance(obj, list) else [obj])
pos = end
except json.JSONDecodeError as exc:
print(f"Failed to parse API response: {exc}", file=sys.stderr)
sys.exit(1)
return items
# ---------------------------------------------------------------------------
# Comment building
# ---------------------------------------------------------------------------
def _comment(cid, author, ts, body, url, location=None, reply_to=None):
"""Build a dict matching the insert_code_review_comments comment schema."""
c = {
"comment_id": cid,
"author": author,
"last_modified_timestamp": ts,
"comment_body": body,
"html_url": url,
}
if reply_to:
c["reply_metadata"] = {"parent_comment_id": reply_to}
elif location:
c["location_metadata"] = location
return c
def _resolve_line(hunk, line, original_line, side):
"""Pick the first line number that is reachable in the hunk on *side*.
Tries *line* first (current diff position), then *original_line*
(position when the comment was placed). If neither is reachable,
falls back to the last reachable line in the hunk on *side*.
Returns the resolved line number, or ``None`` if nothing is reachable.
"""
if line and line_in_hunk(hunk, line, side):
return line
if original_line and line_in_hunk(hunk, original_line, side):
return original_line
return last_reachable_line(hunk, side)
def _resolve_comment_line(comment, hunk):
"""Resolve validated (end_line, start_line, side) for a diff comment.
Uses ``side`` from the GitHub API as the authoritative diff side.
Returns ``(end_line, start_line | None, side)`` or ``None`` when the
comment cannot be attached to any line in the hunk.
"""
side = comment.get("side") or "RIGHT"
end_line = _resolve_line(
hunk,
comment.get("line"),
comment.get("original_line"),
side,
)
if end_line is None:
return None
raw_start = comment.get("start_line")
raw_original_start = comment.get("original_start_line")
if raw_start or raw_original_start:
start_line = _resolve_line(hunk, raw_start, raw_original_start, side)
if start_line is not None and start_line > end_line:
start_line = None
else:
start_line = None
return (end_line, start_line, side)
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
repo_root = run_command(
["git", "rev-parse", "--show-toplevel"],
"Not a git repository",
).strip()
pr = json.loads(
run_command(
[
"gh", "pr", "view",
"--json", "number,headRepository,headRepositoryOwner,baseRefName",
],
"Failed to get PR info (is there an open PR on this branch?)",
)
)
number = pr["number"]
owner = pr["headRepositoryOwner"]["login"]
repo = pr["headRepository"]["name"]
base = pr["baseRefName"]
api = f"/repos/{owner}/{repo}"
issue_comments = run_gh_api(f"{api}/issues/{number}/comments")
diff_comments = run_gh_api(f"{api}/pulls/{number}/comments")
reviews = run_gh_api(f"{api}/pulls/{number}/reviews")
comments = []
# -- Issue comments (PR-level, no location or reply metadata) -----------
for c in issue_comments:
comments.append(
_comment(
str(c["id"]),
c["user"]["login"] if c.get("user") else "[deleted]",
c["updated_at"],
c["body"],
c["html_url"],
)
)
# -- Diff comments (line-level, with location or reply) -----------------
for c in diff_comments:
cid = str(c["id"])
author = c["user"]["login"] if c.get("user") else "[deleted]"
ts = c["updated_at"]
body = c["body"]
url = c["html_url"]
reply_to_id = c.get("in_reply_to_id")
if reply_to_id:
comments.append(
_comment(cid, author, ts, body, url, reply_to=str(reply_to_id))
)
continue
hunk = c.get("diff_hunk", "")
resolved = _resolve_comment_line(c, hunk)
loc = {"filepath": c["path"]}
if resolved:
end_line, start_line, side = resolved
if hunk:
loc["diff_hunk"] = trim_diff_hunk(
hunk, end_line, side=side, start_line=start_line
)
loc["end_line"] = end_line
if start_line:
loc["start_line"] = start_line
loc["side"] = side
comments.append(_comment(cid, author, ts, body, url, location=loc))
# -- Reviews (PR-level, no location) ------------------------------------
for r in reviews:
if not r.get("body"):
continue
comments.append(
_comment(
str(r["id"]),
r["user"]["login"] if r.get("user") else "[deleted]",
r.get("submitted_at", ""),
r["body"],
r["html_url"],
)
)
result = {
"local_repository_path": repo_root,
"base_branch": base,
"comments": comments,
}
json.dump(result, sys.stdout, indent=2)
if __name__ == "__main__":
main()
@@ -0,0 +1,202 @@
"""Tests for _resolve_comment_line and _resolve_line (no network calls).
Uses synthetic GitHub API comment dicts and diff hunks to exercise the
fallback chain: line → original_line → last reachable line → None.
"""
import sys
import os
import unittest
sys.path.insert(0, os.path.dirname(__file__))
from fetch_github_review_comments import _resolve_comment_line, _resolve_line
# ---------------------------------------------------------------------------
# A small reusable hunk: new file lines 20-24, old file lines 10-14.
# ---------------------------------------------------------------------------
_CONTEXT_HUNK = "\n".join(
["@@ -10,5 +20,5 @@"] + [f" line {10 + i}" for i in range(5)]
)
def _gh_comment(**overrides):
"""Build a minimal GitHub-API-shaped comment dict."""
base = {
"side": "RIGHT",
"line": None,
"original_line": None,
"start_line": None,
"original_start_line": None,
}
base.update(overrides)
return base
# ---------------------------------------------------------------------------
# _resolve_line
# ---------------------------------------------------------------------------
class TestResolveLine(unittest.TestCase):
def test_line_matches(self):
assert _resolve_line(_CONTEXT_HUNK, 22, None, "RIGHT") == 22
def test_line_mismatches_original_matches(self):
assert _resolve_line(_CONTEXT_HUNK, 9999, 23, "RIGHT") == 23
def test_both_mismatch_falls_back_to_last_reachable(self):
# last new-file line in the hunk is 24
assert _resolve_line(_CONTEXT_HUNK, 9999, 8888, "RIGHT") == 24
def test_empty_hunk_returns_none(self):
assert _resolve_line("", 22, None, "RIGHT") is None
def test_none_candidates_falls_back_to_last_reachable(self):
assert _resolve_line(_CONTEXT_HUNK, None, None, "RIGHT") == 24
def test_left_side(self):
assert _resolve_line(_CONTEXT_HUNK, 12, None, "LEFT") == 12
# ---------------------------------------------------------------------------
# _resolve_comment_line
# ---------------------------------------------------------------------------
class TestResolveCommentLine(unittest.TestCase):
def test_line_matches_hunk(self):
c = _gh_comment(side="RIGHT", line=22)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
assert result == (22, None, "RIGHT")
def test_line_mismatches_original_matches(self):
c = _gh_comment(side="RIGHT", line=9999, original_line=23)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
assert result == (23, None, "RIGHT")
def test_both_mismatch_returns_last_reachable(self):
c = _gh_comment(side="RIGHT", line=9999, original_line=8888)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
# last reachable new-file line is 24
assert result == (24, None, "RIGHT")
def test_both_mismatch_no_reachable_lines_returns_none(self):
deletion_only = "@@ -5,3 +5,0 @@\n-del A\n-del B\n-del C"
c = _gh_comment(side="RIGHT", line=9999, original_line=8888)
result = _resolve_comment_line(c, deletion_only)
assert result is None
def test_empty_hunk_returns_none(self):
c = _gh_comment(side="RIGHT", line=22)
result = _resolve_comment_line(c, "")
assert result is None
def test_both_line_and_original_none_falls_back(self):
c = _gh_comment(side="RIGHT")
result = _resolve_comment_line(c, _CONTEXT_HUNK)
# No explicit line, but the hunk has reachable lines → last reachable
assert result == (24, None, "RIGHT")
def test_start_line_resolved(self):
c = _gh_comment(side="RIGHT", line=23, start_line=21)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
assert result == (23, 21, "RIGHT")
def test_start_line_falls_back_to_original(self):
c = _gh_comment(
side="RIGHT", line=23,
start_line=9999, original_start_line=20,
)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
assert result == (23, 20, "RIGHT")
def test_side_defaults_to_right(self):
c = _gh_comment(line=22)
c.pop("side")
result = _resolve_comment_line(c, _CONTEXT_HUNK)
assert result[2] == "RIGHT"
# -- Inverted range: start_line > end_line after fallback ----------------
def test_start_line_cleared_when_inverted(self):
"""Both start_line and original_start_line are stale.
_resolve_line falls back to last_reachable_line (24) for the start,
but end_line resolved to an earlier line (21). The fix must clear
start_line to None instead of returning an inverted range.
"""
c = _gh_comment(
side="RIGHT",
line=21,
start_line=9999,
original_start_line=8888,
)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
end_line, start_line, side = result
assert end_line == 21
assert start_line is None
assert side == "RIGHT"
def test_start_line_kept_when_not_inverted(self):
"""start_line resolves to a line before end_line — should be kept."""
c = _gh_comment(
side="RIGHT",
line=23,
start_line=21,
)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
assert result == (23, 21, "RIGHT")
def test_start_line_cleared_when_equal_to_end(self):
"""start_line == end_line is fine — only strictly greater is inverted."""
c = _gh_comment(
side="RIGHT",
line=22,
start_line=22,
)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
assert result == (22, 22, "RIGHT")
def test_start_fallback_exceeds_end_on_left_side(self):
"""Same inverted-range scenario but on the LEFT side."""
c = _gh_comment(
side="LEFT",
line=11,
start_line=9999,
original_start_line=8888,
)
result = _resolve_comment_line(c, _CONTEXT_HUNK)
end_line, start_line, side = result
assert end_line == 11
# last_reachable_line on LEFT = 14, which > 11 → cleared
assert start_line is None
assert side == "LEFT"
# -- Regression: PR #22932 outdated comment ----------------------------
def test_regression_outdated_comment_falls_back(self):
"""Comment 2898341466: line=2899 (repositioned), hunk +3066.
Neither line nor original_line is in the hunk, so we expect the
last reachable line (3071 in the original 6-body-line hunk).
"""
hunk = (
"@@ -2926,6 +3066,17 @@ fn render_response_footer\n"
" }\n"
" }\n"
" \n"
"+ // Bulk-import review comments button\n"
"+ // has any imported review comments.\n"
"+ if props.conversation_has_imported_comments {"
)
c = _gh_comment(side="RIGHT", line=2899, original_line=2899)
result = _resolve_comment_line(c, hunk)
assert result is not None
end_line, _start, side = result
assert side == "RIGHT"
# Should fall back to last reachable new-file line in the hunk body.
assert end_line == 3071
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,597 @@
"""Tests for trim_diff_hunk and _parse_hunk_header.
Reference: app/src/code_review/comments/diff_hunk_parser_tests.rs
"""
import sys
import os
import unittest
sys.path.insert(0, os.path.dirname(__file__))
from trim_diff_hunk import trim_diff_hunk, _parse_hunk_header, _prepare_lines, _annotate_hunk_body, line_in_hunk, last_reachable_line
# ---------------------------------------------------------------------------
# Helper to build large hunks for testing
# ---------------------------------------------------------------------------
def _make_context_hunk(old_start, new_start, count):
"""Build a pure-context hunk with ``count`` lines."""
header = f"@@ -{old_start},{count} +{new_start},{count} @@"
body = [f" line {old_start + i}" for i in range(count)]
return "\n".join([header] + body)
# ---------------------------------------------------------------------------
# _prepare_lines
# ---------------------------------------------------------------------------
class TestPrepareLines(unittest.TestCase):
def test_splits_and_strips_trailing_empties(self):
assert _prepare_lines("a\nb\nc\n\n") == ["a", "b", "c"]
def test_no_trailing_empties(self):
assert _prepare_lines("a\nb") == ["a", "b"]
def test_single_line(self):
assert _prepare_lines("@@ -1,1 +1,1 @@") == ["@@ -1,1 +1,1 @@"]
def test_empty_string(self):
assert _prepare_lines("") == []
# ---------------------------------------------------------------------------
# _parse_hunk_header
# ---------------------------------------------------------------------------
class TestParseHunkHeader(unittest.TestCase):
def test_standard_header(self):
assert _parse_hunk_header("@@ -10,5 +20,7 @@") == (10, 5, 20, 7, "")
def test_header_with_context_text(self):
result = _parse_hunk_header("@@ -10,5 +20,7 @@ fn main()")
assert result == (10, 5, 20, 7, " fn main()")
def test_omitted_counts_default_to_one(self):
assert _parse_hunk_header("@@ -10 +20 @@") == (10, 1, 20, 1, "")
def test_invalid_headers(self):
assert _parse_hunk_header("not a header") is None
assert _parse_hunk_header("@@ invalid @@") is None
assert _parse_hunk_header("") is None
def test_only_old_count_omitted(self):
assert _parse_hunk_header("@@ -10 +20,3 @@") == (10, 1, 20, 3, "")
def test_only_new_count_omitted(self):
assert _parse_hunk_header("@@ -10,3 +20 @@") == (10, 3, 20, 1, "")
# ---------------------------------------------------------------------------
# trim_diff_hunk basic / passthrough cases
# ---------------------------------------------------------------------------
class TestTrimPassthrough(unittest.TestCase):
"""Cases where trim_diff_hunk should return the input unchanged."""
def test_empty_string(self):
assert trim_diff_hunk("", 1) == ""
def test_none_input(self):
assert trim_diff_hunk(None, 1) is None
def test_small_hunk_unchanged(self):
hunk = "@@ -1,2 +1,3 @@\n first line\n+added line\n last line"
assert trim_diff_hunk(hunk, 2, context_lines=3) == hunk
def test_invalid_header_unchanged(self):
hunk = "not a valid header\n+line1\n+line2\n+line3\n+line4\n+line5"
assert trim_diff_hunk(hunk, 1) == hunk
def test_target_not_found_unchanged(self):
hunk = _make_context_hunk(1, 1, 20)
assert trim_diff_hunk(hunk, 999) == hunk
# ---------------------------------------------------------------------------
# trim_diff_hunk trimming behaviour
# ---------------------------------------------------------------------------
class TestTrimBehaviour(unittest.TestCase):
def test_trims_to_context_window(self):
"""Large hunk trimmed to ±3 lines around target."""
hunk = _make_context_hunk(1, 1, 20)
result = trim_diff_hunk(hunk, 10, context_lines=3)
result_lines = result.split("\n")
# Target ± 3 → lines 7-13 = 7 body lines + header
assert result_lines[0] == "@@ -7,7 +7,7 @@"
assert " line 7" in result
assert " line 10" in result
assert " line 13" in result
assert " line 6" not in result
assert " line 14" not in result
def test_target_at_beginning(self):
hunk = _make_context_hunk(1, 1, 20)
result = trim_diff_hunk(hunk, 1, context_lines=3)
result_lines = result.split("\n")
# Can't go before line 1 → lo=0, hi=3
assert result_lines[0] == "@@ -1,4 +1,4 @@"
assert " line 1" in result
assert " line 4" in result
assert " line 5" not in result
def test_target_at_end(self):
hunk = _make_context_hunk(1, 1, 20)
result = trim_diff_hunk(hunk, 20, context_lines=3)
result_lines = result.split("\n")
assert result_lines[0] == "@@ -17,4 +17,4 @@"
assert " line 17" in result
assert " line 20" in result
assert " line 16" not in result
def test_preserves_whitespace(self):
"""Mirrors Rust test_parse_preserves_whitespace."""
lines = ["@@ -1,15 +1,16 @@"]
for i in range(1, 8):
lines.append(f" line {i}")
lines.append("+ heavily indented") # new line 8
for i in range(8, 16):
lines.append(f" line {i}")
hunk = "\n".join(lines)
result = trim_diff_hunk(hunk, 8, side="RIGHT")
assert "+ heavily indented" in result
def test_preserves_header_context_text(self):
lines = ["@@ -1,20 +1,20 @@ fn example()"]
for i in range(1, 21):
lines.append(f" line {i}")
hunk = "\n".join(lines)
result = trim_diff_hunk(hunk, 10)
assert result.split("\n")[0].endswith(" fn example()")
def test_trailing_empty_lines_stripped(self):
hunk = _make_context_hunk(1, 1, 20) + "\n\n\n"
result = trim_diff_hunk(hunk, 10)
assert not result.endswith("\n")
# ---------------------------------------------------------------------------
# trim_diff_hunk LEFT / RIGHT side targeting
# ---------------------------------------------------------------------------
class TestSideTargeting(unittest.TestCase):
def test_right_side_skips_deletions(self):
"""RIGHT side tracks new-file line numbers; deletions are invisible."""
lines = ["@@ -1,12 +1,14 @@"]
for i in range(1, 4):
lines.append(f" line {i}") # old 1-3, new 1-3
lines.append("-deleted A") # old 4
lines.append("-deleted B") # old 5
lines.append("+added A") # new 4
lines.append("+added B") # new 5
lines.append("+added C") # new 6
lines.append("+added D") # new 7
for i in range(6, 14):
lines.append(f" line {i}") # old 6-13, new 8-15
hunk = "\n".join(lines)
result = trim_diff_hunk(hunk, 6, side="RIGHT")
assert "+added C" in result
def test_left_side_skips_additions(self):
"""LEFT side tracks old-file line numbers; additions are invisible."""
lines = ["@@ -10,15 +10,16 @@"]
for i in range(15):
if i == 7:
lines.append("+added line") # new-only, no old num
lines.append(f" context {i}")
hunk = "\n".join(lines)
# old line 15 = old_start(10) + 5 context lines → " context 5"
result = trim_diff_hunk(hunk, 15, side="LEFT")
assert " context 5" in result
def test_left_targets_deletion(self):
"""Targeting a deleted line by old-file number."""
lines = ["@@ -1,12 +1,10 @@"]
for i in range(1, 5):
lines.append(f" line {i}")
lines.append("-removed A") # old 5
lines.append("-removed B") # old 6
for i in range(5, 13):
lines.append(f" line {i}")
hunk = "\n".join(lines)
result = trim_diff_hunk(hunk, 5, side="LEFT")
assert "-removed A" in result
# ---------------------------------------------------------------------------
# trim_diff_hunk multi-line comment ranges
# ---------------------------------------------------------------------------
class TestMultilineRange(unittest.TestCase):
def test_keeps_full_range_plus_context(self):
hunk = _make_context_hunk(1, 1, 20)
# start_line=8, target=12, context=3 → window is [8-3, 12+3] = [5, 15]
result = trim_diff_hunk(hunk, 12, start_line=8, context_lines=3)
result_lines = result.split("\n")
assert result_lines[0] == "@@ -5,11 +5,11 @@"
assert " line 5" in result
assert " line 8" in result
assert " line 12" in result
assert " line 15" in result
assert " line 4" not in result
assert " line 16" not in result
def test_range_start_at_hunk_boundary(self):
hunk = _make_context_hunk(1, 1, 20)
# start_line=1, context=3 → lo clamped to 0
result = trim_diff_hunk(hunk, 5, start_line=1, context_lines=3)
assert " line 1" in result
assert " line 8" in result
# ---------------------------------------------------------------------------
# trim_diff_hunk pure additions / pure deletions
# ---------------------------------------------------------------------------
class TestPureAdditionsAndDeletions(unittest.TestCase):
def test_only_additions(self):
"""Old-file count should be 0 in trimmed header."""
lines = ["@@ -5,0 +5,15 @@"]
for i in range(5, 20):
lines.append(f"+new line {i}")
hunk = "\n".join(lines)
result = trim_diff_hunk(hunk, 10, side="RIGHT")
header = result.split("\n")[0]
# All trimmed lines are additions → old count = 0
assert ",0 +" in header
assert "+new line 10" in result
def test_only_deletions(self):
"""New-file count should be 0 in trimmed header."""
lines = ["@@ -5,15 +5,0 @@"]
for i in range(5, 20):
lines.append(f"-old line {i}")
hunk = "\n".join(lines)
result = trim_diff_hunk(hunk, 10, side="LEFT")
header = result.split("\n")[0]
assert "+5,0 @@" in header
assert "-old line 10" in result
# ---------------------------------------------------------------------------
# trim_diff_hunk special markers
# ---------------------------------------------------------------------------
class TestSpecialMarkers(unittest.TestCase):
def test_no_newline_marker_does_not_shift_line_numbers(self):
r"""'\ No newline at end of file' must not affect line counting."""
lines = ["@@ -1,15 +1,16 @@"]
for i in range(1, 8):
lines.append(f" line {i}")
lines.append("+added line") # new line 8
lines.append("\\ No newline at end of file")
for i in range(8, 16):
lines.append(f" line {i}")
hunk = "\n".join(lines)
# Use context=3 so the marker is within the trim window
result = trim_diff_hunk(hunk, 8, side="RIGHT", context_lines=3)
assert "+added line" in result
def test_no_newline_marker_excluded_from_counts(self):
r"""The marker should not inflate old/new counts in the header."""
lines = ["@@ -1,15 +1,16 @@"]
for i in range(1, 8):
lines.append(f" line {i}")
lines.append("+added line") # new line 8
lines.append("\\ No newline at end of file")
for i in range(8, 16):
lines.append(f" line {i}")
hunk = "\n".join(lines)
# Use context=3 so the marker is within the trim window
result = trim_diff_hunk(hunk, 8, side="RIGHT", context_lines=3)
header = result.split("\n")[0]
parsed = _parse_hunk_header(header)
old_count, new_count = parsed[1], parsed[3]
# Count body lines manually to verify
body = result.split("\n")[1:]
expected_old = sum(
1 for l in body if l and l[0] not in ("+", "\\")
)
expected_new = sum(
1 for l in body if l and l[0] not in ("-", "\\")
)
assert old_count == expected_old
assert new_count == expected_new
# ---------------------------------------------------------------------------
# trim_diff_hunk zero context (default)
# ---------------------------------------------------------------------------
class TestZeroContext(unittest.TestCase):
"""With context_lines=0, only the exact commented line(s) are kept."""
def test_isolates_single_context_line(self):
hunk = _make_context_hunk(1, 1, 20)
result = trim_diff_hunk(hunk, 10)
result_lines = result.split("\n")
assert result_lines[0] == "@@ -10,1 +10,1 @@"
assert len(result_lines) == 2
assert " line 10" in result
def test_isolates_addition(self):
lines = ["@@ -1,5 +1,6 @@"]
for i in range(1, 4):
lines.append(f" line {i}")
lines.append("+added line") # new line 4
for i in range(4, 6):
lines.append(f" line {i}")
hunk = "\n".join(lines)
result = trim_diff_hunk(hunk, 4, side="RIGHT")
result_lines = result.split("\n")
assert len(result_lines) == 2
assert "+added line" in result
header = _parse_hunk_header(result_lines[0])
assert header[1] == 0 # old count = 0
assert header[3] == 1 # new count = 1
def test_isolates_deletion(self):
lines = ["@@ -1,6 +1,5 @@"]
for i in range(1, 4):
lines.append(f" line {i}")
lines.append("-deleted line") # old line 4
for i in range(5, 7):
lines.append(f" line {i}")
hunk = "\n".join(lines)
result = trim_diff_hunk(hunk, 4, side="LEFT")
result_lines = result.split("\n")
assert len(result_lines) == 2
assert "-deleted line" in result
header = _parse_hunk_header(result_lines[0])
assert header[1] == 1 # old count = 1
assert header[3] == 0 # new count = 0
def test_multiline_range_no_context(self):
hunk = _make_context_hunk(1, 1, 20)
result = trim_diff_hunk(hunk, 12, start_line=8)
result_lines = result.split("\n")
assert result_lines[0] == "@@ -8,5 +8,5 @@"
assert " line 8" in result
assert " line 12" in result
assert " line 7" not in result
assert " line 13" not in result
# ---------------------------------------------------------------------------
# line_in_hunk
# ---------------------------------------------------------------------------
class TestLineInHunk(unittest.TestCase):
def test_target_within_range(self):
hunk = _make_context_hunk(10, 20, 5)
assert line_in_hunk(hunk, 22, side="RIGHT") is True
def test_target_within_range_left(self):
hunk = _make_context_hunk(10, 20, 5)
assert line_in_hunk(hunk, 12, side="LEFT") is True
def test_target_past_end(self):
hunk = _make_context_hunk(10, 20, 5) # new 20-24
assert line_in_hunk(hunk, 30, side="RIGHT") is False
def test_target_before_start(self):
hunk = _make_context_hunk(10, 20, 5) # new 20-24
assert line_in_hunk(hunk, 5, side="RIGHT") is False
def test_right_target_in_deletion_only_hunk(self):
"""A hunk with only deletions has no reachable RIGHT lines."""
hunk = "@@ -5,3 +5,0 @@\n-del A\n-del B\n-del C"
assert line_in_hunk(hunk, 5, side="RIGHT") is False
def test_empty_hunk(self):
assert line_in_hunk("", 1) is False
def test_none_hunk(self):
assert line_in_hunk(None, 1) is False
def test_target_past_hunk_end(self):
"""Comment 2892844016: target 11078, hunk new 10816-10822."""
hunk = (
"@@ -10808,9 +10816,7 @@ impl Workspace {\n"
" comment,\n"
" diff_mode,\n"
" } => {\n"
"- if !pane_group.as_ref(ctx).right_panel_open {\n"
"- self.open_code_review_panel_from_arg(open_code_review, pane_group.clone(), ctx);\n"
"- }\n"
"+ self.open_code_review_panel_from_arg(open_code_review, pane_group.clone(), ctx);"
)
assert line_in_hunk(hunk, 11078, side="RIGHT") is False
def test_target_before_hunk_start(self):
"""Comment 2898341466: target 2899, hunk new 3066-3082."""
hunk = (
"@@ -2926,6 +3066,17 @@ fn render_response_footer(props: Props, app: &AppContext) -> Option<Box<dyn Elem\n"
" }\n"
" }\n"
" \n"
"+ // Bulk-import review comments button, shown on the latest exchange when the conversation\n"
"+ // has any imported review comments.\n"
"+ if props.conversation_has_imported_comments && !props.shared_session_status.is_viewer() {"
)
assert line_in_hunk(hunk, 2899, side="RIGHT") is False
def test_truncated_hunk(self):
"""Comment 2954102612: header claims +4770,109 but body has only 5 lines.
Simulates a truncated hunk where the target (4918) is past the body.
"""
lines = ["@@ -4769,6 +4770,109 @@ impl AIBlock {"]
for i in range(4770, 4775):
lines.append(f"+ line {i}")
hunk = "\n".join(lines)
assert line_in_hunk(hunk, 4918, side="RIGHT") is False
# ---------------------------------------------------------------------------
# last_reachable_line
# ---------------------------------------------------------------------------
class TestLastReachableLine(unittest.TestCase):
def test_context_hunk(self):
hunk = _make_context_hunk(10, 20, 5) # new 20-24
assert last_reachable_line(hunk, side="RIGHT") == 24
def test_addition_hunk(self):
hunk = "@@ -5,0 +5,3 @@\n+new A\n+new B\n+new C"
assert last_reachable_line(hunk, side="RIGHT") == 7
def test_deletion_only_right(self):
hunk = "@@ -5,3 +5,0 @@\n-del A\n-del B\n-del C"
assert last_reachable_line(hunk, side="RIGHT") is None
def test_deletion_only_left(self):
hunk = "@@ -5,3 +5,0 @@\n-del A\n-del B\n-del C"
assert last_reachable_line(hunk, side="LEFT") == 7
def test_empty_hunk(self):
assert last_reachable_line("", side="RIGHT") is None
def test_none_hunk(self):
assert last_reachable_line(None, side="RIGHT") is None
# ---------------------------------------------------------------------------
# trim_diff_hunk multi-hunk inputs
# ---------------------------------------------------------------------------
class TestMultiHunk(unittest.TestCase):
"""trim_diff_hunk should find and trim the correct sub-hunk."""
_MULTI = (
"@@ -1,3 +1,4 @@ fn foo()\n"
" ctx1\n"
"+added_early\n"
" ctx2\n"
" ctx3\n"
"@@ -100,3 +101,4 @@ fn bar()\n"
" ctx100\n"
"+added_late\n"
" ctx101\n"
" ctx102"
)
def test_target_in_second_hunk(self):
"""Target 102 (new-file line in second sub-hunk) is found and trimmed."""
result = trim_diff_hunk(self._MULTI, 102, side="RIGHT")
assert "+added_late" in result
# The result should NOT contain the first hunk's content.
assert "+added_early" not in result
header = _parse_hunk_header(result.split("\n")[0])
assert header is not None
# new_start should be from the second hunk's range (101+)
assert header[2] == 102 # trimmed to just line 102
def test_target_in_first_hunk(self):
result = trim_diff_hunk(self._MULTI, 2, side="RIGHT")
assert "+added_early" in result
assert "+added_late" not in result
def test_target_in_no_hunk(self):
result = trim_diff_hunk(self._MULTI, 9999, side="RIGHT")
assert result == self._MULTI
def test_multi_hunk_line_in_hunk(self):
"""line_in_hunk should search across all sub-hunks."""
assert line_in_hunk(self._MULTI, 102, side="RIGHT") is True
assert line_in_hunk(self._MULTI, 2, side="RIGHT") is True
assert line_in_hunk(self._MULTI, 9999, side="RIGHT") is False
# ---------------------------------------------------------------------------
# Edge case: markdown list items in pure-addition hunks
# ---------------------------------------------------------------------------
class TestMarkdownListItem(unittest.TestCase):
r"""Hunks from where commented lines are markdown list items.
The line `+- \`specs/<issue-number>/TECH.md\`` starts with `+-`. The `-`
is a markdown list marker, NOT a diff deletion prefix.
"""
_HUNK = (
"@@ -0,0 +1,116 @@\n"
"+---\n"
"+name: write-tech-spec\n"
"+description: desc\n"
"+---\n"
"+\n"
"+# write-tech-spec\n"
"+\n"
"+Write a spec.\n"
"+\n"
"+## Overview\n"
"+\n"
"+The tech spec overview.\n"
"+\n"
"+Write specs into source control under:\n"
"+\n"
"+- `specs/<issue-number>/TECH.md`"
)
def test_annotate_hunk_body_classifies_plus_dash_as_addition(self):
"""_annotate_hunk_body must treat `+-` lines as additions."""
body = _prepare_lines(self._HUNK)[1:] # skip header
annotated = _annotate_hunk_body(body, 0, 1)
# Line 16 (new-file) should be the markdown list item.
target_entry = annotated[15] # 0-indexed
old_num, new_num, text = target_entry
assert old_num is None, f"Expected no old-file line number, got {old_num}"
assert new_num == 16, f"Expected new-file line 16, got {new_num}"
assert text == "+- `specs/<issue-number>/TECH.md`"
def test_trim_preserves_plus_dash_line(self):
"""trim_diff_hunk must preserve the full `+-` line text."""
result = trim_diff_hunk(self._HUNK, 16, side="RIGHT")
assert "+- `specs/<issue-number>/TECH.md`" in result
def test_trim_header_counts_plus_dash_as_new(self):
"""Trimmed header must count the `+-` line as a new-file line."""
result = trim_diff_hunk(self._HUNK, 16, side="RIGHT")
header = _parse_hunk_header(result.split("\n")[0])
assert header is not None
old_count, new_count = header[1], header[3]
# A single addition line: old_count=0, new_count=1
assert old_count == 0, f"Expected old_count=0, got {old_count}"
assert new_count == 1, f"Expected new_count=1, got {new_count}"
def test_line_in_hunk_finds_plus_dash_line(self):
"""line_in_hunk must locate line 16 on the RIGHT side."""
assert line_in_hunk(self._HUNK, 16, side="RIGHT") is True
def test_last_reachable_line_includes_plus_dash(self):
"""last_reachable_line must reach line 16 on the RIGHT side."""
assert last_reachable_line(self._HUNK, side="RIGHT") == 16
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,212 @@
"""Diff-hunk trimming for unified diffs.
Mirrors the logic in app/src/code_review/comments/diff_hunk_parser.rs:
walk hunk lines tracking old/new file line numbers, locate the target line,
then trim unneeded lines from the start and end (never the middle) and
rewrite the hunk header to match the trimmed window.
"""
import re
_HUNK_HEADER_RE = re.compile(
r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$"
)
def _parse_hunk_header(line):
m = _HUNK_HEADER_RE.match(line)
if not m:
return None
return (
int(m.group(1)),
int(m.group(2)) if m.group(2) is not None else 1,
int(m.group(3)),
int(m.group(4)) if m.group(4) is not None else 1,
m.group(5),
)
def _annotate_hunk_body(body_lines, old_start, new_start):
"""Annotate *body_lines* with ``(old_num | None, new_num | None, text)``."""
old_num, new_num = old_start, new_start
annotated = []
for text in body_lines:
ch = text[0] if text else " "
if ch == "+":
annotated.append((None, new_num, text))
new_num += 1
elif ch == "-":
annotated.append((old_num, None, text))
old_num += 1
elif ch == "\\":
annotated.append((None, None, text))
else:
annotated.append((old_num, new_num, text))
old_num += 1
new_num += 1
return annotated
def _split_hunks(lines):
"""Split *lines* (with trailing empties already stripped) into sub-hunks.
Returns a list of ``(header_tuple, header_text, body_lines)`` where
*header_tuple* is the parsed ``(old_start, old_count, new_start, new_count,
ctx)`` and *body_lines* are the raw diff body strings.
"""
hunks = []
current_header = None
current_header_text = None
current_body = []
for line in lines:
parsed = _parse_hunk_header(line)
if parsed is not None:
if current_header is not None:
hunks.append((current_header, current_header_text, current_body))
current_header = parsed
current_header_text = line
current_body = []
else:
current_body.append(line)
if current_header is not None:
hunks.append((current_header, current_header_text, current_body))
return hunks
def _find_target_idx(annotated, target_line, use_new):
"""Return the index into *annotated* where the target line lives, or None."""
for i, (o, n, _) in enumerate(annotated):
num = n if use_new else o
if num is not None and num == target_line:
return i
return None
# ---------------------------------------------------------------------------
# Public helpers used by fetch_github_review_comments.py
# ---------------------------------------------------------------------------
def _prepare_lines(diff_hunk):
"""Split *diff_hunk* into lines and strip trailing empty strings."""
lines = diff_hunk.split("\n")
while lines and lines[-1] == "":
lines.pop()
return lines
def line_in_hunk(diff_hunk, target_line, side="RIGHT"):
"""Return ``True`` if *target_line* is reachable on *side* of the hunk."""
if not diff_hunk:
return False
lines = _prepare_lines(diff_hunk)
use_new = side != "LEFT"
for header, _, body in _split_hunks(lines):
old_start, _, new_start, _, _ = header
annotated = _annotate_hunk_body(body, old_start, new_start)
if _find_target_idx(annotated, target_line, use_new) is not None:
return True
return False
def last_reachable_line(diff_hunk, side="RIGHT"):
"""Return the last line number reachable on *side*, or ``None``."""
if not diff_hunk:
return None
lines = _prepare_lines(diff_hunk)
use_new = side != "LEFT"
last = None
for header, _, body in _split_hunks(lines):
old_start, _, new_start, _, _ = header
for o, n, _ in _annotate_hunk_body(body, old_start, new_start):
num = n if use_new else o
if num is not None:
last = num
return last
# ---------------------------------------------------------------------------
# trim_diff_hunk
# ---------------------------------------------------------------------------
def trim_diff_hunk(diff_hunk, target_line, side="RIGHT", start_line=None, context_lines=0):
"""Return *diff_hunk* trimmed to ±*context_lines* around *target_line*.
The hunk header is rewritten so the line numbers stay correct.
If the hunk is already small enough, it is returned unchanged.
"""
if not diff_hunk:
return diff_hunk
lines = _prepare_lines(diff_hunk)
hunks = _split_hunks(lines)
if not hunks:
return diff_hunk
use_new = side != "LEFT"
# Find the sub-hunk that contains the target line.
for header, _header_text, body in hunks:
old_start, _, new_start, _, hdr_ctx = header
annotated = _annotate_hunk_body(body, old_start, new_start)
target_idx = _find_target_idx(annotated, target_line, use_new)
if target_idx is None:
continue
# Found the right sub-hunk — trim within it.
if len(annotated) <= context_lines * 2 + 1:
# Small enough already — return just this sub-hunk.
new_hdr = _rewrite_header(annotated, old_start, new_start, hdr_ctx)
return "\n".join([new_hdr] + [t for _, _, t in annotated])
range_start_idx = None
if start_line:
range_start_idx = _find_target_idx(annotated, start_line, use_new)
first = range_start_idx if range_start_idx is not None else target_idx
lo = max(0, first - context_lines)
hi = min(len(annotated) - 1, target_idx + context_lines)
trimmed = annotated[lo : hi + 1]
new_hdr = _rewrite_header(trimmed, old_start, new_start, hdr_ctx)
return "\n".join([new_hdr] + [t for _, _, t in trimmed])
# Target not found in any sub-hunk — return original unchanged.
return diff_hunk
def _rewrite_header(trimmed, old_start, new_start, hdr_ctx):
"""Build a unified diff header from a trimmed annotation window."""
t_os = t_ns = None
t_oc = t_nc = 0
for o, n, text in trimmed:
ch = text[0] if text else " "
if ch == "+":
t_nc += 1
if t_ns is None and n is not None:
t_ns = n
elif ch == "-":
t_oc += 1
if t_os is None and o is not None:
t_os = o
elif ch == "\\":
pass
else:
t_oc += 1
t_nc += 1
if t_os is None and o is not None:
t_os = o
if t_ns is None and n is not None:
t_ns = n
return f"@@ -{t_os or old_start},{t_oc} +{t_ns or new_start},{t_nc} @@{hdr_ctx}"