Sample output
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 1) const jwt = require("jsonwebtoken");
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 2) const SECRET = process.env.JWT_SECRET || "dev-secret";
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 3)
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 4) function verify(token) {
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 5) const claims = jwt.verify(token, SECRET, { algorithms: ["HS256"] });
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 6) if (claims.iss !== "payments-api") {
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 7) throw new Error("bad issuer");
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 8) }
37171b87 (Ada Okafor 2026-07-02 11:03:00 +0000 9) if (claims.exp < Date.now() / 1000 - 30) {
596a5dee (Marco Reyes 2026-06-11 14:41:00 +0000 10) throw new Error("token expired");
596a5dee (Marco Reyes 2026-06-11 14:41:00 +0000 11) }
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 12) return claims;
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 13) }
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 14)
^a81cc22 (Ada Okafor 2026-05-04 09:12:00 +0000 15) module.exports = { verify };
Run plain git blame on this file and eleven of those fifteen lines carry Sam Levine and a date in August, which is when the repository was reformatted. Here the reformat is gone and the file reads correctly again: Ada wrote the verifier in May, Marco added the expiry check on lines 10 and 11 in June, and Ada came back in July to allow thirty seconds of clock skew. The ^ prefix on a hash means a boundary commit, in this case the root commit, and it is git telling you it stopped there rather than that the line is especially old.
When you would reach for it
You are reading a line that looks wrong, or dangerous, or deliberate in a way you do not understand, and you want the commit message that explains it. Instead you get a commit called something like “apply prettier to everything” and a person who has no idea why the line exists. The same problem shows up in reverse during review, when you are trying to work out whether a rule has survived three years of deliberate decisions or was introduced by accident, and the answer depends entirely on whether blame is pointing at the author or at the formatter that ran over them. Once the ignore file is committed, this stops being a command you remember to type and becomes the state of the repository.
Gotchas
- Hashes in the file must be the full forty characters. A short hash produces
fatal: invalid object name: 6fba715and nothing is blamed. Generate entries withgit log -1 --format=%H, noting the capitalH; the lowercase%hgives you the abbreviated form that does not work here. -wand--ignore-revs-filerepair different halves of the same commit, which is why both appear in the command. Whitespace-only reindentation is handled by-walone, but quote style, trailing commas, and rewrapped long lines are real content changes and need the commit ignored outright. A formatter usually does all of it in one pass.- A line attributed through an ignored commit is being credited to a commit whose diff does not contain that exact text, since the text as it stands now was produced by the commit you skipped. Set
blame.markIgnoredLinesto have those lines marked with a?, which turns^a81cc22into^?a81cc2. The marker takes a character from the abbreviated hash rather than adding a column, so copying hashes out of marked output gives you a truncated one.blame.markUnblamableLinesadds a*for lines git could not attribute to anything earlier. - Both options require git 2.23 or later, released in August 2019. That covers the git shipped with macOS command line tools and with Debian 12, but a long-lived build server may be older, and the flags only ever affect display, so an out-of-date machine gives different answers for the same file rather than an obviously broken one.
Variants
$ git config blame.ignoreRevsFile .git-blame-ignore-revs
Set once per repository, after which a plain git blame obeys the file
$ git blame -w -L :verify --ignore-revs-file .git-blame-ignore-revs src/auth.js
Blame a single function by name, with no line numbers to look up first
$ git blame -w --ignore-revs-file .git-blame-ignore-revs --line-porcelain src/auth.js | grep '^author ' | sort | uniq -c | sort -rn
Count surviving lines per author to find out who to ask about this file