You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

subversion.py 6.9KB

5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. from __future__ import absolute_import
  2. import logging
  3. import os
  4. import re
  5. from pip._internal.utils.logging import indent_log
  6. from pip._internal.utils.misc import (
  7. display_path, make_vcs_requirement_url, rmtree, split_auth_from_netloc,
  8. )
  9. from pip._internal.vcs import VersionControl, vcs
  10. _svn_xml_url_re = re.compile('url="([^"]+)"')
  11. _svn_rev_re = re.compile(r'committed-rev="(\d+)"')
  12. _svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"')
  13. _svn_info_xml_url_re = re.compile(r'<url>(.*)</url>')
  14. logger = logging.getLogger(__name__)
  15. class Subversion(VersionControl):
  16. name = 'svn'
  17. dirname = '.svn'
  18. repo_name = 'checkout'
  19. schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn')
  20. def get_base_rev_args(self, rev):
  21. return ['-r', rev]
  22. def export(self, location):
  23. """Export the svn repository at the url to the destination location"""
  24. url, rev_options = self.get_url_rev_options(self.url)
  25. logger.info('Exporting svn repository %s to %s', url, location)
  26. with indent_log():
  27. if os.path.exists(location):
  28. # Subversion doesn't like to check out over an existing
  29. # directory --force fixes this, but was only added in svn 1.5
  30. rmtree(location)
  31. cmd_args = ['export'] + rev_options.to_args() + [url, location]
  32. self.run_command(cmd_args, show_stdout=False)
  33. def fetch_new(self, dest, url, rev_options):
  34. rev_display = rev_options.to_display()
  35. logger.info(
  36. 'Checking out %s%s to %s',
  37. url,
  38. rev_display,
  39. display_path(dest),
  40. )
  41. cmd_args = ['checkout', '-q'] + rev_options.to_args() + [url, dest]
  42. self.run_command(cmd_args)
  43. def switch(self, dest, url, rev_options):
  44. cmd_args = ['switch'] + rev_options.to_args() + [url, dest]
  45. self.run_command(cmd_args)
  46. def update(self, dest, url, rev_options):
  47. cmd_args = ['update'] + rev_options.to_args() + [dest]
  48. self.run_command(cmd_args)
  49. @classmethod
  50. def get_revision(cls, location):
  51. """
  52. Return the maximum revision for all files under a given location
  53. """
  54. # Note: taken from setuptools.command.egg_info
  55. revision = 0
  56. for base, dirs, files in os.walk(location):
  57. if cls.dirname not in dirs:
  58. dirs[:] = []
  59. continue # no sense walking uncontrolled subdirs
  60. dirs.remove(cls.dirname)
  61. entries_fn = os.path.join(base, cls.dirname, 'entries')
  62. if not os.path.exists(entries_fn):
  63. # FIXME: should we warn?
  64. continue
  65. dirurl, localrev = cls._get_svn_url_rev(base)
  66. if base == location:
  67. base = dirurl + '/' # save the root url
  68. elif not dirurl or not dirurl.startswith(base):
  69. dirs[:] = []
  70. continue # not part of the same svn tree, skip it
  71. revision = max(revision, localrev)
  72. return revision
  73. def get_netloc_and_auth(self, netloc, scheme):
  74. """
  75. This override allows the auth information to be passed to svn via the
  76. --username and --password options instead of via the URL.
  77. """
  78. if scheme == 'ssh':
  79. # The --username and --password options can't be used for
  80. # svn+ssh URLs, so keep the auth information in the URL.
  81. return super(Subversion, self).get_netloc_and_auth(
  82. netloc, scheme)
  83. return split_auth_from_netloc(netloc)
  84. def get_url_rev_and_auth(self, url):
  85. # hotfix the URL scheme after removing svn+ from svn+ssh:// readd it
  86. url, rev, user_pass = super(Subversion, self).get_url_rev_and_auth(url)
  87. if url.startswith('ssh://'):
  88. url = 'svn+' + url
  89. return url, rev, user_pass
  90. def make_rev_args(self, username, password):
  91. extra_args = []
  92. if username:
  93. extra_args += ['--username', username]
  94. if password:
  95. extra_args += ['--password', password]
  96. return extra_args
  97. @classmethod
  98. def get_remote_url(cls, location):
  99. # In cases where the source is in a subdirectory, not alongside
  100. # setup.py we have to look up in the location until we find a real
  101. # setup.py
  102. orig_location = location
  103. while not os.path.exists(os.path.join(location, 'setup.py')):
  104. last_location = location
  105. location = os.path.dirname(location)
  106. if location == last_location:
  107. # We've traversed up to the root of the filesystem without
  108. # finding setup.py
  109. logger.warning(
  110. "Could not find setup.py for directory %s (tried all "
  111. "parent directories)",
  112. orig_location,
  113. )
  114. return None
  115. return cls._get_svn_url_rev(location)[0]
  116. @classmethod
  117. def _get_svn_url_rev(cls, location):
  118. from pip._internal.exceptions import InstallationError
  119. entries_path = os.path.join(location, cls.dirname, 'entries')
  120. if os.path.exists(entries_path):
  121. with open(entries_path) as f:
  122. data = f.read()
  123. else: # subversion >= 1.7 does not have the 'entries' file
  124. data = ''
  125. if (data.startswith('8') or
  126. data.startswith('9') or
  127. data.startswith('10')):
  128. data = list(map(str.splitlines, data.split('\n\x0c\n')))
  129. del data[0][0] # get rid of the '8'
  130. url = data[0][3]
  131. revs = [int(d[9]) for d in data if len(d) > 9 and d[9]] + [0]
  132. elif data.startswith('<?xml'):
  133. match = _svn_xml_url_re.search(data)
  134. if not match:
  135. raise ValueError('Badly formatted data: %r' % data)
  136. url = match.group(1) # get repository URL
  137. revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)] + [0]
  138. else:
  139. try:
  140. # subversion >= 1.7
  141. xml = cls.run_command(
  142. ['info', '--xml', location],
  143. show_stdout=False,
  144. )
  145. url = _svn_info_xml_url_re.search(xml).group(1)
  146. revs = [
  147. int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml)
  148. ]
  149. except InstallationError:
  150. url, revs = None, []
  151. if revs:
  152. rev = max(revs)
  153. else:
  154. rev = 0
  155. return url, rev
  156. @classmethod
  157. def get_src_requirement(cls, location, project_name):
  158. repo = cls.get_remote_url(location)
  159. if repo is None:
  160. return None
  161. repo = 'svn+' + repo
  162. rev = cls.get_revision(location)
  163. return make_vcs_requirement_url(repo, rev, project_name)
  164. def is_commit_id_equal(self, dest, name):
  165. """Always assume the versions don't match"""
  166. return False
  167. vcs.register(Subversion)