fix: date each prose citation on its own

realign_prose dated a whole field from the last commit that changed any of its citations. idtech4a++ corrected two notes citations by hand in the commit that advanced its pin, so the untouched Game.java:69-72 and GameResourceUrl.java:14-118 read as written under the current pin and were never offered again. Each citation is now followed through the history on its own. The two idtech4a++ ranges move to 72-75 and 15-121, read at ce49510f.
This commit is contained in:
Abdessamad Derraz committed 2026-11-06 08:24:49 +01:00
1 parent 7a03578dd9
commit 8dc3ac82c7
3 files changed
+54 -60

No files matched your search

+2 -2
View File
@@ -110,7 +110,7 @@ notes: |
Q3E/src/main/jni/quake2/yquake2/src/common/filesystem.c:1801-1850,
Q3E/src/main/jni/zdoom/src/d_iwad.cpp:738,
Q3E/src/main/jni/ecwolf/src/wl_iwad.cpp:766,
idTech4Amm/src/main/java/com/karin/idTech4Amm/sys/GameResourceUrl.java:14-118
idTech4Amm/src/main/java/com/karin/idTech4Amm/sys/GameResourceUrl.java:15-121
The Quake III lineage keeps id's release checksums for `baseq3` pak0 through
pak8 and `missionpack` pak0 through pak3, and an archive whose value disagrees
@@ -130,7 +130,7 @@ notes: |
two games whose data is a file rather than a folder.
ref: Q3E/src/main/jni/zdoom/src/d_iwad.cpp:287-297, 321-342,
Q3E/src/main/jni/zdoom/src/version.h:100-102,
idTech4Amm/src/main/java/com/karin/idTech4Amm/sys/Game.java:69-72,
idTech4Amm/src/main/java/com/karin/idTech4Amm/sys/Game.java:72-75,
Q3E/src/main/java/com/n0n3m4/q3e/Q3EInterface.java:710-713
Starting a game extracts that game's bundled resources first. A text file named
+34 -58
View File
@@ -3742,15 +3742,6 @@ def _git_file_at(path: Path, sha: str) -> dict | None:
return document if isinstance(document, dict) else None
def _scalar_values(document: dict) -> dict[str, str]:
"""String scalars of the document, keyed by field path."""
return {
field: value
for kind, field, _, _, value in _walk_document(document)
if kind == "str"
}
def _pin_of(
document: dict, field: str, mode: str = "",
repo: upstream.Repo | None = None,
@@ -3788,26 +3779,6 @@ def _pin_of(
return value
_CITATION_RUN_RE = re.compile(
PROSE_CITE_RE.pattern + r"(?:, *\d+(?:-\d+)?)*"
)
def _scalar_citations(text: str | None) -> tuple[str, ...] | None:
"""The citations a scalar carries, in order, without the prose around them.
A note reworded around unchanged citations was still written under the
pin those citations describe: comparing whole texts moved the writing
revision to the rewording commit, whose pin is current, and the stale
citations were never offered for realignment again.
"""
if text is None:
return None
return tuple(
_citation_key(m.group(0)) for m in _CITATION_RUN_RE.finditer(text)
)
def _writing_pins(
revisions: list[tuple[str, dict]], intro_sha: str, field: str, mode: str = "",
repo: upstream.Repo | None = None,
@@ -4066,68 +4037,73 @@ def realign_prose(
if not history:
return []
current_values = _scalar_values(document)
fields = {citation.field for citation in citations}
alive = dict.fromkeys(fields, True)
intro: dict[str, str | None] = dict.fromkeys(fields)
# Each citation is followed on its own: a field's text changes whenever
# one of its citations is corrected by hand, and dating the whole field
# from that commit put idtech4a++'s untouched Game.java:69-72 under the
# current pin, so the stale range was never offered again.
keys = [(c.field, _citation_key(c.ref)) for c in citations]
alive = [True] * len(citations)
intro: list[str | None] = [None] * len(citations)
revisions: list[tuple[str, dict]] = []
# Fields whose writing pin lies past a revision that cannot be read:
# Citations 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] = {}
unknown: dict[int, 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
# on its two sides while the citation is absent 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
def carried_by(past: dict) -> set[tuple[str, str]]:
return {
(c.field, _citation_key(c.ref))
for c in collect_citations(past) if c.kind == "prose"
}
for sha in history:
if not any(alive.values()):
if not any(alive):
break
past = _git_file_at(path, sha)
if past is None:
gap = gap or sha
continue
held = carried_by(past)
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
for index, live in enumerate(alive):
if live and moved and keys[index] not in held:
unknown[index] = gap
alive[index] = False
gap = None
revisions.append((sha, past))
values = _scalar_values(past)
for field in fields:
if not alive[field]:
for index, live in enumerate(alive):
if not live:
continue
if _scalar_citations(values.get(field)) == _scalar_citations(
current_values.get(field)
):
intro[field] = sha
if keys[index] in held:
intro[index] = sha
else:
alive[field] = False
alive[index] = 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})
unknown.update({index: gap for index, live in enumerate(alive) if live})
messages: list[str] = []
jobs: list[tuple[Citation, dict]] = []
for citation in citations:
if citation.field in unknown:
for index, citation in enumerate(citations):
if index in unknown:
messages.append(
f"read again: {citation.field}: {citation.ref} "
f"(revision {unknown[citation.field][:8]} unreadable)"
f"(revision {unknown[index][:8]} unreadable)"
)
continue
intro_sha = intro.get(citation.field)
intro_sha = intro[index]
if intro_sha is None:
# The scalar is not committed yet: written now, under this pin.
continue
+18
View File
@@ -4766,6 +4766,24 @@ class TestRealignProse(unittest.TestCase):
any("pin moved under this text" in m for m in messages), messages
)
def test_a_citation_corrected_beside_another_dates_only_itself(self):
"""idtech4a++ advanced its pin in the commit that corrected two
citations of its notes by hand. Dated per field, the untouched
Game.java:69-72 took that commit's current pin and was never
offered again, though its lines had moved."""
self._repo_with_advanced_pin()
self._commit(
PROSE_SAMPLE.replace("(b.c:2,8-9)", "(b.c:3,9-10)"),
"advance the pin, b.c corrected by hand",
)
self.files[("oldpin", "a.c")] = ["x"] * 9 + ["subject"]
self.files[("pin", "a.c")] = ["x"] * 13 + ["subject"]
self.files[("pin", "b.c")] = list("pqrstuvwxyz")
messages = profile_sync.realign_prose(self.path, self.tmp.name, dry_run=True)
flagged = [m for m in messages if "a.c:10" in m]
self.assertTrue(flagged, messages)
self.assertFalse([m for m in messages if "b.c:" in m], messages)
def test_a_citation_in_an_unmoved_upstream_needs_no_reading(self):
"""nethersx2 cites AetherSX2 files that only its upstream carries,
at an upstream pin that never moved. The port's pin did move under