Who really owns this code? A git blame sampler you can run in one command
Efe GençDev.to (EN Zone)
2 views
Commit counts measure activity. They do not measure whether any of it is still there.
I found this out while trying to describe my share of two private codebases in a way a stranger could check. Commits were the obvious number and the wrong one. On the frontend my share of surviving lines was higher than my commit share, which meant my code had replaced other people's. On the backend it was lower, which meant the opposite. Both facts said more than either count.
So I wrote the script properly and published it. One file, no dependencies, Node 20 or newer.
npx surviving-lines --sample 5 --include '**/*.ts' --exclude '**/*.test.ts'
Here it is on langchain-ai/openwiki at 1e6d54c, run on 4 September 2026. It took 0.3 seconds.
ref HEAD · files 50/203 sampled (1 in 5) · 14,722 of 59,049 lines attributed
git blame -w -M · commits 339, merges excluded
author lines share commits share
------------------------------------------------
Colin Francis 7,718 52.4% 61 18.0%
Brace Sproul 3,314 22.5% 61 18.0%
Greg Land 356 2.4% 9 2.7%
…
What this cannot show: quality of the lines, review work, design done in documents,
or code that was deleted on purpose. Share of surviving lines is about survivorship, not merit.
Two people with the same commit share, 61 each. One of them wrote more than half of the code that is still alive in the sample; the other wrote just under a quarter. A commit log would have called them equal.
What it measures
Two numbers, side by side, for one ref.
Surviving lines. git blame -w -M --line-porcelain on each sampled file, counted by author. -w ignores whitespace-only changes and -M follows lines moved inside a file, so a reformat or a relocation does not steal authorship. Add --copies for -C, which also follows lines copied between files; it is slower.
Commits. Non-merge commits reachable from the ref, optionally inside a --since and --until window. When you compare people who joined at different times, scope the window to a tenure, or the person who was there longest wins by default.
Binary files are skipped. Identities go through the repository's .mailmap, and when two rows still share a name the table shows the address so they cannot be confused.
Why sample
git blame walks history for every file, and on a large repository that is slow. Sampling makes the run cheap. The part I care about more is that the sample is deterministic: each path is hashed with FNV-1a, salted with an optional seed, and the file is in the sample when the hash is divisible by n.
export function fnv1a(text) {
let h = 0x811c9dc5;
for (let i = 0; i < text.length; i++) {
h ^= text.charCodeAt(i);
h = Math.imul(h, 0x01000193) >>> 0;
}
return h >>> 0;
}
export function inSample(path, n, seed) {
return n === 1 || fnv1a(seed + " " + path) % n === 0;
}
Anyone with the repository and the same command gets the same files and the same numbers. If you suspect the sample is flattering, change the seed and run it again. --sample 1 blames every file, and on a 60k-line TypeScript repository that is still well under a minute.
Parsing blame without a dependency
--line-porcelain repeats the full commit header for every line, so the parser is a scan, not a state machine:
export function countBlameLines(porcelain) {
const counts = new Map();
let name = "";
for (const line of porcelain.split("\n")) {
if (line.startsWith("author ")) name = line.slice(7);
else if (line.startsWith("author-mail ")) {
const mail = line.slice(12).replace(/^<|>$/g, "").toLowerCase();
const entry = counts.get(mail) ?? { name, lines: 0 };
entry.lines++;
counts.set(mail, entry);
}
}
return counts;
}
The file list and the total line count come from a single git diff --numstat against the empty tree, which also marks binaries as - so they can be dropped without opening them.
What it cannot show
The tool prints the caveat under every table on purpose, because the number is easy to misuse:
Nothing about the quality of the lines, or whether they should exist.
Review comments, design documents, pairing and mentoring leave no lines behind.
Code deleted on purpose counts for nobody, even when deleting it was the best contribution that month.
Generated and vendored files inflate whoever committed them. Exclude them.
A high share in a file nobody else touches is not the same as a high share in a file everybody touches. There is no weighting by contention.
It answers "whose code is still here?". It does not answer "who is the best engineer?", and I would distrust anyone who used it that way, including me.
Try it
npx surviving-lines --help
Source, tests and a CI matrix across Linux, macOS and Windows: github.com/Bubblegunn/surviving-lines. MIT.
The node:test suite builds a throwaway repository with two authors, a partial rewrite, a rename and a binary file, and checks that the rename is followed and the binary is skipped. If you run it on your own repository and the shares surprise you, I would like to hear which way they surprised you.
I wanted to see whether the extra brightness range on modern HDR displays could be useful for ordinary webpages, not just video. The result is Brightpixels, a dependency-free library built around two custom elements: ```html <bright-text intensity="12">Important words</bright-text> <b
Pretty happy with the overall design , especially how it handle on mobile. I plan to add more features, like weather layers and, of course, real time flight display. submitted by /u/CYRIAQU3 [link] [留言]
Over the past few days, I was trying out gov.uk prototype system for my static website. Like every night owl, the unbranded template's white background was hurting my eyes. I googled for some sort of dark mode, found The National Archives Design System. It suits my purpose, has the dark mode, and i