← refacto-feature-scout
"""Keep the cloud's copy of each app in step with Library Mode (the source of truth)."""
from __future__ import annotations
import os
import subprocess
from pathlib import Path
from urllib.parse import quote, urlsplit, urlunsplit
from .config import CLOUD, load_env
from .projects import Project
SCOUT_NAME = os.environ.get("SCOUT_GIT_NAME", "Feature Scout")
SCOUT_EMAIL = os.environ.get("SCOUT_GIT_EMAIL", "feature-scout@localhost")
def auth_url(url: str, token: str, user: str = "scout") -> str:
"""Library Mode accepts the device token as HTTP Basic with any username."""
parts = urlsplit(url)
return urlunsplit((parts.scheme, f"{quote(user)}:{quote(token, safe='')}@{parts.netloc}", parts.path, "", ""))
def _git(repo: Path, *args: str, check: bool = True, timeout: int = 600) -> subprocess.CompletedProcess:
return subprocess.run(["git", "-C", str(repo), *args], capture_output=True, text=True, check=check, timeout=timeout)
def ensure_clone(project: Project, env: dict[str, str] | None = None) -> Path:
"""Clone the app from Library Mode if this machine doesn't have it yet."""
repo = project.folder
if (repo / ".git").exists():
return repo
if not project.librarymode_url:
raise RuntimeError(f"{project.name}: no Library Mode address configured")
env = env or load_env()
token = env.get("LIBRARYMODE_TOKEN")
if not token:
raise RuntimeError("LIBRARYMODE_TOKEN is not set, so I cannot fetch the app's code")
repo.parent.mkdir(parents=True, exist_ok=True)
subprocess.run(["git", "clone", "-q", "--origin", "librarymode", auth_url(project.librarymode_url, token), str(repo)],
check=True, capture_output=True, text=True, timeout=900)
_git(repo, "config", "user.name", SCOUT_NAME)
_git(repo, "config", "user.email", SCOUT_EMAIL)
return repo
def sync_main(project: Project) -> str:
"""Cloud only: make the mirror's main exactly Library Mode's main. Returns the new HEAD."""
if not CLOUD:
raise RuntimeError("sync_main must never run against Ken's own checkout")
repo = ensure_clone(project)
_git(repo, "fetch", "-q", "librarymode")
_git(repo, "checkout", "-q", "-B", "main", "librarymode/main")
return _git(repo, "rev-parse", "HEAD").stdout.strip()
def push_main(project: Project) -> tuple[bool, str]:
"""Send the mirror's main to Library Mode as a new version. (ok, message)."""
p = _git(project.folder, "push", "-q", "librarymode", "main:main", check=False)
return p.returncode == 0, (p.stderr + p.stdout).strip()