Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge branch 'main' into gh-77609-glob-follow-symlinks
  • Loading branch information
barneygale committed May 3, 2023
commit 5fb7d28889d57cc8482613fb2cdcd918153aa8a6
38 changes: 20 additions & 18 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ def _ignore_error(exception):
return (getattr(exception, 'errno', None) in _IGNORED_ERRNOS or
getattr(exception, 'winerror', None) in _IGNORED_WINERRORS)


def _is_case_sensitive(flavour):
return flavour.normcase('Aa') == 'Aa'

#
# Globbing helpers
#
Expand All @@ -69,8 +73,6 @@ def _make_selector(pattern_parts, flavour):
cls = _ParentSelector
elif '**' in pat:
raise ValueError("Invalid pattern: '**' can only be an entire path component")
elif pat == '..':
cls = _ParentSelector
else:
cls = _WildcardSelector
return cls(pat, child_parts, flavour)
Expand All @@ -94,25 +96,25 @@ def select_from(self, parent_path, follow_symlinks):
selector. This can contain parent_path itself."""
path_cls = type(parent_path)
scandir = path_cls._scandir
normcase = path_cls._flavour.normcase
if not parent_path.is_dir():
return iter([])
return self._select_from(parent_path, follow_symlinks, scandir, normcase)
return self._select_from(parent_path, scandir, follow_symlinks)


class _TerminatingSelector:

def _select_from(self, parent_path, follow_symlinks, scandir, normcase):
def _select_from(self, parent_path, scandir, follow_symlinks):
yield parent_path


class _ParentSelector(_Selector):
def __init__(self, pat, child_parts, flavour):
def __init__(self, name, child_parts, flavour):
_Selector.__init__(self, child_parts, flavour)

def _select_from(self, parent_path, follow_symlinks, scandir, normcase):
def _select_from(self, parent_path, scandir, follow_symlinks):
path = parent_path._make_child_relpath('..')
return self.successor._select_from(path, follow_symlinks, scandir, normcase)
for p in self.successor._select_from(path, scandir, follow_symlinks):
yield p


class _WildcardSelector(_Selector):
Expand All @@ -122,7 +124,7 @@ def __init__(self, pat, child_parts, flavour):
self.match = re.compile(fnmatch.translate(pat), flags=flags).fullmatch
_Selector.__init__(self, child_parts, flavour)

def _select_from(self, parent_path, follow_symlinks, scandir, normcase):
def _select_from(self, parent_path, scandir, follow_symlinks):
follow_dirlinks = True if follow_symlinks is None else follow_symlinks
try:
# We must close the scandir() object before proceeding to
Expand All @@ -144,7 +146,7 @@ def _select_from(self, parent_path, follow_symlinks, scandir, normcase):
name = entry.name
if self.match(name):
path = parent_path._make_child_relpath(name)
for p in self.successor._select_from(path, follow_symlinks, scandir, normcase):
for p in self.successor._select_from(path, scandir, follow_symlinks):
yield p
except PermissionError:
return
Expand All @@ -155,8 +157,7 @@ class _RecursiveWildcardSelector(_Selector):
def __init__(self, pat, child_parts, flavour):
_Selector.__init__(self, child_parts, flavour)

def _iterate_directories(self, parent_path, follow_symlinks, scandir):
follow_dirlinks = False if follow_symlinks is None else follow_symlinks
def _iterate_directories(self, parent_path, scandir, follow_symlinks):
yield parent_path
try:
# We must close the scandir() object before proceeding to
Expand All @@ -166,24 +167,25 @@ def _iterate_directories(self, parent_path, follow_symlinks, scandir):
for entry in entries:
entry_is_dir = False
try:
entry_is_dir = entry.is_dir(follow_symlinks=follow_dirlinks)
entry_is_dir = entry.is_dir(follow_symlinks=follow_symlinks)
except OSError as e:
if not _ignore_error(e):
raise
if entry_is_dir:
path = parent_path._make_child_relpath(entry.name)
for p in self._iterate_directories(path, follow_symlinks, scandir):
for p in self._iterate_directories(path, scandir, follow_symlinks):
yield p
except PermissionError:
return

def _select_from(self, parent_path, follow_symlinks, scandir, normcase):
def _select_from(self, parent_path, scandir, follow_symlinks):
follow_dirlinks = False if follow_symlinks is None else follow_symlinks
try:
yielded = set()
try:
successor_select = self.successor._select_from
for starting_point in self._iterate_directories(parent_path, follow_symlinks, scandir):
for p in successor_select(starting_point, follow_symlinks, scandir, normcase):
for starting_point in self._iterate_directories(parent_path, scandir, follow_dirlinks):
for p in successor_select(starting_point, scandir, follow_symlinks):
if p not in yielded:
yield p
yielded.add(p)
Expand Down Expand Up @@ -855,7 +857,7 @@ def rglob(self, pattern, *, follow_symlinks=None):
"to either True or False.")
warnings._deprecated("pathlib.Path.rglob(pattern, follow_symlinks=None)", msg,
remove=(3, 14))
drv, root, pattern_parts = self._parse_parts((pattern,))
drv, root, pattern_parts = self._parse_path(pattern)
if drv or root:
raise NotImplementedError("Non-relative patterns are unsupported")
if pattern and pattern[-1] in (self._flavour.sep, self._flavour.altsep):
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,8 @@ def _check(path, glob, expected):
_check(p, "*/fileB", ['dirB/fileB'])
else:
_check(p, "*/fileB", ['dirB/fileB', 'linkB/fileB'])
if os_helper.can_symlink():
_check(p, "brokenLink", ['brokenLink'])

if not os_helper.can_symlink():
_check(p, "*/", ["dirA", "dirB", "dirC", "dirE"])
Expand Down Expand Up @@ -1984,8 +1986,13 @@ def test_glob_dotdot(self):
P = self.cls
p = P(BASE)
self.assertEqual(set(p.glob("..", follow_symlinks=True)), { P(BASE, "..") })
self.assertEqual(set(p.glob("../..", follow_symlinks=True)), { P(BASE, "..", "..") })
self.assertEqual(set(p.glob("dirA/..", follow_symlinks=True)), { P(BASE, "dirA", "..") })
self.assertEqual(set(p.glob("dirA/../file*", follow_symlinks=True)), { P(BASE, "dirA/../fileA") })
self.assertEqual(set(p.glob("dirA/../file*/..", follow_symlinks=True)), set())
self.assertEqual(set(p.glob("../xyzzy", follow_symlinks=True)), set())
self.assertEqual(set(p.glob("xyzzy/..", follow_symlinks=True)), set())
self.assertEqual(set(p.glob("/".join([".."] * 50), follow_symlinks=True)), { P(BASE, *[".."] * 50)})

@os_helper.skip_unless_symlink
def test_glob_permissions(self):
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.