#!/usr/bin/env python3
"""Flag commits whose header order differs from git's reading. Usage: git_header_audit.py <repo> [...]"""
import subprocess, sys

def objects(repo):
    out = subprocess.run(["git", "-C", repo, "cat-file", "--batch-check=%(objectname) %(objecttype)",
                          "--batch-all-objects", "--unordered"], capture_output=True, text=True, check=True).stdout
    return [l.split()[0] for l in out.splitlines() if l.endswith(" commit")]

def read(repo, ids):
    p = subprocess.run(["git", "-C", repo, "cat-file", "--batch"], input=("\n".join(ids) + "\n").encode(),
                       capture_output=True, check=True)
    data, pos, res = p.stdout, 0, {}
    while pos < len(data):
        nl = data.index(b"\n", pos); oid, _, size = data[pos:nl].decode().split(); size = int(size)
        res[oid] = data[nl + 1:nl + 1 + size]; pos = nl + 1 + size + 1
    return res

def problems(raw):
    header = raw.split(b"\n\n", 1)[0].split(b"\n")
    keys = [l.split(b" ", 1)[0] for l in header if l and not l.startswith(b" ")]
    out, after = [], False
    if keys[:1] != [b"tree"]: out.append("first header is not tree")
    for k in keys[1:]:
        if k == b"committer": after = True
        elif after and k in (b"tree", b"parent", b"author", b"committer"): out.append(k.decode() + " after committer")
    if keys.count(b"tree") > 1: out.append("multiple tree headers")
    return out

bad = 0
for repo in sys.argv[1:]:
    ids = objects(repo)
    for i in range(0, len(ids), 500):
        for oid, raw in read(repo, ids[i:i + 500]).items():
            p = problems(raw)
            if p:
                bad += 1
                refs = subprocess.run(["git", "-C", repo, "for-each-ref", "--points-at", oid, "--format=%(refname)"],
                                      capture_output=True, text=True).stdout.split()
                print(f"{repo}: commit {oid}: {', '.join(p)}; refs: {refs or 'none'}")
    print(f"{repo}: {len(ids)} commits checked")
sys.exit(1 if bad else 0)
