Path: blob/master/venv/Lib/site-packages/pip/_internal/vcs/subversion.py
811 views
# The following comment should be removed at some point in the future.1# mypy: disallow-untyped-defs=False23from __future__ import absolute_import45import logging6import os7import re89from pip._internal.utils.logging import indent_log10from pip._internal.utils.misc import (11display_path,12is_console_interactive,13rmtree,14split_auth_from_netloc,15)16from pip._internal.utils.subprocess import make_command17from pip._internal.utils.typing import MYPY_CHECK_RUNNING18from pip._internal.vcs.versioncontrol import VersionControl, vcs1920_svn_xml_url_re = re.compile('url="([^"]+)"')21_svn_rev_re = re.compile(r'committed-rev="(\d+)"')22_svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"')23_svn_info_xml_url_re = re.compile(r'<url>(.*)</url>')242526if MYPY_CHECK_RUNNING:27from typing import Optional, Tuple28from pip._internal.utils.subprocess import CommandArgs29from pip._internal.utils.misc import HiddenText30from pip._internal.vcs.versioncontrol import AuthInfo, RevOptions313233logger = logging.getLogger(__name__)343536class Subversion(VersionControl):37name = 'svn'38dirname = '.svn'39repo_name = 'checkout'40schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn')4142@classmethod43def should_add_vcs_url_prefix(cls, remote_url):44return True4546@staticmethod47def get_base_rev_args(rev):48return ['-r', rev]4950@classmethod51def get_revision(cls, location):52"""53Return the maximum revision for all files under a given location54"""55# Note: taken from setuptools.command.egg_info56revision = 05758for base, dirs, files in os.walk(location):59if cls.dirname not in dirs:60dirs[:] = []61continue # no sense walking uncontrolled subdirs62dirs.remove(cls.dirname)63entries_fn = os.path.join(base, cls.dirname, 'entries')64if not os.path.exists(entries_fn):65# FIXME: should we warn?66continue6768dirurl, localrev = cls._get_svn_url_rev(base)6970if base == location:71base = dirurl + '/' # save the root url72elif not dirurl or not dirurl.startswith(base):73dirs[:] = []74continue # not part of the same svn tree, skip it75revision = max(revision, localrev)76return revision7778@classmethod79def get_netloc_and_auth(cls, netloc, scheme):80"""81This override allows the auth information to be passed to svn via the82--username and --password options instead of via the URL.83"""84if scheme == 'ssh':85# The --username and --password options can't be used for86# svn+ssh URLs, so keep the auth information in the URL.87return super(Subversion, cls).get_netloc_and_auth(netloc, scheme)8889return split_auth_from_netloc(netloc)9091@classmethod92def get_url_rev_and_auth(cls, url):93# type: (str) -> Tuple[str, Optional[str], AuthInfo]94# hotfix the URL scheme after removing svn+ from svn+ssh:// readd it95url, rev, user_pass = super(Subversion, cls).get_url_rev_and_auth(url)96if url.startswith('ssh://'):97url = 'svn+' + url98return url, rev, user_pass99100@staticmethod101def make_rev_args(username, password):102# type: (Optional[str], Optional[HiddenText]) -> CommandArgs103extra_args = [] # type: CommandArgs104if username:105extra_args += ['--username', username]106if password:107extra_args += ['--password', password]108109return extra_args110111@classmethod112def get_remote_url(cls, location):113# In cases where the source is in a subdirectory, not alongside114# setup.py we have to look up in the location until we find a real115# setup.py116orig_location = location117while not os.path.exists(os.path.join(location, 'setup.py')):118last_location = location119location = os.path.dirname(location)120if location == last_location:121# We've traversed up to the root of the filesystem without122# finding setup.py123logger.warning(124"Could not find setup.py for directory %s (tried all "125"parent directories)",126orig_location,127)128return None129130return cls._get_svn_url_rev(location)[0]131132@classmethod133def _get_svn_url_rev(cls, location):134from pip._internal.exceptions import InstallationError135136entries_path = os.path.join(location, cls.dirname, 'entries')137if os.path.exists(entries_path):138with open(entries_path) as f:139data = f.read()140else: # subversion >= 1.7 does not have the 'entries' file141data = ''142143if (data.startswith('8') or144data.startswith('9') or145data.startswith('10')):146data = list(map(str.splitlines, data.split('\n\x0c\n')))147del data[0][0] # get rid of the '8'148url = data[0][3]149revs = [int(d[9]) for d in data if len(d) > 9 and d[9]] + [0]150elif data.startswith('<?xml'):151match = _svn_xml_url_re.search(data)152if not match:153raise ValueError(154'Badly formatted data: {data!r}'.format(**locals()))155url = match.group(1) # get repository URL156revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)] + [0]157else:158try:159# subversion >= 1.7160# Note that using get_remote_call_options is not necessary here161# because `svn info` is being run against a local directory.162# We don't need to worry about making sure interactive mode163# is being used to prompt for passwords, because passwords164# are only potentially needed for remote server requests.165xml = cls.run_command(166['info', '--xml', location],167show_stdout=False,168)169url = _svn_info_xml_url_re.search(xml).group(1)170revs = [171int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml)172]173except InstallationError:174url, revs = None, []175176if revs:177rev = max(revs)178else:179rev = 0180181return url, rev182183@classmethod184def is_commit_id_equal(cls, dest, name):185"""Always assume the versions don't match"""186return False187188def __init__(self, use_interactive=None):189# type: (bool) -> None190if use_interactive is None:191use_interactive = is_console_interactive()192self.use_interactive = use_interactive193194# This member is used to cache the fetched version of the current195# ``svn`` client.196# Special value definitions:197# None: Not evaluated yet.198# Empty tuple: Could not parse version.199self._vcs_version = None # type: Optional[Tuple[int, ...]]200201super(Subversion, self).__init__()202203def call_vcs_version(self):204# type: () -> Tuple[int, ...]205"""Query the version of the currently installed Subversion client.206207:return: A tuple containing the parts of the version information or208``()`` 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.0214# svn, version 1.7.14 (r1542130)215# compiled Mar 28 2018, 08:49:13 on x86_64-pc-linux-gnu216version_prefix = 'svn, version '217version = self.run_command(['--version'], show_stdout=False)218if not version.startswith(version_prefix):219return ()220221version = version[len(version_prefix):].split()[0]222version_list = version.split('.')223try:224parsed_version = tuple(map(int, version_list))225except ValueError:226return ()227228return parsed_version229230def get_vcs_version(self):231# type: () -> Tuple[int, ...]232"""Return the version of the currently installed Subversion client.233234If the version of the Subversion client has already been queried,235a cached value will be used.236237:return: A tuple containing the parts of the version information or238``()`` if the version returned from ``svn`` could not be parsed.239:raises: BadCommand: If ``svn`` is not installed.240"""241if 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.245return self._vcs_version246247vcs_version = self.call_vcs_version()248self._vcs_version = vcs_version249return vcs_version250251def get_remote_call_options(self):252# type: () -> CommandArgs253"""Return options to be used on calls to Subversion that contact the server.254255These options are applicable for the following ``svn`` subcommands used256in this class.257258- checkout259- export260- switch261- update262263:return: A list of command line arguments to pass to ``svn``.264"""265if 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.268return ['--non-interactive']269270svn_version = self.get_vcs_version()271# By default, Subversion >= 1.8 runs in non-interactive mode if272# stdin is not a TTY. Since that is how pip invokes SVN, in273# call_subprocess(), pip must pass --force-interactive to ensure274# the user can be prompted for a password, if required.275# SVN added the --force-interactive option in SVN 1.8. Since276# e.g. RHEL/CentOS 7, which is supported until 2024, ships with277# SVN 1.7, pip should continue to support SVN 1.7. Therefore, pip278# can't safely add the option if the SVN version is < 1.8 (or unknown).279if svn_version >= (1, 8):280return ['--force-interactive']281282return []283284def export(self, location, url):285# type: (str, HiddenText) -> None286"""Export the svn repository at the url to the destination location"""287url, rev_options = self.get_url_rev_options(url)288289logger.info('Exporting svn repository %s to %s', url, location)290with indent_log():291if os.path.exists(location):292# Subversion doesn't like to check out over an existing293# directory --force fixes this, but was only added in svn 1.5294rmtree(location)295cmd_args = make_command(296'export', self.get_remote_call_options(),297rev_options.to_args(), url, location,298)299self.run_command(cmd_args, show_stdout=False)300301def fetch_new(self, dest, url, rev_options):302# type: (str, HiddenText, RevOptions) -> None303rev_display = rev_options.to_display()304logger.info(305'Checking out %s%s to %s',306url,307rev_display,308display_path(dest),309)310cmd_args = make_command(311'checkout', '-q', self.get_remote_call_options(),312rev_options.to_args(), url, dest,313)314self.run_command(cmd_args)315316def switch(self, dest, url, rev_options):317# type: (str, HiddenText, RevOptions) -> None318cmd_args = make_command(319'switch', self.get_remote_call_options(), rev_options.to_args(),320url, dest,321)322self.run_command(cmd_args)323324def update(self, dest, url, rev_options):325# type: (str, HiddenText, RevOptions) -> None326cmd_args = make_command(327'update', self.get_remote_call_options(), rev_options.to_args(),328dest,329)330self.run_command(cmd_args)331332333vcs.register(Subversion)334335336