clawbench/scripts/git_checkpoint.py
scoootscooob 8a5be9c686 clawbench: per-sweep cache archiving + generic sweep templates
- scripts/_archive_cache.sh: snapshot run_cache/<model>/ to
  run_cache_archive/<sweep_tag>/ at sweep exit with metadata.json.
  Sourced by sweep scripts so transcripts survive the next sweep's
  cache wipe and stay available for audits.
- scripts/container_sweep_single.sh: base multi-model sweep.
  Adds CACHE_SUB entries for claude-opus-4-7 / claude-sonnet-4-7 so
  their caches are force-cleared at sweep start. Calls archive helper
  on exit.
- scripts/container_sweep_minimal.sh: 1-run-per-task variant for fast
  fix validation (~20 min) instead of full 3-run sweep (~60 min).
- Dockerfile.main: parametrized clawbench-on-openclaw image with
  ARG BASE for pinning to any openclaw tag.
- scripts/git_checkpoint.py + README: documented checkpoint workflow
  for tagging known-good states during risky work.
- .gitignore: un-ignore scripts/, keep targeted ignores for
  __pycache__, .tmp, .local.py.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-18 12:46:45 -07:00

115 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""Create an annotated git checkpoint tag for a clean working tree."""
from __future__ import annotations
import argparse
import re
import subprocess
import sys
from datetime import datetime
from pathlib import Path
def run_git(args: list[str], repo_root: Path, capture_output: bool = True) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["git", *args],
cwd=repo_root,
check=True,
text=True,
capture_output=capture_output,
)
def repo_root() -> Path:
try:
result = subprocess.run(
["git", "rev-parse", "--show-toplevel"],
check=True,
text=True,
capture_output=True,
)
except subprocess.CalledProcessError as exc:
raise SystemExit("Not inside a git repository.") from exc
return Path(result.stdout.strip())
def sanitize_label(label: str) -> str:
slug = re.sub(r"[^a-z0-9]+", "-", label.strip().lower()).strip("-")
if not slug:
raise SystemExit("Checkpoint name must contain at least one letter or number.")
return slug[:48]
def ensure_clean_worktree(root: Path) -> None:
status = run_git(["status", "--porcelain"], root).stdout.strip()
if status:
raise SystemExit(
"Working tree is not clean. Commit or stash your changes first, or rerun with --allow-dirty."
)
def current_branch(root: Path) -> str:
return run_git(["rev-parse", "--abbrev-ref", "HEAD"], root).stdout.strip()
def tag_exists(root: Path, tag_name: str) -> bool:
result = run_git(["tag", "--list", tag_name], root)
return result.stdout.strip() == tag_name
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Create an annotated checkpoint tag for the current HEAD commit."
)
parser.add_argument("name", help="Human-readable checkpoint name, e.g. 'before benchmark rerun'.")
parser.add_argument(
"--allow-dirty",
action="store_true",
help="Allow tagging even if the working tree has uncommitted changes.",
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Print the tag that would be created without modifying git state.",
)
return parser
def main() -> int:
parser = build_parser()
args = parser.parse_args()
root = repo_root()
if not args.allow_dirty:
ensure_clean_worktree(root)
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
branch = current_branch(root)
slug = sanitize_label(args.name)
tag_name = f"checkpoint/{timestamp}-{slug}"
if tag_exists(root, tag_name):
raise SystemExit(f"Checkpoint tag already exists: {tag_name}")
message = f"Checkpoint '{args.name}' from branch '{branch}' at {timestamp}"
if args.dry_run:
print(tag_name)
print(message)
return 0
run_git(["tag", "-a", tag_name, "-m", message], root, capture_output=False)
print(f"Created {tag_name}")
print(f"Push it with: git push origin {tag_name}")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except subprocess.CalledProcessError as exc:
if exc.stderr:
sys.stderr.write(exc.stderr)
raise SystemExit(exc.returncode) from exc