verify.sh no longer cds to /root/troupe-standup; repo root comes from $0 /
__file__. Paths are read with ls-tree -z (raw), the census is pinned to the
commit it describes (repo_census.pin), and the verifier grades that pin, so a
later commit does not redden the gate. New committed extractor regenerates the
three TSVs; artifacts re-derived from 716c3b3 (5311 files, 217 dirs, 34 exts).
91 lines
3.4 KiB
Python
91 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Regenerate the ledger file-tree census TSVs from a pinned commit.
|
|
|
|
Writes, beside this script:
|
|
repo_tree.tsv rank, directory, file_count tracked files grouped by directory
|
|
repo_exts.tsv rank, extension, file_count tracked files grouped by lowercased extension
|
|
repo_files.tsv rank, path every tracked file, one row
|
|
repo_census.pin the commit and path count this census describes
|
|
|
|
The subject is the commit checked out when this runs (`git rev-parse HEAD`), so
|
|
the census is a stable snapshot: the verifier grades it against that immutable
|
|
commit, not against a moving HEAD, and a later commit does not redden the gate.
|
|
A census cannot contain itself -- the files this run lands, including the pin,
|
|
are one commit newer than the tree it describes; that is stated in the README.
|
|
|
|
The repository root is derived from __file__, never a hardcoded host path.
|
|
Paths are read with `git ls-tree -r -z`, raw rather than through git's quoted
|
|
rendering, which is how an escaped blob once landed in repo_files.tsv.
|
|
|
|
Ordering is file_count descending then name ascending; repo_files.tsv is sorted
|
|
by path. Exit codes: 0 wrote all four files, 2 could not read the tree.
|
|
"""
|
|
from pathlib import Path
|
|
|
|
import csv
|
|
import subprocess
|
|
import sys
|
|
from collections import Counter
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
REPO = HERE.parents[1]
|
|
PIN_NAME = "repo_census.pin"
|
|
|
|
|
|
def git(repo, *args):
|
|
proc = subprocess.run(["git", "-C", str(repo)] + list(args),
|
|
capture_output=True)
|
|
if proc.returncode != 0:
|
|
print("FATAL git %s rc=%d: %s" % (" ".join(args), proc.returncode,
|
|
proc.stderr.decode("utf-8", "replace").strip()))
|
|
return None
|
|
return proc.stdout
|
|
|
|
|
|
def main():
|
|
commit_out = git(REPO, "rev-parse", "HEAD")
|
|
if commit_out is None:
|
|
return 2
|
|
commit = commit_out.decode().strip()
|
|
|
|
raw = git(REPO, "ls-tree", "-r", "-z", "--name-only", commit)
|
|
if raw is None:
|
|
return 2
|
|
paths = [p for p in raw.decode("utf-8", "surrogateescape").split("\0") if p]
|
|
if not paths:
|
|
print("FATAL commit %s listed no files" % commit)
|
|
return 2
|
|
|
|
dir_counts = Counter()
|
|
ext_counts = Counter()
|
|
for p in paths:
|
|
d = str(Path(p).parent).replace("\\", "/")
|
|
dir_counts[d if d != "." else "(root)"] += 1
|
|
ext = Path(p).suffix.lstrip(".").lower() or "(none)"
|
|
ext_counts[ext] += 1
|
|
|
|
tree_rows = sorted(dir_counts.items(), key=lambda kv: (-kv[1], kv[0]))
|
|
ext_rows = sorted(ext_counts.items(), key=lambda kv: (-kv[1], kv[0]))
|
|
file_rows = [(p,) for p in sorted(paths)]
|
|
|
|
def write_tsv(name, header, rows):
|
|
with open(HERE / name, "w", newline="", encoding="utf-8") as f:
|
|
w = csv.writer(f, delimiter="\t")
|
|
w.writerow(header)
|
|
for i, row in enumerate(rows, 1):
|
|
w.writerow([i] + list(row))
|
|
print("wrote %s: %d data row(s)" % (name, len(rows)))
|
|
|
|
write_tsv("repo_tree.tsv", ["rank", "directory", "file_count"], tree_rows)
|
|
write_tsv("repo_exts.tsv", ["rank", "extension", "file_count"], ext_rows)
|
|
write_tsv("repo_files.tsv", ["rank", "path"], file_rows)
|
|
(HERE / PIN_NAME).write_text("commit %s\nfiles %d\n" % (commit, len(paths)))
|
|
|
|
print("pinned commit %s: %d files, %d directories, %d extensions"
|
|
% (commit, len(paths), len(tree_rows), len(ext_rows)))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|