From 5e9c2c87b66bbb9aa01560255007fe2454d207d0 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Thu, 5 Nov 2026 11:57:58 +0100 Subject: [PATCH] fix: link prose citations of two-repo profiles --- scripts/sitesources.py | 25 ++++++++++++++----------- tests/test_site_exports.py | 22 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/scripts/sitesources.py b/scripts/sitesources.py index e631a9774..20844939b 100644 --- a/scripts/sitesources.py +++ b/scripts/sitesources.py @@ -527,16 +527,18 @@ def link_prose_citations(profile: dict, text: str) -> str: """Prose file:line citations as pinned links, where the target is certain. profile_sync keeps these citations on their pin, and the page printed - 4,328 of them as plain text. A citation links only when the profile reads - one repository and the path is rooted (it holds a directory) or is one a - structured source_ref of the profile already uses: a bare name could be - anywhere in the tree, and a link to the wrong file is a false citation. + 4,328 of them as plain text. A citation links only when the path is + rooted (it holds a directory) or is one a structured source_ref of the + profile already uses: a bare name could be anywhere in the tree, and a + link to the wrong file is a false citation. Of the repositories a profile + reads, the one its role word names is taken, else the one tree holding + the path: held by both, the citation does not say which it means. Text already in backticks or in a link is left alone. """ candidates = _forge_sources(profile) - if len(candidates) != 1 or ":" not in text: + if not candidates or ":" not in text: return text - repo, pin = candidates[0] + by_role = role_sources(profile) structured = { part.path for entry in profile.get("files") or [] @@ -554,14 +556,15 @@ def link_prose_citations(profile: dict, text: str) -> str: continue if "/" not in path and path not in structured: continue - role = parts[0].role - if role and (role_sources(profile).get(role) or (None,))[0] != repo: - # `upstream a.c:5` names a repository this link would not reach. - continue if any(lo <= path_span[0] < hi for lo, hi in guarded): continue - if not tree_holds(repo, pin, path): + role = parts[0].role + # `upstream a.c:5` names its repository: none other is linked. + named = [by_role[role]] if role in by_role else [] if role else candidates + holders = [tree for tree in named if tree_holds(*tree, path)] + if len(holders) != 1: continue + repo, pin = holders[0] for index, ((lo, hi), part) in enumerate(zip(spans, parts)): start = path_span[0] if index == 0 else lo url = _source_permalink(repo, pin, part.path, part.start, part.end) diff --git a/tests/test_site_exports.py b/tests/test_site_exports.py index 09b2240f8..1a3dea8fe 100644 --- a/tests/test_site_exports.py +++ b/tests/test_site_exports.py @@ -800,6 +800,28 @@ class ProseCitationsLinkToThePin(unittest.TestCase): two = {**self.PROFILE, "upstream": "https://github.com/x/y", "upstream_commit": "a" * 40} self.assertEqual(link_prose_citations(two, "src/a.c:5"), "src/a.c:5") + def test_two_repositories_link_into_the_one_holding_the_path(self): + """1,044 citations in 86 profiles that read two repositories stayed + text, the swanstation note on FindBIOSImageInDirectory among them.""" + from sitesources import link_prose_citations + + two = {**self.PROFILE, "upstream": "https://github.com/x/y", "upstream_commit": "a" * 40} + + def holds(repo, _pin, path): + return path == "src/both.c" or (path == "src/port.c") == (repo.owner == "libretro") + + with mock.patch("sitesources.tree_holds", holds): + text = link_prose_citations(two, "src/port.c:5, src/emu.c:7 and src/both.c:9") + upstream_only = link_prose_citations(two, "upstream src/port.c:5") + named = link_prose_citations(two, "upstream src/both.c:9") + self.assertIn("[`src/both.c:9`](https://github.com/x/y/blob/", named) + self.assertIn("[`src/port.c:5`](https://github.com/libretro/FreeChaF/blob/", text) + self.assertIn("[`src/emu.c:7`](https://github.com/x/y/blob/" + "a" * 40, text) + # Held by both: the citation does not say which it means. + self.assertIn(" src/both.c:9", text) + # The role word names a repository that does not hold it. + self.assertNotIn("](", upstream_only) + class ACitationSaysWhoseCodeItIs(unittest.TestCase): """Readers could not tell whether the libretro core or the emulator asked