fix: judge each profile ref by its own repository

This commit is contained in:
Abdessamad Derraz committed 2026-10-06 04:37:48 +02:00
1 parent fe877ff79c
commit 4a32ef2bb3
2 files changed
+104 -33

No files matched your search

+73 -33
View File
@@ -1012,6 +1012,13 @@ class ProfileReport:
# rather than dropped: a `source` on an unknown host used to fall back to
# `upstream` in silence, and a divergence between the two went unseen.
unread: list[str] = field(default_factory=list)
# The upstream view when the profile declares one: its pin and HEAD. A
# recale moves its parts onto upstream HEAD lines, so a bump advances
# upstream_commit with source_commit, or the profile describes two
# revisions at once (geolith jg.c drifted 213 -> 221 -> 229).
upstream_pin: str | None = None
upstream_head: str | None = None
upstream_origin: str | None = None
def needs_review(self) -> int:
counts = self.counts or {}
@@ -1195,22 +1202,32 @@ def verify_at_pin(part: RefPart, pin_lines, tokens, hash_tokens=()) -> PartResul
)
def reconcile_self_check(parts: list[PartResult]) -> list[PartResult]:
def reconcile_self_check(
parts: list[PartResult], eligible: list[bool] | None = None
) -> list[PartResult]:
"""Judge a self-checked entry whole rather than part by part.
A ref often cites both the table carrying the value and the code that
loads the file. Once one part anchors on the value, the others describe
behaviour and cannot be judged by value: the checker would only rediscover
the value where the first part already points. A structurally absent part
still counts, since that verdict does not rest on the value.
loads the file. Once one part anchors on the value, the others in the
SAME file describe behaviour and cannot be judged by value: the checker
would only rediscover the value where the first part already points. A
part in another file carries its own value and is judged on it: geolith's
jg.c part, sixteen lines off, was settled by an anchored libretro.c part.
A structurally absent part still counts, and a part that was compared
against HEAD rather than self-checked is never rewritten.
"""
if not any(part.status == "ANCHORED" for part in parts):
return parts
eligible = eligible if eligible is not None else [True] * len(parts)
anchored_files = {
part.part.path
for part, ok in zip(parts, eligible)
if ok and part.status == "ANCHORED"
}
kept = ("ANCHORED", "GONE", "EXTERNAL", "BINARY")
return [
part if part.status in kept
else PartResult(part.part, "ANCHORED", None, None, None, [])
for part in parts
PartResult(part.part, "ANCHORED", None, None, None, [])
if ok and part.status not in kept and part.part.path in anchored_files
else part
for part, ok in zip(parts, eligible)
]
@@ -1364,6 +1381,11 @@ def build_report(
report.repos = [v.repo.slug for v in views]
report.pin, report.pin_origin = primary.pin, primary.origin
report.head = primary.head
for view in views[1:]:
if view.field == "upstream":
report.upstream_pin = view.pin
report.upstream_head = view.head
report.upstream_origin = view.origin
# A profile can hold its pin on purpose, documenting a build that is that
# revision. Comparing it to HEAD says nothing and recaling would repoint
# its refs at code the build never had, so it is judged like a frozen tag:
@@ -1639,26 +1661,24 @@ def build_report(
)
# Comparing a revision with itself anchors every ref whatever it cites, so
# a profile already sitting on HEAD is judged on self-consistency instead.
self_check = bool(report.pinned_tag) or primary.pin == primary.head
if self_check:
staged = [
(entry_name, ref, citation, reconcile_self_check([
verify_at_pin(
part, fetch(PIN, part.path, part.start, tokens), tokens, hashes
)
for part in ref_parts
]))
for entry_name, ref, tokens, hashes, ref_parts, citation in refs
]
else:
staged = [
(entry_name, ref, citation, [
anchor_across_views(part, tokens)
for part in ref_parts
])
for entry_name, ref, tokens, hashes, ref_parts, citation in refs
# a part whose repository already sits on HEAD is judged on
# self-consistency instead. Decided per part, by the repository that owns
# it: a primary on HEAD says nothing about an upstream that moved on.
def self_checked(part, tokens) -> bool:
if report.pinned_tag:
return True
view, _ = resolve_path(part.path, part.start, tuple(tokens))
return view.pin == view.head
staged = []
for entry_name, ref, tokens, hashes, ref_parts, citation in refs:
flags = [self_checked(part, tokens) for part in ref_parts]
judged = [
verify_at_pin(part, fetch(PIN, part.path, part.start, tokens), tokens, hashes)
if flag else anchor_across_views(part, tokens)
for part, flag in zip(ref_parts, flags)
]
staged.append((entry_name, ref, citation, reconcile_self_check(judged, flags)))
shifts = dominant_shifts([p for _, _, _, parts in staged for p in parts])
for entry_name, ref, citation, parts in staged:
@@ -2434,20 +2454,40 @@ def bump_commit(
if pending_recale(report, accept_changed, text):
return False
document = yaml.safe_load(text)
if document.get("source_commit") == report.head:
upstream_moves = bool(
report.upstream_head and report.upstream_pin != report.upstream_head
)
if document.get("source_commit") == report.head and not upstream_moves:
# Rewriting the pin to the value it already holds is not a bump, and
# announcing it buries the profiles that did move.
return False
if isinstance(document.get("upstream_commit"), dict):
# A pin per build mode names no single upstream revision to advance.
return False
expected = dict(document)
expected["source_commit"] = report.head
if document.get("source_commit"):
new_text = text
if document.get("source_commit") == report.head:
pass
elif document.get("source_commit"):
new_text, _ = replace_field_line(
text, "source_commit", str(document["source_commit"]), report.head
new_text, "source_commit", str(document["source_commit"]), report.head
)
else:
new_text = insert_after_line(
text, "profiled_date", f'source_commit: "{report.head}"'
new_text, "profiled_date", f'source_commit: "{report.head}"'
)
if upstream_moves:
expected["upstream_commit"] = report.upstream_head
if document.get("upstream_commit"):
new_text, _ = replace_field_line(
new_text, "upstream_commit", str(document["upstream_commit"]),
report.upstream_head,
)
else:
new_text = insert_after_line(
new_text, "source_commit", f'upstream_commit: "{report.upstream_head}"'
)
apply_edit(path, new_text, expected)
return True
+31
View File
@@ -2483,6 +2483,22 @@ class TestBumpCommit(unittest.TestCase):
self.assertEqual(profile_sync.pending_recale(report), 0)
self.assertTrue(bump_commit(self.path, report))
def test_upstream_pin_moves_with_source_pin(self):
"""A recale onto upstream HEAD lines must advance upstream_commit too."""
part = PartResult(
RefPart("a.c", 10, 12, "a.c:10-12"), "ANCHORED", None, 10, 12, []
)
report = ProfileReport(
name="p", repo="o/n", pin="pin", head="newhead",
entries=[EntryReport("a.bin", "a.c:10-12", "ANCHORED", [part])],
counts={"ANCHORED": 1},
upstream_pin="uppin", upstream_head="uphead", upstream_origin="upstream_commit",
)
self.assertTrue(bump_commit(self.path, report))
written = yaml.safe_load(self.path.read_text())
self.assertEqual(written["source_commit"], "newhead")
self.assertEqual(written["upstream_commit"], "uphead")
def test_refused_when_pinned_to_a_superseded_tag(self):
report = ProfileReport(
name="p", repo="o/n", pin="pin", head="newhead",
@@ -3333,6 +3349,21 @@ class TestOutputModesApplyTheirFlags(unittest.TestCase):
self.assertIn("--check-version", err)
class TestReconcilePerFile(unittest.TestCase):
def test_an_anchored_part_settles_only_its_own_file(self):
anchored = PartResult(RefPart("libretro.c", 5, 5, "5"), "ANCHORED", None, None, None, [])
same = PartResult(RefPart("libretro.c", 9, 9, "9"), "AMBIGUOUS", None, None, None, [])
other = PartResult(RefPart("jg.c", 229, 230, "229-230"), "AMBIGUOUS", None, None, None, [])
result = profile_sync.reconcile_self_check([anchored, same, other])
self.assertEqual([p.status for p in result], ["ANCHORED", "ANCHORED", "AMBIGUOUS"])
def test_a_compared_part_is_never_rewritten(self):
anchored = PartResult(RefPart("a.c", 5, 5, "5"), "ANCHORED", None, None, None, [])
shifted = PartResult(RefPart("a.c", 9, 9, "9"), "SHIFTED", None, 12, 12, [])
result = profile_sync.reconcile_self_check([anchored, shifted], [True, False])
self.assertEqual(result[1].status, "SHIFTED")
class TestRealignFlagMatrix(unittest.TestCase):
"""--realign-prose applies a flag or refuses it, never swallows it."""