61 lines
2.2 KiB
Python
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
|