gh-99029: Fix handling of PureWindowsPath('C:\<blah>').relative_to('C:') (GH-99031)

`relative_to()` now treats naked drive paths as relative. This brings its
behaviour in line with other parts of pathlib, and with `ntpath.relpath()`,
and so allows us to factor out the pathlib-specific implementation.
This commit is contained in:
Barney Gale 2022-11-25 19:15:57 +00:00 committed by GitHub
parent 7d2dcc53d0
commit ae234fbc5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 20 additions and 52 deletions

View file

@ -632,57 +632,27 @@ class PurePath(object):
The *walk_up* parameter controls whether `..` may be used to resolve
the path.
"""
# For the purpose of this method, drive and root are considered
# separate parts, i.e.:
# Path('c:/').relative_to('c:') gives Path('/')
# Path('c:/').relative_to('/') raise ValueError
if not other:
raise TypeError("need at least one argument")
parts = self._parts
drv = self._drv
root = self._root
if root:
abs_parts = [drv, root] + parts[1:]
else:
abs_parts = parts
other_drv, other_root, other_parts = self._parse_args(other)
if other_root:
other_abs_parts = [other_drv, other_root] + other_parts[1:]
else:
other_abs_parts = other_parts
num_parts = len(other_abs_parts)
casefold = self._flavour.casefold_parts
num_common_parts = 0
for part, other_part in zip(casefold(abs_parts), casefold(other_abs_parts)):
if part != other_part:
path_cls = type(self)
other = path_cls(*other)
for step, path in enumerate([other] + list(other.parents)):
if self.is_relative_to(path):
break
num_common_parts += 1
if walk_up:
failure = root != other_root
if drv or other_drv:
failure = casefold([drv]) != casefold([other_drv]) or (failure and num_parts > 1)
error_message = "{!r} is not on the same drive as {!r}"
up_parts = (num_parts-num_common_parts)*['..']
else:
failure = (root or drv) if num_parts == 0 else num_common_parts != num_parts
error_message = "{!r} is not in the subpath of {!r}"
up_parts = []
error_message += " OR one path is relative and the other is absolute."
if failure:
formatted = self._format_parsed_parts(other_drv, other_root, other_parts)
raise ValueError(error_message.format(str(self), str(formatted)))
path_parts = up_parts + abs_parts[num_common_parts:]
new_root = root if num_common_parts == 1 else ''
return self._from_parsed_parts('', new_root, path_parts)
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
if step and not walk_up:
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
parts = ('..',) * step + self.parts[len(path.parts):]
return path_cls(*parts)
def is_relative_to(self, *other):
"""Return True if the path is relative to another path or False.
"""
try:
self.relative_to(*other)
return True
except ValueError:
return False
if not other:
raise TypeError("need at least one argument")
other = type(self)(*other)
return other == self or other in self.parents
@property
def parts(self):