oct.git.commit_observer module#

Purpose#

Detect silent mutations of tracked-but-unstaged files during the oct git commit pipeline (B1.0). Snapshots the working-tree state of every tracked file outside the staged set at the start of the commit, then compares again right before and right after the underlying git commit invocation. Any byte-level change is reported as either a warning or a hard abort, depending on the git.observe_unstaged_mutation octrc setting.

Responsibilities#

  • Provide snapshot_unstaged_tracked() — returns a mapping of project-relative path -> sha256 hex digest for every tracked file not in the staged set.

  • Provide diff_snapshots() — returns the list of paths whose hash changed (new file, deleted file, or content drift) between two snapshots.

  • Be defensive: large files (> _MAX_HASH_BYTES) are recorded with the sentinel digest "OVERSIZE" so they don’t dominate commit latency, and a missing/unreadable file maps to "" so the diff path treats it consistently.

Diagnostics#

Domain: GIT Levels:

L1 — errors: file IO failures during snapshot L2 — lifecycle: snapshot start / snapshot complete L3 — details: file count, total bytes hashed L4 — deep trace: per-file hash + size

Contracts#

  • This module is pure: no Click, no subprocess of its own. It uses oct.core.git.git_run() to enumerate tracked files.

  • Snapshot calls must never raise on individual-file IO errors — they record the file as unreadable and continue.

  • The hash digest is content-only (no metadata). Renames are detected via the path key, not the digest.

oct.git.commit_observer.diff_snapshots(before: dict[str, str], after: dict[str, str]) list[str][source]#

Return the list of paths whose digest changed between snapshots.

Includes:
  • Paths present in both with different digests (mutation).

  • Paths present in before but missing from after (deletion or unreadable).

  • Paths newly tracked in after (rare; would only appear if git add ran during the commit pipeline).

A path with the same _OVERSIZE_SENTINEL on both sides is NOT flagged — we cannot tell whether the content of an oversize file changed without a full hash. Document this caveat to the caller via the per-file payload (omitted from the simple list return; see diff_snapshots_detail() for the verbose form).

oct.git.commit_observer.snapshot_unstaged_tracked(repo_root: Path, staged_set: set[str] | None = None) dict[str, str][source]#

Return {rel_path: sha256_hex} for every tracked-but-unstaged file.

repo_root must be the actual git repository root (not the OCT project root in monorepo layouts). staged_set is a set of repo-relative path strings already in the index; those files are excluded from the snapshot because their mutations are caller intent, not silent regression.

The snapshot includes:
  • Tracked files that are clean (working tree == HEAD).

  • Tracked files modified but not staged (M in git status -s second column).

The snapshot excludes:
  • Files in staged_set.

  • Untracked files (??).

  • Submodules and worktree symlinks pointing outside repo_root.