mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-11 05:53:23 -05:00
feat: attribute a shared path by its cited subject
This commit is contained in:
1 parent
44ba5e45c3
commit
dd7faf2b5f
2 files changed
+61
-19
No files matched your search
+41
-18
@@ -482,7 +482,7 @@ def anchor_part(
|
||||
slug, url, actual = (
|
||||
describe(part.path) if describe else (None, None, part.path)
|
||||
)
|
||||
head_lines = fetch(HEAD, part.path)
|
||||
head_lines = fetch(HEAD, part.path, part.start)
|
||||
path = actual
|
||||
renamed = actual != part.path
|
||||
|
||||
@@ -513,7 +513,7 @@ def anchor_part(
|
||||
url,
|
||||
)
|
||||
path = moved
|
||||
head_lines = fetch(HEAD, path)
|
||||
head_lines = fetch(HEAD, path, part.start)
|
||||
if head_lines is None:
|
||||
return PartResult(
|
||||
part, "GONE", None, None, None, [], None, slug, url
|
||||
@@ -528,12 +528,12 @@ def anchor_part(
|
||||
part, status, path if renamed else None, None, None, [], None, slug, url
|
||||
)
|
||||
|
||||
pin_lines = fetch(PIN, part.path)
|
||||
pin_lines = fetch(PIN, part.path, part.start)
|
||||
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)
|
||||
pin_lines = fetch(PIN, path, part.start)
|
||||
if pin_lines is None:
|
||||
return PartResult(
|
||||
part, "GONE", None, None, None, [], "pin revision missing", slug, url
|
||||
@@ -867,35 +867,58 @@ def build_report(
|
||||
report.skipped = "no source_ref"
|
||||
return report
|
||||
|
||||
owners: dict[str, RepoView] = {}
|
||||
owners: dict[tuple[str, int | None], tuple[RepoView, str]] = {}
|
||||
context: dict[str, object] = {}
|
||||
|
||||
def _locate(path: str) -> tuple[RepoView, str]:
|
||||
def _locate(path: str, start: int | None = None) -> tuple[RepoView, str]:
|
||||
"""Repository and real path carrying a cited path, HEAD before pin.
|
||||
|
||||
A ref may prefix the path with the repository directory name, as it
|
||||
appears in a parent folder holding both clones: 270 parts across 21
|
||||
profiles do. That prefix is stripped only as a last resort, once the
|
||||
path as written has failed against every repository and revision.
|
||||
|
||||
The pin comes before HEAD because that is the revision the ref was
|
||||
written against. `mame` cites files that both mamedev/mame and the
|
||||
libretro fork carry today, but only the upstream pin holds them; the
|
||||
fork's pin is an older tree where they lived elsewhere.
|
||||
"""
|
||||
candidates = [path]
|
||||
head, _, tail = path.partition("/")
|
||||
if tail and any(head == v.repo.name for v in views):
|
||||
candidates.append(tail)
|
||||
|
||||
def carries_subject(lines) -> bool:
|
||||
"""A citation never points at a blank line.
|
||||
|
||||
Two repositories can hold the same path with different contents:
|
||||
`mame` cites files that both mamedev/mame and its libretro fork
|
||||
carry. Where the cited line is empty, that repository is not the
|
||||
one the ref was written against.
|
||||
"""
|
||||
if start is None:
|
||||
return True
|
||||
return start <= len(lines) and bool(lines[start - 1].strip())
|
||||
|
||||
fallback = None
|
||||
for candidate in candidates:
|
||||
for sha_of in (lambda v: v.head, lambda v: v.pin):
|
||||
for sha_of in (lambda v: v.pin, lambda v: v.head):
|
||||
for view in views:
|
||||
found = upstream.fetch_file(
|
||||
view.repo, sha_of(view), candidate, cache_dir, offline
|
||||
)
|
||||
if found is not None:
|
||||
if found is None:
|
||||
continue
|
||||
if carries_subject(found):
|
||||
return view, candidate
|
||||
return primary, path
|
||||
fallback = fallback or (view, candidate)
|
||||
return fallback or (primary, path)
|
||||
|
||||
def resolve_path(path: str) -> tuple[RepoView, str]:
|
||||
if path not in owners:
|
||||
owners[path] = _locate(path)
|
||||
return owners[path]
|
||||
def resolve_path(path: str, start: int | None = None) -> tuple[RepoView, str]:
|
||||
key = (path, start)
|
||||
if key not in owners:
|
||||
owners[key] = _locate(path, start)
|
||||
return owners[key]
|
||||
|
||||
def _context_for(view: RepoView):
|
||||
key = view.repo.slug
|
||||
@@ -936,17 +959,17 @@ def build_report(
|
||||
candidates = near
|
||||
return None, candidates
|
||||
|
||||
lines_cache: dict[tuple[str, str], list[str] | None] = {}
|
||||
lines_cache: dict[tuple[str, str, int | None], list[str] | None] = {}
|
||||
|
||||
def fetch(which: str, path: str):
|
||||
def fetch(which: str, path: str, start: int | None = None):
|
||||
"""Lines for one path at one revision, read once per run.
|
||||
|
||||
The same object is handed back every time so the opcode cache can key
|
||||
on identity.
|
||||
"""
|
||||
key = (which, path)
|
||||
key = (which, path, start)
|
||||
if key not in lines_cache:
|
||||
view, actual = resolve_path(path)
|
||||
view, actual = resolve_path(path, start)
|
||||
sha = view.pin if which == PIN else view.head
|
||||
lines_cache[key] = upstream.fetch_file(
|
||||
view.repo, sha, actual, cache_dir, offline
|
||||
@@ -964,7 +987,7 @@ def build_report(
|
||||
if self_check:
|
||||
staged = [
|
||||
(entry_name, ref, reconcile_self_check([
|
||||
verify_at_pin(part, fetch(PIN, part.path), tokens)
|
||||
verify_at_pin(part, fetch(PIN, part.path, part.start), tokens)
|
||||
for part in split_source_ref(ref)
|
||||
]))
|
||||
for entry_name, ref, tokens in refs
|
||||
|
||||
@@ -562,7 +562,9 @@ HEAD = profile_sync.HEAD
|
||||
|
||||
|
||||
def make_fetch(files: dict[tuple[str, str], list[str]]):
|
||||
return lambda sha, path: files.get((sha, path))
|
||||
# The real fetch takes the cited line too, so that a path carried by two
|
||||
# repositories is attributed to the one whose revision is not blank there.
|
||||
return lambda sha, path, start=None: files.get((sha, path))
|
||||
|
||||
|
||||
def renamer(result: CompareResult, head_paths=()):
|
||||
@@ -1089,6 +1091,23 @@ class TestBuildReport(unittest.TestCase):
|
||||
report = build_report("test", profile, self.dir)
|
||||
self.assertEqual(report.entries[0].status, "ANCHORED")
|
||||
|
||||
def test_the_repository_with_a_subject_owns_a_shared_path(self):
|
||||
# A port and its upstream can both carry a path; only one holds the
|
||||
# cited subject, and a blank line is never a citation.
|
||||
self.files[("portpin", "src/a.cpp")] = ["x", "", "y"]
|
||||
self.files[("uppin", "src/a.cpp")] = ["x", "GAME(1985, bubsys", "y"]
|
||||
self.files[("headsha", "src/a.cpp")] = ["x", "GAME(1985, bubsys", "y"]
|
||||
profile = {
|
||||
"emulator": "MAME",
|
||||
"source": "https://github.com/libretro/mame",
|
||||
"upstream": "https://github.com/mamedev/mame",
|
||||
"source_commit": "portpin",
|
||||
"upstream_commit": "uppin",
|
||||
"files": [{"name": "bubsys.zip", "source_ref": "src/a.cpp:2"}],
|
||||
}
|
||||
report = build_report("mame", profile, self.dir)
|
||||
self.assertEqual(report.entries[0].status, "ANCHORED")
|
||||
|
||||
def test_pin_on_the_declared_version_tag_is_flagged(self):
|
||||
self.files[("pinsha", "a.c")] = ["x", "hit"]
|
||||
self.files[("headsha", "a.c")] = ["x", "hit"]
|
||||
|
||||
Reference in new issue
Block a user