comparison env/lib/python3.9/site-packages/pip/_internal/vcs/subversion.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # The following comment should be removed at some point in the future.
2 # mypy: disallow-untyped-defs=False
3
4 import logging
5 import os
6 import re
7
8 from pip._internal.utils.logging import indent_log
9 from pip._internal.utils.misc import (
10 display_path,
11 is_console_interactive,
12 rmtree,
13 split_auth_from_netloc,
14 )
15 from pip._internal.utils.subprocess import make_command
16 from pip._internal.utils.typing import MYPY_CHECK_RUNNING
17 from pip._internal.vcs.versioncontrol import RemoteNotFoundError, VersionControl, vcs
18
19 _svn_xml_url_re = re.compile('url="([^"]+)"')
20 _svn_rev_re = re.compile(r'committed-rev="(\d+)"')
21 _svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"')
22 _svn_info_xml_url_re = re.compile(r'<url>(.*)</url>')
23
24
25 if MYPY_CHECK_RUNNING:
26 from typing import Optional, Tuple
27
28 from pip._internal.utils.misc import HiddenText
29 from pip._internal.utils.subprocess import CommandArgs
30 from pip._internal.vcs.versioncontrol import AuthInfo, RevOptions
31
32
33 logger = logging.getLogger(__name__)
34
35
36 class Subversion(VersionControl):
37 name = 'svn'
38 dirname = '.svn'
39 repo_name = 'checkout'
40 schemes = (
41 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn', 'svn+file'
42 )
43
44 @classmethod
45 def should_add_vcs_url_prefix(cls, remote_url):
46 return True
47
48 @staticmethod
49 def get_base_rev_args(rev):
50 return ['-r', rev]
51
52 @classmethod
53 def get_revision(cls, location):
54 # type: (str) -> str
55 """
56 Return the maximum revision for all files under a given location
57 """
58 # Note: taken from setuptools.command.egg_info
59 revision = 0
60
61 for base, dirs, _ in os.walk(location):
62 if cls.dirname not in dirs:
63 dirs[:] = []
64 continue # no sense walking uncontrolled subdirs
65 dirs.remove(cls.dirname)
66 entries_fn = os.path.join(base, cls.dirname, 'entries')
67 if not os.path.exists(entries_fn):
68 # FIXME: should we warn?
69 continue
70
71 dirurl, localrev = cls._get_svn_url_rev(base)
72
73 if base == location:
74 base = dirurl + '/' # save the root url
75 elif not dirurl or not dirurl.startswith(base):
76 dirs[:] = []
77 continue # not part of the same svn tree, skip it
78 revision = max(revision, localrev)
79 return str(revision)
80
81 @classmethod
82 def get_netloc_and_auth(cls, netloc, scheme):
83 """
84 This override allows the auth information to be passed to svn via the
85 --username and --password options instead of via the URL.
86 """
87 if scheme == 'ssh':
88 # The --username and --password options can't be used for
89 # svn+ssh URLs, so keep the auth information in the URL.
90 return super().get_netloc_and_auth(netloc, scheme)
91
92 return split_auth_from_netloc(netloc)
93
94 @classmethod
95 def get_url_rev_and_auth(cls, url):
96 # type: (str) -> Tuple[str, Optional[str], AuthInfo]
97 # hotfix the URL scheme after removing svn+ from svn+ssh:// readd it
98 url, rev, user_pass = super().get_url_rev_and_auth(url)
99 if url.startswith('ssh://'):
100 url = 'svn+' + url
101 return url, rev, user_pass
102
103 @staticmethod
104 def make_rev_args(username, password):
105 # type: (Optional[str], Optional[HiddenText]) -> CommandArgs
106 extra_args = [] # type: CommandArgs
107 if username:
108 extra_args += ['--username', username]
109 if password:
110 extra_args += ['--password', password]
111
112 return extra_args
113
114 @classmethod
115 def get_remote_url(cls, location):
116 # type: (str) -> str
117 # In cases where the source is in a subdirectory, not alongside
118 # setup.py we have to look up in the location until we find a real
119 # setup.py
120 orig_location = location
121 while not os.path.exists(os.path.join(location, 'setup.py')):
122 last_location = location
123 location = os.path.dirname(location)
124 if location == last_location:
125 # We've traversed up to the root of the filesystem without
126 # finding setup.py
127 logger.warning(
128 "Could not find setup.py for directory %s (tried all "
129 "parent directories)",
130 orig_location,
131 )
132 raise RemoteNotFoundError
133
134 url, _rev = cls._get_svn_url_rev(location)
135 if url is None:
136 raise RemoteNotFoundError
137
138 return url
139
140 @classmethod
141 def _get_svn_url_rev(cls, location):
142 from pip._internal.exceptions import InstallationError
143
144 entries_path = os.path.join(location, cls.dirname, 'entries')
145 if os.path.exists(entries_path):
146 with open(entries_path) as f:
147 data = f.read()
148 else: # subversion >= 1.7 does not have the 'entries' file
149 data = ''
150
151 if (data.startswith('8') or
152 data.startswith('9') or
153 data.startswith('10')):
154 data = list(map(str.splitlines, data.split('\n\x0c\n')))
155 del data[0][0] # get rid of the '8'
156 url = data[0][3]
157 revs = [int(d[9]) for d in data if len(d) > 9 and d[9]] + [0]
158 elif data.startswith('<?xml'):
159 match = _svn_xml_url_re.search(data)
160 if not match:
161 raise ValueError(
162 'Badly formatted data: {data!r}'.format(**locals()))
163 url = match.group(1) # get repository URL
164 revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)] + [0]
165 else:
166 try:
167 # subversion >= 1.7
168 # Note that using get_remote_call_options is not necessary here
169 # because `svn info` is being run against a local directory.
170 # We don't need to worry about making sure interactive mode
171 # is being used to prompt for passwords, because passwords
172 # are only potentially needed for remote server requests.
173 xml = cls.run_command(
174 ['info', '--xml', location],
175 show_stdout=False,
176 stdout_only=True,
177 )
178 url = _svn_info_xml_url_re.search(xml).group(1)
179 revs = [
180 int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml)
181 ]
182 except InstallationError:
183 url, revs = None, []
184
185 if revs:
186 rev = max(revs)
187 else:
188 rev = 0
189
190 return url, rev
191
192 @classmethod
193 def is_commit_id_equal(cls, dest, name):
194 """Always assume the versions don't match"""
195 return False
196
197 def __init__(self, use_interactive=None):
198 # type: (bool) -> None
199 if use_interactive is None:
200 use_interactive = is_console_interactive()
201 self.use_interactive = use_interactive
202
203 # This member is used to cache the fetched version of the current
204 # ``svn`` client.
205 # Special value definitions:
206 # None: Not evaluated yet.
207 # Empty tuple: Could not parse version.
208 self._vcs_version = None # type: Optional[Tuple[int, ...]]
209
210 super().__init__()
211
212 def call_vcs_version(self):
213 # type: () -> Tuple[int, ...]
214 """Query the version of the currently installed Subversion client.
215
216 :return: A tuple containing the parts of the version information or
217 ``()`` if the version returned from ``svn`` could not be parsed.
218 :raises: BadCommand: If ``svn`` is not installed.
219 """
220 # Example versions:
221 # svn, version 1.10.3 (r1842928)
222 # compiled Feb 25 2019, 14:20:39 on x86_64-apple-darwin17.0.0
223 # svn, version 1.7.14 (r1542130)
224 # compiled Mar 28 2018, 08:49:13 on x86_64-pc-linux-gnu
225 # svn, version 1.12.0-SlikSvn (SlikSvn/1.12.0)
226 # compiled May 28 2019, 13:44:56 on x86_64-microsoft-windows6.2
227 version_prefix = 'svn, version '
228 version = self.run_command(
229 ['--version'], show_stdout=False, stdout_only=True
230 )
231 if not version.startswith(version_prefix):
232 return ()
233
234 version = version[len(version_prefix):].split()[0]
235 version_list = version.partition('-')[0].split('.')
236 try:
237 parsed_version = tuple(map(int, version_list))
238 except ValueError:
239 return ()
240
241 return parsed_version
242
243 def get_vcs_version(self):
244 # type: () -> Tuple[int, ...]
245 """Return the version of the currently installed Subversion client.
246
247 If the version of the Subversion client has already been queried,
248 a cached value will be used.
249
250 :return: A tuple containing the parts of the version information or
251 ``()`` if the version returned from ``svn`` could not be parsed.
252 :raises: BadCommand: If ``svn`` is not installed.
253 """
254 if self._vcs_version is not None:
255 # Use cached version, if available.
256 # If parsing the version failed previously (empty tuple),
257 # do not attempt to parse it again.
258 return self._vcs_version
259
260 vcs_version = self.call_vcs_version()
261 self._vcs_version = vcs_version
262 return vcs_version
263
264 def get_remote_call_options(self):
265 # type: () -> CommandArgs
266 """Return options to be used on calls to Subversion that contact the server.
267
268 These options are applicable for the following ``svn`` subcommands used
269 in this class.
270
271 - checkout
272 - export
273 - switch
274 - update
275
276 :return: A list of command line arguments to pass to ``svn``.
277 """
278 if not self.use_interactive:
279 # --non-interactive switch is available since Subversion 0.14.4.
280 # Subversion < 1.8 runs in interactive mode by default.
281 return ['--non-interactive']
282
283 svn_version = self.get_vcs_version()
284 # By default, Subversion >= 1.8 runs in non-interactive mode if
285 # stdin is not a TTY. Since that is how pip invokes SVN, in
286 # call_subprocess(), pip must pass --force-interactive to ensure
287 # the user can be prompted for a password, if required.
288 # SVN added the --force-interactive option in SVN 1.8. Since
289 # e.g. RHEL/CentOS 7, which is supported until 2024, ships with
290 # SVN 1.7, pip should continue to support SVN 1.7. Therefore, pip
291 # can't safely add the option if the SVN version is < 1.8 (or unknown).
292 if svn_version >= (1, 8):
293 return ['--force-interactive']
294
295 return []
296
297 def export(self, location, url):
298 # type: (str, HiddenText) -> None
299 """Export the svn repository at the url to the destination location"""
300 url, rev_options = self.get_url_rev_options(url)
301
302 logger.info('Exporting svn repository %s to %s', url, location)
303 with indent_log():
304 if os.path.exists(location):
305 # Subversion doesn't like to check out over an existing
306 # directory --force fixes this, but was only added in svn 1.5
307 rmtree(location)
308 cmd_args = make_command(
309 'export', self.get_remote_call_options(),
310 rev_options.to_args(), url, location,
311 )
312 self.run_command(cmd_args, show_stdout=False)
313
314 def fetch_new(self, dest, url, rev_options):
315 # type: (str, HiddenText, RevOptions) -> None
316 rev_display = rev_options.to_display()
317 logger.info(
318 'Checking out %s%s to %s',
319 url,
320 rev_display,
321 display_path(dest),
322 )
323 cmd_args = make_command(
324 'checkout', '-q', self.get_remote_call_options(),
325 rev_options.to_args(), url, dest,
326 )
327 self.run_command(cmd_args)
328
329 def switch(self, dest, url, rev_options):
330 # type: (str, HiddenText, RevOptions) -> None
331 cmd_args = make_command(
332 'switch', self.get_remote_call_options(), rev_options.to_args(),
333 url, dest,
334 )
335 self.run_command(cmd_args)
336
337 def update(self, dest, url, rev_options):
338 # type: (str, HiddenText, RevOptions) -> None
339 cmd_args = make_command(
340 'update', self.get_remote_call_options(), rev_options.to_args(),
341 dest,
342 )
343 self.run_command(cmd_args)
344
345
346 vcs.register(Subversion)