Mercurial only allows exceptions to ignore patterns to be encoded in the same rule that defines the ignore pattern (usually via (?!...)
, (?<!...)
or similar regular expression constructs), even though its matching engine is considerably more powerful (and can in principle match on file size and other things specified in hg help filesets
).
This has two reasons, composability and performance.
Composability
When you have an include/exclude system for patterns, then order matters. Mercurial has various sources for ignore files: The .hgignore
file in the repository, as well as any ui.ignore
and ui.ignore.*
option in a hgrc
file that Mercurial processes, and every file included by a include:
directive from the above list. Allowing rules to interact with each other across various ignore files in surprising ways can lead to surprising situations that are difficult to debug. Alternatively, one kind of pattern (inclusion or exclusion) could be prioritized over others, but that poses problems of its own.
Performance
Mercurial uses ignore files to prune directory tree walks. If a directory matches an ignore rule, it is skipped in its entirety (such as a build output directory with thousands of temporary files). When you have an ignore pattern that matches a directory, but also an exception that matches a file in the directory, your options are to either not prune the directory walk or to disregard the exception for the directory (Git chooses to do the latter). Not pruning the directory walk is inefficient, disregarding exceptions within the directory severely limits the number of use cases that aren't already handled well by regular expressions.
Solution & Workarounds
A major difference between Mercurial and Git is that Mercurial provides the option to use Python's extended regular expressions in addition to glob patterns (Git uses glob patterns only). Regular expressions eliminate the need for exceptions for most (but not all) use cases.
These cases are usually handled adequately via a re:(?!exception)pattern
line in your .hgignore
file (and in some cases, other advanced regular expression patterns). Note that the (?x)
meta pattern, which ignores unescaped whitespace and comments in a regular expression, can be useful to make complex expressions more readable. For example, the following two are equivalent:
re:(?x) (^|/) (?!foobar) foo # do not ignore files starting with `foobar`.
re:(^|/)(?!foobar)foo
This works reasonably well for ignore files, since file patterns rarely include whitespace or hash symbols. Note also that you can switch to regular expression syntax on a line-by-line basis by prefixing it with re:
or regexp:
, even if you normally use syntax: glob
.
If that is insufficient, is possible to modify the existing behavior via extensions if no other option reasonably applies. You can override mercurial.dirstate.dirstate.{_ignore,_dirignore}
or you can automatically add -I/-X
options to the relevant commands based on (say) an .hginclexcl
file.