A bare `!` line in .dvcignore kills every command with `unexpected error` (git accepts it silently)
One line in a .dvcignore that git accepts without complaint takes every DVC command in the repo down with unexpected error, exit 255. There is no partial failure and no way around it short of editing the file: status, add and data status all die before doing anything.
Repro
git init repro && cd repro && dvc init -q
printf '!\n' > .dvcignore
dvc statusERROR: unexpected error - Invalid git pattern: '!': Pattern normalized to nothing.Same for dvc add, dvc data status. A nested .dvcignore (e.g. data/.dvcignore) does it too — that is how I ran into it, on a tree built from psf/black's ignore file, which has a bare ! line in it.
Which lines, and what git does with them
.dvcignore holding that single line, then dvc status; alongside, the same line in a .gitignore and git check-ignore -q --no-index f.txt. rc=1 from git means "not ignored" — the line has no effect, and git says nothing about it.
| line | pattern_to_regex (pathspec 1.1.1) |
git 2.55.0 | dvc status |
|---|---|---|---|
! |
raises GitIgnorePatternError |
rc=1, silent | rc=255 |
! |
raises GitIgnorePatternError |
rc=1, silent | rc=255 |
\ |
raises GitIgnorePatternError |
rc=1, silent | rc=255 |
| `` (empty) | (None, None) |
rc=1, silent | rc=0 |
#x |
(None, None) |
rc=1, silent | rc=0 |
/ |
(None, None) |
rc=1, silent | rc=0 |
[a- |
(None, None) |
rc=1, silent | rc=0 |
!!, !/, // |
compiles | rc=1, silent | rc=0 |
So the degenerate-line case is already handled — it is the signalling that splits. pathspec returns (None, None) for most no-op lines and raises for three of them, and only those three reach the user as a crash.
Where
dvc/ignore.py, in DvcIgnorePatterns.__init__:
regex, ignore = GitIgnoreSpecPattern.pattern_to_regex(pattern_info.patterns)
if regex is not None and ignore is not None:The is not None guard on the second line is exactly the right intent — a line that compiles to nothing gets dropped. The call above it can raise, and nothing catches it, so three spellings of "line that compiles to nothing" take the process instead. Wrapping the call and treating the exception like the (None, None) it already handles (a logger.warning would be more than git offers) lands those three in the same bucket as the rest.
Versions
dvc 3.67.1, pathspec 1.1.1, git 2.55.0, Python 3.14.7, Linux. Nothing in the tracker matches — I searched Invalid git pattern, Pattern normalized to nothing, GitWildMatchPatternError, dvcignore + crash/invalid before writing.
Happy to send the patch if you want it that way.
Source: treeverse/dvc