Skip to content

Commit cbeee32

Browse files
committed
fix deploy review file links
Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech>
1 parent 63296ff commit cbeee32

2 files changed

Lines changed: 340 additions & 12 deletions

File tree

deploy/coven-github/coven_github_adapter.py

Lines changed: 194 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from datetime import datetime, timezone
1212
from pathlib import Path
1313
from urllib.error import HTTPError
14+
from urllib.parse import quote
1415
from urllib.request import Request, urlopen
1516

1617

@@ -1263,13 +1264,14 @@ def publish_result_if_configured(task, result_path, token):
12631264

12641265
def publication_comment_body(task, result):
12651266
status = result.get("status") or "unknown"
1266-
summary = result.get("summary") or "No summary returned."
1267-
pr_body = result.get("pr_body") or ""
12681267
files_changed = result.get("files_changed") or []
12691268
commits = result.get("commits") or []
12701269
task_id = task.get("task_id") or ""
12711270
evidence = task.get("review_evidence") or {}
12721271
review = result.get("review") or {}
1272+
known_files = github_known_file_set(task, review)
1273+
summary = link_github_file_mentions(task, result.get("summary") or "No summary returned.", known_files)
1274+
pr_body = link_github_file_mentions(task, result.get("pr_body") or "", known_files)
12731275

