Files
brain-of-reese/scripts/git_sync.py
T

61 lines
2.2 KiB
Python

"""Git source sync for import_docs (phase 28).
clone_or_pull(url, dest) clones ``url`` into ``dest`` (shallow, depth 1)
the first time, or fast-forwards an existing checkout with ``git pull
--ff-only`` on subsequent runs.
Auth: nothing special — an ``https://…`` URL uses the OS credential
helper / prompts; a ``git@host:repo.git`` URL uses the machine's SSH key.
No credentials are stored here; whatever the URL/SSH config supplies is
used.
This module is the only place the ``git`` CLI is invoked (A11: stdlib
``subprocess`` only, no new packages).
"""
from __future__ import annotations
import subprocess
from pathlib import Path
__all__ = ["GitSyncError", "clone_or_pull"]
class GitSyncError(RuntimeError):
"""A git clone/pull failed (or git is missing); carries git's stderr."""
def clone_or_pull(url: str, dest: Path | str) -> Path:
"""Clone ``url`` into ``dest`` (shallow, first run) or fast-forward it.
- dest without a ``.git`` (or absent) → ``git clone --depth 1 url dest``
(shallow: the KB is re-imported incrementally anyway).
- dest with a ``.git`` → ``git pull --ff-only`` (refuses to merge
unrelated histories — a broken checkout fails loudly rather than
producing a dirty index).
Returns the destination path. Raises :class:`GitSyncError` when git is
missing or a git invocation exits non-zero (with git's stderr in the
message, so the caller can name the failing repo + reason).
"""
dest = Path(dest)
if not dest.exists() or not (dest / ".git").exists():
dest.parent.mkdir(parents=True, exist_ok=True)
_run(["git", "clone", "--depth", "1", url, str(dest)], cwd=dest.parent)
else:
_run(["git", "pull", "--ff-only"], cwd=dest)
return dest
def _run(argv: list[str], cwd: Path) -> str:
"""Run a git command, capturing output; raise GitSyncError on failure."""
try:
proc = subprocess.run(argv, cwd=cwd, capture_output=True, text=True)
except FileNotFoundError:
raise GitSyncError("git was not found on PATH — install git and retry") from None
if proc.returncode != 0:
raise GitSyncError(
f"git {' '.join(argv[1:])} failed (exit {proc.returncode}): "
f"{proc.stderr.strip()}"
)
return proc.stdout