feat: resolve bare and annotated ref paths

This commit is contained in:
Abdessamad Derraz committed 2026-08-08 12:05:59 +02:00
1 parent 344599f7ef
commit c585452d46
2 files changed
+90 -4

No files matched your search

+28 -3
View File
@@ -45,7 +45,8 @@ HEAD = "head"
_REF_RE = re.compile(r"^(?P<path>[^:]+?)(?::(?P<start>\d+)(?:-(?P<end>\d+))?)?$")
_BARE_RANGE_RE = re.compile(r"^\d+(?:-\d+)?$")
_SPLIT_RE = re.compile(r"[,;]")
_ANNOTATION_RE = re.compile(r"\s*\([^)]*\)")
_ANNOTATION_RE = re.compile(r"\s*\([^)]*\)?")
_LOCATED_RE = re.compile(r"^[^\s:]+:\d+(?:-\d+)?$")
@dataclass(frozen=True)
@@ -119,8 +120,11 @@ def split_source_ref(ref: str) -> list[RefPart]:
across 50 profiles carry them.
"""
parts: list[RefPart] = []
for raw in _SPLIT_RE.split(str(ref or "")):
chunk = _ANNOTATION_RE.sub("", raw).strip()
# Annotations come out first: they may contain the separators themselves,
# and splitting inside one would cut it into meaningless fragments.
cleaned = _ANNOTATION_RE.sub("", str(ref or ""))
for raw in _SPLIT_RE.split(cleaned):
chunk = _trim_prose(raw.strip())
if not chunk:
continue
if _BARE_RANGE_RE.match(chunk) and parts:
@@ -134,6 +138,22 @@ def split_source_ref(ref: str) -> list[RefPart]:
return parts
def _trim_prose(chunk: str) -> str:
"""Drop a trailing comment written without parentheses.
`libretro.cpp:1520-1521 candidates_a1200` cites a location followed by a
note. The leading token is kept only when it already reads as a reference,
so `munt ROMInfo.cpp`, which names an external project, stays intact and
is reported rather than silently truncated to `munt`.
"""
if " " not in chunk:
return chunk
head = chunk.split(" ", 1)[0]
if _LOCATED_RE.match(head) or _BARE_RANGE_RE.match(head):
return head
return chunk
def collect_tokens(entry: dict) -> list[str]:
"""Values declared by one file entry: hashes, then name as fallback."""
tokens: list[str] = []
@@ -334,6 +354,11 @@ def anchor_part(part: RefPart, fetch, rename_getter, describe=None) -> PartResul
)
pin_lines = fetch(PIN, part.path)
if pin_lines is None and path != part.path:
# A ref may cite a bare filename the tree search resolved at HEAD; the
# same resolved path usually holds at the pin. A genuine rename keeps
# the old path at the pin, which is why that one is tried first.
pin_lines = fetch(PIN, path)
if pin_lines is None:
return PartResult(
part, "GONE", None, None, None, [], "pin revision missing", slug, url
+62 -1
View File
@@ -140,6 +140,33 @@ class TestSplitSourceRef(unittest.TestCase):
[("src/emulator.cpp", 234, 234), ("src/core/aes.cpp", 13, 92)],
)
def test_annotation_containing_a_separator_is_not_split(self):
parts = split_source_ref(
"libretro.cpp:944-951 (system_dir + name, then fopen), core.c:12"
)
self.assertEqual(
[(p.path, p.start) for p in parts],
[("libretro.cpp", 944), ("core.c", 12)],
)
def test_trailing_prose_after_a_location_is_dropped(self):
parts = split_source_ref("libretro/libretro.cpp:1520-1521 candidates_a1200")
self.assertEqual(
(parts[0].path, parts[0].start, parts[0].end),
("libretro/libretro.cpp", 1520, 1521),
)
def test_bare_range_followed_by_prose_still_continues(self):
parts = split_source_ref("a.c:10-12, 151-156 load loop")
self.assertEqual(
[(p.path, p.start, p.end) for p in parts],
[("a.c", 10, 12), ("a.c", 151, 156)],
)
def test_external_project_citation_is_kept_whole(self):
parts = split_source_ref("munt ROMInfo.cpp")
self.assertEqual(parts[0].path, "munt ROMInfo.cpp")
def test_leading_bare_range_stays_a_path(self):
parts = split_source_ref("273-285")
self.assertEqual(parts[0].path, "273-285")
@@ -359,6 +386,32 @@ class TestAnchorPart(unittest.TestCase):
self.assertEqual(result.new_path, "new.c")
self.assertEqual(result.start, 2)
def test_bare_filename_resolves_at_both_revisions(self):
fetch = make_fetch(
{
(PIN, "src/midi/mt32.cpp"): ["x", "hit"],
(HEAD, "src/midi/mt32.cpp"): ["x", "hit"],
}
)
comparison = CompareResult([], True)
result = anchor_part(
RefPart("mt32.cpp", 2, 2),
fetch,
renamer(comparison, ["src/midi/mt32.cpp"]),
)
self.assertEqual(result.status, "RENAMED")
self.assertEqual(result.new_path, "src/midi/mt32.cpp")
self.assertEqual(result.start, 2)
def test_genuine_rename_still_reads_the_pin_at_the_old_path(self):
fetch = make_fetch(
{(PIN, "old.c"): ["x", "hit"], (HEAD, "new.c"): ["pad", "x", "hit"]}
)
comparison = CompareResult([FileChange("renamed", "new.c", "old.c")], False)
result = anchor_part(RefPart("old.c", 2, 2), fetch, renamer(comparison))
self.assertEqual(result.status, "RENAMED")
self.assertEqual(result.start, 3)
def test_gone_when_pin_file_missing(self):
result = anchor_part(
RefPart("a.c", 1, 1), make_fetch({}), renamer(CompareResult([], False))
@@ -595,10 +648,18 @@ class TestBuildReport(unittest.TestCase):
self.assertEqual(part.status, "ANCHORED")
self.assertIsNone(part.new_path)
def test_prefix_matching_no_repository_is_not_stripped(self):
def test_unknown_prefix_resolves_by_basename_not_by_stripping(self):
self.files[("pinsha", "a.c")] = ["x", "hit"]
self.files[("headsha", "a.c")] = ["x", "hit"]
report = build_report("test", self._profile(["other/a.c:2"]), self.dir)
part = report.entries[0].parts[0]
self.assertEqual(part.status, "RENAMED")
self.assertEqual(part.new_path, "a.c")
def test_basename_absent_everywhere_stays_gone(self):
self.files[("pinsha", "a.c")] = ["x", "hit"]
self.files[("headsha", "a.c")] = ["x", "hit"]
report = build_report("test", self._profile(["other/absent.c:2"]), self.dir)
self.assertEqual(report.entries[0].parts[0].status, "GONE")
def test_missing_date_and_commit_is_skipped(self):