Conversation
`DvcIgnorePatterns.__init__` already drops a pattern that compiles to
nothing - that is what the `regex is not None and ignore is not None`
guard is for. But `pattern_to_regex` raises for a few spellings of the
same thing instead of returning `(None, None)`, and nothing catches it,
so a `.dvcignore` containing a bare `!`, `! ` or `\` fails every command
in the repo with `unexpected error` and exit 255:
printf '!\n' > .dvcignore
dvc status
ERROR: unexpected error - Invalid git pattern: '!': Pattern normalized to nothing.
Git accepts all of those lines silently, as it does the ones pathspec
reports with `(None, None)`. Catch the error and drop the pattern the
same way, with a warning naming the offending line.
Fixes treeverse#11104
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #11106 +/- ##
==========================================
+ Coverage 90.68% 90.98% +0.30%
==========================================
Files 504 505 +1
Lines 39795 41149 +1354
Branches 3141 3263 +122
==========================================
+ Hits 36087 37439 +1352
- Misses 3042 3071 +29
+ Partials 666 639 -27 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
I opened #11104, so read this as an interested party measuring your patch, not as a review — I read the diff, I didn't audit it. I keep an outside conformance bench for
It fixes no divergence and it breaks none. What it moves is crashes into answers, and the repository it unblocks has a name: Worth knowing why this looks small through one door and total through the other. The offending line is nested (black ships it in Prevalence, since it argues against the size of the problem: exactly one of my 33 repositories ships such a line, and black ships it deliberately, in a fixture directory named Two smaller things I checked rather than assumed. All three spellings ( Adapter and corpus: https://github.com/KaizenShogun/gitignore-conformance ( |
Fixes #11104
The bug
A
.dvcignorecontaining a single bare!makes every command in the repo fail:Git accepts the same line without a word.
dvc addanddvc data statusfail identically, and a nested.dvcignoredoes it too — the reporter hit this on a tree built frompsf/black's ignore file, which has a bare!in it.Root cause
DvcIgnorePatterns.__init__already knows a pattern can compile to nothing; that is what the existing guard is for:pathspec signals "this line is a no-op" two different ways, though. For most such lines it returns
(None, None), which that guard handles. For a few it raisesGitIgnorePatternErrorinstead, and nothing catches it — so those three spellings of the same condition escape as a crash rather than being dropped.Reproduced on
main(56e59829) with pathspec 1.1.1, comparing each line against what git does with it in a.gitignore(rc=1fromgit check-ignoremeans "not ignored", i.e. the line had no effect):pattern_to_regexdvc statusbeforedvc statusafter!!\(None, None)#x(None, None)/(None, None)[a-(None, None)!!,!/,//The fix
Catch the error at the one call site and drop the pattern the way the
(None, None)case is already dropped, logging a warning that names the offending line:ValueErroris the caught type rather than the concrete class because the exception differs between pathspec versions —GitIgnorePatternErroron pathspec≥1 andGitWildMatchPatternErroron pathspec<1, bothValueErrorsubclasses — anddvc/ignore.pysupports both. That keeps the existing version shim from needing a third branch.Testing
Added
test_ignore_degenerate_patternnext totest_ignore_blank_line, parametrized over the three lines. It asserts both halves: the command does not blow up, and the valid pattern on the following line still takes effect (so a fix that swallowed the whole file would not pass).I confirmed the baseline first — reverting only
dvc/ignore.pyand re-running gives:With the fix,
tests/unit/test_ignore.pyis 57 passed andtests/func/test_ignore.pyis 30 passed / 3 failed. Those 3 (test_ignored_output,test_ignored_output_nested,test_run_dvcignored_dep) fail identically on a clean checkout ofmain— they are pre-existing and unrelated to this change.🤖 Generated with Claude Code