diff --git a/CHANGELOG.md b/CHANGELOG.md index 501ca603b..b6aa21fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ breaking changes may land in a minor release. ## [Unreleased] +### Removed + +- **`verify.same_commit` is gone — nothing called it.** #645's `_canonical_commit_oid` displaced + its last call site and compares canonical full object ids exactly. The helper's leftover + prefix-tolerant equality — either argument a prefix of the other once both reach 7 characters — + would have handed that looseness to whichever caller reached for it next. + ### Fixed - **psmux: a hand-back that succeeded no longer reports as failed — or undoes itself (#659).** diff --git a/src/bmad_loop/verify.py b/src/bmad_loop/verify.py index 3dbd58b89..9a08c6b99 100644 --- a/src/bmad_loop/verify.py +++ b/src/bmad_loop/verify.py @@ -252,8 +252,8 @@ def git_bytes( def rev_parse_head(repo: Path) -> str: """The sha HEAD resolves to. Reads stdout alone (`_git_out`): git exits 0 while - still warning on stderr, and a warning-suffixed "sha" flows into `same_commit` - comparisons and into persisted run baselines (#442).""" + still warning on stderr, and a warning-suffixed "sha" flows into every commit + comparison and into persisted run baselines (#442).""" rc, out, detail = _git_out(repo, "rev-parse", "HEAD") if rc != 0: raise GitError(f"git rev-parse HEAD failed in {repo}: {detail}") @@ -316,17 +316,6 @@ def worktree_clean(repo: Path) -> bool: return proc.stdout.strip() == "" -def same_commit(a: str, b: str) -> bool: - """Hash equality tolerant of abbreviated forms (>= 7 chars); sessions - sometimes report `git rev-parse --short HEAD`. The 7 is git's *minimum* auto - abbreviation, not a fixed default: `core.abbrev` defaults to `auto`, which - scales the length with repository size and clamps upward to 7 only for small - repos, so there is no single "default --short length" to mirror.""" - if len(a) < 7 or len(b) < 7: - return a == b - return a.startswith(b) or b.startswith(a) - - def is_ancestor(repo: Path, ancestor: str, descendant: str) -> bool: """True when `ancestor` is an ancestor of (or equal to) `descendant`. @@ -346,7 +335,9 @@ def is_ancestor(repo: Path, ancestor: str, descendant: str) -> bool: # session stamped it. Hex spelling is necessary but insufficient: Git also permits # all-hex ref names. The stamp is `git rev-parse HEAD` output by contract, so requiring # a uniquely disambiguated direct commit costs a well-behaved session nothing. Length -# floor mirrors `same_commit`'s 7; ceiling admits sha256. +# floor is git's shortest auto abbreviation: with `core.abbrev` unset the length scales +# with the repository's object count and clamps upward to 7 only for small repos, so +# nothing git abbreviates on its own is shorter. Ceiling admits sha256. _OBJECT_ID = re.compile(r"\A[0-9a-fA-F]{7,64}\Z") diff --git a/tests/test_verify.py b/tests/test_verify.py index a25e14dd9..aeeac33e3 100644 --- a/tests/test_verify.py +++ b/tests/test_verify.py @@ -122,7 +122,17 @@ def test_attempt_dirty_tracked_change(project): def test_file_bytes_at_revision_distinguishes_blob_absence_tree_and_git_failure(project): - """The baseline oracle returns only proven blob bytes, never tree listings.""" + """The baseline oracle returns only proven blob bytes, never tree listings. + + Ablation: drop either side of ``entry is None or entry[1] != "blob"`` from + either oracle — four mutations, each reddening exactly one of the four ``is + None`` assertions. The absence side raises ``TypeError`` on the ``missing.bin`` + case; the type side reddens the ``oracle`` case, and only there do the two + oracles differ. Plain ``cat-file blob`` is refused by git on a tree oid, so + that mutation still fails loudly; ``cat-file --filters`` instead renders the + tree's listing and hands it back as file content, which nothing but this + clause keeps out of a baseline comparison. + """ repo = project.project nested = repo / "oracle" / "spec.bin" nested.parent.mkdir() @@ -2001,10 +2011,10 @@ def test_verify_dev_bundle_single_char_ref_baseline_is_refused(project): def test_verify_dev_bundle_below_floor_abbreviation_is_refused(project): """Characterizes the deliberate 7-character floor on the bundle path: an abbreviation git itself resolves is still refused when it is shorter than - ``_OBJECT_ID``'s floor. That floor mirrors ``same_commit``'s 7; it is not a - length git derives (``core.abbrev`` defaults to ``auto``, which scales with - repository size and clamps upward to 7 only for small repos, so there is no - fixed default to mirror). The stamp is ``git rev-parse HEAD`` output by + ``_OBJECT_ID``'s floor. That 7 is the gate's own constant, set where git's + auto abbreviation bottoms out (``core.abbrev`` defaults to ``auto``, which + scales with repository size and clamps upward to 7 only for small repos, so + there is no fixed default to mirror). The stamp is ``git rev-parse HEAD`` output by contract, so the floor costs a well-behaved session nothing, and accepting shorter claims would re-admit prefix collisions the gate cannot distinguish from drift. The refusal is the floor's doing, not an @@ -3682,7 +3692,7 @@ def noisy(cmd, repo, **kw): def test_rev_parse_head_reads_stdout_alone_under_host_noise(project): - """A warning-suffixed "sha" is not a sha. It reaches `same_commit` comparisons + """A warning-suffixed "sha" is not a sha. It reaches every commit comparison and the baselines persisted in run state, so a resume grades a warning-carrying string against a clean one and reads "moved" — silent, with a plausible-looking value.