diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index b7459194..b0de1ba8 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -3178,13 +3178,34 @@ def realign_prose( # Fields whose writing pin lies past a revision that cannot be read: # where their text came in is unknown, so they are not moved. unknown: dict[str, str] = {} + # A revision that does not parse is passed over unless the pins differ + # on its two sides while the field's citations changed across it: then + # the text may have come in there under either pin. Otherwise nothing + # it could hold changes the reading. lindbergh-loader + # committed invalid YAML once (df7346b3), fixed the same day, and every + # note older than that commit stayed blocked for good. + gap: str | None = None for sha in history: if not any(alive.values()): break past = _git_file_at(path, sha) if past is None: - unknown = {field: sha for field, live in alive.items() if live} - break + gap = gap or sha + continue + if gap: + newer = revisions[-1][1] if revisions else document + moved = any( + newer.get(pin) != past.get(pin) + for pin in ("source_commit", "upstream_commit") + ) + values = _scalar_values(past) + for field, live in alive.items(): + if live and moved and _scalar_citations(values.get(field)) != ( + _scalar_citations(current_values.get(field)) + ): + unknown[field] = gap + alive[field] = False + gap = None revisions.append((sha, past)) values = _scalar_values(past) for field in fields: @@ -3196,6 +3217,10 @@ def realign_prose( intro[field] = sha else: alive[field] = False + if gap: + # The oldest revisions do not parse: where the text came in is past + # what can be read. + unknown.update({field: gap for field, live in alive.items() if live}) messages: list[str] = [] jobs: list[tuple[Citation, dict]] = [] diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index ebfedead..aa852bd5 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -3845,6 +3845,46 @@ class TestRealignProse(unittest.TestCase): self.assertTrue(all(m.startswith("read again: notes:") for m in messages)) self.assertIn("(a.c:10)", self.path.read_text()) + def _repo_with_a_broken_revision(self, broken_pin: str): + self._git("init", "-q") + self._git("config", "user.email", "t@t") + self._git("config", "user.name", "t") + written = PROSE_SAMPLE.replace('"pin"', '"oldpin"') + self._commit(written, "profile") + broken = PROSE_SAMPLE.replace('"pin"', f'"{broken_pin}"') + self._commit(broken + "files: [\n", "invalid yaml") + self._commit(written, "repair") + self.path.write_text(PROSE_SAMPLE, encoding="utf-8") + self.files[("oldpin", "a.c")] = ["x"] * 9 + ["subject"] + self.files[("pin", "a.c")] = ["x"] * 13 + ["subject"] + self.files[("oldpin", "b.c")] = list("pqrstuvwx") + self.files[("pin", "b.c")] = list("pqrstuvwx") + + def test_a_revision_that_does_not_parse_is_passed_over(self): + """lindbergh-loader committed invalid YAML once, repaired the same + day under the same pin. Every older note was reported unreadable + and the recale stayed refused for good.""" + self._repo_with_a_broken_revision("oldpin") + messages = profile_sync.realign_prose(self.path, self.tmp.name, dry_run=True) + self.assertFalse(any("unreadable" in m for m in messages), messages) + self.assertTrue(any("would recale" in m for m in messages), messages) + + def test_a_revision_that_does_not_parse_still_blocks_when_it_can_matter(self): + """The pins differ across the gap and the note's citations changed: + the text may have come in there under either pin.""" + self._git("init", "-q") + self._git("config", "user.email", "t@t") + self._git("config", "user.name", "t") + self._commit( + PROSE_SAMPLE.replace('"pin"', '"older"').replace("a.c:10", "a.c:3"), + "profile", + ) + self._commit(PROSE_SAMPLE + "files: [\n", "invalid yaml") + self._commit(PROSE_SAMPLE.replace('"pin"', '"oldpin"'), "repair") + self.path.write_text(PROSE_SAMPLE, encoding="utf-8") + messages = profile_sync.realign_prose(self.path, self.tmp.name, dry_run=True) + self.assertTrue(any("unreadable" in m for m in messages), messages) + def test_scalar_written_at_the_current_pin_is_left_alone(self): self._git("init", "-q") self._git("config", "user.email", "t@t")