import os import subprocess import tempfile import hashlib from datetime import datetime # ---------------------------- # utility: run command # ---------------------------- def run(cmd, cwd="/"): process = subprocess.Popen( cmd, shell=True, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True ) out = [] for line in process.stdout: out.append(line) process.wait() return "".join(out), process.returncode # ---------------------------- # git (Forgejo primary) # ---------------------------- def snapshot_git(repo_url): workdir = tempfile.mkdtemp(prefix="fester-snap-git-") run(f"git clone --depth 1 {repo_url} {workdir}") return workdir # ---------------------------- # hg adapter (snapshot only) # ---------------------------- def snapshot_hg(repo_url): workdir = tempfile.mkdtemp(prefix="fester-snap-hg-") run(f"hg clone {repo_url} {workdir}") return workdir # ---------------------------- # svn adapter (export only) # ---------------------------- def snapshot_svn(repo_url): workdir = tempfile.mkdtemp(prefix="fester-snap-svn-") run(f"svn checkout {repo_url} {workdir}") return workdir # ---------------------------- # cvs adapter (legacy dump) # ---------------------------- def snapshot_cvs(repo_url): workdir = tempfile.mkdtemp(prefix="fester-snap-cvs-") # best-effort export only run(f"cvs export -d {workdir} {repo_url}") return workdir # ---------------------------- # unified entry point # ---------------------------- def create_snapshot(source): kind = source["type"] url = source["url"] if kind == "git": path = snapshot_git(url) elif kind == "hg": path = snapshot_hg(url) elif kind == "svn": path = snapshot_svn(url) elif kind == "cvs": path = snapshot_cvs(url) else: raise Exception(f"Unsupported VCS type: {kind}") return fingerprint(path) # ---------------------------- # deterministic fingerprint # ---------------------------- def fingerprint(path): sha = hashlib.sha256() for root, dirs, files in os.walk(path): for f in sorted(files): fp = os.path.join(root, f) try: with open(fp, "rb") as fh: sha.update(fh.read()) except: pass return { "path": path, "hash": sha.hexdigest(), "timestamp": datetime.utcnow().isoformat() + "Z" }