64 lines
3 KiB
Python
64 lines
3 KiB
Python
#!/usr/bin/env python3
|
|
"""Populate clean, disposable paired worktrees with the exact uncommitted integration source."""
|
|
import argparse
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT / "tools"))
|
|
from campaign import Campaign, ControlError, git_value, source_manifest
|
|
|
|
|
|
def files(root):
|
|
return {name for name in git_value(root, "ls-files", "-z", "--cached", "--others", "--exclude-standard").split("\0") if name}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--engine-worktree", required=True, type=Path)
|
|
parser.add_argument("--re-worktree", required=True, type=Path)
|
|
args = parser.parse_args()
|
|
campaign = Campaign(str(ROOT))
|
|
contract = campaign.load("controls-bootstrap")
|
|
for kind, target in (("engine", args.engine_worktree), ("re", args.re_worktree)):
|
|
target = target.resolve()
|
|
source = Path(contract["baseline"][kind]["path"])
|
|
if target == source or not str(target).startswith("/tmp/opencode/sots-final-"):
|
|
raise ControlError("only rollout-owned sots-final-* temporary worktrees may be populated")
|
|
if git_value(target, "status", "--porcelain", "--untracked-files=all"):
|
|
raise ControlError("snapshot destination must be clean; never overwrite existing work")
|
|
if git_value(target, "rev-parse", "--path-format=absolute", "--git-common-dir") != git_value(source, "rev-parse", "--path-format=absolute", "--git-common-dir"):
|
|
raise ControlError("destination is not a linked worktree of this repository")
|
|
if git_value(target, "rev-parse", "HEAD") != contract["baseline"][kind]["commit"]:
|
|
raise ControlError("destination baseline mismatch")
|
|
before = source_manifest(source, kind)
|
|
names = files(source)
|
|
for name in files(target) - names:
|
|
path = target / name
|
|
if path.is_file():
|
|
path.unlink()
|
|
for name in names:
|
|
if name.startswith(("campaign/runtime/", "campaign/evidence/")):
|
|
continue
|
|
src, dst = source / name, target / name
|
|
if src.is_symlink():
|
|
raise ControlError("source symlink unsupported")
|
|
if src.is_file():
|
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(src, dst)
|
|
elif dst.is_file():
|
|
dst.unlink() # Reproduce tracked source deletions in this disposable tree.
|
|
after, actual = source_manifest(source, kind), source_manifest(target, kind)
|
|
if after != before or actual["sha256"] != before["sha256"]:
|
|
raise ControlError("source changed or copied integration snapshot differs")
|
|
print(f"{kind}: exact integration source {actual['sha256']} at {target}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except (ControlError, OSError, ValueError, subprocess.SubprocessError) as exc:
|
|
print(str(exc), file=sys.stderr)
|
|
sys.exit(1)
|