nanobrush / scripts /assets.py
nmarshall's picture
workspace: nix and xet setup
29ed697
Raw History Blame Contribute Delete
2.18 kB
#!/usr/bin/env python3
"""Hydrate and verify Xet-backed assets in the single Nanobrush Git checkout."""
import argparse
import hashlib
from pathlib import Path
import subprocess
import sys
import storage_policy as policy
ROOT = Path(__file__).resolve().parents[1]
def run(*args, capture=False):
return subprocess.run(args, cwd=ROOT, check=True, stdout=subprocess.PIPE if capture else None).stdout
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("command", choices=("check", "fetch"))
args = parser.parse_args()
policy.check_setup()
policy.check_index()
if args.command == "fetch":
run("git", "lfs", "pull", "hf")
# A fresh clone's cache starts empty; verify hydrated working bytes directly.
count = 0
paths = policy.git("ls-files", "-z").split(b"\0")
for raw in paths:
if not raw:
continue
path = raw.decode("utf-8")
if not policy.is_asset(path):
continue
pointer = policy.git("show", ":" + path)
policy.check_asset(path, pointer)
if not pointer:
expected_hash, size = hashlib.sha256(b"").hexdigest(), 0
else:
lines = pointer.decode().splitlines()
expected_hash, size = lines[1].split(":", 1)[1], int(lines[2].split()[1])
payload = ROOT / path
if payload.is_symlink() or not payload.is_file() or payload.stat().st_size != size:
raise ValueError(f"Missing/unhydrated asset: {path}; run assets-fetch")
digest = hashlib.sha256()
with payload.open("rb") as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
if digest.hexdigest() != expected_hash:
raise ValueError(f"Modified/unhydrated asset: {path}; preserve edits before fetching")
count += 1
print(f"Verified {count} hydrated Xet/LFS assets against staged pointer identities.")
if __name__ == "__main__":
try:
main()
except (ValueError, OSError, subprocess.CalledProcessError) as error:
print(f"Assets: {error}", file=sys.stderr)
sys.exit(1)