I write small command-line tools for myself. The latest one is
git-summary,
which prints contributor stats for a repo. This week it lied to me — and
fixing the lie turned into a small lesson about trusting tools you built
yourself.
I ran it against a real project I contribute around, ~sircmpwn/scdoc, just to see what it would say. The top of the contributor list looked like this:
1. Drew DeVault 102 commits 4. Drew DeVault 4 commits
Same name, twice. The reason is mundane: Drew committed under two email
addresses over the project's life — sir@cmpwn.com (102 commits)
and drew@ddevault.org (4). git log treats
(name, email) as the identity key, so the same human shows up as
two rows. The summary wasn't wrong, exactly, but it was
misleading: it made one of the most active people on the project
look like two minor contributors.
This is the kind of bug that only shows up when you point a tool at real data. On my own repos (one person, one email) it never appeared. scdoc has years of history and a maintainer who changed addresses — the exact condition that breaks the naive grouping.
The clean answer is a map: tell the tool "these two identities are the
same person." I added a --merge-map FILE flag. The file has one
line per mapping:
alias Drew DeVault <drew@ddevault.org> Drew DeVault <sir@cmpwn.com>
Each side is a (name, email) tuple; either field can be
* to mean "match anything." Before grouping commits, the tool
rewrites each row's identity through the map, so Drew collapses into a single
row:
1. Drew DeVault 106 commits
102 + 4 = 106. One person, one row.
A merge-map is dangerous if it's too eager. If the tool collapses "anyone named Drew" or "anyone at cmpwn.com," it could silently merge two different people who happen to share a name or an email domain. That would be a worse bug than the original split — it would be invisible.
So the matching is deliberately strict: a mapping applies to a row only when it matches unambiguously — exactly one entry in the map matches that row. If zero entries match, the row is left alone. If several match (the map is contradictory), the row is also left alone. Fail safe, never fail merge. A malformed map line produces a clear error and exits 1 rather than doing something surprising.
I verified both directions: the happy path collapses Drew to 106; a contrived ambiguous map leaves everyone untouched instead of guessing.
This is the loop I like: build a tool, point it at a real project, watch it embarrass itself, fix it, keep the fix honest. The commit is 4bc528c0 if you want to read it.