12741276
parts = [
12751277
"## Cody dogfood result",
@@ -1295,10 +1297,14 @@ def publication_comment_body(task, result):
12951297
]
12961298
)
12971299
if changed_files:
1298-
parts.append("- Files: {}".format(", ".join("`{}`".format(f) for f in changed_files[:20])))
1300+
parts.append(
1301+
"- Files: {}".format(
1302+
", ".join(github_file_markdown(task, f) for f in changed_files[:20])
1303+
)
1304+
)
12991305
else:
13001306
parts.append("- No PR review evidence was captured for this run.")
1301-
parts.extend(structured_review_lines(review))
1307+
parts.extend(structured_review_lines(review, task, known_files))
13021308
parts.extend(
13031309
[
13041310
"",
@@ -1311,6 +1317,179 @@ def publication_comment_body(task, result):
13111317
return "\n".join(parts)
13121318

13131319

1320+
def github_blob_base(task):
1321+
repository = str(task.get("repository") or "").strip()
1322+
if not re.match(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$", repository):
1323+
return None
1324+
evidence = task.get("review_evidence") or {}
1325+
ref = str(
1326+
evidence.get("head_sha")
1327+
or evidence.get("workspace_head_sha")
1328+
or task.get("default_branch")
1329+
or ""
1330+
).strip()
1331+
if not ref:
1332+
return None
1333+
return "https://github.com/{}/blob/{}".format(repository, quote(ref, safe="/"))
1334+
1335+
1336+
def parse_repo_relative_path(raw_path):
1337+
raw = str(raw_path)
1338+
display = raw.strip()
1339+
if not display or display != raw or re.search(r"[\s<>\[\]]", display):
1340+
return None
1341+
if display.startswith(("/", "\\")) or re.match(r"^[A-Za-z]:[\\/]", display):
1342+
return None
1343+
1344+
path = display.replace("\\", "/")
1345+
line = None
1346+
end_line = None
1347+
line_match = re.match(r"^(.*):(\d+)(?:-(\d+))?$", path)
1348+
if line_match:
1349+
path = line_match.group(1)
1350+
line = int(line_match.group(2))
1351+
end_line = int(line_match.group(3)) if line_match.group(3) else None
1352+
if re.match(r"^[A-Za-z][A-Za-z0-9+.-]*:", path):
1353+
return None
1354+
while path.startswith("./"):
1355+
path = path[2:]
1356+
1357+
segments = path.split("/")
1358+
filename = segments[-1] if segments else ""
1359+
if (
1360+
not path
1361+
or "//" in path
1362+
or any(not segment or segment in {".", ".."} for segment in segments)
1363+
or not re.search(r"\.[A-Za-z][A-Za-z0-9._-]{0,15}$", filename)
1364+
):
1365+
return None
1366+
return {"display": display, "path": path, "line": line, "end_line": end_line}
1367+
1368+
1369+
def github_file_markdown(task, raw_path, line_override=None):
1370+
target = parse_repo_relative_path(raw_path)
1371+
base = github_blob_base(task)
1372+
if not target or not base:
1373+
return "`{}`".format(raw_path)
1374+
encoded_path = "/".join(quote(segment, safe="") for segment in target["path"].split("/"))
1375+
line = line_override if line_override is not None else target["line"]
1376+
end_line = None if line_override is not None else target["end_line"]
1377+
if isinstance(line, int) and line > 0:
1378+
line_anchor = "#L{}".format(line)
1379+
if isinstance(end_line, int) and end_line > line:
1380+
line_anchor = "{}-L{}".format(line_anchor, end_line)
1381+
else:
1382+
line_anchor = ""
1383+
return "[`{}`]({}/{}{})".format(target["display"], base, encoded_path, line_anchor)
1384+
1385+
1386+
def github_known_file_set(task, review=None):
1387+
known_files = set()
1388+
evidence = task.get("review_evidence") or {}
1389+
review = review or {}
1390+
1391+
def add_files(value):
1392+
if not isinstance(value, list):
1393+
return
1394+
for item in value:
1395+
target = parse_repo_relative_path(str(item))
1396+
if target:
1397+
known_files.add(target["path"])
1398+
1399+
add_files(evidence.get("changed_files"))
1400+
add_files(review.get("reviewed_files"))
1401+
add_files(review.get("supporting_files"))
1402+
return known_files
1403+
1404+
1405+
def inline_code_with_github_file_links(task, raw_text):
1406+
raw_text = str(raw_text)
1407+
direct = github_file_markdown(task, raw_text)
1408+
if direct != "`{}`".format(raw_text):
1409+
return direct
1410+
1411+
linked_any = False
1412+
parts = []
1413+
for part in re.split(r"(\s+)", raw_text):
1414+
if not part or part.isspace():
1415+
parts.append(part)
1416+
continue
1417+
linked = github_file_markdown(task, part)
1418+
if linked != "`{}`".format(part):
1419+
linked_any = True
1420+
parts.append(linked)
1421+
else:
1422+
parts.append("`{}`".format(part))
1423+
1424+
return "".join(parts) if linked_any else "`{}`".format(raw_text)
1425+
1426+
1427+
def bare_file_mention_allowed(task, raw_path, known_files):
1428+
target = parse_repo_relative_path(raw_path)
1429+
if not target:
1430+
return False
1431+
return "/" in target["path"] or target["path"] in known_files
1432+
1433+
1434+
def link_bare_github_file_mentions(task, text, known_files):
1435+
markdown_link_pattern = re.compile(r"(\[[^\]]+\]\([^)]+\))")
1436+
bare_path_pattern = re.compile(
1437+
r"(^|[^\w./:\[\]()`-])"
1438+
r"([A-Za-z0-9_.-]+(?:/[A-Za-z0-9_.-]+)*\.[A-Za-z][A-Za-z0-9_-]{0,15}(?::\d+(?:-\d+)?)?)"
1439+
r"(?=$|[^\w/-])"
1440+
)
1441+
1442+
def replace_segment(segment):
1443+
def replace_match(match):
1444+
prefix = match.group(1)
1445+
raw_path = match.group(2)
1446+
if not bare_file_mention_allowed(task, raw_path, known_files):
1447+
return match.group(0)
1448+
linked = github_file_markdown(task, raw_path)
1449+
return match.group(0) if linked == "`{}`".format(raw_path) else "{}{}".format(prefix, linked)
1450+
1451+
return bare_path_pattern.sub(replace_match, segment)
1452+
1453+
return "".join(
1454+
segment if index % 2 == 1 else replace_segment(segment)
1455+
for index, segment in enumerate(markdown_link_pattern.split(str(text)))
1456+
)
1457+
1458+
1459+
def link_github_file_mentions(task, text, known_files=None):
1460+
known_files = github_known_file_set(task) if known_files is None else known_files
1461+
fence_pattern = re.compile(r"(```[\s\S]*?```)")
1462+
segments = fence_pattern.split(str(text))
1463+
1464+
def replace_segment(segment):
1465+
def replace_match(match):
1466+
raw_path = match.group(1)
1467+
already_link_text = (
1468+
match.start() > 0
1469+
and segment[match.start() - 1] == "["
1470+
and segment[match.end() : match.end() + 2] == "]("
1471+
)
1472+
if already_link_text:
1473+
return match.group(0)
1474+
linked = inline_code_with_github_file_links(task, raw_path)
1475+
return match.group(0) if linked == "`{}`".format(raw_path) else linked
1476+
1477+
linked_inline_code = re.sub(r"`([^`\n]+)`", replace_match, segment)
1478+
return link_bare_github_file_mentions(task, linked_inline_code, known_files)
1479+
1480+
return "".join(
1481+
segment if index % 2 == 1 else replace_segment(segment)
1482+
for index, segment in enumerate(segments)
1483+
)
1484+
1485+
1486+
def review_test_command_markdown(task, raw_command):
1487+
command = str(raw_command).strip()
1488+
if not command:
1489+
return "`unknown command`"
1490+
return inline_code_with_github_file_links(task, command)
1491+
1492+
13141493
def review_fix_loop_lines(task):
13151494
loops = task.get("review_fix_loops") or []
13161495
if not loops:
@@ -1335,7 +1514,8 @@ def review_fix_loop_lines(task):
13351514
return lines
13361515

13371516

1338-
def structured_review_lines(review):
1517+
def structured_review_lines(review, task, known_files=None):
1518+
known_files = github_known_file_set(task, review) if known_files is None else known_files
13391519
if not review:
13401520
return ["", "### Structured review", "- No structured review result was emitted."]
13411521

@@ -1351,7 +1531,7 @@ def structured_review_lines(review):
13511531
if reviewed_files:
13521532
lines.append(
13531533
"- Reviewed file list: {}".format(
1354-
", ".join("`{}`".format(path) for path in reviewed_files[:20])
1534+
", ".join(github_file_markdown(task, path) for path in reviewed_files[:20])
13551535
)
13561536
)
13571537
if len(reviewed_files) > 20:
@@ -1362,7 +1542,7 @@ def structured_review_lines(review):
13621542
if supporting_files:
13631543
lines.append(
13641544
"- Supporting file list: {}".format(
1365-
", ".join("`{}`".format(path) for path in supporting_files[:20])
1545+
", ".join(github_file_markdown(task, path) for path in supporting_files[:20])
13661546
)
13671547
)
13681548
if len(supporting_files) > 20:
@@ -1374,11 +1554,12 @@ def structured_review_lines(review):
13741554
location = finding.get("file") or "unknown file"
13751555
if finding.get("line") is not None:
13761556
location = "{}:{}".format(location, finding.get("line"))
1557+
linked_location = github_file_markdown(task, location, finding.get("line"))
13771558
lines.append(
13781559
" {}. `{}` {} - {}".format(
13791560
index,
13801561
finding.get("severity") or "unknown",
1381-
location,
1562+
linked_location,
13821563
finding.get("title") or "Untitled finding",
13831564
)
13841565
)
@@ -1387,16 +1568,17 @@ def structured_review_lines(review):
13871568

13881569
no_findings_reason = review.get("no_findings_reason")
13891570
if no_findings_reason:
1390-
lines.append("- No-findings reason: {}".format(no_findings_reason))
1571+
lines.append("- No-findings reason: {}".format(link_github_file_mentions(task, no_findings_reason, known_files)))
13911572

13921573
tests_run = review.get("tests_run") or []
13931574
lines.append("- Tests reported by runtime: {}".format(len(tests_run)))
13941575
for test in tests_run[:10]:
13951576
summary = test.get("output_summary")
1396-
suffix = " - {}".format(summary) if summary else ""
1577+
command = review_test_command_markdown(task, test.get("command") or "unknown command")
1578+
suffix = " - {}".format(link_github_file_mentions(task, summary, known_files)) if summary else ""
13971579
lines.append(
1398-
" - `{}`: `{}`{}".format(
1399-
test.get("command") or "unknown command",
1580+
" - {}: `{}`{}".format(
1581+
command,
14001582
test.get("status") or "unknown",
14011583
suffix,
14021584
)

0 commit comments

Comments
 (0)