Skip to content

Commit

Permalink
pythonGH-113528: Deoptimise pathlib._abc.PathBase.resolve()
Browse files Browse the repository at this point in the history
Replace use of `_from_parsed_parts()` with `with_segments()` in
`resolve()`.
  • Loading branch information
barneygale committed Jan 6, 2024
1 parent 2205510 commit ac02c78
Showing 1 changed file with 32 additions and 18 deletions.
50 changes: 32 additions & 18 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,9 +956,15 @@ def _split_stack(self):
uppermost parent of the path (equivalent to path.parents[-1]), and
*parts* is a reversed list of parts following the anchor.
"""
if not self._tail:
return self, []
return self._from_parsed_parts(self.drive, self.root, []), self._tail[::-1]
split = self.pathmod.split
path = str(self)
parent, name = split(path)
names = []
while path != parent:
names.append(name)
path = parent
parent, name = split(path)
return path, names

def resolve(self, strict=False):
"""
Expand All @@ -967,43 +973,53 @@ def resolve(self, strict=False):
"""
if self._resolving:
return self
path, parts = self._split_stack()
path_root, parts = self._split_stack()
path = self.with_segments(path_root)
try:
path = path.absolute()
except UnsupportedOperation:
pass
path_tail = []
else:
path_root, path_tail = path._split_stack()
path_tail.reverse()

# If the user has *not* overridden the `readlink()` method, then symlinks are unsupported
# and (in non-strict mode) we can improve performance by not calling `stat()`.
querying = strict or getattr(self.readlink, '_supported', True)
link_count = 0
while parts:
part = parts.pop()
if not part or part == '.':
continue
if part == '..':
if not path._tail:
if path.root:
if not path_tail:
if path_root:
# Delete '..' segment immediately following root
continue
elif path._tail[-1] != '..':
elif path_tail[-1] != '..':
# Delete '..' segment and its predecessor
path = path.parent
path_tail.pop()
continue
next_path = path._make_child_relpath(part)
path_tail.append(part)
if querying and part != '..':
next_path._resolving = True
path = self.with_segments(path_root + self.pathmod.sep.join(path_tail))
path._resolving = True
try:
st = next_path.stat(follow_symlinks=False)
st = path.stat(follow_symlinks=False)
if S_ISLNK(st.st_mode):
# Like Linux and macOS, raise OSError(errno.ELOOP) if too many symlinks are
# encountered during resolution.
link_count += 1
if link_count >= _MAX_SYMLINKS:
raise OSError(ELOOP, "Too many symbolic links in path", str(self))
target, target_parts = next_path.readlink()._split_stack()
target_root, target_parts = path.readlink()._split_stack()
# If the symlink target is absolute (like '/etc/hosts'), set the current
# path to its uppermost parent (like '/').
if target.root:
path = target
if target_root:
path_root = target_root
path_tail.clear()
else:
path_tail.pop()
# Add the symlink target's reversed tail parts (like ['hosts', 'etc']) to
# the stack of unresolved path parts.
parts.extend(target_parts)
Expand All @@ -1015,9 +1031,7 @@ def resolve(self, strict=False):
raise
else:
querying = False
next_path._resolving = False
path = next_path
return path
return self.with_segments(path_root + self.pathmod.sep.join(path_tail))

def symlink_to(self, target, target_is_directory=False):
"""
Expand Down

0 comments on commit ac02c78

Please sign in to comment.