Add git commit with attribution on every page/story save and delete

This commit is contained in:
brutsalvadi
2026-05-07 22:28:48 +02:00
parent 9da4094f72
commit dd69a6b117
+49 -4
View File
@@ -43,6 +43,41 @@ _SAFE_SLUG = re.compile(r"^[a-zA-Z0-9_][a-zA-Z0-9_\-/]*$")
_ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"} _ALLOWED_IMAGE_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
_MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB _MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB
# Git attribution — on VPS the work tree has no .git dir; bare repo is separate.
# Set GIT_DIR to the bare repo path in the systemd service; GIT_WORK_TREE is _ROOT.
_GIT_DIR = os.environ.get("GIT_DIR")
_git_lock = asyncio.Lock()
async def _git_commit(file_path: Path, handle: str, verb: str) -> None:
rel = str(file_path.relative_to(_ROOT))
env = os.environ.copy()
if _GIT_DIR:
env["GIT_DIR"] = _GIT_DIR
env["GIT_WORK_TREE"] = str(_ROOT)
env.setdefault("GIT_COMMITTER_NAME", "bincio-wiki")
env.setdefault("GIT_COMMITTER_EMAIL", "wiki@bincio.wiki")
try:
async with _git_lock:
add = await asyncio.create_subprocess_exec(
"git", "add", rel,
cwd=str(_ROOT), env=env,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
await add.wait()
commit = await asyncio.create_subprocess_exec(
"git", "commit",
"-m", f"{handle}: {verb} {file_path.stem}",
"--author", f"{handle} <{handle}@bincio.wiki>",
cwd=str(_ROOT), env=env,
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
await commit.wait()
except Exception as e:
print(f"[git] commit failed for {rel}: {e}", flush=True)
def _unique_name(directory: Path, filename: str) -> str: def _unique_name(directory: Path, filename: str) -> str:
stem, suffix = Path(filename).stem, Path(filename).suffix stem, suffix = Path(filename).stem, Path(filename).suffix
@@ -260,11 +295,16 @@ async def get_page(slug: str, user: User = Depends(require_auth)) -> JSONRespons
@app.post("/pages/{slug:path}") @app.post("/pages/{slug:path}")
async def save_page(slug: str, body: PageBody, user: User = Depends(require_auth)) -> JSONResponse: async def save_page(slug: str, body: PageBody, user: User = Depends(require_auth)) -> JSONResponse:
return _save(slug, body, pages_dir) result = _save(slug, body, pages_dir)
await _git_commit(_slug_to_path(slug, pages_dir), user.handle, "edited")
return result
@app.delete("/pages/{slug:path}") @app.delete("/pages/{slug:path}")
async def delete_page(slug: str, user: User = Depends(require_auth)) -> JSONResponse: async def delete_page(slug: str, user: User = Depends(require_auth)) -> JSONResponse:
return _delete(slug, pages_dir) path = _slug_to_path(slug, pages_dir)
result = _delete(slug, pages_dir)
await _git_commit(path, user.handle, "deleted")
return result
# ── Story endpoints ─────────────────────────────────────────────────────────── # ── Story endpoints ───────────────────────────────────────────────────────────
@@ -279,11 +319,16 @@ async def get_story(slug: str, user: User = Depends(require_auth)) -> JSONRespon
@app.post("/stories/{slug:path}") @app.post("/stories/{slug:path}")
async def save_story(slug: str, body: PageBody, user: User = Depends(require_auth)) -> JSONResponse: async def save_story(slug: str, body: PageBody, user: User = Depends(require_auth)) -> JSONResponse:
return _save(slug, body, stories_dir) result = _save(slug, body, stories_dir)
await _git_commit(_slug_to_path(slug, stories_dir), user.handle, "edited")
return result
@app.delete("/stories/{slug:path}") @app.delete("/stories/{slug:path}")
async def delete_story(slug: str, user: User = Depends(require_auth)) -> JSONResponse: async def delete_story(slug: str, user: User = Depends(require_auth)) -> JSONResponse:
return _delete(slug, stories_dir) path = _slug_to_path(slug, stories_dir)
result = _delete(slug, stories_dir)
await _git_commit(path, user.handle, "deleted")
return result
# ── Asset upload ───────────────────────────────────────────────────────────── # ── Asset upload ─────────────────────────────────────────────────────────────