diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 17ea9426..f8e050e9 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -873,6 +873,12 @@ def _written_against_head(part: RefPart, head_lines: list[str] | None) -> bool: ) +# What the rename search answers when a tree it needed came back truncated: +# a file missing from a partial listing may still exist, so absence is not +# established. eden's fs_paths.h and path_util.cpp were reported GONE. +TREE_TRUNCATED = "(HEAD tree truncated by the forge)" + + def anchor_part( part: RefPart, fetch: Callable[[str, str, int | None, Sequence[str]], list[str] | None], @@ -906,6 +912,13 @@ def anchor_part( if head_lines is None: moved, candidates = rename_getter(part.path) if moved is None: + if candidates == [TREE_TRUNCATED]: + return PartResult( + part, "AMBIGUOUS", None, None, None, [], + "absent from a HEAD tree the forge truncated: " + "the file may still exist", + slug, url, + ) if candidates: return PartResult( part, @@ -1579,6 +1592,7 @@ def build_report( owners: dict[tuple[str, int | None], tuple[RepoView, str]] = {} context: dict[str, object] = {} + truncated_trees: set[str] = set() pin_trees: dict[str, list[str] | None] = {} def _locate( @@ -1745,6 +1759,8 @@ def build_report( view.repo, view.head, cache_dir, offline ) context[key] = (comparison, tree) + if truncated: + truncated_trees.add(key) if truncated and tree: print( f"{name}: {key} HEAD tree truncated by the forge, " @@ -1772,6 +1788,8 @@ def build_report( # across repositories turns a resolvable path into an ambiguous one. if view is owner: candidates = near + if not candidates and any(v.repo.slug in truncated_trees for v in ordered): + return None, [TREE_TRUNCATED] return None, candidates # Pin trees before HEAD trees, each read only when reached. diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 9701e9e2..2a42e857 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -1245,6 +1245,19 @@ class TestBuildReport(unittest.TestCase): self.assertEqual(statuses, ["UNCHECKED"]) self.assertIn("refused", report.entries[0].parts[0].reason) + def test_a_truncated_head_tree_is_not_an_absence(self): + """git.eden-emu.dev truncates the HEAD tree: fs_paths.h, cited bare, + was reported GONE though it lives under src/common/fs/.""" + self.files[("pinsha", "fs_paths.h")] = ["x", "hit"] + profile_sync.upstream.list_tree = ( + lambda repo, sha, cache_dir, offline=False: (["other.c"], True) + ) + with contextlib.redirect_stderr(io.StringIO()): + report = build_report("test", self._profile(["fs_paths.h:2"]), self.dir) + part = report.entries[0].parts[0] + self.assertEqual(part.status, "AMBIGUOUS") + self.assertIn("truncated", part.reason) + def test_counts_by_status(self): self.files[("pinsha", "a.c")] = ["x", "hit", "y"] self.files[("headsha", "a.c")] = ["pad", "x", "hit", "y"]