Skip to content

Commit 8e61f41

Browse files
committed
More complicated conversion
1 parent dd8a117 commit 8e61f41

File tree

1 file changed

+40
-3
lines changed

1 file changed

+40
-3
lines changed

src/pip/_internal/commands/show.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import pathlib
44
from optparse import Values
5-
from typing import Iterator, List, NamedTuple, Optional
5+
from typing import Iterator, List, NamedTuple, Optional, Tuple
66

77
from pip._vendor.packaging.utils import canonicalize_name
88

@@ -66,6 +66,33 @@ class _PackageInfo(NamedTuple):
6666
files: Optional[List[str]]
6767

6868

69+
def _covert_legacy_entry(entry: Tuple[str], info: Tuple[str]) -> str:
70+
"""Convert a legacy installed-files.txt path into modern RECORD path.
71+
72+
The legacy format stores paths relative to the info directory, while the
73+
modern format stores paths relative to the package root, e.g. the
74+
site-packages directory.
75+
76+
:param entry: Path parts of the installed-files.txt entry.
77+
:param info: Path parts of the egg-info directory relative to package root.
78+
:returns: The converted entry.
79+
80+
For best compatibility with symlinks, this does not use ``abspath()`` or
81+
``Path.resolve()``, but tries to work with path parts:
82+
83+
1. While ``entry`` starts with ``..``, remove the equal amounts of parts
84+
from ``info``; if ``info`` is empty, start appending ``..`` instead.
85+
2. Join the two directly.
86+
"""
87+
while entry and entry[0] == "..":
88+
if not info or info[-1] == "..":
89+
info += ("..",)
90+
else:
91+
info = info[:-1]
92+
entry = entry[1:]
93+
return str(pathlib.Path(*info, *entry))
94+
95+
6996
def search_packages_info(query: List[str]) -> Iterator[_PackageInfo]:
7097
"""
7198
Gather details from installed distributions. Print distribution name,
@@ -100,7 +127,8 @@ def _files_from_record(dist: BaseDistribution) -> Optional[Iterator[str]]:
100127
text = dist.read_text('RECORD')
101128
except FileNotFoundError:
102129
return None
103-
return (row[0] for row in csv.reader(text.splitlines()))
130+
# This extra Path-str cast normalizes entries.
131+
return (str(pathlib.Path(row[0])) for row in csv.reader(text.splitlines()))
104132

105133
def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
106134
try:
@@ -112,7 +140,16 @@ def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
112140
info = dist.info_directory
113141
if root is None or info is None:
114142
return paths
115-
return (str(pathlib.Path(info, p).relative_to(root)) for p in paths)
143+
try:
144+
info_rel = pathlib.Path(info).relative_to(root)
145+
except ValueError: # info is not relative to root.
146+
return paths
147+
if not info_rel.parts: # info *is* root.
148+
return paths
149+
return (
150+
_covert_legacy_entry(pathlib.Path(p).parts, info_rel.parts)
151+
for p in paths
152+
)
116153

117154
for query_name in query_names:
118155
try:

0 commit comments

Comments
 (0)