diff options
Diffstat (limited to 'venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/__init__.py')
-rw-r--r-- | venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/__init__.py | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/__init__.py b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/__init__.py new file mode 100644 index 0000000..bff94fa --- /dev/null +++ b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/vcs/__init__.py | |||
@@ -0,0 +1,471 @@ | |||
1 | """Handles all VCS (version control) support""" | ||
2 | from __future__ import absolute_import | ||
3 | |||
4 | import copy | ||
5 | import errno | ||
6 | import logging | ||
7 | import os | ||
8 | import shutil | ||
9 | import sys | ||
10 | |||
11 | from pip._vendor.six.moves.urllib import parse as urllib_parse | ||
12 | |||
13 | from pip._internal.exceptions import BadCommand | ||
14 | from pip._internal.utils.misc import ( | ||
15 | display_path, backup_dir, call_subprocess, rmtree, ask_path_exists, | ||
16 | ) | ||
17 | from pip._internal.utils.typing import MYPY_CHECK_RUNNING | ||
18 | |||
19 | if MYPY_CHECK_RUNNING: | ||
20 | from typing import Dict, Optional, Tuple | ||
21 | from pip._internal.basecommand import Command | ||
22 | |||
23 | __all__ = ['vcs', 'get_src_requirement'] | ||
24 | |||
25 | |||
26 | logger = logging.getLogger(__name__) | ||
27 | |||
28 | |||
29 | class RevOptions(object): | ||
30 | |||
31 | """ | ||
32 | Encapsulates a VCS-specific revision to install, along with any VCS | ||
33 | install options. | ||
34 | |||
35 | Instances of this class should be treated as if immutable. | ||
36 | """ | ||
37 | |||
38 | def __init__(self, vcs, rev=None, extra_args=None): | ||
39 | """ | ||
40 | Args: | ||
41 | vcs: a VersionControl object. | ||
42 | rev: the name of the revision to install. | ||
43 | extra_args: a list of extra options. | ||
44 | """ | ||
45 | if extra_args is None: | ||
46 | extra_args = [] | ||
47 | |||
48 | self.extra_args = extra_args | ||
49 | self.rev = rev | ||
50 | self.vcs = vcs | ||
51 | |||
52 | def __repr__(self): | ||
53 | return '<RevOptions {}: rev={!r}>'.format(self.vcs.name, self.rev) | ||
54 | |||
55 | @property | ||
56 | def arg_rev(self): | ||
57 | if self.rev is None: | ||
58 | return self.vcs.default_arg_rev | ||
59 | |||
60 | return self.rev | ||
61 | |||
62 | def to_args(self): | ||
63 | """ | ||
64 | Return the VCS-specific command arguments. | ||
65 | """ | ||
66 | args = [] | ||
67 | rev = self.arg_rev | ||
68 | if rev is not None: | ||
69 | args += self.vcs.get_base_rev_args(rev) | ||
70 | args += self.extra_args | ||
71 | |||
72 | return args | ||
73 | |||
74 | def to_display(self): | ||
75 | if not self.rev: | ||
76 | return '' | ||
77 | |||
78 | return ' (to revision {})'.format(self.rev) | ||
79 | |||
80 | def make_new(self, rev): | ||
81 | """ | ||
82 | Make a copy of the current instance, but with a new rev. | ||
83 | |||
84 | Args: | ||
85 | rev: the name of the revision for the new object. | ||
86 | """ | ||
87 | return self.vcs.make_rev_options(rev, extra_args=self.extra_args) | ||
88 | |||
89 | |||
90 | class VcsSupport(object): | ||
91 | _registry = {} # type: Dict[str, Command] | ||
92 | schemes = ['ssh', 'git', 'hg', 'bzr', 'sftp', 'svn'] | ||
93 | |||
94 | def __init__(self): | ||
95 | # Register more schemes with urlparse for various version control | ||
96 | # systems | ||
97 | urllib_parse.uses_netloc.extend(self.schemes) | ||
98 | # Python >= 2.7.4, 3.3 doesn't have uses_fragment | ||
99 | if getattr(urllib_parse, 'uses_fragment', None): | ||
100 | urllib_parse.uses_fragment.extend(self.schemes) | ||
101 | super(VcsSupport, self).__init__() | ||
102 | |||
103 | def __iter__(self): | ||
104 | return self._registry.__iter__() | ||
105 | |||
106 | @property | ||
107 | def backends(self): | ||
108 | return list(self._registry.values()) | ||
109 | |||
110 | @property | ||
111 | def dirnames(self): | ||
112 | return [backend.dirname for backend in self.backends] | ||
113 | |||
114 | @property | ||
115 | def all_schemes(self): | ||
116 | schemes = [] | ||
117 | for backend in self.backends: | ||
118 | schemes.extend(backend.schemes) | ||
119 | return schemes | ||
120 | |||
121 | def register(self, cls): | ||
122 | if not hasattr(cls, 'name'): | ||
123 | logger.warning('Cannot register VCS %s', cls.__name__) | ||
124 | return | ||
125 | if cls.name not in self._registry: | ||
126 | self._registry[cls.name] = cls | ||
127 | logger.debug('Registered VCS backend: %s', cls.name) | ||
128 | |||
129 | def unregister(self, cls=None, name=None): | ||
130 | if name in self._registry: | ||
131 | del self._registry[name] | ||
132 | elif cls in self._registry.values(): | ||
133 | del self._registry[cls.name] | ||
134 | else: | ||
135 | logger.warning('Cannot unregister because no class or name given') | ||
136 | |||
137 | def get_backend_name(self, location): | ||
138 | """ | ||
139 | Return the name of the version control backend if found at given | ||
140 | location, e.g. vcs.get_backend_name('/path/to/vcs/checkout') | ||
141 | """ | ||
142 | for vc_type in self._registry.values(): | ||
143 | if vc_type.controls_location(location): | ||
144 | logger.debug('Determine that %s uses VCS: %s', | ||
145 | location, vc_type.name) | ||
146 | return vc_type.name | ||
147 | return None | ||
148 | |||
149 | def get_backend(self, name): | ||
150 | name = name.lower() | ||
151 | if name in self._registry: | ||
152 | return self._registry[name] | ||
153 | |||
154 | def get_backend_from_location(self, location): | ||
155 | vc_type = self.get_backend_name(location) | ||
156 | if vc_type: | ||
157 | return self.get_backend(vc_type) | ||
158 | return None | ||
159 | |||
160 | |||
161 | vcs = VcsSupport() | ||
162 | |||
163 | |||
164 | class VersionControl(object): | ||
165 | name = '' | ||
166 | dirname = '' | ||
167 | # List of supported schemes for this Version Control | ||
168 | schemes = () # type: Tuple[str, ...] | ||
169 | # Iterable of environment variable names to pass to call_subprocess(). | ||
170 | unset_environ = () # type: Tuple[str, ...] | ||
171 | default_arg_rev = None # type: Optional[str] | ||
172 | |||
173 | def __init__(self, url=None, *args, **kwargs): | ||
174 | self.url = url | ||
175 | super(VersionControl, self).__init__(*args, **kwargs) | ||
176 | |||
177 | def get_base_rev_args(self, rev): | ||
178 | """ | ||
179 | Return the base revision arguments for a vcs command. | ||
180 | |||
181 | Args: | ||
182 | rev: the name of a revision to install. Cannot be None. | ||
183 | """ | ||
184 | raise NotImplementedError | ||
185 | |||
186 | def make_rev_options(self, rev=None, extra_args=None): | ||
187 | """ | ||
188 | Return a RevOptions object. | ||
189 | |||
190 | Args: | ||
191 | rev: the name of a revision to install. | ||
192 | extra_args: a list of extra options. | ||
193 | """ | ||
194 | return RevOptions(self, rev, extra_args=extra_args) | ||
195 | |||
196 | def _is_local_repository(self, repo): | ||
197 | """ | ||
198 | posix absolute paths start with os.path.sep, | ||
199 | win32 ones start with drive (like c:\\folder) | ||
200 | """ | ||
201 | drive, tail = os.path.splitdrive(repo) | ||
202 | return repo.startswith(os.path.sep) or drive | ||
203 | |||
204 | # See issue #1083 for why this method was introduced: | ||
205 | # https://github.com/pypa/pip/issues/1083 | ||
206 | def translate_egg_surname(self, surname): | ||
207 | # For example, Django has branches of the form "stable/1.7.x". | ||
208 | return surname.replace('/', '_') | ||
209 | |||
210 | def export(self, location): | ||
211 | """ | ||
212 | Export the repository at the url to the destination location | ||
213 | i.e. only download the files, without vcs informations | ||
214 | """ | ||
215 | raise NotImplementedError | ||
216 | |||
217 | def get_url_rev(self): | ||
218 | """ | ||
219 | Returns the correct repository URL and revision by parsing the given | ||
220 | repository URL | ||
221 | """ | ||
222 | error_message = ( | ||
223 | "Sorry, '%s' is a malformed VCS url. " | ||
224 | "The format is <vcs>+<protocol>://<url>, " | ||
225 | "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp" | ||
226 | ) | ||
227 | assert '+' in self.url, error_message % self.url | ||
228 | url = self.url.split('+', 1)[1] | ||
229 | scheme, netloc, path, query, frag = urllib_parse.urlsplit(url) | ||
230 | rev = None | ||
231 | if '@' in path: | ||
232 | path, rev = path.rsplit('@', 1) | ||
233 | url = urllib_parse.urlunsplit((scheme, netloc, path, query, '')) | ||
234 | return url, rev | ||
235 | |||
236 | def get_info(self, location): | ||
237 | """ | ||
238 | Returns (url, revision), where both are strings | ||
239 | """ | ||
240 | assert not location.rstrip('/').endswith(self.dirname), \ | ||
241 | 'Bad directory: %s' % location | ||
242 | return self.get_url(location), self.get_revision(location) | ||
243 | |||
244 | def normalize_url(self, url): | ||
245 | """ | ||
246 | Normalize a URL for comparison by unquoting it and removing any | ||
247 | trailing slash. | ||
248 | """ | ||
249 | return urllib_parse.unquote(url).rstrip('/') | ||
250 | |||
251 | def compare_urls(self, url1, url2): | ||
252 | """ | ||
253 | Compare two repo URLs for identity, ignoring incidental differences. | ||
254 | """ | ||
255 | return (self.normalize_url(url1) == self.normalize_url(url2)) | ||
256 | |||
257 | def obtain(self, dest): | ||
258 | """ | ||
259 | Called when installing or updating an editable package, takes the | ||
260 | source path of the checkout. | ||
261 | """ | ||
262 | raise NotImplementedError | ||
263 | |||
264 | def switch(self, dest, url, rev_options): | ||
265 | """ | ||
266 | Switch the repo at ``dest`` to point to ``URL``. | ||
267 | |||
268 | Args: | ||
269 | rev_options: a RevOptions object. | ||
270 | """ | ||
271 | raise NotImplementedError | ||
272 | |||
273 | def update(self, dest, rev_options): | ||
274 | """ | ||
275 | Update an already-existing repo to the given ``rev_options``. | ||
276 | |||
277 | Args: | ||
278 | rev_options: a RevOptions object. | ||
279 | """ | ||
280 | raise NotImplementedError | ||
281 | |||
282 | def is_commit_id_equal(self, dest, name): | ||
283 | """ | ||
284 | Return whether the id of the current commit equals the given name. | ||
285 | |||
286 | Args: | ||
287 | dest: the repository directory. | ||
288 | name: a string name. | ||
289 | """ | ||
290 | raise NotImplementedError | ||
291 | |||
292 | def check_destination(self, dest, url, rev_options): | ||
293 | """ | ||
294 | Prepare a location to receive a checkout/clone. | ||
295 | |||
296 | Return True if the location is ready for (and requires) a | ||
297 | checkout/clone, False otherwise. | ||
298 | |||
299 | Args: | ||
300 | rev_options: a RevOptions object. | ||
301 | """ | ||
302 | checkout = True | ||
303 | prompt = False | ||
304 | rev_display = rev_options.to_display() | ||
305 | if os.path.exists(dest): | ||
306 | checkout = False | ||
307 | if os.path.exists(os.path.join(dest, self.dirname)): | ||
308 | existing_url = self.get_url(dest) | ||
309 | if self.compare_urls(existing_url, url): | ||
310 | logger.debug( | ||
311 | '%s in %s exists, and has correct URL (%s)', | ||
312 | self.repo_name.title(), | ||
313 | display_path(dest), | ||
314 | url, | ||
315 | ) | ||
316 | if not self.is_commit_id_equal(dest, rev_options.rev): | ||
317 | logger.info( | ||
318 | 'Updating %s %s%s', | ||
319 | display_path(dest), | ||
320 | self.repo_name, | ||
321 | rev_display, | ||
322 | ) | ||
323 | self.update(dest, rev_options) | ||
324 | else: | ||
325 | logger.info( | ||
326 | 'Skipping because already up-to-date.') | ||
327 | else: | ||
328 | logger.warning( | ||
329 | '%s %s in %s exists with URL %s', | ||
330 | self.name, | ||
331 | self.repo_name, | ||
332 | display_path(dest), | ||
333 | existing_url, | ||
334 | ) | ||
335 | prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ', | ||
336 | ('s', 'i', 'w', 'b')) | ||
337 | else: | ||
338 | logger.warning( | ||
339 | 'Directory %s already exists, and is not a %s %s.', | ||
340 | dest, | ||
341 | self.name, | ||
342 | self.repo_name, | ||
343 | ) | ||
344 | prompt = ('(i)gnore, (w)ipe, (b)ackup ', ('i', 'w', 'b')) | ||
345 | if prompt: | ||
346 | logger.warning( | ||
347 | 'The plan is to install the %s repository %s', | ||
348 | self.name, | ||
349 | url, | ||
350 | ) | ||
351 | response = ask_path_exists('What to do? %s' % prompt[0], | ||
352 | prompt[1]) | ||
353 | |||
354 | if response == 's': | ||
355 | logger.info( | ||
356 | 'Switching %s %s to %s%s', | ||
357 | self.repo_name, | ||
358 | display_path(dest), | ||
359 | url, | ||
360 | rev_display, | ||
361 | ) | ||
362 | self.switch(dest, url, rev_options) | ||
363 | elif response == 'i': | ||
364 | # do nothing | ||
365 | pass | ||
366 | elif response == 'w': | ||
367 | logger.warning('Deleting %s', display_path(dest)) | ||
368 | rmtree(dest) | ||
369 | checkout = True | ||
370 | elif response == 'b': | ||
371 | dest_dir = backup_dir(dest) | ||
372 | logger.warning( | ||
373 | 'Backing up %s to %s', display_path(dest), dest_dir, | ||
374 | ) | ||
375 | shutil.move(dest, dest_dir) | ||
376 | checkout = True | ||
377 | elif response == 'a': | ||
378 | sys.exit(-1) | ||
379 | return checkout | ||
380 | |||
381 | def unpack(self, location): | ||
382 | """ | ||
383 | Clean up current location and download the url repository | ||
384 | (and vcs infos) into location | ||
385 | """ | ||
386 | if os.path.exists(location): | ||
387 | rmtree(location) | ||
388 | self.obtain(location) | ||
389 | |||
390 | def get_src_requirement(self, dist, location): | ||
391 | """ | ||
392 | Return a string representing the requirement needed to | ||
393 | redownload the files currently present in location, something | ||
394 | like: | ||
395 | {repository_url}@{revision}#egg={project_name}-{version_identifier} | ||
396 | """ | ||
397 | raise NotImplementedError | ||
398 | |||
399 | def get_url(self, location): | ||
400 | """ | ||
401 | Return the url used at location | ||
402 | Used in get_info or check_destination | ||
403 | """ | ||
404 | raise NotImplementedError | ||
405 | |||
406 | def get_revision(self, location): | ||
407 | """ | ||
408 | Return the current commit id of the files at the given location. | ||
409 | """ | ||
410 | raise NotImplementedError | ||
411 | |||
412 | def run_command(self, cmd, show_stdout=True, cwd=None, | ||
413 | on_returncode='raise', | ||
414 | command_desc=None, | ||
415 | extra_environ=None, spinner=None): | ||
416 | """ | ||
417 | Run a VCS subcommand | ||
418 | This is simply a wrapper around call_subprocess that adds the VCS | ||
419 | command name, and checks that the VCS is available | ||
420 | """ | ||
421 | cmd = [self.name] + cmd | ||
422 | try: | ||
423 | return call_subprocess(cmd, show_stdout, cwd, | ||
424 | on_returncode, | ||
425 | command_desc, extra_environ, | ||
426 | unset_environ=self.unset_environ, | ||
427 | spinner=spinner) | ||
428 | except OSError as e: | ||
429 | # errno.ENOENT = no such file or directory | ||
430 | # In other words, the VCS executable isn't available | ||
431 | if e.errno == errno.ENOENT: | ||
432 | raise BadCommand( | ||
433 | 'Cannot find command %r - do you have ' | ||
434 | '%r installed and in your ' | ||
435 | 'PATH?' % (self.name, self.name)) | ||
436 | else: | ||
437 | raise # re-raise exception if a different error occurred | ||
438 | |||
439 | @classmethod | ||
440 | def controls_location(cls, location): | ||
441 | """ | ||
442 | Check if a location is controlled by the vcs. | ||
443 | It is meant to be overridden to implement smarter detection | ||
444 | mechanisms for specific vcs. | ||
445 | """ | ||
446 | logger.debug('Checking in %s for %s (%s)...', | ||
447 | location, cls.dirname, cls.name) | ||
448 | path = os.path.join(location, cls.dirname) | ||
449 | return os.path.exists(path) | ||
450 | |||
451 | |||
452 | def get_src_requirement(dist, location): | ||
453 | version_control = vcs.get_backend_from_location(location) | ||
454 | if version_control: | ||
455 | try: | ||
456 | return version_control().get_src_requirement(dist, | ||
457 | location) | ||
458 | except BadCommand: | ||
459 | logger.warning( | ||
460 | 'cannot determine version of editable source in %s ' | ||
461 | '(%s command not found in path)', | ||
462 | location, | ||
463 | version_control.name, | ||
464 | ) | ||
465 | return dist.as_requirement() | ||
466 | logger.warning( | ||
467 | 'cannot determine version of editable source in %s (is not SVN ' | ||
468 | 'checkout, Git clone, Mercurial clone or Bazaar branch)', | ||
469 | location, | ||
470 | ) | ||
471 | return dist.as_requirement() | ||