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