fester/backend/snapshot.py

115 lines
2.5 KiB
Python

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"
}