fix(metablog-webhook): pass safe.directory to git on every call (ref #117)

Site dirs at /srv/metablogizer/sites/* are owned by root (from sub-B
ingest), but the FastAPI service runs as secubox. Git 2.35+ refuses
to operate with `fatal: detected dubious ownership in repository`,
turning every webhook-triggered deploy into a 500.

Pass `-c safe.directory=<site_dir>` per-invocation. Scoped to each
git call — no global git config change.

Existing git_pull test updated for the new arg layout
([git, -c, safe.directory=…, -C, <site>, <op>, ...]) and asserts the
override is on every call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-05-13 07:43:18 +02:00
parent f128e8fe8f
commit 6b0c3fa7b5
2 changed files with 15 additions and 3 deletions

View File

@ -127,9 +127,16 @@ def test_git_pull_returns_old_and_new_sha(tmp_path, monkeypatch):
old, new = webhook.git_pull(site, "main")
assert old == "aaa1111"
assert new == "bbb2222"
# ensure the 4 expected git commands happened in order
op_names = [c[3] for c in calls]
# ensure the 4 expected git commands happened in order. The git arg
# layout is now [git, -c, safe.directory=..., -C, <site>, <op>, ...]
# so the op verb is at index 5.
op_names = [c[5] for c in calls]
assert op_names == ["rev-parse", "fetch", "reset", "rev-parse"]
# every call must carry the safe.directory override (fixes dubious-ownership
# under the secubox service user on root-owned site dirs).
for c in calls:
assert c[1] == "-c"
assert c[2] == f"safe.directory={site}"
def test_git_pull_timeout_propagates(tmp_path, monkeypatch):

View File

@ -58,9 +58,14 @@ def git_pull(site_dir: Path, branch: str) -> tuple[str, str]:
Raises subprocess.TimeoutExpired or CalledProcessError on git failure.
"""
# Per-invocation safe.directory: site dirs are owned by root (from sub-B
# ingest) but the service runs as secubox; without this, git 2.35+
# refuses with "fatal: detected dubious ownership".
safe_dir = f"safe.directory={site_dir}"
def _git(*args: str, timeout: int = GIT_OP_TIMEOUT) -> str:
result = subprocess.run(
["git", "-C", str(site_dir), *args],
["git", "-c", safe_dir, "-C", str(site_dir), *args],
capture_output=True, text=True, timeout=timeout, check=True,
)
return result.stdout.strip()