Pulse/scripts/release_control/internal/verify_commit_slice_test.py
rcourtman 971520a8e5 fix(release-control): scrub hook git env in remaining scratch-repo git users
The core.bare=true corruption of the shared repository recurred on
2026-07-17: a script that runs scratch git commands while the pre-commit
environment from a linked worktree (absolute GIT_DIR) is still exported
re-initializes the REAL repository as bare. a0fda6b26 fixed four helper
test files but missed two spots in scripts/release_control/internal:

- verify_commit_slice_test.py's git() helper only popped GIT_INDEX_FILE,
  so its scratch 'git init' calls re-init the real repo when GIT_DIR is
  inherited. It now scrubs via the shared repo_file_io.strip_local_git_env.
- verify_commit_slice.py's production git_env() kept the inherited hook
  env even when unit tests patch REPO_ROOT to a temporary repository,
  pointing git plumbing (including index writes) at the wrong repo. It
  now scrubs in the test-patched branch only, matching format_staged_go.

Regression teeth:
- verify_commit_slice_test.py gains a canary test that exports the real
  hook env shape (absolute GIT_DIR + GIT_INDEX_FILE, no GIT_WORK_TREE —
  with GIT_WORK_TREE set the corruption does not reproduce) against a
  scratch repo + linked worktree and asserts core.bare stays false.
- repo_file_io_test.py (runs in the pre-commit battery) gains a static
  guard failing any release-control *_test.py that runs scratch
  'git init' without referencing strip_local_git_env.
- The six hand-rolled 4-var pop loops from a0fda6b26 migrate to the
  shared strip_local_git_env helper so the guard enforces one pattern.

Verified: full release-control battery green; every touched test file
also green with GIT_DIR/GIT_INDEX_FILE pointed at a canary repo's linked
worktree, canary config and status intact afterward.
2026-07-17 16:39:17 +01:00

147 lines
5.9 KiB
Python

import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
INTERNAL_DIR = Path(__file__).resolve().parent
RELEASE_CONTROL_DIR = INTERNAL_DIR.parent
for extra_dir in (INTERNAL_DIR, RELEASE_CONTROL_DIR):
if str(extra_dir) not in sys.path:
sys.path.insert(0, str(extra_dir))
from repo_file_io import strip_local_git_env
from verify_commit_slice import main, repo_relative_path
class VerifyCommitSliceTest(unittest.TestCase):
def git(self, repo_root: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess:
# Scrub the full hook environment: with only GIT_INDEX_FILE removed, a
# pre-commit run from a linked worktree exports an absolute GIT_DIR and
# "git init" here re-initializes the REAL repository as bare.
env = strip_local_git_env(os.environ.copy())
return subprocess.run(
["git", *args],
cwd=repo_root,
check=check,
capture_output=True,
text=True,
env=env,
)
def init_repo(self, repo_root: Path) -> None:
self.git(repo_root, "init")
self.git(repo_root, "config", "user.name", "Pulse Test")
self.git(repo_root, "config", "user.email", "pulse-test@example.com")
def test_repo_relative_path_normalizes_absolute_paths(self) -> None:
repo_root = Path("/tmp/pulse")
with patch("verify_commit_slice.REPO_ROOT", repo_root):
self.assertEqual(repo_relative_path(repo_root / "docs/file.txt"), "docs/file.txt")
def test_main_stages_requested_paths_in_isolated_index(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
repo_root = Path(tmpdir)
hook = repo_root / ".husky" / "pre-commit"
tracked = repo_root / "tracked.txt"
explicit = repo_root / "explicit.txt"
ignored = repo_root / "ignored.txt"
hook.parent.mkdir(parents=True, exist_ok=True)
tracked.write_text("tracked\n", encoding="utf-8")
explicit.write_text("explicit\n", encoding="utf-8")
ignored.write_text("ignored\n", encoding="utf-8")
(repo_root / ".gitignore").write_text("ignored.txt\n", encoding="utf-8")
hook.write_text(
"#!/usr/bin/env bash\n"
"set -euo pipefail\n"
"git diff --cached --name-only | sort > slice.out\n",
encoding="utf-8",
)
hook.chmod(0o755)
self.init_repo(repo_root)
self.git(repo_root, "add", ".gitignore", "tracked.txt", ".husky/pre-commit")
self.git(repo_root, "commit", "-m", "initial")
tracked.write_text("tracked updated\n", encoding="utf-8")
with patch("verify_commit_slice.REPO_ROOT", repo_root), patch(
"verify_commit_slice.HOOK_PATH", hook
):
self.assertEqual(
main(["--add-updated", "explicit.txt", "ignored.txt"]),
0,
)
self.assertEqual(
(repo_root / "slice.out").read_text(encoding="utf-8").splitlines(),
["explicit.txt", "ignored.txt", "tracked.txt"],
)
live_index = self.git(repo_root, "diff", "--cached", "--name-only").stdout.strip()
self.assertEqual(live_index, "")
def test_main_propagates_hook_failure(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
repo_root = Path(tmpdir)
hook = repo_root / ".husky" / "pre-commit"
tracked = repo_root / "tracked.txt"
hook.parent.mkdir(parents=True, exist_ok=True)
tracked.write_text("tracked\n", encoding="utf-8")
hook.write_text("#!/usr/bin/env bash\nexit 7\n", encoding="utf-8")
hook.chmod(0o755)
self.init_repo(repo_root)
self.git(repo_root, "add", "tracked.txt", ".husky/pre-commit")
self.git(repo_root, "commit", "--no-verify", "-m", "initial")
tracked.write_text("tracked updated\n", encoding="utf-8")
with patch("verify_commit_slice.REPO_ROOT", repo_root), patch(
"verify_commit_slice.HOOK_PATH", hook
):
self.assertEqual(main(["--add-updated"]), 7)
def test_scratch_git_init_under_worktree_hook_env_leaves_hook_repo_intact(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
base = Path(tmpdir)
canary = base / "canary"
linked_worktree = base / "canary-worktree"
scratch = base / "scratch"
canary.mkdir()
scratch.mkdir()
self.init_repo(canary)
(canary / "tracked.txt").write_text("tracked\n", encoding="utf-8")
self.git(canary, "add", "tracked.txt")
self.git(canary, "commit", "--no-verify", "-m", "initial")
self.git(canary, "worktree", "add", str(linked_worktree), "-b", "hook-branch")
git_dir = self.git(
linked_worktree, "rev-parse", "--path-format=absolute", "--git-dir"
).stdout.strip()
# A pre-commit run from a linked worktree exports exactly this
# shape: absolute GIT_DIR plus GIT_INDEX_FILE, no GIT_WORK_TREE.
# (With GIT_WORK_TREE also set, "git init" keeps bare=false and
# the corruption does not reproduce.)
hook_env = {
"GIT_DIR": git_dir,
"GIT_INDEX_FILE": str(Path(git_dir) / "index"),
}
with patch.dict(os.environ, hook_env, clear=False):
self.git(scratch, "init")
self.assertTrue((scratch / ".git").is_dir())
config_text = (canary / ".git" / "config").read_text(encoding="utf-8")
self.assertNotIn("bare = true", config_text)
self.git(canary, "status")
self.git(linked_worktree, "status")
if __name__ == "__main__":
unittest.main()