ledger: commit census by author re-derived from git at pinned HEAD 0f0d02b
This commit is contained in:
1 parent
0f0d02b4b1
commit
e4f521aa72
6 files changed
+396
No files matched your search
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# CHECK -- ledger commit census (2026-10-28) extraction verification gate.
|
||||
#
|
||||
# Re-derives the committed census from git at the pinned head (committed
|
||||
# commits_by_author.json 'head' field), re-renders csv/json/md with the same
|
||||
# functions the extraction uses, and diffs each against the committed file.
|
||||
# Exits 0 only when every file matches byte-identically.
|
||||
set -u
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$DIR" || exit 1
|
||||
exec python3 "$DIR/verify_commits_by_author.py" "$DIR"
|
||||
@@ -0,0 +1,24 @@
|
||||
rank author commits pct
|
||||
1 Timmy 556 26.64
|
||||
2 ledger 219 10.49
|
||||
3 timmy 209 10.01
|
||||
4 Volt 189 9.06
|
||||
5 spark 183 8.77
|
||||
6 Zephyrus Sparkborn 123 5.89
|
||||
7 Merlin 118 5.65
|
||||
8 beam 97 4.65
|
||||
9 sol 77 3.69
|
||||
10 quill 70 3.35
|
||||
11 volt 64 3.07
|
||||
12 forge 62 2.97
|
||||
13 sphinx 32 1.53
|
||||
14 null 28 1.34
|
||||
15 Mercury 20 0.96
|
||||
16 zephyrus-sparkborn 14 0.67
|
||||
17 pipe 11 0.53
|
||||
18 troupe-pipe 6 0.29
|
||||
19 Cassian Assayborn 3 0.14
|
||||
20 argus 3 0.14
|
||||
21 Argus 1 0.05
|
||||
22 rockachopa 1 0.05
|
||||
23 sentinel 1 0.05
|
||||
|
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"repo": "/root/troupe-standup",
|
||||
"head": "0f0d02b4b1fb1f4a63a13d6732f4c5b0f970b1a4",
|
||||
"commits": 23,
|
||||
"by_author": [
|
||||
{
|
||||
"author": "Timmy",
|
||||
"commits": 556,
|
||||
"pct": 26.64
|
||||
},
|
||||
{
|
||||
"author": "ledger",
|
||||
"commits": 219,
|
||||
"pct": 10.49
|
||||
},
|
||||
{
|
||||
"author": "timmy",
|
||||
"commits": 209,
|
||||
"pct": 10.01
|
||||
},
|
||||
{
|
||||
"author": "Volt",
|
||||
"commits": 189,
|
||||
"pct": 9.06
|
||||
},
|
||||
{
|
||||
"author": "spark",
|
||||
"commits": 183,
|
||||
"pct": 8.77
|
||||
},
|
||||
{
|
||||
"author": "Zephyrus Sparkborn",
|
||||
"commits": 123,
|
||||
"pct": 5.89
|
||||
},
|
||||
{
|
||||
"author": "Merlin",
|
||||
"commits": 118,
|
||||
"pct": 5.65
|
||||
},
|
||||
{
|
||||
"author": "beam",
|
||||
"commits": 97,
|
||||
"pct": 4.65
|
||||
},
|
||||
{
|
||||
"author": "sol",
|
||||
"commits": 77,
|
||||
"pct": 3.69
|
||||
},
|
||||
{
|
||||
"author": "quill",
|
||||
"commits": 70,
|
||||
"pct": 3.35
|
||||
},
|
||||
{
|
||||
"author": "volt",
|
||||
"commits": 64,
|
||||
"pct": 3.07
|
||||
},
|
||||
{
|
||||
"author": "forge",
|
||||
"commits": 62,
|
||||
"pct": 2.97
|
||||
},
|
||||
{
|
||||
"author": "sphinx",
|
||||
"commits": 32,
|
||||
"pct": 1.53
|
||||
},
|
||||
{
|
||||
"author": "null",
|
||||
"commits": 28,
|
||||
"pct": 1.34
|
||||
},
|
||||
{
|
||||
"author": "Mercury",
|
||||
"commits": 20,
|
||||
"pct": 0.96
|
||||
},
|
||||
{
|
||||
"author": "zephyrus-sparkborn",
|
||||
"commits": 14,
|
||||
"pct": 0.67
|
||||
},
|
||||
{
|
||||
"author": "pipe",
|
||||
"commits": 11,
|
||||
"pct": 0.53
|
||||
},
|
||||
{
|
||||
"author": "troupe-pipe",
|
||||
"commits": 6,
|
||||
"pct": 0.29
|
||||
},
|
||||
{
|
||||
"author": "Cassian Assayborn",
|
||||
"commits": 3,
|
||||
"pct": 0.14
|
||||
},
|
||||
{
|
||||
"author": "argus",
|
||||
"commits": 3,
|
||||
"pct": 0.14
|
||||
},
|
||||
{
|
||||
"author": "Argus",
|
||||
"commits": 1,
|
||||
"pct": 0.05
|
||||
},
|
||||
{
|
||||
"author": "rockachopa",
|
||||
"commits": 1,
|
||||
"pct": 0.05
|
||||
},
|
||||
{
|
||||
"author": "sentinel",
|
||||
"commits": 1,
|
||||
"pct": 0.05
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# Commit census by author
|
||||
|
||||
Source: `git log --format=%an` over every commit reachable from HEAD.
|
||||
Counts grouped verbatim on the author name string (git default, case-aware); `Timmy`/`timmy`, `Volt`/`volt` remain separate buckets. No commits are created, moved or deleted.
|
||||
|
||||
| Rank | Author (%an) | Commits | Share |
|
||||
|---|---|---|---|
|
||||
| 1 | Timmy | 556 | 26.64% |
|
||||
| 2 | ledger | 219 | 10.49% |
|
||||
| 3 | timmy | 209 | 10.01% |
|
||||
| 4 | Volt | 189 | 9.06% |
|
||||
| 5 | spark | 183 | 8.77% |
|
||||
| 6 | Zephyrus Sparkborn | 123 | 5.89% |
|
||||
| 7 | Merlin | 118 | 5.65% |
|
||||
| 8 | beam | 97 | 4.65% |
|
||||
| 9 | sol | 77 | 3.69% |
|
||||
| 10 | quill | 70 | 3.35% |
|
||||
| 11 | volt | 64 | 3.07% |
|
||||
| 12 | forge | 62 | 2.97% |
|
||||
| 13 | sphinx | 32 | 1.53% |
|
||||
| 14 | null | 28 | 1.34% |
|
||||
| 15 | Mercury | 20 | 0.96% |
|
||||
| 16 | zephyrus-sparkborn | 14 | 0.67% |
|
||||
| 17 | pipe | 11 | 0.53% |
|
||||
| 18 | troupe-pipe | 6 | 0.29% |
|
||||
| 19 | Cassian Assayborn | 3 | 0.14% |
|
||||
| 20 | argus | 3 | 0.14% |
|
||||
| 21 | Argus | 1 | 0.05% |
|
||||
| 22 | rockachopa | 1 | 0.05% |
|
||||
| 23 | sentinel | 1 | 0.05% |
|
||||
|
||||
Total commits: 23.
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
# Extract the ranked-by-author commit census of the troupe repo as of HEAD.
|
||||
#
|
||||
# Method (evidence): for every commit reachable from a pinned head, `git log
|
||||
# --format=%an` reports the author name verbatim (git's default, case-aware;
|
||||
# 'Timmy' and 'timmy', 'Volt' and 'volt' are separate buckets and are not
|
||||
# normalized). Counts are grouped verbatim on that string. No commits are
|
||||
# created, moved or deleted.
|
||||
#
|
||||
# Outputs (canonical): commits_by_author.csv | .json | .md, plus this script.
|
||||
# The committed files are rendered by the SAME functions the CHECK gate
|
||||
# re-derives, so regeneration is byte-identical by construction.
|
||||
import json
|
||||
import subprocess
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
REPO = '/root/troupe-standup'
|
||||
OUT = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def git(*args):
|
||||
return subprocess.run(
|
||||
['git', '-C', REPO] + list(args),
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout
|
||||
|
||||
|
||||
def build_rows(head):
|
||||
"""Re-derive author-census rows from a pinned (or any) git head."""
|
||||
names = git('log', '--format=%an', head).splitlines()
|
||||
total = len(names)
|
||||
counts = Counter(names)
|
||||
rows = []
|
||||
for i, (name, n) in enumerate(
|
||||
sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])), 1
|
||||
):
|
||||
rows.append({
|
||||
'rank': i,
|
||||
'author': name,
|
||||
'commits': n,
|
||||
'pct': round(n / total * 100.0, 2) if total else 0.0,
|
||||
})
|
||||
return rows
|
||||
|
||||
|
||||
def render_csv(rows):
|
||||
lines = ['rank\tauthor\tcommits\tpct']
|
||||
for r in rows:
|
||||
lines.append('%d\t%s\t%d\t%.2f' % (
|
||||
r['rank'], r['author'].replace('\t', ' '), r['commits'], r['pct']))
|
||||
return '\n'.join(lines) + '\n'
|
||||
|
||||
|
||||
def render_json(rows, head):
|
||||
data = {
|
||||
'repo': REPO,
|
||||
'head': head,
|
||||
'commits': len(rows),
|
||||
'by_author': [
|
||||
{'author': r['author'], 'commits': r['commits'], 'pct': r['pct']}
|
||||
for r in rows
|
||||
],
|
||||
}
|
||||
return json.dumps(data, indent=2) + '\n'
|
||||
|
||||
|
||||
def render_md(rows):
|
||||
lines = []
|
||||
lines.append('# Commit census by author')
|
||||
lines.append('')
|
||||
lines.append('Source: `git log --format=%an` over every commit reachable from HEAD.')
|
||||
lines.append('Counts grouped verbatim on the author name string (git default, case-aware); '
|
||||
'`Timmy`/`timmy`, `Volt`/`volt` remain separate buckets. No commits are created, '
|
||||
'moved or deleted.')
|
||||
lines.append('')
|
||||
lines.append('| Rank | Author (%an) | Commits | Share |')
|
||||
lines.append('|---|---|---|---|')
|
||||
for r in rows:
|
||||
lines.append('| %d | %s | %d | %.2f%% |' % (
|
||||
r['rank'], r['author'].replace('\t', ' '), r['commits'], r['pct']))
|
||||
lines.append('')
|
||||
lines.append('Total commits: %d.' % len(rows))
|
||||
lines.append('')
|
||||
return '\n'.join(lines) + '\n'
|
||||
|
||||
|
||||
def main():
|
||||
head = git('rev-parse', 'HEAD').strip()
|
||||
rows = build_rows(head)
|
||||
(OUT / 'commits_by_author.csv').write_text(render_csv(rows))
|
||||
(OUT / 'commits_by_author.json').write_text(render_json(rows, head))
|
||||
(OUT / 'commits_by_author.md').write_text(render_md(rows))
|
||||
print('total=%d head=%s rows=%d' % (len(rows), head, len(rows)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
# CHECK gate for the ledger commit census (2026-10-28) extraction.
|
||||
#
|
||||
# Re-derives the committed census from git at the PINNED HEAD (the `head` field
|
||||
# of the committed commits_by_author.json), re-renders the three output files
|
||||
# with the identical functions the extraction uses, and diffs each against the
|
||||
# committed file in this directory. Exits 0 only when every file matches
|
||||
# byte-identically.
|
||||
#
|
||||
# Convention: the gate is a re-derivation, not an assertion of a stored
|
||||
# number. It runs `git log --format=%an` at the pinned head, rebuilds the rows,
|
||||
# re-renders csv/json/md with the same code the extraction uses, and requires
|
||||
# byte-identical output against what was committed.
|
||||
#
|
||||
# Usage: python3 verify_commits_by_author.py <artifact_dir>
|
||||
import difflib
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO = '/root/troupe-standup'
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
from extract_commits_by_author import ( # noqa: E402
|
||||
build_rows,
|
||||
render_csv,
|
||||
render_json,
|
||||
render_md,
|
||||
)
|
||||
|
||||
FILES = [
|
||||
'commits_by_author.csv',
|
||||
'commits_by_author.json',
|
||||
'commits_by_author.md',
|
||||
]
|
||||
|
||||
|
||||
def read_bytes(name):
|
||||
with open(name, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def main():
|
||||
artdir = sys.argv[1] if len(sys.argv) > 1 else '.'
|
||||
os.chdir(artdir)
|
||||
|
||||
with open('commits_by_author.json') as f:
|
||||
fixed = json.load(f)
|
||||
head = fixed.get('head') or fixed.get('head_sha')
|
||||
if not head:
|
||||
print('FAIL: committed commits_by_author.json carries no head pin')
|
||||
sys.exit(1)
|
||||
|
||||
rows = build_rows(head)
|
||||
|
||||
ok = True
|
||||
|
||||
def report(name, observed_bytes, expected_bytes):
|
||||
nonlocal ok
|
||||
if observed_bytes == expected_bytes:
|
||||
print('PASS: %s matches re-derivation from git at pinned head %s'
|
||||
% (name, head))
|
||||
else:
|
||||
ok = False
|
||||
print('FAIL: %s differs from re-derivation at pinned head %s'
|
||||
% (name, head))
|
||||
exp = expected_bytes.decode('utf-8', 'replace').splitlines()
|
||||
obs = observed_bytes.decode('utf-8', 'replace').splitlines()
|
||||
diff = difflib.unified_diff(
|
||||
exp, obs,
|
||||
fromfile='expected@' + head[:8],
|
||||
tofile='committed@' + head[:8],
|
||||
lineterm='',
|
||||
)
|
||||
for line in list(diff)[:12]:
|
||||
print(' ' + line)
|
||||
|
||||
# row count
|
||||
if len(rows) == fixed.get('commits'):
|
||||
print('PASS: committed row count %d == re-derived %d'
|
||||
% (fixed.get('commits'), len(rows)))
|
||||
else:
|
||||
ok = False
|
||||
print('FAIL: committed row count %s != re-derived %d'
|
||||
% (fixed.get('commits'), len(rows)))
|
||||
|
||||
for name in FILES:
|
||||
committed = read_bytes(name)
|
||||
expected = {
|
||||
'commits_by_author.csv': render_csv(rows),
|
||||
'commits_by_author.json': render_json(rows, head),
|
||||
'commits_by_author.md': render_md(rows),
|
||||
}[name]
|
||||
report(name, committed, expected.encode('utf-8'))
|
||||
|
||||
if ok:
|
||||
print('ALL CHECKS PASSED: census re-derives byte-identically from '
|
||||
'git at pinned head %s' % head)
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user