summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/pip/vcs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/pip/vcs')
-rw-r--r--lib/python2.7/site-packages/pip/vcs/__init__.py366
-rw-r--r--lib/python2.7/site-packages/pip/vcs/bazaar.py116
-rw-r--r--lib/python2.7/site-packages/pip/vcs/git.py300
-rw-r--r--lib/python2.7/site-packages/pip/vcs/mercurial.py103
-rw-r--r--lib/python2.7/site-packages/pip/vcs/subversion.py269
5 files changed, 1154 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/pip/vcs/__init__.py b/lib/python2.7/site-packages/pip/vcs/__init__.py
new file mode 100644
index 0000000..8d3dbb2
--- /dev/null
+++ b/lib/python2.7/site-packages/pip/vcs/__init__.py
@@ -0,0 +1,366 @@
+"""Handles all VCS (version control) support"""
+from __future__ import absolute_import
+
+import errno
+import logging
+import os
+import shutil
+import sys
+
+from pip._vendor.six.moves.urllib import parse as urllib_parse
+
+from pip.exceptions import BadCommand
+from pip.utils import (display_path, backup_dir, call_subprocess,
+ rmtree, ask_path_exists)
+
+
+__all__ = ['vcs', 'get_src_requirement']
+
+
+logger = logging.getLogger(__name__)
+
+
+class VcsSupport(object):
+ _registry = {}
+ schemes = ['ssh', 'git', 'hg', 'bzr', 'sftp', 'svn']
+
+ def __init__(self):
+ # Register more schemes with urlparse for various version control
+ # systems
+ urllib_parse.uses_netloc.extend(self.schemes)
+ # Python >= 2.7.4, 3.3 doesn't have uses_fragment
+ if getattr(urllib_parse, 'uses_fragment', None):
+ urllib_parse.uses_fragment.extend(self.schemes)
+ super(VcsSupport, self).__init__()
+
+ def __iter__(self):
+ return self._registry.__iter__()
+
+ @property
+ def backends(self):
+ return list(self._registry.values())
+
+ @property
+ def dirnames(self):
+ return [backend.dirname for backend in self.backends]
+
+ @property
+ def all_schemes(self):
+ schemes = []
+ for backend in self.backends:
+ schemes.extend(backend.schemes)
+ return schemes
+
+ def register(self, cls):
+ if not hasattr(cls, 'name'):
+ logger.warning('Cannot register VCS %s', cls.__name__)
+ return
+ if cls.name not in self._registry:
+ self._registry[cls.name] = cls
+ logger.debug('Registered VCS backend: %s', cls.name)
+
+ def unregister(self, cls=None, name=None):
+ if name in self._registry:
+ del self._registry[name]
+ elif cls in self._registry.values():
+ del self._registry[cls.name]
+ else:
+ logger.warning('Cannot unregister because no class or name given')
+
+ def get_backend_name(self, location):
+ """
+ Return the name of the version control backend if found at given
+ location, e.g. vcs.get_backend_name('/path/to/vcs/checkout')
+ """
+ for vc_type in self._registry.values():
+ if vc_type.controls_location(location):
+ logger.debug('Determine that %s uses VCS: %s',
+ location, vc_type.name)
+ return vc_type.name
+ return None
+
+ def get_backend(self, name):
+ name = name.lower()
+ if name in self._registry:
+ return self._registry[name]
+
+ def get_backend_from_location(self, location):
+ vc_type = self.get_backend_name(location)
+ if vc_type:
+ return self.get_backend(vc_type)
+ return None
+
+
+vcs = VcsSupport()
+
+
+class VersionControl(object):
+ name = ''
+ dirname = ''
+ # List of supported schemes for this Version Control
+ schemes = ()
+
+ def __init__(self, url=None, *args, **kwargs):
+ self.url = url
+ super(VersionControl, self).__init__(*args, **kwargs)
+
+ def _is_local_repository(self, repo):
+ """
+ posix absolute paths start with os.path.sep,
+ win32 ones start with drive (like c:\\folder)
+ """
+ drive, tail = os.path.splitdrive(repo)
+ return repo.startswith(os.path.sep) or drive
+
+ # See issue #1083 for why this method was introduced:
+ # https://github.com/pypa/pip/issues/1083
+ def translate_egg_surname(self, surname):
+ # For example, Django has branches of the form "stable/1.7.x".
+ return surname.replace('/', '_')
+
+ def export(self, location):
+ """
+ Export the repository at the url to the destination location
+ i.e. only download the files, without vcs informations
+ """
+ raise NotImplementedError
+
+ def get_url_rev(self):
+ """
+ Returns the correct repository URL and revision by parsing the given
+ repository URL
+ """
+ error_message = (
+ "Sorry, '%s' is a malformed VCS url. "
+ "The format is <vcs>+<protocol>://<url>, "
+ "e.g. svn+http://myrepo/svn/MyApp#egg=MyApp"
+ )
+ assert '+' in self.url, error_message % self.url
+ url = self.url.split('+', 1)[1]
+ scheme, netloc, path, query, frag = urllib_parse.urlsplit(url)
+ rev = None
+ if '@' in path:
+ path, rev = path.rsplit('@', 1)
+ url = urllib_parse.urlunsplit((scheme, netloc, path, query, ''))
+ return url, rev
+
+ def get_info(self, location):
+ """
+ Returns (url, revision), where both are strings
+ """
+ assert not location.rstrip('/').endswith(self.dirname), \
+ 'Bad directory: %s' % location
+ return self.get_url(location), self.get_revision(location)
+
+ def normalize_url(self, url):
+ """
+ Normalize a URL for comparison by unquoting it and removing any
+ trailing slash.
+ """
+ return urllib_parse.unquote(url).rstrip('/')
+
+ def compare_urls(self, url1, url2):
+ """
+ Compare two repo URLs for identity, ignoring incidental differences.
+ """
+ return (self.normalize_url(url1) == self.normalize_url(url2))
+
+ def obtain(self, dest):
+ """
+ Called when installing or updating an editable package, takes the
+ source path of the checkout.
+ """
+ raise NotImplementedError
+
+ def switch(self, dest, url, rev_options):
+ """
+ Switch the repo at ``dest`` to point to ``URL``.
+ """
+ raise NotImplementedError
+
+ def update(self, dest, rev_options):
+ """
+ Update an already-existing repo to the given ``rev_options``.
+ """
+ raise NotImplementedError
+
+ def check_version(self, dest, rev_options):
+ """
+ Return True if the version is identical to what exists and
+ doesn't need to be updated.
+ """
+ raise NotImplementedError
+
+ def check_destination(self, dest, url, rev_options, rev_display):
+ """
+ Prepare a location to receive a checkout/clone.
+
+ Return True if the location is ready for (and requires) a
+ checkout/clone, False otherwise.
+ """
+ checkout = True
+ prompt = False
+ if os.path.exists(dest):
+ checkout = False
+ if os.path.exists(os.path.join(dest, self.dirname)):
+ existing_url = self.get_url(dest)
+ if self.compare_urls(existing_url, url):
+ logger.debug(
+ '%s in %s exists, and has correct URL (%s)',
+ self.repo_name.title(),
+ display_path(dest),
+ url,
+ )
+ if not self.check_version(dest, rev_options):
+ logger.info(
+ 'Updating %s %s%s',
+ display_path(dest),
+ self.repo_name,
+ rev_display,
+ )
+ self.update(dest, rev_options)
+ else:
+ logger.info(
+ 'Skipping because already up-to-date.')
+ else:
+ logger.warning(
+ '%s %s in %s exists with URL %s',
+ self.name,
+ self.repo_name,
+ display_path(dest),
+ existing_url,
+ )
+ prompt = ('(s)witch, (i)gnore, (w)ipe, (b)ackup ',
+ ('s', 'i', 'w', 'b'))
+ else:
+ logger.warning(
+ 'Directory %s already exists, and is not a %s %s.',
+ dest,
+ self.name,
+ self.repo_name,
+ )
+ prompt = ('(i)gnore, (w)ipe, (b)ackup ', ('i', 'w', 'b'))
+ if prompt:
+ logger.warning(
+ 'The plan is to install the %s repository %s',
+ self.name,
+ url,
+ )
+ response = ask_path_exists('What to do? %s' % prompt[0],
+ prompt[1])
+
+ if response == 's':
+ logger.info(
+ 'Switching %s %s to %s%s',
+ self.repo_name,
+ display_path(dest),
+ url,
+ rev_display,
+ )
+ self.switch(dest, url, rev_options)
+ elif response == 'i':
+ # do nothing
+ pass
+ elif response == 'w':
+ logger.warning('Deleting %s', display_path(dest))
+ rmtree(dest)
+ checkout = True
+ elif response == 'b':
+ dest_dir = backup_dir(dest)
+ logger.warning(
+ 'Backing up %s to %s', display_path(dest), dest_dir,
+ )
+ shutil.move(dest, dest_dir)
+ checkout = True
+ elif response == 'a':
+ sys.exit(-1)
+ return checkout
+
+ def unpack(self, location):
+ """
+ Clean up current location and download the url repository
+ (and vcs infos) into location
+ """
+ if os.path.exists(location):
+ rmtree(location)
+ self.obtain(location)
+
+ def get_src_requirement(self, dist, location):
+ """
+ Return a string representing the requirement needed to
+ redownload the files currently present in location, something
+ like:
+ {repository_url}@{revision}#egg={project_name}-{version_identifier}
+ """
+ raise NotImplementedError
+
+ def get_url(self, location):
+ """
+ Return the url used at location
+ Used in get_info or check_destination
+ """
+ raise NotImplementedError
+
+ def get_revision(self, location):
+ """
+ Return the current revision of the files at location
+ Used in get_info
+ """
+ raise NotImplementedError
+
+ def run_command(self, cmd, show_stdout=True, cwd=None,
+ on_returncode='raise',
+ command_desc=None,
+ extra_environ=None, spinner=None):
+ """
+ Run a VCS subcommand
+ This is simply a wrapper around call_subprocess that adds the VCS
+ command name, and checks that the VCS is available
+ """
+ cmd = [self.name] + cmd
+ try:
+ return call_subprocess(cmd, show_stdout, cwd,
+ on_returncode,
+ command_desc, extra_environ,
+ spinner)
+ except OSError as e:
+ # errno.ENOENT = no such file or directory
+ # In other words, the VCS executable isn't available
+ if e.errno == errno.ENOENT:
+ raise BadCommand('Cannot find command %r' % self.name)
+ else:
+ raise # re-raise exception if a different error occurred
+
+ @classmethod
+ def controls_location(cls, location):
+ """
+ Check if a location is controlled by the vcs.
+ It is meant to be overridden to implement smarter detection
+ mechanisms for specific vcs.
+ """
+ logger.debug('Checking in %s for %s (%s)...',
+ location, cls.dirname, cls.name)
+ path = os.path.join(location, cls.dirname)
+ return os.path.exists(path)
+
+
+def get_src_requirement(dist, location):
+ version_control = vcs.get_backend_from_location(location)
+ if version_control:
+ try:
+ return version_control().get_src_requirement(dist,
+ location)
+ except BadCommand:
+ logger.warning(
+ 'cannot determine version of editable source in %s '
+ '(%s command not found in path)',
+ location,
+ version_control.name,
+ )
+ return dist.as_requirement()
+ logger.warning(
+ 'cannot determine version of editable source in %s (is not SVN '
+ 'checkout, Git clone, Mercurial clone or Bazaar branch)',
+ location,
+ )
+ return dist.as_requirement()
diff --git a/lib/python2.7/site-packages/pip/vcs/bazaar.py b/lib/python2.7/site-packages/pip/vcs/bazaar.py
new file mode 100644
index 0000000..0f09584
--- /dev/null
+++ b/lib/python2.7/site-packages/pip/vcs/bazaar.py
@@ -0,0 +1,116 @@
+from __future__ import absolute_import
+
+import logging
+import os
+import tempfile
+
+# TODO: Get this into six.moves.urllib.parse
+try:
+ from urllib import parse as urllib_parse
+except ImportError:
+ import urlparse as urllib_parse
+
+from pip.utils import rmtree, display_path
+from pip.vcs import vcs, VersionControl
+from pip.download import path_to_url
+
+
+logger = logging.getLogger(__name__)
+
+
+class Bazaar(VersionControl):
+ name = 'bzr'
+ dirname = '.bzr'
+ repo_name = 'branch'
+ schemes = (
+ 'bzr', 'bzr+http', 'bzr+https', 'bzr+ssh', 'bzr+sftp', 'bzr+ftp',
+ 'bzr+lp',
+ )
+
+ def __init__(self, url=None, *args, **kwargs):
+ super(Bazaar, self).__init__(url, *args, **kwargs)
+ # Python >= 2.7.4, 3.3 doesn't have uses_fragment or non_hierarchical
+ # Register lp but do not expose as a scheme to support bzr+lp.
+ if getattr(urllib_parse, 'uses_fragment', None):
+ urllib_parse.uses_fragment.extend(['lp'])
+ urllib_parse.non_hierarchical.extend(['lp'])
+
+ def export(self, location):
+ """
+ Export the Bazaar repository at the url to the destination location
+ """
+ temp_dir = tempfile.mkdtemp('-export', 'pip-')
+ self.unpack(temp_dir)
+ if os.path.exists(location):
+ # Remove the location to make sure Bazaar can export it correctly
+ rmtree(location)
+ try:
+ self.run_command(['export', location], cwd=temp_dir,
+ show_stdout=False)
+ finally:
+ rmtree(temp_dir)
+
+ def switch(self, dest, url, rev_options):
+ self.run_command(['switch', url], cwd=dest)
+
+ def update(self, dest, rev_options):
+ self.run_command(['pull', '-q'] + rev_options, cwd=dest)
+
+ def obtain(self, dest):
+ url, rev = self.get_url_rev()
+ if rev:
+ rev_options = ['-r', rev]
+ rev_display = ' (to revision %s)' % rev
+ else:
+ rev_options = []
+ rev_display = ''
+ if self.check_destination(dest, url, rev_options, rev_display):
+ logger.info(
+ 'Checking out %s%s to %s',
+ url,
+ rev_display,
+ display_path(dest),
+ )
+ self.run_command(['branch', '-q'] + rev_options + [url, dest])
+
+ def get_url_rev(self):
+ # hotfix the URL scheme after removing bzr+ from bzr+ssh:// readd it
+ url, rev = super(Bazaar, self).get_url_rev()
+ if url.startswith('ssh://'):
+ url = 'bzr+' + url
+ return url, rev
+
+ def get_url(self, location):
+ urls = self.run_command(['info'], show_stdout=False, cwd=location)
+ for line in urls.splitlines():
+ line = line.strip()
+ for x in ('checkout of branch: ',
+ 'parent branch: '):
+ if line.startswith(x):
+ repo = line.split(x)[1]
+ if self._is_local_repository(repo):
+ return path_to_url(repo)
+ return repo
+ return None
+
+ def get_revision(self, location):
+ revision = self.run_command(
+ ['revno'], show_stdout=False, cwd=location)
+ return revision.splitlines()[-1]
+
+ def get_src_requirement(self, dist, location):
+ repo = self.get_url(location)
+ if not repo:
+ return None
+ if not repo.lower().startswith('bzr:'):
+ repo = 'bzr+' + repo
+ egg_project_name = dist.egg_name().split('-', 1)[0]
+ current_rev = self.get_revision(location)
+ return '%s@%s#egg=%s' % (repo, current_rev, egg_project_name)
+
+ def check_version(self, dest, rev_options):
+ """Always assume the versions don't match"""
+ return False
+
+
+vcs.register(Bazaar)
diff --git a/lib/python2.7/site-packages/pip/vcs/git.py b/lib/python2.7/site-packages/pip/vcs/git.py
new file mode 100644
index 0000000..2187dd8
--- /dev/null
+++ b/lib/python2.7/site-packages/pip/vcs/git.py
@@ -0,0 +1,300 @@
+from __future__ import absolute_import
+
+import logging
+import tempfile
+import os.path
+
+from pip.compat import samefile
+from pip.exceptions import BadCommand
+from pip._vendor.six.moves.urllib import parse as urllib_parse
+from pip._vendor.six.moves.urllib import request as urllib_request
+from pip._vendor.packaging.version import parse as parse_version
+
+from pip.utils import display_path, rmtree
+from pip.vcs import vcs, VersionControl
+
+
+urlsplit = urllib_parse.urlsplit
+urlunsplit = urllib_parse.urlunsplit
+
+
+logger = logging.getLogger(__name__)
+
+
+class Git(VersionControl):
+ name = 'git'
+ dirname = '.git'
+ repo_name = 'clone'
+ schemes = (
+ 'git', 'git+http', 'git+https', 'git+ssh', 'git+git', 'git+file',
+ )
+
+ def __init__(self, url=None, *args, **kwargs):
+
+ # Works around an apparent Git bug
+ # (see http://article.gmane.org/gmane.comp.version-control.git/146500)
+ if url:
+ scheme, netloc, path, query, fragment = urlsplit(url)
+ if scheme.endswith('file'):
+ initial_slashes = path[:-len(path.lstrip('/'))]
+ newpath = (
+ initial_slashes +
+ urllib_request.url2pathname(path)
+ .replace('\\', '/').lstrip('/')
+ )
+ url = urlunsplit((scheme, netloc, newpath, query, fragment))
+ after_plus = scheme.find('+') + 1
+ url = scheme[:after_plus] + urlunsplit(
+ (scheme[after_plus:], netloc, newpath, query, fragment),
+ )
+
+ super(Git, self).__init__(url, *args, **kwargs)
+
+ def get_git_version(self):
+ VERSION_PFX = 'git version '
+ version = self.run_command(['version'], show_stdout=False)
+ if version.startswith(VERSION_PFX):
+ version = version[len(VERSION_PFX):]
+ else:
+ version = ''
+ # get first 3 positions of the git version becasue
+ # on windows it is x.y.z.windows.t, and this parses as
+ # LegacyVersion which always smaller than a Version.
+ version = '.'.join(version.split('.')[:3])
+ return parse_version(version)
+
+ def export(self, location):
+ """Export the Git repository at the url to the destination location"""
+ temp_dir = tempfile.mkdtemp('-export', 'pip-')
+ self.unpack(temp_dir)
+ try:
+ if not location.endswith('/'):
+ location = location + '/'
+ self.run_command(
+ ['checkout-index', '-a', '-f', '--prefix', location],
+ show_stdout=False, cwd=temp_dir)
+ finally:
+ rmtree(temp_dir)
+
+ def check_rev_options(self, rev, dest, rev_options):
+ """Check the revision options before checkout to compensate that tags
+ and branches may need origin/ as a prefix.
+ Returns the SHA1 of the branch or tag if found.
+ """
+ revisions = self.get_short_refs(dest)
+
+ origin_rev = 'origin/%s' % rev
+ if origin_rev in revisions:
+ # remote branch
+ return [revisions[origin_rev]]
+ elif rev in revisions:
+ # a local tag or branch name
+ return [revisions[rev]]
+ else:
+ logger.warning(
+ "Could not find a tag or branch '%s', assuming commit.", rev,
+ )
+ return rev_options
+
+ def check_version(self, dest, rev_options):
+ """
+ Compare the current sha to the ref. ref may be a branch or tag name,
+ but current rev will always point to a sha. This means that a branch
+ or tag will never compare as True. So this ultimately only matches
+ against exact shas.
+ """
+ return self.get_revision(dest).startswith(rev_options[0])
+
+ def switch(self, dest, url, rev_options):
+ self.run_command(['config', 'remote.origin.url', url], cwd=dest)
+ self.run_command(['checkout', '-q'] + rev_options, cwd=dest)
+
+ self.update_submodules(dest)
+
+ def update(self, dest, rev_options):
+ # First fetch changes from the default remote
+ if self.get_git_version() >= parse_version('1.9.0'):
+ # fetch tags in addition to everything else
+ self.run_command(['fetch', '-q', '--tags'], cwd=dest)
+ else:
+ self.run_command(['fetch', '-q'], cwd=dest)
+ # Then reset to wanted revision (maybe even origin/master)
+ if rev_options:
+ rev_options = self.check_rev_options(
+ rev_options[0], dest, rev_options,
+ )
+ self.run_command(['reset', '--hard', '-q'] + rev_options, cwd=dest)
+ #: update submodules
+ self.update_submodules(dest)
+
+ def obtain(self, dest):
+ url, rev = self.get_url_rev()
+ if rev:
+ rev_options = [rev]
+ rev_display = ' (to %s)' % rev
+ else:
+ rev_options = ['origin/master']
+ rev_display = ''
+ if self.check_destination(dest, url, rev_options, rev_display):
+ logger.info(
+ 'Cloning %s%s to %s', url, rev_display, display_path(dest),
+ )
+ self.run_command(['clone', '-q', url, dest])
+
+ if rev:
+ rev_options = self.check_rev_options(rev, dest, rev_options)
+ # Only do a checkout if rev_options differs from HEAD
+ if not self.check_version(dest, rev_options):
+ self.run_command(
+ ['checkout', '-q'] + rev_options,
+ cwd=dest,
+ )
+ #: repo may contain submodules
+ self.update_submodules(dest)
+
+ def get_url(self, location):
+ """Return URL of the first remote encountered."""
+ remotes = self.run_command(
+ ['config', '--get-regexp', 'remote\..*\.url'],
+ show_stdout=False, cwd=location)
+ remotes = remotes.splitlines()
+ found_remote = remotes[0]
+ for remote in remotes:
+ if remote.startswith('remote.origin.url '):
+ found_remote = remote
+ break
+ url = found_remote.split(' ')[1]
+ return url.strip()
+
+ def get_revision(self, location):
+ current_rev = self.run_command(
+ ['rev-parse', 'HEAD'], show_stdout=False, cwd=location)
+ return current_rev.strip()
+
+ def get_full_refs(self, location):
+ """Yields tuples of (commit, ref) for branches and tags"""
+ output = self.run_command(['show-ref'],
+ show_stdout=False, cwd=location)
+ for line in output.strip().splitlines():
+ commit, ref = line.split(' ', 1)
+ yield commit.strip(), ref.strip()
+
+ def is_ref_remote(self, ref):
+ return ref.startswith('refs/remotes/')
+
+ def is_ref_branch(self, ref):
+ return ref.startswith('refs/heads/')
+
+ def is_ref_tag(self, ref):
+ return ref.startswith('refs/tags/')
+
+ def is_ref_commit(self, ref):
+ """A ref is a commit sha if it is not anything else"""
+ return not any((
+ self.is_ref_remote(ref),
+ self.is_ref_branch(ref),
+ self.is_ref_tag(ref),
+ ))
+
+ # Should deprecate `get_refs` since it's ambiguous
+ def get_refs(self, location):
+ return self.get_short_refs(location)
+
+ def get_short_refs(self, location):
+ """Return map of named refs (branches or tags) to commit hashes."""
+ rv = {}
+ for commit, ref in self.get_full_refs(location):
+ ref_name = None
+ if self.is_ref_remote(ref):
+ ref_name = ref[len('refs/remotes/'):]
+ elif self.is_ref_branch(ref):
+ ref_name = ref[len('refs/heads/'):]
+ elif self.is_ref_tag(ref):
+ ref_name = ref[len('refs/tags/'):]
+ if ref_name is not None:
+ rv[ref_name] = commit
+ return rv
+
+ def _get_subdirectory(self, location):
+ """Return the relative path of setup.py to the git repo root."""
+ # find the repo root
+ git_dir = self.run_command(['rev-parse', '--git-dir'],
+ show_stdout=False, cwd=location).strip()
+ if not os.path.isabs(git_dir):
+ git_dir = os.path.join(location, git_dir)
+ root_dir = os.path.join(git_dir, '..')
+ # find setup.py
+ orig_location = location
+ while not os.path.exists(os.path.join(location, 'setup.py')):
+ last_location = location
+ location = os.path.dirname(location)
+ if location == last_location:
+ # We've traversed up to the root of the filesystem without
+ # finding setup.py
+ logger.warning(
+ "Could not find setup.py for directory %s (tried all "
+ "parent directories)",
+ orig_location,
+ )
+ return None
+ # relative path of setup.py to repo root
+ if samefile(root_dir, location):
+ return None
+ return os.path.relpath(location, root_dir)
+
+ def get_src_requirement(self, dist, location):
+ repo = self.get_url(location)
+ if not repo.lower().startswith('git:'):
+ repo = 'git+' + repo
+ egg_project_name = dist.egg_name().split('-', 1)[0]
+ if not repo:
+ return None
+ current_rev = self.get_revision(location)
+ req = '%s@%s#egg=%s' % (repo, current_rev, egg_project_name)
+ subdirectory = self._get_subdirectory(location)
+ if subdirectory:
+ req += '&subdirectory=' + subdirectory
+ return req
+
+ def get_url_rev(self):
+ """
+ Prefixes stub URLs like 'user@hostname:user/repo.git' with 'ssh://'.
+ That's required because although they use SSH they sometimes doesn't
+ work with a ssh:// scheme (e.g. Github). But we need a scheme for
+ parsing. Hence we remove it again afterwards and return it as a stub.
+ """
+ if '://' not in self.url:
+ assert 'file:' not in self.url
+ self.url = self.url.replace('git+', 'git+ssh://')
+ url, rev = super(Git, self).get_url_rev()
+ url = url.replace('ssh://', '')
+ else:
+ url, rev = super(Git, self).get_url_rev()
+
+ return url, rev
+
+ def update_submodules(self, location):
+ if not os.path.exists(os.path.join(location, '.gitmodules')):
+ return
+ self.run_command(
+ ['submodule', 'update', '--init', '--recursive', '-q'],
+ cwd=location,
+ )
+
+ @classmethod
+ def controls_location(cls, location):
+ if super(Git, cls).controls_location(location):
+ return True
+ try:
+ r = cls().run_command(['rev-parse'],
+ cwd=location,
+ show_stdout=False,
+ on_returncode='ignore')
+ return not r
+ except BadCommand:
+ logger.debug("could not determine if %s is under git control "
+ "because git is not available", location)
+ return False
+
+
+vcs.register(Git)
diff --git a/lib/python2.7/site-packages/pip/vcs/mercurial.py b/lib/python2.7/site-packages/pip/vcs/mercurial.py
new file mode 100644
index 0000000..1aa83b9
--- /dev/null
+++ b/lib/python2.7/site-packages/pip/vcs/mercurial.py
@@ -0,0 +1,103 @@
+from __future__ import absolute_import
+
+import logging
+import os
+import tempfile
+
+from pip.utils import display_path, rmtree
+from pip.vcs import vcs, VersionControl
+from pip.download import path_to_url
+from pip._vendor.six.moves import configparser
+
+
+logger = logging.getLogger(__name__)
+
+
+class Mercurial(VersionControl):
+ name = 'hg'
+ dirname = '.hg'
+ repo_name = 'clone'
+ schemes = ('hg', 'hg+http', 'hg+https', 'hg+ssh', 'hg+static-http')
+
+ def export(self, location):
+ """Export the Hg repository at the url to the destination location"""
+ temp_dir = tempfile.mkdtemp('-export', 'pip-')
+ self.unpack(temp_dir)
+ try:
+ self.run_command(
+ ['archive', location], show_stdout=False, cwd=temp_dir)
+ finally:
+ rmtree(temp_dir)
+
+ def switch(self, dest, url, rev_options):
+ repo_config = os.path.join(dest, self.dirname, 'hgrc')
+ config = configparser.SafeConfigParser()
+ try:
+ config.read(repo_config)
+ config.set('paths', 'default', url)
+ with open(repo_config, 'w') as config_file:
+ config.write(config_file)
+ except (OSError, configparser.NoSectionError) as exc:
+ logger.warning(
+ 'Could not switch Mercurial repository to %s: %s', url, exc,
+ )
+ else:
+ self.run_command(['update', '-q'] + rev_options, cwd=dest)
+
+ def update(self, dest, rev_options):
+ self.run_command(['pull', '-q'], cwd=dest)
+ self.run_command(['update', '-q'] + rev_options, cwd=dest)
+
+ def obtain(self, dest):
+ url, rev = self.get_url_rev()
+ if rev:
+ rev_options = [rev]
+ rev_display = ' (to revision %s)' % rev
+ else:
+ rev_options = []
+ rev_display = ''
+ if self.check_destination(dest, url, rev_options, rev_display):
+ logger.info(
+ 'Cloning hg %s%s to %s',
+ url,
+ rev_display,
+ display_path(dest),
+ )
+ self.run_command(['clone', '--noupdate', '-q', url, dest])
+ self.run_command(['update', '-q'] + rev_options, cwd=dest)
+
+ def get_url(self, location):
+ url = self.run_command(
+ ['showconfig', 'paths.default'],
+ show_stdout=False, cwd=location).strip()
+ if self._is_local_repository(url):
+ url = path_to_url(url)
+ return url.strip()
+
+ def get_revision(self, location):
+ current_revision = self.run_command(
+ ['parents', '--template={rev}'],
+ show_stdout=False, cwd=location).strip()
+ return current_revision
+
+ def get_revision_hash(self, location):
+ current_rev_hash = self.run_command(
+ ['parents', '--template={node}'],
+ show_stdout=False, cwd=location).strip()
+ return current_rev_hash
+
+ def get_src_requirement(self, dist, location):
+ repo = self.get_url(location)
+ if not repo.lower().startswith('hg:'):
+ repo = 'hg+' + repo
+ egg_project_name = dist.egg_name().split('-', 1)[0]
+ if not repo:
+ return None
+ current_rev_hash = self.get_revision_hash(location)
+ return '%s@%s#egg=%s' % (repo, current_rev_hash, egg_project_name)
+
+ def check_version(self, dest, rev_options):
+ """Always assume the versions don't match"""
+ return False
+
+vcs.register(Mercurial)
diff --git a/lib/python2.7/site-packages/pip/vcs/subversion.py b/lib/python2.7/site-packages/pip/vcs/subversion.py
new file mode 100644
index 0000000..4b23156
--- /dev/null
+++ b/lib/python2.7/site-packages/pip/vcs/subversion.py
@@ -0,0 +1,269 @@
+from __future__ import absolute_import
+
+import logging
+import os
+import re
+
+from pip._vendor.six.moves.urllib import parse as urllib_parse
+
+from pip.index import Link
+from pip.utils import rmtree, display_path
+from pip.utils.logging import indent_log
+from pip.vcs import vcs, VersionControl
+
+_svn_xml_url_re = re.compile('url="([^"]+)"')
+_svn_rev_re = re.compile('committed-rev="(\d+)"')
+_svn_url_re = re.compile(r'URL: (.+)')
+_svn_revision_re = re.compile(r'Revision: (.+)')
+_svn_info_xml_rev_re = re.compile(r'\s*revision="(\d+)"')
+_svn_info_xml_url_re = re.compile(r'<url>(.*)</url>')
+
+
+logger = logging.getLogger(__name__)
+
+
+class Subversion(VersionControl):
+ name = 'svn'
+ dirname = '.svn'
+ repo_name = 'checkout'
+ schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https', 'svn+svn')
+
+ def get_info(self, location):
+ """Returns (url, revision), where both are strings"""
+ assert not location.rstrip('/').endswith(self.dirname), \
+ 'Bad directory: %s' % location
+ output = self.run_command(
+ ['info', location],
+ show_stdout=False,
+ extra_environ={'LANG': 'C'},
+ )
+ match = _svn_url_re.search(output)
+ if not match:
+ logger.warning(
+ 'Cannot determine URL of svn checkout %s',
+ display_path(location),
+ )
+ logger.debug('Output that cannot be parsed: \n%s', output)
+ return None, None
+ url = match.group(1).strip()
+ match = _svn_revision_re.search(output)
+ if not match:
+ logger.warning(
+ 'Cannot determine revision of svn checkout %s',
+ display_path(location),
+ )
+ logger.debug('Output that cannot be parsed: \n%s', output)
+ return url, None
+ return url, match.group(1)
+
+ def export(self, location):
+ """Export the svn repository at the url to the destination location"""
+ url, rev = self.get_url_rev()
+ rev_options = get_rev_options(url, rev)
+ url = self.remove_auth_from_url(url)
+ logger.info('Exporting svn repository %s to %s', url, location)
+ with indent_log():
+ if os.path.exists(location):
+ # Subversion doesn't like to check out over an existing
+ # directory --force fixes this, but was only added in svn 1.5
+ rmtree(location)
+ self.run_command(
+ ['export'] + rev_options + [url, location],
+ show_stdout=False)
+
+ def switch(self, dest, url, rev_options):
+ self.run_command(['switch'] + rev_options + [url, dest])
+
+ def update(self, dest, rev_options):
+ self.run_command(['update'] + rev_options + [dest])
+
+ def obtain(self, dest):
+ url, rev = self.get_url_rev()
+ rev_options = get_rev_options(url, rev)
+ url = self.remove_auth_from_url(url)
+ if rev:
+ rev_display = ' (to revision %s)' % rev
+ else:
+ rev_display = ''
+ if self.check_destination(dest, url, rev_options, rev_display):
+ logger.info(
+ 'Checking out %s%s to %s',
+ url,
+ rev_display,
+ display_path(dest),
+ )
+ self.run_command(['checkout', '-q'] + rev_options + [url, dest])
+
+ def get_location(self, dist, dependency_links):
+ for url in dependency_links:
+ egg_fragment = Link(url).egg_fragment
+ if not egg_fragment:
+ continue
+ if '-' in egg_fragment:
+ # FIXME: will this work when a package has - in the name?
+ key = '-'.join(egg_fragment.split('-')[:-1]).lower()
+ else:
+ key = egg_fragment
+ if key == dist.key:
+ return url.split('#', 1)[0]
+ return None
+
+ def get_revision(self, location):
+ """
+ Return the maximum revision for all files under a given location
+ """
+ # Note: taken from setuptools.command.egg_info
+ revision = 0
+
+ for base, dirs, files in os.walk(location):
+ if self.dirname not in dirs:
+ dirs[:] = []
+ continue # no sense walking uncontrolled subdirs
+ dirs.remove(self.dirname)
+ entries_fn = os.path.join(base, self.dirname, 'entries')
+ if not os.path.exists(entries_fn):
+ # FIXME: should we warn?
+ continue
+
+ dirurl, localrev = self._get_svn_url_rev(base)
+
+ if base == location:
+ base_url = dirurl + '/' # save the root url
+ elif not dirurl or not dirurl.startswith(base_url):
+ dirs[:] = []
+ continue # not part of the same svn tree, skip it
+ revision = max(revision, localrev)
+ return revision
+
+ def get_url_rev(self):
+ # hotfix the URL scheme after removing svn+ from svn+ssh:// readd it
+ url, rev = super(Subversion, self).get_url_rev()
+ if url.startswith('ssh://'):
+ url = 'svn+' + url
+ return url, rev
+
+ def get_url(self, location):
+ # In cases where the source is in a subdirectory, not alongside
+ # setup.py we have to look up in the location until we find a real
+ # setup.py
+ orig_location = location
+ while not os.path.exists(os.path.join(location, 'setup.py')):
+ last_location = location
+ location = os.path.dirname(location)
+ if location == last_location:
+ # We've traversed up to the root of the filesystem without
+ # finding setup.py
+ logger.warning(
+ "Could not find setup.py for directory %s (tried all "
+ "parent directories)",
+ orig_location,
+ )
+ return None
+
+ return self._get_svn_url_rev(location)[0]
+
+ def _get_svn_url_rev(self, location):
+ from pip.exceptions import InstallationError
+
+ entries_path = os.path.join(location, self.dirname, 'entries')
+ if os.path.exists(entries_path):
+ with open(entries_path) as f:
+ data = f.read()
+ else: # subversion >= 1.7 does not have the 'entries' file
+ data = ''
+
+ if (data.startswith('8') or
+ data.startswith('9') or
+ data.startswith('10')):
+ data = list(map(str.splitlines, data.split('\n\x0c\n')))
+ del data[0][0] # get rid of the '8'
+ url = data[0][3]
+ revs = [int(d[9]) for d in data if len(d) > 9 and d[9]] + [0]
+ elif data.startswith('<?xml'):
+ match = _svn_xml_url_re.search(data)
+ if not match:
+ raise ValueError('Badly formatted data: %r' % data)
+ url = match.group(1) # get repository URL
+ revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)] + [0]
+ else:
+ try:
+ # subversion >= 1.7
+ xml = self.run_command(
+ ['info', '--xml', location],
+ show_stdout=False,
+ )
+ url = _svn_info_xml_url_re.search(xml).group(1)
+ revs = [
+ int(m.group(1)) for m in _svn_info_xml_rev_re.finditer(xml)
+ ]
+ except InstallationError:
+ url, revs = None, []
+
+ if revs:
+ rev = max(revs)
+ else:
+ rev = 0
+
+ return url, rev
+
+ def get_src_requirement(self, dist, location):
+ repo = self.get_url(location)
+ if repo is None:
+ return None
+ # FIXME: why not project name?
+ egg_project_name = dist.egg_name().split('-', 1)[0]
+ rev = self.get_revision(location)
+ return 'svn+%s@%s#egg=%s' % (repo, rev, egg_project_name)
+
+ def check_version(self, dest, rev_options):
+ """Always assume the versions don't match"""
+ return False
+
+ @staticmethod
+ def remove_auth_from_url(url):
+ # Return a copy of url with 'username:password@' removed.
+ # username/pass params are passed to subversion through flags
+ # and are not recognized in the url.
+
+ # parsed url
+ purl = urllib_parse.urlsplit(url)
+ stripped_netloc = \
+ purl.netloc.split('@')[-1]
+
+ # stripped url
+ url_pieces = (
+ purl.scheme, stripped_netloc, purl.path, purl.query, purl.fragment
+ )
+ surl = urllib_parse.urlunsplit(url_pieces)
+ return surl
+
+
+def get_rev_options(url, rev):
+ if rev:
+ rev_options = ['-r', rev]
+ else:
+ rev_options = []
+
+ r = urllib_parse.urlsplit(url)
+ if hasattr(r, 'username'):
+ # >= Python-2.5
+ username, password = r.username, r.password
+ else:
+ netloc = r[1]
+ if '@' in netloc:
+ auth = netloc.split('@')[0]
+ if ':' in auth:
+ username, password = auth.split(':', 1)
+ else:
+ username, password = auth, None
+ else:
+ username, password = None, None
+
+ if username:
+ rev_options += ['--username', username]
+ if password:
+ rev_options += ['--password', password]
+ return rev_options
+
+
+vcs.register(Subversion)