Skip to content

Commit d5ed72b

Browse files
authored
[3.12] GH-106330: Fix matching of empty path in pathlib.PurePath.match() (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)
1 parent 930df7b commit d5ed72b

File tree

3 files changed

+25
-8
lines changed

3 files changed

+25
-8
lines changed

Lib/pathlib.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,19 @@ def _compile_pattern_lines(pattern_lines, case_sensitive):
127127
# Match the start of the path, or just after a path separator
128128
parts = ['^']
129129
for part in pattern_lines.splitlines(keepends=True):
130-
# We slice off the common prefix and suffix added by translate() to
131-
# ensure that re.DOTALL is not set, and the end of the string not
132-
# matched, respectively. With DOTALL not set, '*' wildcards will not
133-
# match path separators, because the '.' characters in the pattern
134-
# will not match newlines.
135-
parts.append(fnmatch.translate(part)[_FNMATCH_SLICE])
130+
if part == '*\n':
131+
part = r'.+\n'
132+
elif part == '*':
133+
part = r'.+'
134+
else:
135+
# Any other component: pass to fnmatch.translate(). We slice off
136+
# the common prefix and suffix added by translate() to ensure that
137+
# re.DOTALL is not set, and the end of the string not matched,
138+
# respectively. With DOTALL not set, '*' wildcards will not match
139+
# path separators, because the '.' characters in the pattern will
140+
# not match newlines.
141+
part = fnmatch.translate(part)[_FNMATCH_SLICE]
142+
parts.append(part)
136143
# Match the end of the path, always.
137144
parts.append(r'\Z')
138145
flags = re.MULTILINE
@@ -501,8 +508,12 @@ def _lines(self):
501508
try:
502509
return self._lines_cached
503510
except AttributeError:
504-
trans = _SWAP_SEP_AND_NEWLINE[self._flavour.sep]
505-
self._lines_cached = str(self).translate(trans)
511+
path_str = str(self)
512+
if path_str == '.':
513+
self._lines_cached = ''
514+
else:
515+
trans = _SWAP_SEP_AND_NEWLINE[self._flavour.sep]
516+
self._lines_cached = path_str.translate(trans)
506517
return self._lines_cached
507518

508519
def __eq__(self, other):

Lib/test/test_pathlib.py

+4
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ def test_match_common(self):
317317
self.assertTrue(P('A.py').match('a.PY', case_sensitive=False))
318318
self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True))
319319
self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False))
320+
# Matching against empty path
321+
self.assertFalse(P().match('*'))
322+
self.assertTrue(P().match('**'))
323+
self.assertFalse(P().match('**/*'))
320324

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

0 commit comments

Comments
 (0)