mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-04 13:23:28 +00:00
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.a0fda6b26fixed 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 froma0fda6b26migrate 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.
104 lines
3.1 KiB
Python
104 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Run pre-commit against a proposed commit slice in an isolated git index."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from typing import Iterable
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
DEFAULT_REPO_ROOT = REPO_ROOT
|
|
HOOK_PATH = REPO_ROOT / ".husky" / "pre-commit"
|
|
|
|
|
|
def git_env(index_path: Path) -> dict[str, str]:
|
|
env = os.environ.copy()
|
|
# Unit tests patch REPO_ROOT to a temporary repository. In that case, the
|
|
# inherited hook environment (the absolute GIT_DIR a pre-commit run from a
|
|
# linked worktree exports) must not point git plumbing at a different
|
|
# repository than the patched one.
|
|
if REPO_ROOT != DEFAULT_REPO_ROOT:
|
|
for name in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_COMMON_DIR"):
|
|
env.pop(name, None)
|
|
env["GIT_INDEX_FILE"] = str(index_path)
|
|
return env
|
|
|
|
|
|
def repo_relative_path(path: str | Path) -> str:
|
|
candidate = Path(path)
|
|
if candidate.is_absolute():
|
|
candidate = candidate.relative_to(REPO_ROOT)
|
|
return candidate.as_posix()
|
|
|
|
|
|
def git(index_path: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
["git", *args],
|
|
cwd=REPO_ROOT,
|
|
check=check,
|
|
capture_output=True,
|
|
text=True,
|
|
env=git_env(index_path),
|
|
)
|
|
|
|
|
|
def stage_paths(index_path: Path, paths: Iterable[str]) -> None:
|
|
normalized = [repo_relative_path(path) for path in paths]
|
|
if not normalized:
|
|
return
|
|
git(index_path, "add", "-f", "--", *normalized)
|
|
|
|
|
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Verify a proposed commit slice by running .husky/pre-commit in a copied index."
|
|
)
|
|
parser.add_argument(
|
|
"--add-updated",
|
|
action="store_true",
|
|
help="Stage all tracked modifications (`git add -u`) into the copied index before explicit paths.",
|
|
)
|
|
parser.add_argument(
|
|
"--show-staged",
|
|
action="store_true",
|
|
help="Print the staged file list in the copied index before running the hook.",
|
|
)
|
|
parser.add_argument(
|
|
"paths",
|
|
nargs="*",
|
|
help="Explicit repo-relative or absolute paths to stage into the copied index.",
|
|
)
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = parse_args(list(argv or sys.argv[1:]))
|
|
with tempfile.NamedTemporaryFile(prefix="pulse-index.", delete=True) as tmp_index:
|
|
index_path = Path(tmp_index.name)
|
|
git(index_path, "read-tree", "HEAD")
|
|
if args.add_updated:
|
|
git(index_path, "add", "-u")
|
|
stage_paths(index_path, args.paths)
|
|
|
|
if args.show_staged:
|
|
staged = git(index_path, "diff", "--cached", "--name-only").stdout.strip()
|
|
if staged:
|
|
print(staged)
|
|
|
|
result = subprocess.run(
|
|
[str(HOOK_PATH)],
|
|
cwd=REPO_ROOT,
|
|
env=git_env(index_path),
|
|
text=True,
|
|
)
|
|
return result.returncode
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|