copier
copier copied to clipboard
Unordered pattern matching can lead to ignored negate conditions
https://github.com/copier-org/copier/blob/fa3515eff2cc6b69048ce5c66b5baef3efd5543f/copier/main.py#L216-L221
Pathspec.match file leverages pathspec.util.match_file^1
def match_file(self, file, separators=None):
# type: (Union[Text, PathLike], Optional[Collection[Text]]) -> bool
"""
Matches the file to this path-spec.
*file* (:class:`str` or :class:`~pathlib.PurePath`) is the file path
to be matched against :attr:`self.patterns <PathSpec.patterns>`.
*separators* (:class:`~collections.abc.Collection` of :class:`str`)
optionally contains the path separators to normalize. See
:func:`~pathspec.util.normalize_file` for more information.
Returns :data:`True` if *file* matched; otherwise, :data:`False`.
"""
norm_file = util.normalize_file(file, separators=separators)
return util.match_file(self.patterns, norm_file)
Pathspec.util.match_file^2 will iterate through each pattern until finished, and return a boolean (pattern match or not).
def match_file(patterns, file):
# type: (Iterable[Pattern], Text) -> bool
"""
Matches the file to the patterns.
*patterns* (:class:`~collections.abc.Iterable` of :class:`~pathspec.pattern.Pattern`)
contains the patterns to use.
*file* (:class:`str`) is the normalized file path to be matched
against *patterns*.
Returns :data:`True` if *file* matched; otherwise, :data:`False`.
"""
matched = False
for pattern in patterns:
if pattern.include is not None:
if file in pattern.match((file,)):
matched = pattern.include
return matched
This leads to a situation where a negate-pattern placed before a wildcard is ignored.
Example:
>>> x = ["!foo.txt", "*.txt", "bar.env", "blah.other"]
The most straightforward way to handle this is to sort the patterns before passing them to the pathspec library (modifying copier.worker._path_matcher).
Previous example, sorted:
['*.txt', 'bar.env', 'blah.other', '!foo.txt']
A PR is forthcoming.