Source code for SlicerWizard.Subversion

"""Python API for simple interaction with Subversion."""

import os
import subprocess

from .Utilities import *

__all__ = [
  'Client',
  'Repository',
]

#=============================================================================
class CommandError(Exception):
  """
  .. attribute:: command

    Complete command (including arguments) which experienced the error.

  .. attribute:: code

    Command's status code.

  .. attribute:: stderr

    Raw text of the command's standard error stream.
  """

  #---------------------------------------------------------------------------
  def __init__(self, command, code, stderr):
    super(Exception, self).__init__("%r command exited with non-zero status" %
                                    command[0])
    self.command = command
    self.code = code
    self.stderr = stderr

#=============================================================================
[docs]class Client(object): """Wrapper for executing the ``svn`` process. This class provides a convenience wrapping for invoking the ``svn`` process. In addition to the :meth:`~Client.execute` method, names of subversion commands are implicitly available as methods: .. code-block:: python c = Subversion.Client() c.log('.', limit=5) """ #--------------------------------------------------------------------------- def __init__(self, repo=None): self._wc_root = repo.wc_root if repo is not None else None #--------------------------------------------------------------------------- def __getattr__(self, name): """Return a lambda to invoke the svn command ``name``.""" if name[0] == "_": raise AttributeError("%r object has no attribute %r" % (self.__class__.__name__, name)) return lambda *args, **kwargs: self.execute(name, *args, **kwargs) #---------------------------------------------------------------------------
[docs] def execute(self, command, *args, **kwargs): """Execute ``command`` and return line-split output. :param args: Subversion command to execute. :type args: :class:`basestring` :param args: Arguments to pass to ``command``. :type args: :class:`~collections.Sequence` :param kwargs: Named options to pass to ``command``. :type kwargs: :class:`dict` :return: Standard output from running the command, as a list (split by line). :rtype: :class:`list` of :class:`str` :raises: :class:`.CommandError` if the command exits with non-zero status. This executes the specified ``svn`` command and returns the standard output from the execution. See :func:`.buildProcessArgs` for an explanation of how ``args`` and ``kwargs`` are processed. .. seealso:: :func:`.buildProcessArgs` """ command = ["svn", command] + buildProcessArgs(*args, **kwargs) cwd = self._wc_root if self._wc_root is not None else os.getcwd() proc = subprocess.Popen(command, cwd=cwd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE) out, err = proc.communicate() # Raise exception if process failed if proc.returncode != 0: raise CommandError(command, proc.returncode, err) # Strip trailing newline(s) while out.endswith("\n"): out = out[:-1] return out.split("\n") #---------------------------------------------------------------------------
[docs] def info(self, *args, **kwargs): """Return information about the specified item. :type args: :class:`basestring` :param args: Arguments to pass to ``svn info``. :type args: :class:`~collections.Sequence` :param kwargs: Named options to pass to ``svn info``. :type kwargs: :class:`dict` :return: Mapping of information fields returned by ``svn info``. :rtype: :class:`dict` of :class:`str` |rarr| :class:`str` :raises: :class:`.CommandError` if the command exits with non-zero status. This wraps the ``svn info`` command, returning the resulting information as a :class:`dict`. The dictionary keys are the value names as printed by ``svn info``. .. |rarr| unicode:: U+02192 .. right arrow """ out = self.execute("info", *args, **kwargs) result = {} for line in out: parts = line.split(": ", 1) result[parts[0]] = parts[1] return result #=============================================================================
[docs]class Repository(object): """Abstract representation of a subversion repository. .. attribute:: url The remote URL of the base of the working copy checkout. .. attribute:: root_url The root URL of the remote repository. .. attribute:: uuid The universally unique identifier of the repository. .. attribute:: wc_root The absolute path to the top level directory of the repository working copy. .. attribute:: revision The revision at which the working copy is checked out. .. attribute:: last_change_revision The last revision which contains a change to content contained in the working copy. .. attribute:: svn_dir The absolute path to the working copy ``.svn`` directory. .. attribute:: client A :class:`.Client` object which may be used to interact with the repository. The client interprets non-absolute paths as relative to the working copy root. """ #--------------------------------------------------------------------------- def __init__(self, path=os.getcwd()): """ :param path: Location of the repository checkout. :type path: :class:`basestring` :raises: * :exc:`.CommandError` if the request to get the repository information fails (e.g. if ``path`` is not a repository). * :exc:`~exceptions.KeyError` if the repository information is missing a required value. """ c = Client() info = c.info(path) info = c.info(info["Working Copy Root Path"]) self.url = info["URL"] self.root_url = info["Repository Root"] self.uuid = info["Repository UUID"] self.wc_root = info["Working Copy Root Path"] self.revision = info["Revision"] self.last_change_revision = info["Last Changed Rev"] self.svn_dir = os.path.join(self.wc_root, ".svn") self.client = Client(self)