Two checks the human asked for after Phase 1: whether the mapping list from the 2026-08-07 history rewrite still resolves older references, and how far the anonymisation rule actually reaches. sha_refs.tsv verifies all 251 mapping rows against the repos and resolves every SHA cited in a management doc. timestamp_anonymisation.tsv separates the project's own commits from upstream fork history before counting non-compliant timestamps.
145 lines
5.5 KiB
Python
145 lines
5.5 KiB
Python
"""Resolve commit-SHA references -> analysis/data/sha_refs.tsv.
|
|
|
|
Added at the human's prompt during Phase 2: the 2026-08-07 history
|
|
rewrite gave 251 commits new SHAs, and shared/commit-zuordnung-2026-08-07.md
|
|
is the mapping list meant to keep older references resolvable. This
|
|
script tests whether that actually holds.
|
|
|
|
Two row kinds share one file:
|
|
|
|
doc-reference a SHA cited in a management-repo doc
|
|
mapping-entry an (old -> new) pair from the mapping table itself
|
|
|
|
Scope note: only management-repo docs are scanned for references. The
|
|
component repos are forks whose upstream docs cite thousands of upstream
|
|
SHAs that were never part of this project's history; including them
|
|
would bury the signal.
|
|
"""
|
|
|
|
import re
|
|
|
|
from common import (
|
|
DOC_SUFFIXES, MGMT_REPO, cell, git, repos, tracked_files, write_tsv,
|
|
)
|
|
|
|
MAPPING_DOC = "shared/commit-zuordnung-2026-08-07.md"
|
|
|
|
# Section heading -> repo name used elsewhere in the analysis.
|
|
SECTION_REPO = {
|
|
"gitops": "axion1337.chat-gitops",
|
|
"management": "management",
|
|
"ThreadNet-Web": "ThreadNet-Web",
|
|
"threadnet-call": "threadnet-call",
|
|
}
|
|
|
|
MAPPING_ROW = re.compile(r"^\|\s*`([0-9a-f]{7,40})`\s*\|\s*`([0-9a-f]{7,40})`\s*\|")
|
|
SECTION = re.compile(r"^##\s+(.+?)\s+—\s+(\d+)\s+Commits\s*$")
|
|
|
|
# A SHA-shaped token: 7-40 hex chars, mixing letters and digits so that
|
|
# plain numbers and hex-free words do not qualify.
|
|
SHA_TOKEN = re.compile(r"(?<![0-9a-zA-Z_/-])([0-9a-f]{7,40})(?![0-9a-zA-Z_-])")
|
|
|
|
|
|
def all_commits(repo):
|
|
"""Every commit SHA reachable from any ref, including remotes and tags."""
|
|
out = git(repo, "rev-list", "--all")
|
|
return {line.strip() for line in out.splitlines() if line.strip()}
|
|
|
|
|
|
def resolve(sha, index):
|
|
"""Repos in which sha is a commit (prefix match), sorted."""
|
|
hits = []
|
|
for name, commits in index.items():
|
|
if sha in commits or any(c.startswith(sha) for c in commits):
|
|
hits.append(name)
|
|
return sorted(hits)
|
|
|
|
|
|
def parse_mapping(text):
|
|
"""[(repo, old, new, declared_section_count)] from the mapping table."""
|
|
rows, section, declared = [], None, 0
|
|
for line in text.splitlines():
|
|
m = SECTION.match(line)
|
|
if m:
|
|
section = SECTION_REPO.get(m.group(1), m.group(1))
|
|
declared = int(m.group(2))
|
|
continue
|
|
m = MAPPING_ROW.match(line)
|
|
if m and section:
|
|
rows.append((section, m.group(1), m.group(2), declared))
|
|
return rows
|
|
|
|
|
|
def main():
|
|
index = {name: all_commits(path) for name, path in repos()}
|
|
known_repos = set(index)
|
|
|
|
mapping_text = (MGMT_REPO / MAPPING_DOC).read_text(encoding="utf-8")
|
|
mapping = parse_mapping(mapping_text)
|
|
old_to_new = {old: (repo, new) for repo, old, new, _ in mapping}
|
|
|
|
rows = []
|
|
|
|
# 1. The mapping table checked against the repos it describes.
|
|
for repo, old, new, _declared in mapping:
|
|
if repo not in known_repos:
|
|
status, note = "unverifiable", f"repo {repo} not in analysis scope"
|
|
else:
|
|
new_ok = bool(resolve(new, {repo: index[repo]}))
|
|
old_ok = bool(resolve(old, {repo: index[repo]}))
|
|
if new_ok and not old_ok:
|
|
status, note = "ok", "new resolves, old gone as expected"
|
|
elif new_ok and old_ok:
|
|
status, note = "ok-both-present", "old SHA still reachable from some ref"
|
|
elif not new_ok and old_ok:
|
|
status, note = "inverted", "new SHA missing but old one resolves"
|
|
else:
|
|
status, note = "unresolvable", "neither old nor new SHA exists in the repo"
|
|
rows.append(["mapping-entry", repo, MAPPING_DOC, "", f"{old}->{new}",
|
|
status, repo if status.startswith("ok") else "", note])
|
|
|
|
# 2. Every SHA cited in a management doc.
|
|
docs = [f for f in tracked_files("management", MGMT_REPO)
|
|
if any(f.endswith(s) for s in DOC_SUFFIXES)]
|
|
for src in sorted(docs):
|
|
text = (MGMT_REPO / src).read_text(encoding="utf-8")
|
|
for lineno, line in enumerate(text.splitlines(), start=1):
|
|
if src == MAPPING_DOC and MAPPING_ROW.match(line):
|
|
continue # already covered as mapping-entry rows
|
|
for sha in SHA_TOKEN.findall(line):
|
|
hits = resolve(sha, index)
|
|
if hits:
|
|
status = "resolves"
|
|
note = f"found in {','.join(hits)}"
|
|
elif sha in old_to_new:
|
|
repo, new = old_to_new[sha]
|
|
if repo in known_repos and resolve(new, {repo: index[repo]}):
|
|
status = "resolves-via-mapping"
|
|
note = f"pre-rewrite SHA; mapping -> {new} in {repo}"
|
|
hits = [repo]
|
|
else:
|
|
status = "mapping-stale"
|
|
note = f"mapping points to {new} in {repo}, which does not exist"
|
|
else:
|
|
status = "orphan"
|
|
note = "resolves in no analysed repo and is not in the mapping table"
|
|
rows.append(["doc-reference", "management", src, lineno, sha,
|
|
status, ",".join(hits), cell(line.strip())[:160]])
|
|
|
|
rows.sort(key=lambda r: (r[0], r[2], r[3] if r[3] != "" else 0, r[4]))
|
|
write_tsv(
|
|
"sha_refs.tsv",
|
|
["kind", "repo", "path", "line", "sha", "status", "resolved_in", "note"],
|
|
rows,
|
|
)
|
|
|
|
counts = {}
|
|
for r in rows:
|
|
counts[(r[0], r[5])] = counts.get((r[0], r[5]), 0) + 1
|
|
for key in sorted(counts):
|
|
print(f" {key[0]:<14} {key[1]:<22} {counts[key]}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|