Files

100 lines
2.8 KiB
Python
Raw Permalink Normal View History

2026-05-18 09:41:16 +08:00
#!/usr/bin/env python3
"""Install the opt-in local git hook workflow for the C++ template project."""
from __future__ import annotations
import argparse
import os
import pathlib
import stat
import subprocess
from collections.abc import Sequence
from typing import cast
from lib import common
STATUS_PASS = "PASS"
STATUS_FAIL = "FAIL"
def main(argv: Sequence[str] | None = None) -> int:
args = build_parser().parse_args(argv)
root = common.find_project_root(pathlib.Path(__file__))
hooks_dir = root / ".githooks"
hook_path = hooks_dir / "pre-commit"
status = cast(bool, args.status)
dry_run = cast(bool, args.dry_run)
if status:
return report_status(root, hooks_dir, hook_path)
plan = build_plan(root)
if dry_run:
common.print_plan(plan)
print(f"{STATUS_PASS}: dry-run planned opt-in local hook setup")
return 0
print("local git hook setup")
common.print_plan(plan)
hooks_dir.mkdir(parents=True, exist_ok=True)
if not hook_path.is_file():
print(f"{STATUS_FAIL}: expected hook file is missing: {hook_path}")
return 2
ensure_executable(hook_path)
return common.run_plans(plan, dry_run=False)
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
_ = parser.add_argument("--dry-run", action="store_true", help="Print planned setup without writing hooks or git config.")
_ = parser.add_argument("--status", action="store_true", help="Report local hook status without mutating files or git config.")
return parser
def build_plan(root: pathlib.Path) -> tuple[common.CommandPlan, ...]:
return (
common.CommandPlan(
label="set-local-hooks-path",
command=("git", "config", "--local", "core.hooksPath", ".githooks"),
cwd=root,
required_tool="git",
),
)
def report_status(root: pathlib.Path, hooks_dir: pathlib.Path, hook_path: pathlib.Path) -> int:
print(f"hooks_dir: {hooks_dir}")
print(f"pre_commit_hook_exists: {hook_path.is_file()}")
print(f"pre_commit_hook_executable: {os.access(hook_path, os.X_OK)}")
print(f"local core.hooksPath: {local_hooks_path(root)}")
return 0
def local_hooks_path(root: pathlib.Path) -> str:
result = subprocess.run(
["git", "config", "--local", "--get", "core.hooksPath"],
cwd=str(root),
check=False,
capture_output=True,
text=True,
)
if result.returncode == 0:
return result.stdout.strip()
return "<unset>"
def ensure_executable(path: pathlib.Path) -> None:
current_mode = path.stat().st_mode
executable_bits = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
if current_mode & executable_bits == executable_bits:
return
path.chmod(current_mode | executable_bits)
if __name__ == "__main__":
raise SystemExit(main())