Initial SyncGames tree: agent, Android, deploy, docs.
Session-gated MinIO save sync with AppImage GUI, CLI edit/session flow, and Gitea release helper. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def sha256_file(path: Path) -> str:
|
||||
h = hashlib.sha256()
|
||||
with path.open("rb") as f:
|
||||
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
||||
h.update(chunk)
|
||||
return f"sha256:{h.hexdigest()}"
|
||||
|
||||
|
||||
def sha256_bytes(data: bytes) -> str:
|
||||
return f"sha256:{hashlib.sha256(data).hexdigest()}"
|
||||
|
||||
|
||||
def tree_hash(file_checksums: dict[str, str]) -> str:
|
||||
"""Canonical tree hash per docs/protocol.md."""
|
||||
lines: list[bytes] = []
|
||||
for rel in sorted(file_checksums.keys()):
|
||||
digest = file_checksums[rel]
|
||||
if digest.startswith("sha256:"):
|
||||
hexdigest = digest[len("sha256:") :]
|
||||
else:
|
||||
hexdigest = digest
|
||||
lines.append(f"{rel}\0{hexdigest}\n".encode("utf-8"))
|
||||
return sha256_bytes(b"".join(lines))
|
||||
|
||||
|
||||
def checksum_dir(root: Path, files: list[Path] | None = None) -> dict[str, str]:
|
||||
"""Hash files under root; keys are relative posix paths."""
|
||||
out: dict[str, str] = {}
|
||||
if files is None:
|
||||
paths = sorted(p for p in root.rglob("*") if p.is_file())
|
||||
else:
|
||||
paths = files
|
||||
for p in paths:
|
||||
rel = p.relative_to(root).as_posix()
|
||||
out[rel] = sha256_file(p)
|
||||
return out
|
||||
Reference in New Issue
Block a user