beam: standalone authorship-aware counterparty signature tool
This commit is contained in:
1 parent
cd3b285a66
commit
76ac549088
1 file changed
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
# Authorship-aware repository signature (Q15 monitor logic, standalone).
|
||||
import hashlib
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def run(repo, *args):
|
||||
r = subprocess.run(["git", "-C", repo, *args], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError("git %s: %s" % (" ".join(args), r.stderr.strip()))
|
||||
return r.stdout
|
||||
|
||||
|
||||
def commits(repo, n=200):
|
||||
out = run(repo, "log", "-%d" % n, "--format=%H%x1f%an%x1f%ae")
|
||||
rows = []
|
||||
for line in out.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
parts = line.split("\x1f")
|
||||
if len(parts) != 3:
|
||||
continue
|
||||
rows.append((parts[0], parts[1], parts[2]))
|
||||
return rows
|
||||
|
||||
|
||||
def is_self(author_name, author_email, self_name):
|
||||
return author_name == self_name or author_email == self_name
|
||||
|
||||
|
||||
def newest_commit_for_path(repo, path, self_name, cache):
|
||||
if path in cache:
|
||||
return cache[path]
|
||||
out = run(repo, "log", "-1", "--format=%H%x1f%an%x1f%ae", "--", path)
|
||||
line = out.strip()
|
||||
if not line:
|
||||
cache[path] = None
|
||||
return None
|
||||
parts = line.split("\x1f")
|
||||
sha, name, email = parts[0], parts[1], parts[2]
|
||||
res = (sha, name, email, not is_self(name, email, self_name))
|
||||
cache[path] = res
|
||||
return res
|
||||
|
||||
|
||||
def signature(repo, self_name, folders):
|
||||
head = None
|
||||
for sha, name, email in [(c[0], c[1], c[2]) for c in commits(repo)]:
|
||||
if not is_self(name, email, self_name):
|
||||
head = sha
|
||||
break
|
||||
if head is None:
|
||||
raise RuntimeError("no counterparty commit found")
|
||||
|
||||
files = []
|
||||
for folder in folders:
|
||||
out = run(repo, "ls-tree", "-r", "--name-only", head, folder)
|
||||
for p in out.splitlines():
|
||||
if p.strip():
|
||||
files.append(p.strip())
|
||||
files = sorted(set(files))
|
||||
|
||||
cache = {}
|
||||
lines = ["head %s" % head[:12]]
|
||||
for path in files:
|
||||
info = newest_commit_for_path(repo, path, self_name, cache)
|
||||
counterparty = True if info is None else info[3]
|
||||
if not counterparty:
|
||||
continue
|
||||
blob = run(repo, "rev-parse", "%s:%s" % (head, path)).strip()
|
||||
lines.append("%s %s" % (path, blob[:12]))
|
||||
|
||||
text = "\n".join(lines) + "\n"
|
||||
digest = hashlib.sha256(text.encode()).hexdigest()
|
||||
return text, digest
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 4:
|
||||
print("usage: counterparty_sig.py <repo> <self_name> <folder> [folder ...]", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
repo, self_name, folders = sys.argv[1], sys.argv[2], sys.argv[3:]
|
||||
text, digest = signature(repo, self_name, folders)
|
||||
sys.stdout.write(text)
|
||||
print("sha256 %s" % digest)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user