Skip to content

Commit

Permalink
[3.12] GH-106330: Fix matching of empty path in `pathlib.PurePath.mat…
Browse files Browse the repository at this point in the history
…ch()` (GH-106331) (GH-106372)

We match paths using the `_lines` attribute, which is derived from the
path's string representation. The bug arises because an empty path's string
representation is `'.'` (not `''`), which is matched by the `'*'` wildcard.
(cherry picked from commit b4efdf8)
  • Loading branch information
barneygale committed Jul 4, 2023
1 parent 930df7b commit d5ed72b
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 8 deletions.
27 changes: 19 additions & 8 deletions Lib/pathlib.py
Expand Up @@ -127,12 +127,19 @@ def _compile_pattern_lines(pattern_lines, case_sensitive):
# Match the start of the path, or just after a path separator
parts = ['^']
for part in pattern_lines.splitlines(keepends=True):
# We slice off the common prefix and suffix added by translate() to
# ensure that re.DOTALL is not set, and the end of the string not
# matched, respectively. With DOTALL not set, '*' wildcards will not
# match path separators, because the '.' characters in the pattern
# will not match newlines.
parts.append(fnmatch.translate(part)[_FNMATCH_SLICE])
if part == '*\n':
part = r'.+\n'
elif part == '*':
part = r'.+'
else:
# Any other component: pass to fnmatch.translate(). We slice off
# the common prefix and suffix added by translate() to ensure that
# re.DOTALL is not set, and the end of the string not matched,
# respectively. With DOTALL not set, '*' wildcards will not match
# path separators, because the '.' characters in the pattern will
# not match newlines.
part = fnmatch.translate(part)[_FNMATCH_SLICE]
parts.append(part)
# Match the end of the path, always.
parts.append(r'\Z')
flags = re.MULTILINE
Expand Down Expand Up @@ -501,8 +508,12 @@ def _lines(self):
try:
return self._lines_cached
except AttributeError:
trans = _SWAP_SEP_AND_NEWLINE[self._flavour.sep]
self._lines_cached = str(self).translate(trans)
path_str = str(self)
if path_str == '.':
self._lines_cached = ''
else:
trans = _SWAP_SEP_AND_NEWLINE[self._flavour.sep]
self._lines_cached = path_str.translate(trans)
return self._lines_cached

def __eq__(self, other):
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_pathlib.py
Expand Up @@ -317,6 +317,10 @@ def test_match_common(self):
self.assertTrue(P('A.py').match('a.PY', case_sensitive=False))
self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True))
self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False))
# Matching against empty path
self.assertFalse(P().match('*'))
self.assertTrue(P().match('**'))
self.assertFalse(P().match('**/*'))

def test_ordering_common(self):
# Ordering is tuple-alike.
Expand Down
@@ -0,0 +1,2 @@
Fix incorrect matching of empty paths in :meth:`pathlib.PurePath.match`.
This bug was introduced in Python 3.12.0 beta 1.

0 comments on commit d5ed72b

Please sign in to comment.