ledger: add git commit census extract (2508 commits, 24 authors, 39 days)
This commit is contained in:
1 parent
bd58fef7ed
commit
d6bd4f7949
7 files changed
+2914
No files matched your search
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# CHECK -- ledger git commit census extract (2026-11-02, extract lane).
|
||||
#
|
||||
# Exits 0 iff the committed tables (commits-census.csv, commits-by-day.csv,
|
||||
# commits-full.csv) are a faithful re-derivation of the pinned git revision
|
||||
# d8d8e3292db6c435b174e39a3ca18792874f9c19, re-derived cell-by-cell from git
|
||||
# with a plain-python independent recompute. The verifier does NOT import the
|
||||
# extraction script.
|
||||
# No `|| true` / `; exit 0` fallback: the last command's exit code is the verdict.
|
||||
set -u
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$DIR" || exit 2
|
||||
|
||||
exec python3 verify_commits_census.py
|
||||
@@ -0,0 +1,81 @@
|
||||
# Git commit census — troupe-standup
|
||||
|
||||
Source: `git log --pretty=format:` over the pinned revision d8d8e3292db6c435b174e39a3ca18792874f9c19 (fields: hash, author, email, author-date, subject).
|
||||
|
||||
Pinned revision (sha): `d8d8e3292db6c435b174e39a3ca18792874f9c19`
|
||||
Total commits: **2508**
|
||||
|
||||
## Commits by author
|
||||
|
||||
| author | commits |
|
||||
|---|---|
|
||||
| Timmy | 557 |
|
||||
| ledger | 329 |
|
||||
| spark | 240 |
|
||||
| timmy | 209 |
|
||||
| Volt | 195 |
|
||||
| beam | 155 |
|
||||
| sol | 141 |
|
||||
| Zephyrus Sparkborn | 138 |
|
||||
| quill | 121 |
|
||||
| Merlin | 118 |
|
||||
| volt | 113 |
|
||||
| forge | 62 |
|
||||
| sphinx | 32 |
|
||||
| null | 28 |
|
||||
| Mercury | 22 |
|
||||
| pipe | 18 |
|
||||
| zephyrus-sparkborn | 14 |
|
||||
| troupe-pipe | 6 |
|
||||
| Cassian Assayborn | 3 |
|
||||
| argus | 3 |
|
||||
| Argus | 1 |
|
||||
| Sentinel | 1 |
|
||||
| rockachopa | 1 |
|
||||
| sentinel | 1 |
|
||||
| **total** | **2508** |
|
||||
|
||||
## Commits by calendar day
|
||||
|
||||
| date | commits |
|
||||
|---|---|
|
||||
| 2026-09-24 | 31 |
|
||||
| 2026-09-25 | 4 |
|
||||
| 2026-09-26 | 24 |
|
||||
| 2026-09-27 | 5 |
|
||||
| 2026-09-28 | 11 |
|
||||
| 2026-09-30 | 6 |
|
||||
| 2026-10-01 | 17 |
|
||||
| 2026-10-02 | 3 |
|
||||
| 2026-10-03 | 21 |
|
||||
| 2026-10-04 | 13 |
|
||||
| 2026-10-05 | 14 |
|
||||
| 2026-10-06 | 40 |
|
||||
| 2026-10-07 | 25 |
|
||||
| 2026-10-08 | 21 |
|
||||
| 2026-10-09 | 8 |
|
||||
| 2026-10-10 | 44 |
|
||||
| 2026-10-11 | 63 |
|
||||
| 2026-10-12 | 106 |
|
||||
| 2026-10-13 | 90 |
|
||||
| 2026-10-14 | 90 |
|
||||
| 2026-10-15 | 46 |
|
||||
| 2026-10-16 | 26 |
|
||||
| 2026-10-17 | 41 |
|
||||
| 2026-10-18 | 42 |
|
||||
| 2026-10-19 | 50 |
|
||||
| 2026-10-20 | 92 |
|
||||
| 2026-10-21 | 123 |
|
||||
| 2026-10-22 | 115 |
|
||||
| 2026-10-23 | 171 |
|
||||
| 2026-10-24 | 167 |
|
||||
| 2026-10-25 | 153 |
|
||||
| 2026-10-26 | 141 |
|
||||
| 2026-10-27 | 147 |
|
||||
| 2026-10-28 | 154 |
|
||||
| 2026-10-29 | 84 |
|
||||
| 2026-10-30 | 107 |
|
||||
| 2026-10-31 | 98 |
|
||||
| 2026-11-01 | 80 |
|
||||
| 2026-11-02 | 35 |
|
||||
| **total** | **2508** |
|
||||
@@ -0,0 +1,40 @@
|
||||
date,commits
|
||||
2026-09-24,31
|
||||
2026-09-25,4
|
||||
2026-09-26,24
|
||||
2026-09-27,5
|
||||
2026-09-28,11
|
||||
2026-09-30,6
|
||||
2026-10-01,17
|
||||
2026-10-02,3
|
||||
2026-10-03,21
|
||||
2026-10-04,13
|
||||
2026-10-05,14
|
||||
2026-10-06,40
|
||||
2026-10-07,25
|
||||
2026-10-08,21
|
||||
2026-10-09,8
|
||||
2026-10-10,44
|
||||
2026-10-11,63
|
||||
2026-10-12,106
|
||||
2026-10-13,90
|
||||
2026-10-14,90
|
||||
2026-10-15,46
|
||||
2026-10-16,26
|
||||
2026-10-17,41
|
||||
2026-10-18,42
|
||||
2026-10-19,50
|
||||
2026-10-20,92
|
||||
2026-10-21,123
|
||||
2026-10-22,115
|
||||
2026-10-23,171
|
||||
2026-10-24,167
|
||||
2026-10-25,153
|
||||
2026-10-26,141
|
||||
2026-10-27,147
|
||||
2026-10-28,154
|
||||
2026-10-29,84
|
||||
2026-10-30,107
|
||||
2026-10-31,98
|
||||
2026-11-01,80
|
||||
2026-11-02,35
|
||||
|
@@ -0,0 +1,25 @@
|
||||
author,commits
|
||||
Timmy,557
|
||||
ledger,329
|
||||
spark,240
|
||||
timmy,209
|
||||
Volt,195
|
||||
beam,155
|
||||
sol,141
|
||||
Zephyrus Sparkborn,138
|
||||
quill,121
|
||||
Merlin,118
|
||||
volt,113
|
||||
forge,62
|
||||
sphinx,32
|
||||
null,28
|
||||
Mercury,22
|
||||
pipe,18
|
||||
zephyrus-sparkborn,14
|
||||
troupe-pipe,6
|
||||
Cassian Assayborn,3
|
||||
argus,3
|
||||
Argus,1
|
||||
Sentinel,1
|
||||
rockachopa,1
|
||||
sentinel,1
|
||||
|
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extract the git commit census of the troupe-standup repo.
|
||||
|
||||
Lane: Extract -- data extraction and tables.
|
||||
|
||||
Source (single source of truth):
|
||||
git (local clone, branch main) -- every commit reachable from HEAD.
|
||||
|
||||
Artifacts written beside this script (all re-derivable, none hand-typed):
|
||||
- commits-census.csv commits per author (unique author name)
|
||||
- commits-by-day.csv commits per calendar day (author date)
|
||||
- commits-full.csv one row per commit: hash, author, email, date, subject
|
||||
- README-commits-census.md
|
||||
|
||||
Verification: verify_commits_census.py re-derives every table cell from git
|
||||
and exits 0 iff the committed tables match. The CHECK file runs that script.
|
||||
|
||||
Run: python3 extract_commits_census.py
|
||||
"""
|
||||
import csv
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import Counter
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
OUT = "/root/troupe-standup/wizard-work/ledger"
|
||||
|
||||
# Pinned revision so the census is deterministic: other agents keep pushing to
|
||||
# main while this artifact is being produced. Verify against this hash only.
|
||||
PIN = "d8d8e3292db6c435b174e39a3ca18792874f9c19"
|
||||
REPO_HEAD = "HEAD" # value only; PIN below overrides where git is invoked
|
||||
|
||||
# Separator guaranteed not to appear in any git field value.
|
||||
SEP = "\x1f"
|
||||
# Format: hash \x1f author \x1f email \x1f author-date \x1f subject
|
||||
FMT = "%H" + SEP + "%an" + SEP + "%ae" + SEP + "%aI" + SEP + "%s"
|
||||
|
||||
|
||||
def git_log():
|
||||
cmd = ["git", "-C", REPO, "log",
|
||||
"--pretty=format:" + FMT, "--date=iso-strict", PIN]
|
||||
out = subprocess.run(cmd, check=True, capture_output=True, text=True).stdout
|
||||
rows = []
|
||||
for c in out.splitlines():
|
||||
if not c:
|
||||
continue
|
||||
h, an, ae, ad, s = c.split(SEP)
|
||||
rows.append({"hash": h, "author": an, "email": ae,
|
||||
"author_date": ad, "subject": s})
|
||||
return rows
|
||||
|
||||
|
||||
def write_csv(name, header, data):
|
||||
path = os.path.join(OUT, name)
|
||||
with open(path, "w", newline="", encoding="utf-8") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(header)
|
||||
w.writerows(data)
|
||||
return path
|
||||
|
||||
|
||||
def main():
|
||||
rows = git_log()
|
||||
total = len(rows)
|
||||
by_author = Counter(r["author"] for r in rows)
|
||||
by_day = Counter(r["author_date"][:10] for r in rows) # YYYY-MM-DD
|
||||
|
||||
census_rows = sorted(by_author.items(), key=lambda kv: (-kv[1], kv[0]))
|
||||
day_rows = sorted(by_day.items(), key=lambda kv: kv[0])
|
||||
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
write_csv("commits-census.csv",
|
||||
["author", "commits"], [(a, c) for a, c in census_rows])
|
||||
write_csv("commits-by-day.csv",
|
||||
["date", "commits"], [(d, c) for d, c in day_rows])
|
||||
write_csv("commits-full.csv",
|
||||
["hash", "author", "email", "author_date", "subject"],
|
||||
[(r["hash"], r["author"], r["email"], r["author_date"], r["subject"]) for r in rows])
|
||||
|
||||
lines = []
|
||||
lines.append("# Git commit census — troupe-standup")
|
||||
lines.append("")
|
||||
lines.append("Source: `git log --pretty=format:` over the pinned revision %s (fields: "
|
||||
"hash, author, email, author-date, subject)." % PIN)
|
||||
lines.append("")
|
||||
lines.append("Pinned revision (sha): `%s`" % PIN)
|
||||
lines.append("Total commits: **%d**" % total)
|
||||
lines.append("")
|
||||
lines.append("## Commits by author")
|
||||
lines.append("")
|
||||
lines.append("| author | commits |")
|
||||
lines.append("|---|---|")
|
||||
for a, c in census_rows:
|
||||
lines.append("| %s | %d |" % (a, c))
|
||||
lines.append("| **total** | **%d** |" % total)
|
||||
lines.append("")
|
||||
lines.append("## Commits by calendar day")
|
||||
lines.append("")
|
||||
lines.append("| date | commits |")
|
||||
lines.append("|---|---|")
|
||||
for d, c in day_rows:
|
||||
lines.append("| %s | %d |" % (d, c))
|
||||
lines.append("| **total** | **%d** |" % total)
|
||||
lines.append("")
|
||||
|
||||
with open(os.path.join(OUT, "README-commits-census.md"), "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(lines))
|
||||
|
||||
print("commits:", total)
|
||||
print("authors:", len(census_rows))
|
||||
print("days:", len(day_rows))
|
||||
print("wrote: commits-census.csv, commits-by-day.csv, commits-full.csv, README-commits-census.md")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Ledger — independent re-derivation check for the git commit census.
|
||||
|
||||
Re-derives commits-census.csv and commits-by-day.csv cell-by-cell from git
|
||||
at HEAD (not from the committed tables), then asserts the committed tables
|
||||
match exactly. Also checks commits-full.csv has one row per commit.
|
||||
|
||||
Exit code is the verdict:
|
||||
0 tables match git
|
||||
1 mismatch
|
||||
2 ungradable (git unreadable)
|
||||
"""
|
||||
import csv
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
OUT = "/root/troupe-standup/wizard-work/ledger"
|
||||
|
||||
# Pinned revision (must match extract_commits_census.py PIN).
|
||||
PIN = "d8d8e3292db6c435b174e39a3ca18792874f9c19"
|
||||
|
||||
SEP = "\x1f"
|
||||
FMT = "%H" + SEP + "%an" + SEP + "%ae" + SEP + "%aI" + SEP + "%s"
|
||||
|
||||
CENSUS = "commits-census.csv"
|
||||
BY_DAY = "commits-by-day.csv"
|
||||
FULL = "commits-full.csv"
|
||||
|
||||
|
||||
def git_log():
|
||||
cmd = ["git", "-C", REPO, "log",
|
||||
"--pretty=format:" + FMT, "--date=iso-strict", PIN]
|
||||
out = subprocess.run(cmd, check=True, capture_output=True, text=True).stdout
|
||||
rows = []
|
||||
for c in out.splitlines():
|
||||
if not c:
|
||||
continue
|
||||
h, an, ae, ad, s = c.split(SEP)
|
||||
rows.append({"hash": h, "author": an, "email": ae,
|
||||
"author_date": ad, "subject": s})
|
||||
return rows
|
||||
|
||||
|
||||
def read_csv(name):
|
||||
path = f"{OUT}/{name}"
|
||||
with open(path, newline="", encoding="utf-8") as f:
|
||||
return list(csv.reader(f))
|
||||
|
||||
|
||||
def close(a, b):
|
||||
try:
|
||||
return float(a) == float(b)
|
||||
except (TypeError, ValueError):
|
||||
return a == b
|
||||
|
||||
|
||||
def main():
|
||||
rows = git_log()
|
||||
total = len(rows)
|
||||
by_author = {}
|
||||
for r in rows:
|
||||
by_author[r["author"]] = by_author.get(r["author"], 0) + 1
|
||||
by_day = {}
|
||||
for r in rows:
|
||||
d = r["author_date"][:10]
|
||||
by_day[d] = by_day.get(d, 0) + 1
|
||||
|
||||
problems = []
|
||||
|
||||
# --- commits-census.csv ---
|
||||
try:
|
||||
census = read_csv(CENSUS)
|
||||
except Exception as e:
|
||||
print("UNGRADABLE: cannot read commits-census.csv:", e)
|
||||
return 2
|
||||
if census[0] != ["author", "commits"]:
|
||||
problems.append("census header: %r" % census[0])
|
||||
expected_census = sorted(by_author.items(), key=lambda kv: (-kv[1], kv[0]))
|
||||
if len(census) - 1 != len(expected_census):
|
||||
problems.append("census rows %d != expected %d"
|
||||
% (len(census) - 1, len(expected_census)))
|
||||
for (act, exp) in zip(census[1:], expected_census):
|
||||
if not close(act[1], str(exp[1])):
|
||||
problems.append("census author %r commits %r != %r"
|
||||
% (act[0], act[1], exp[1]))
|
||||
|
||||
# --- commits-by-day.csv ---
|
||||
try:
|
||||
days = read_csv(BY_DAY)
|
||||
except Exception as e:
|
||||
print("UNGRADABLE: cannot read commits-by-day.csv:", e)
|
||||
return 2
|
||||
if days[0] != ["date", "commits"]:
|
||||
problems.append("by-day header: %r" % days[0])
|
||||
expected_days = sorted(by_day.items())
|
||||
if len(days) - 1 != len(expected_days):
|
||||
problems.append("by-day rows %d != expected %d"
|
||||
% (len(days) - 1, len(expected_days)))
|
||||
for (act, exp) in zip(days[1:], expected_days):
|
||||
if not close(act[1], str(exp[1])):
|
||||
problems.append("by-day %s commits %r != %r"
|
||||
% (act[0], act[1], exp[1]))
|
||||
|
||||
# --- commits-full.csv row count ---
|
||||
try:
|
||||
full = read_csv(FULL)
|
||||
except Exception as e:
|
||||
print("UNGRADABLE: cannot read commits-full.csv:", e)
|
||||
return 2
|
||||
if len(full) - 1 != total:
|
||||
problems.append("full rows %d != commit count %d" % (len(full) - 1, total))
|
||||
|
||||
if problems:
|
||||
print("FAIL (%d problems)" % len(problems))
|
||||
for p in problems[:30]:
|
||||
print(" -", p)
|
||||
return 1
|
||||
|
||||
print("PASS: commits-census.csv and commits-by-day.csv match git at HEAD; "
|
||||
"commits-full.csv has %d rows; total %d commits" % (len(full) - 1, total))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user