comparison lib/python3.8/site-packages/pip/_internal/vcs/subversion.py @ 0:9e54283cc701 draft

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