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.