2
2
import logging
3
3
import pathlib
4
4
from optparse import Values
5
- from typing import Iterator , List , NamedTuple , Optional
5
+ from typing import Iterator , List , NamedTuple , Optional , Tuple
6
6
7
7
from pip ._vendor .packaging .utils import canonicalize_name
8
8
@@ -66,6 +66,33 @@ class _PackageInfo(NamedTuple):
66
66
files : Optional [List [str ]]
67
67
68
68
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
+
69
96
def search_packages_info (query : List [str ]) -> Iterator [_PackageInfo ]:
70
97
"""
71
98
Gather details from installed distributions. Print distribution name,
@@ -100,7 +127,8 @@ def _files_from_record(dist: BaseDistribution) -> Optional[Iterator[str]]:
100
127
text = dist .read_text ('RECORD' )
101
128
except FileNotFoundError :
102
129
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 ()))
104
132
105
133
def _files_from_legacy (dist : BaseDistribution ) -> Optional [Iterator [str ]]:
106
134
try :
@@ -112,7 +140,16 @@ def _files_from_legacy(dist: BaseDistribution) -> Optional[Iterator[str]]:
112
140
info = dist .info_directory
113
141
if root is None or info is None :
114
142
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
+ )
116
153
117
154
for query_name in query_names :
118
155
try :
0 commit comments