Defuse the clone-and-run trap: tracked pipeline data warns loudly
Eric spotted it: the README told people to clone this repo, rm the committed data, and run — which writes THEIR pipeline artifacts at git-TRACKED paths. The next `git pull` (this repo commits data every session) refuses to merge, and the internet's standard remedies for that error — reset --hard, checkout ., stash, clean -fdx — destroy their review decisions, hand-written games, upload log, and photos. Two layers. The README's "Bring your own shelves" now leads with `uv tool install git+…` and running in a directory of your own: data lands untracked by construction and a bug fix is `uv tool upgrade`, which cannot touch it. And because nobody re-reads a README, Config gains tracked_data_warning(): if artifacts under data_dir are git-tracked, `bggpipe init` and the web dashboard both warn in plain words. The owner's exemption is data/.own_repo — a GITIGNORED marker, so the author's checkout is silent while a fresh clone of the same repo still gets the warning (a committed marker or config key would have shipped the exemption to exactly the people who need warning). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
co-authored by
Claude Fable 5
parent
77d330748d
commit
dec2bfc7b6
@@ -9,6 +9,7 @@ account has exactly one home.
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tomllib
|
||||
import warnings
|
||||
from dataclasses import dataclass, replace
|
||||
@@ -74,6 +75,38 @@ class Config:
|
||||
def games_path(self) -> Path:
|
||||
return self.data_dir / "games.json"
|
||||
|
||||
def tracked_data_warning(self) -> str | None:
|
||||
"""The clone-and-run trap: pipeline data written at git-TRACKED
|
||||
paths is one `git pull` + one panicked `git reset --hard` away
|
||||
from destruction. Warn whenever artifacts under data_dir are
|
||||
tracked — unless the owner has marked this checkout as their own
|
||||
repo (the marker is gitignored, so a fresh clone never inherits
|
||||
the exemption)."""
|
||||
if (self.data_dir / ".own_repo").exists():
|
||||
return None
|
||||
try:
|
||||
tracked = subprocess.run( # noqa: S603
|
||||
["git", "ls-files", "--", str(self.data_dir)], # noqa: S607
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
cwd=self.data_dir.resolve().parent,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired):
|
||||
return None # no git, no repo, no trap
|
||||
if tracked.returncode != 0 or not tracked.stdout.strip():
|
||||
return None
|
||||
return (
|
||||
f"your pipeline data ({self.data_dir}) sits at git-TRACKED "
|
||||
"paths — a `git pull` here can refuse to merge, and the usual "
|
||||
"remedies (reset --hard, checkout ., stash) would destroy your "
|
||||
"review decisions, hand-written games, and upload log. Run "
|
||||
"bggpipe from a directory outside this repository (see README: "
|
||||
"Bring your own shelves). If this repo is genuinely where you "
|
||||
f"version your own data, `touch {self.data_dir}/.own_repo` to "
|
||||
"accept the arrangement and silence this warning."
|
||||
)
|
||||
|
||||
@property
|
||||
def local_games_path(self) -> Path:
|
||||
# hand-written metadata for games BGG doesn't have — the only
|
||||
|
||||
@@ -88,6 +88,7 @@ class InitReport:
|
||||
keys_written: list[str] = field(default_factory=list)
|
||||
keys_missing: list[str] = field(default_factory=list)
|
||||
browser_installed: bool = False
|
||||
warnings: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def _env_file_keys(env_path: Path) -> set[str]:
|
||||
@@ -153,6 +154,9 @@ def run_init(
|
||||
|
||||
report = InitReport()
|
||||
typer.echo("bggpipe setup — checks what exists, fills only the gaps.\n")
|
||||
if trap := cfg.tracked_data_warning():
|
||||
typer.echo(f"WARNING: {trap}\n", err=True)
|
||||
report.warnings.append(trap)
|
||||
|
||||
# -- directories ----------------------------------------------------
|
||||
for path in (project_dir / cfg.photos_dir, project_dir / cfg.data_dir):
|
||||
|
||||
@@ -354,6 +354,9 @@ def create_app(
|
||||
# server-side degradations, shown in-browser (quarantined dismiss file,
|
||||
# unreadable thumbnails, torn artifacts)
|
||||
app_warnings: list[str] = list(startup_notes)
|
||||
if trap := cfg.tracked_data_warning():
|
||||
typer.echo(f"WARNING: {trap}", err=True)
|
||||
app_warnings.append(trap)
|
||||
|
||||
def warn_once(note: str) -> None:
|
||||
if note not in app_warnings:
|
||||
|
||||
Reference in New Issue
Block a user