Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).**
Expand Down
19 changes: 5 additions & 14 deletions src/bmad_loop/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down Expand Up @@ -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`.

Expand All @@ -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")


Expand Down
22 changes: 16 additions & 6 deletions tests/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down