asa / scripts /lib /git_ops.py
defenestrate's picture
ASA Space: squashed history (instructions and settings live in the private pack)
526950e
Raw History Blame Contribute Delete
2.31 kB
# scripts/lib/git_ops.py
"""Narrow git wrapper: subprocess calls, no third-party library."""
from __future__ import annotations
import subprocess
from pathlib import Path
from typing import Iterable
def _run(args: list[str], *, cwd: Path | None = None, check: bool = True) -> subprocess.CompletedProcess:
return subprocess.run(
["git", *args], cwd=cwd, check=check, capture_output=True, text=True
)
def current_commit() -> str:
return _run(["rev-parse", "HEAD"]).stdout.strip()
def worktree_clean(*, allowlist: Iterable[str] | None = None) -> bool:
"""True if `git status --porcelain=v1` is empty after filtering allowlisted paths.
Allowlist entries match either an exact path or a directory prefix (`<entry>/...`).
Limitations:
- Renames (`R old -> new` lines) are not parsed; a rename of an allowlisted path
will be reported as unclean. Callers in this project don't rename tracked
paths during /refresh, so this is acceptable.
- Paths containing spaces or non-ASCII characters may be quoted by git
(`core.quotepath` default), breaking exact-match comparison. Project-controlled
paths (index/, derived/, surfaces/, STATE.json) are ASCII without spaces.
"""
out = _run(["status", "--porcelain=v1"]).stdout
if not out.strip():
return True
if allowlist is None:
return False
allowed = set(allowlist)
for line in out.splitlines():
path = line[3:].strip()
if path not in allowed and not any(path.startswith(p + "/") for p in allowed):
return False
return True
def pull() -> None:
_run(["pull", "--ff-only"])
def push() -> None:
_run(["push"])
def add_and_commit(paths: list[str], message: str) -> None:
_run(["add", "--", *paths])
_run(["commit", "-m", message])
def add_and_commit_if_changes(paths: list[str], message: str) -> bool:
"""Stage paths and commit only if there are staged changes. Returns True if a commit was made."""
_run(["add", "--", *paths])
# `git diff --cached --quiet` exits 0 if no staged changes, 1 if there are staged changes.
result = _run(["diff", "--cached", "--quiet", "--", *paths], check=False)
if result.returncode == 0:
return False
_run(["commit", "-m", message])
return True