From 1fb717324b7fc005fc035b86c73e3753559d2d10 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:45:51 +0200 Subject: [PATCH] fix: name the stale pin instead of a missing file A ref whose file is absent at the pinned revision reported "pin revision missing", which reads as code that vanished. When the same file is at HEAD and the cited range fits it, nothing vanished: the ref was written against HEAD while source_commit still names an older revision, and the profile describes two trees at once. The reason now says so, because the fix is the pin and not a hunt for a move that never happened. This is the state three profiles were left in during their own reprofiling, against a warning the repository already carries. A range that overruns HEAD stays a plain miss, and a file absent from both revisions is unchanged. --- scripts/profile_sync.py | 11 ++++++++--- tests/test_profile_sync.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 421395c1..23963cc6 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -815,9 +815,14 @@ def anchor_part( # the old path at the pin, which is why that one is tried first. pin_lines = fetch(PIN, path, part.start, tokens) if pin_lines is None: - return PartResult( - part, "GONE", None, None, None, [], "pin revision missing", slug, url - ) + # Present at HEAD, absent at the pin, and the cited range fits the + # HEAD file: the ref was written against HEAD while source_commit + # still names an older revision. Saying "missing" sends the reader + # hunting for a move that never happened; the fix is the pin. + reason = "pin revision missing" + if head_lines is not None and part.start <= len(head_lines): + reason = "written against HEAD, pin names an older revision" + return PartResult(part, "GONE", None, None, None, [], reason, slug, url) end = part.end or part.start anchored = anchor_block(pin_lines, head_lines, part.start, end) diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 9af5227b..ab46d728 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -1435,6 +1435,39 @@ def _sample_report(): ) +class TestRefsAheadOfTheirPin(TestBuildReport): + """Say when a ref describes HEAD and the pin still names an older tree. + + Reprofiling against HEAD and forgetting to advance source_commit leaves + the profile describing two revisions at once. The symptom was "pin + revision missing", which reads as code that vanished and sends the + reader hunting for a move that never happened. The cause is the pin. + """ + + def test_present_at_head_and_absent_at_the_pin_names_the_pin(self): + self.files[("headsha", "a.c")] = ["x", "the cited line", "y"] + report = build_report("test", self._profile(["a.c:2"]), self.dir) + part = report.entries[0].parts[0] + self.assertEqual(part.status, "GONE") + self.assertEqual( + part.reason, "written against HEAD, pin names an older revision" + ) + + def test_a_range_beyond_head_is_still_just_missing(self): + """Not every pin miss is a stale pin; only one that fits HEAD.""" + self.files[("headsha", "a.c")] = ["x"] + report = build_report("test", self._profile(["a.c:40"]), self.dir) + part = report.entries[0].parts[0] + self.assertEqual(part.status, "GONE") + self.assertNotEqual( + part.reason, "written against HEAD, pin names an older revision" + ) + + def test_a_file_missing_from_both_is_unchanged(self): + report = build_report("test", self._profile(["ghost.c:2"]), self.dir) + self.assertEqual(report.entries[0].parts[0].status, "GONE") + + class TestBareNameResolvedAtThePin(TestBuildReport): """A bare filename belongs to the revision the ref was written against.