mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-05 17:43:54 -06:00
feat: settle ambiguity by file line shift
This commit is contained in:
1 parent
9982748e49
commit
6b0306b43d
2 files changed
+112
-5
No files matched your search
+61
-4
@@ -36,7 +36,7 @@ STATUS_ORDER = ("ANCHORED", "SHIFTED", "RENAMED", "AMBIGUOUS", "CHANGED", "GONE"
|
||||
REVIEW_STATUSES = ("CHANGED", "GONE", "AMBIGUOUS")
|
||||
REBASE_STATUSES = ("SHIFTED", "RENAMED")
|
||||
|
||||
WIDEN_STEPS = (0, 3, 6, 12)
|
||||
WIDEN_STEPS = (0, 3, 6, 12, 25, 50)
|
||||
MAX_MATCH_LINES = 20000
|
||||
|
||||
PIN = "pin"
|
||||
@@ -381,6 +381,57 @@ def anchor_part(part: RefPart, fetch, rename_getter, describe=None) -> PartResul
|
||||
)
|
||||
|
||||
|
||||
SHIFT_SUPPORT = 3
|
||||
|
||||
|
||||
def dominant_shifts(parts, support: int = SHIFT_SUPPORT) -> dict[str, int]:
|
||||
"""Most common line shift per file, taken from the unambiguous anchors.
|
||||
|
||||
A file whose refs mostly moved by the same amount gives strong evidence
|
||||
for the ones that stayed ambiguous: repetitive tables, as in the NeoGeo
|
||||
driver, match a cited line in several places, and only one of them sits
|
||||
where the rest of the file moved to.
|
||||
"""
|
||||
by_file: dict[str, dict[int, int]] = {}
|
||||
for part in parts:
|
||||
if part.status not in ("ANCHORED", "SHIFTED"):
|
||||
continue
|
||||
if part.start is None or part.part.start is None:
|
||||
continue
|
||||
counts = by_file.setdefault(part.part.path, {})
|
||||
delta = part.start - part.part.start
|
||||
counts[delta] = counts.get(delta, 0) + 1
|
||||
shifts = {}
|
||||
for path, counts in by_file.items():
|
||||
delta, seen = max(counts.items(), key=lambda kv: kv[1])
|
||||
if seen >= support:
|
||||
shifts[path] = delta
|
||||
return shifts
|
||||
|
||||
|
||||
def resolve_by_shift(part: PartResult, shifts: dict[str, int]) -> PartResult:
|
||||
"""Settle an ambiguous part when one candidate matches the file's shift."""
|
||||
if part.status != "AMBIGUOUS" or not part.candidates:
|
||||
return part
|
||||
if part.part.start is None or part.part.path not in shifts:
|
||||
return part
|
||||
wanted = part.part.start + shifts[part.part.path]
|
||||
if part.candidates.count(wanted) != 1:
|
||||
return part
|
||||
span = (part.part.end or part.part.start) - part.part.start
|
||||
return PartResult(
|
||||
part.part,
|
||||
"ANCHORED" if wanted == part.part.start else "SHIFTED",
|
||||
part.new_path,
|
||||
wanted,
|
||||
wanted + span,
|
||||
[],
|
||||
f"settled by the {shifts[part.part.path]:+d} line shift of its file",
|
||||
part.repo,
|
||||
part.head_url,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EntryReport:
|
||||
name: str
|
||||
@@ -576,11 +627,17 @@ def build_report(
|
||||
slug = view.repo.slug if view is not primary else None
|
||||
return slug, upstream.raw_url(view.repo, view.head, actual), actual
|
||||
|
||||
for entry_name, ref in refs:
|
||||
parts = [
|
||||
staged = [
|
||||
(entry_name, ref, [
|
||||
anchor_part(part, fetch, rename_getter, describe)
|
||||
for part in split_source_ref(ref)
|
||||
]
|
||||
])
|
||||
for entry_name, ref in refs
|
||||
]
|
||||
|
||||
shifts = dominant_shifts([p for _, _, parts in staged for p in parts])
|
||||
for entry_name, ref, parts in staged:
|
||||
parts = [resolve_by_shift(p, shifts) for p in parts]
|
||||
status = worst_status([p.status for p in parts])
|
||||
report.entries.append(EntryReport(entry_name, ref, status, parts))
|
||||
report.counts[status] = report.counts.get(status, 0) + 1
|
||||
|
||||
@@ -244,7 +244,7 @@ class TestAnchorBlock(unittest.TestCase):
|
||||
self.assertEqual((result.status, result.start), ("SHIFTED", 6))
|
||||
|
||||
def test_ambiguous_when_widening_never_resolves(self):
|
||||
result = anchor_block(["dup"] * 40, ["dup"] * 40, 5, 5)
|
||||
result = anchor_block(["dup"] * 400, ["dup"] * 400, 5, 5)
|
||||
self.assertEqual(result.status, "AMBIGUOUS")
|
||||
self.assertGreater(len(result.candidates), 1)
|
||||
|
||||
@@ -275,6 +275,56 @@ class TestAnchorBlock(unittest.TestCase):
|
||||
self.assertIsNone(result.start)
|
||||
|
||||
|
||||
def _part(path, old, new, status):
|
||||
return PartResult(RefPart(path, old, old), status, None, new, new, [])
|
||||
|
||||
|
||||
def _ambiguous(path, old, candidates):
|
||||
return PartResult(RefPart(path, old, old), "AMBIGUOUS", None, None, None, candidates)
|
||||
|
||||
|
||||
class TestDominantShift(unittest.TestCase):
|
||||
def test_shift_needs_enough_support(self):
|
||||
parts = [_part("a.c", 10, 30, "SHIFTED"), _part("a.c", 20, 40, "SHIFTED")]
|
||||
self.assertEqual(profile_sync.dominant_shifts(parts), {})
|
||||
|
||||
def test_shift_is_taken_from_resolved_anchors(self):
|
||||
parts = [_part("a.c", i, i + 20, "SHIFTED") for i in (10, 20, 30)]
|
||||
self.assertEqual(profile_sync.dominant_shifts(parts), {"a.c": 20})
|
||||
|
||||
def test_ambiguous_parts_do_not_vote(self):
|
||||
parts = [_part("a.c", i, i + 20, "SHIFTED") for i in (10, 20, 30)]
|
||||
parts.append(_ambiguous("a.c", 40, [99, 500]))
|
||||
self.assertEqual(profile_sync.dominant_shifts(parts), {"a.c": 20})
|
||||
|
||||
def test_candidate_matching_the_shift_is_selected(self):
|
||||
part = _ambiguous("a.c", 1624, [1644, 1790, 1834])
|
||||
settled = profile_sync.resolve_by_shift(part, {"a.c": 20})
|
||||
self.assertEqual(settled.status, "SHIFTED")
|
||||
self.assertEqual(settled.start, 1644)
|
||||
self.assertIn("+20", settled.reason)
|
||||
|
||||
def test_no_candidate_matches_the_shift(self):
|
||||
part = _ambiguous("a.c", 1624, [1790, 1834])
|
||||
self.assertEqual(profile_sync.resolve_by_shift(part, {"a.c": 20}).status,
|
||||
"AMBIGUOUS")
|
||||
|
||||
def test_two_candidates_on_the_shift_stay_ambiguous(self):
|
||||
part = _ambiguous("a.c", 1624, [1644, 1644])
|
||||
self.assertEqual(profile_sync.resolve_by_shift(part, {"a.c": 20}).status,
|
||||
"AMBIGUOUS")
|
||||
|
||||
def test_range_length_is_preserved(self):
|
||||
part = PartResult(RefPart("a.c", 100, 104), "AMBIGUOUS", None, None, None, [120])
|
||||
settled = profile_sync.resolve_by_shift(part, {"a.c": 20})
|
||||
self.assertEqual((settled.start, settled.end), (120, 124))
|
||||
|
||||
def test_file_without_a_known_shift_is_untouched(self):
|
||||
part = _ambiguous("b.c", 10, [30])
|
||||
self.assertEqual(profile_sync.resolve_by_shift(part, {"a.c": 20}).status,
|
||||
"AMBIGUOUS")
|
||||
|
||||
|
||||
PIN = profile_sync.PIN
|
||||
HEAD = profile_sync.HEAD
|
||||
|
||||
|
||||
Reference in new issue
Block a user