Repository navigation
fix: six correctness issues in author matching, excludes and lead time - #7
Merged
Merged
Conversation
Found by an adversarial review pass and each reproduced before fixing.
Author matching was case-sensitive. --fixed-strings does not fold case, so
a repo whose ident reads "Alice@Corp.com" returned 0 added, 0 commits and
exit status 0 for anyone typing their address in lower case. Email domains
are case-insensitive by spec and local parts are in practice, so this was a
confident silent zero, indistinguishable from having shipped nothing.
--regexp-ignore-case now composes with --fixed-strings.
An uncompilable --exclude glob excluded nothing, silently. filepath.Match
reports a bad pattern as an error rather than a non-match and every call
site discarded it, so `-e '[invalid'` produced totals identical to no
exclude at all. Patterns are now validated once at flag-parse time.
"vendor/**" excluded nothing while "vendor/*" excluded the whole subtree.
filepath.Match has no globstar, and the directory fast path that makes the
single star recursive did not fire for the double star. The more explicit
pattern was the one that quietly did nothing. "**" now folds to "*", and a
leading "./" is stripped, which previously made './vendor/*' a no-op.
Non-ASCII filenames defeated every exclude. core.quotePath defaults on, so
git wraps such paths in double quotes with octal escapes
("caf\303\251.txt") and the surrounding quotes make globs miss. All three
numstat readers now go through git.LogArgs, which disables it.
authoredLeadTime selected the wrong commits. A committer date later than
the author date identifies a rewritten commit, not a reviewed one, so
cherry-picks, amends and history rewrites qualified while commits that went
straight to main were excluded. A repo with two healthy same-day commits
and one cherry-pick of two-year-old work reported "Median 732.0 days". Gaps
longer than the analysed period are now discarded and a median needs at
least three samples, which keeps genuine rebase workflows working while
suppressing rewritten history.
Also: a daily average below 0.5 printed as a flat "0 lines/day" because of
%.0f, which reads as "shipped nothing" on the headline number.
The previous commit normalised exclude patterns with filepath.Clean, whose separator is a backslash on Windows. That rewrote "vendor/*" to "vendor\*", which matched nothing, so --exclude stopped working entirely there. CI caught it: TestAnalyzeExcludePatterns reported 110 added instead of 10. git reports forward-slash paths on every platform, so pattern handling now uses path rather than filepath throughout. filepath remains where it belongs, for real filesystem walks. Also renames the IsGitRepo and FindRepos parameters from "path" to "dir". They shadowed the newly imported package, which the compiler surfaced as soon as the shadow was removed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Six findings from an adversarial review pass. Each was reproduced before fixing and has a regression test.
HIGH
1. Author matching was case-sensitive, producing a silent zero
--fixed-stringsdoes not fold case. A repo whose ident readsAlice@Corp.com:Email domains are case-insensitive by spec and local parts are in practice, so people type their own address lowercase. The result was indistinguishable from "you did no work this period".
--regexp-ignore-casenow composes with--fixed-strings.2. An uncompilable
--excludeglob excluded nothingfilepath.Matchreports a bad pattern asErrBadPatternrather than a non-match, and all three call sites discarded the error:Patterns are now validated once at flag-parse time and rejected loudly.
3.
authoredLeadTimeselected exactly the wrong commitsA committer date later than the author date identifies a rewritten commit, not a reviewed one. Cherry-picks, amends, rebases and whole-history rewrites all qualify, while commits that went straight to main have
ct == atand were excluded. The sample was biased entirely toward rewritten history.A repo with two healthy same-day commits plus one cherry-pick of two-year-old work:
A repo that has run
filter-reporewrites the committer date on every commit, so every sample would be garbage.Two guards: gaps longer than the analysed period are discarded, since work that took longer than the window did not flow through it, and a median now requires at least three samples. Verified both directions — a genuine rebase repo with four commits landing two days after authoring still reports
Median 2.0 days (4 commits), while the cherry-pick case now reports no signal.MEDIUM
4.
dir/**excluded nothing whiledir/*excluded everythingfilepath.Matchhas no globstar, and the directory fast path that makes the single star recursive did not fire for**. Backwards from gitignore intuition: the more explicit pattern was the one that silently did nothing.**now folds to*.5. A leading
./made a pattern a no-op-e './vendor/*'excluded nothing. numstat paths are repo-relative with no./, and the directory branch split on the first/giving.as the prefix, which never matched. Tab-completing a path is the obvious way to produce this. Patterns are now cleaned.6. Non-ASCII filenames defeated every exclude
core.quotePathdefaults on, so git wraps such paths in double quotes with octal escapes:The surrounding quotes make every glob miss, so
-e '*.txt'left 2 lines counted. All three numstat readers now go through a sharedgit.LogArgsthat setscore.quotePath=false.LOW
A daily average below 0.5 printed as a flat
0 lines/daybecause of%.0f. That is the headline number and "0" reads as "shipped nothing"; small rates now show a decimal.Confirmed clean during the same pass
Worth recording since these were probed hard and held up:
(int(t.Weekday())+6)%7is a correct Monday anchor; keys sort lexically and chronologically across a year boundary; no DST exposure because only calendar dates are used. Verified across Mon 2025-12-29 → Mon 2026-01-05.<>anchoring cannot be defeated by a crafted name. Committing asGIT_AUTHOR_NAME='Mallory <alice@corp.com>'makes git storeMallory alice@corp.com <mallory@evil.com>; the angle brackets are stripped from the name field, so the impersonation never reaches the searched string.--authormatches the mailmap-resolved ident, so a.mailmapunifying two addresses is honoured. Desirable, but it means totals depend on each repo's.mailmapwhen-raggregates across repos. Worth documenting later.Verification
go build,go vet,go test,gofmt -lcleanNaN/Infinity; 7 HTML paths render with no unrendered directivesFollow-up in this PR: a Windows regression the CI caught
The first pass normalised patterns with
filepath.Clean, whose separator is a backslash on Windows. That rewrotevendor/*tovendor\*, matching nothing, so--excludestopped working entirely on that platform:git reports forward-slash paths on every platform, so pattern handling now uses
pathrather thanfilepaththroughout;filepathstays where it belongs, for real filesystem walks. Removing the shadow also surfaced thatIsGitRepoandFindRepostook a parameter namedpath, nowdir.This is the cross-platform CI added in #4 paying for itself on its first real test.