Skip to content

fix: bound the numstat split so a tab in a path does not throw - #105

Merged
matt-edmondson merged 2 commits into
mainfrom
claude/issue-102-numstat-tab
Sep 13, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
claude/issue-102-numstat-tab

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #102

The bug

ReadNumstatSection split each record on every tab:

string[] fields = record.Split('\t');

if (fields.Length != 3)
{
    throw new GitParseException($"Malformed numstat diff record: '{record}'.");
}

Unlike the raw section, where a path is its own NUL-delimited token, the numstat record carries its path inside the same token as the counts — 1\t0\ttab\tname.txt. A tab is a legal byte in a git path, so such a file yields four fields and throws, and because the throw takes out the whole listing rather than one entry, Diff().WithLineCounts() becomes unusable on that repository while plain Diff() reads it fine.

The fix

Bound the split at three fields and relax the length check to < 3, matching the pattern GitLogParser and GitTagParser already use for exactly this reason:

string[] fields = record.Split('\t', 3);

if (fields.Length < 3)

Both existing shapes read unchanged: the rename form "1\t0\t" still yields an empty third field and still consumes its two following path tokens, and a record with too few fields (e.g. the space-delimited "0 1 d.txt" covered by ThrowsForAMalformedNumstatRecord) still throws.

Tests

Two tests added to GitDiffParserTests:

  • ReadsANumstatPathContainingATabWithoutMistakingItForAnExtraField — the shape from the issue, captured from git 2.43 for a file named tab<TAB>name.txt. Verified to fail without the change (GitParseException: Malformed numstat diff record: '1\t0\ttab\tname.txt') and pass with it.
  • ReadsARenameWhoseNumstatPathsContainTabs — guards the rename form against the bound, since its empty path field and two trailing tokens are the shape most at risk from a change here.

Full suite: 578 passed, 0 failed. dotnet build -c Release: 0 warnings, 0 errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Vh7BAYZXphEn2dcrQddn1X


Generated by Claude Code

matt-edmondson and others added 2 commits September 13, 2026 07:23
The numstat record carries its path inside the same NUL-delimited token as
the counts, so splitting on every tab turns a legally tab-named path into a
fourth field and throws GitParseException — taking out the entire listing
rather than one entry. Bound the split at three fields and relax the length
check to `< 3`, matching what GitLogParser and GitTagParser already do.

Both existing shapes still read the same: a rename's "0\t0\t" yields an empty
third field and consumes its two following path tokens, and a malformed
record with too few fields still throws.

Fixes #102

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vh7BAYZXphEn2dcrQddn1X
The two new parser tests read a tab-named path end to end, which Windows CI
refused: RelativeFilePath rejects a control character there, and the raw
section converts its path before any numstat record is read, so the entry is
turned away before the bounded split is reached.

That refusal is deliberate — GitParseValues documents it as reporting the
path rather than dropping the entry — so branch on OperatingSystem.IsWindows()
and assert what each platform can observe: the full read on POSIX, and on
Windows that the failure is the representability one rather than a malformed
numstat record.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vh7BAYZXphEn2dcrQddn1X

Copy link
Copy Markdown
Contributor Author

Analyze & Release is red on an upstream outage, not on this change

Failing check: Analyze & Release on 6f40677. It fails in SonarScanner pre-processing, before any code is analysed:

07:35:30.896  Downloading from https://sonarcloud.io/api/server/version failed. Http status code is ServiceUnavailable.
07:35:30.897  An error occured while querying the server version! Please check if the server is running and if the address is correct.
07:35:30.898  Pre-processing failed. Exit code: 1

sonarcloud.io returned 503 to the scanner's very first request. That is an external service this diff does not touch — the change is three lines in GitDiffParser.ReadNumstatSection plus two parser tests — and the job never reaches the analysis it would fail on.

I re-ran the failed job once (run 34745325469) and it reproduced identically, same 503 at the same step. That is my one re-run spent.

Everything else on this head is green:

Check Result
Test on ubuntu-latest
Test on windows-latest
Test on macos-latest
CodeQL / Analyze (csharp, actions)
github-advanced-security

There is no fix to port: the scanner is behaving correctly and the remote is down. Nothing in this PR can make that request succeed, and working around it would mean disabling the analysis step, which is not this PR's to change. The job should pass on the next run once SonarCloud is reachable again.

I'll keep watching the PR and re-check until it's green.


Generated by Claude Code

@matt-edmondson
matt-edmondson merged commit 3b78bc4 into main Sep 13, 2026
16 of 18 checks passed
@matt-edmondson
matt-edmondson deleted the claude/issue-102-numstat-tab branch September 13, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Diff().WithLineCounts() throws GitParseException on any path containing a tab

1 participant