def fill_archive(self, stream=None, kind='tgz', prefix=None, subrepos=False): """ Fills up given stream. :param stream: file like object. :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``. Default: ``tgz``. :param prefix: name of root directory in archive. Default is repository name and changeset's raw_id joined with dash (``repo-tip.<KIND>``). :param subrepos: include subrepos in this archive. :raise ImproperArchiveTypeError: If given kind is wrong. :raise VcsError: If given stream is None """ allowed_kinds = settings.ARCHIVE_SPECS.keys() if kind not in allowed_kinds: raise ImproperArchiveTypeError('Archive kind not supported use one' 'of %s', allowed_kinds) if prefix is None: prefix = '%s-%s' % (self.repository.name, self.short_id) elif prefix.startswith('/'): raise VCSError("Prefix cannot start with leading slash") elif prefix.strip() == '': raise VCSError("Prefix cannot be empty") if kind == 'zip': frmt = 'zip' else: frmt = 'tar' _git_path = settings.GIT_EXECUTABLE_PATH cmd = '%s archive --format=%s --prefix=%s/ %s' % (_git_path, frmt, prefix, self.raw_id) if kind == 'tgz': cmd += ' | gzip -9' elif kind == 'tbz2': cmd += ' | bzip2 -9' if stream is None: raise VCSError('You need to pass in a valid stream for filling' ' with archival data') popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True, cwd=self.repository.path) buffer_size = 1024 * 8 chunk = popen.stdout.read(buffer_size) while chunk: stream.write(chunk) chunk = popen.stdout.read(buffer_size) # Make sure all descriptors would be read popen.communicate()
def fill_archive(self, stream=None, kind='tgz', prefix=None, subrepos=False): """ Fills up given stream. :param stream: file like object. :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``. Default: ``tgz``. :param prefix: name of root directory in archive. Default is repository name and changeset's raw_id joined with dash (``repo-tip.<KIND>``). :param subrepos: include subrepos in this archive. :raise ImproperArchiveTypeError: If given kind is wrong. :raise VcsError: If given stream is None """ allowed_kinds = settings.ARCHIVE_SPECS.keys() if kind not in allowed_kinds: raise ImproperArchiveTypeError( 'Archive kind not supported use one' 'of %s', allowed_kinds) if stream is None: raise VCSError('You need to pass in a valid stream for filling' ' with archival data') if prefix is None: prefix = '%s-%s' % (self.repository.name, self.short_id) elif prefix.startswith('/'): raise VCSError("Prefix cannot start with leading slash") elif prefix.strip() == '': raise VCSError("Prefix cannot be empty") archival.archive(self.repository._repo, stream, self.raw_id, kind, prefix=prefix, subrepos=subrepos) if stream.closed and hasattr(stream, 'name'): stream = open(stream.name, 'rb') elif hasattr(stream, 'mode') and 'r' not in stream.mode: stream = open(stream.name, 'rb') else: stream.seek(0)
def get_repo(path=None, alias=None, create=False): """ Returns ``Repository`` object of type linked with given ``alias`` at the specified ``path``. If ``alias`` is not given it will try to guess it using get_scm method """ if create: if not (path or alias): raise TypeError( "If create is specified, we need path and scm type") return get_backend(alias)(path, create=True) if path is None: path = abspath(os.path.curdir) try: scm, path = get_scm(path, search_up=True) path = abspath(path) alias = scm except VCSError: raise VCSError("No scm found at %s" % path) if alias is None: alias = get_scm(path)[0] backend = get_backend(alias) repo = backend(path, create=create) return repo
def get_scms_for_path(path): """ Returns all scm's found at the given path. If no scm is recognized - empty list is returned. :param path: path to directory which should be checked. May be callable. :raises VCSError: if given ``path`` is not a directory """ from vcs.backends import get_backend if hasattr(path, '__call__'): path = path() if not os.path.isdir(path): raise VCSError("Given path %r is not a directory" % path) result = [] for key in ALIASES: dirname = os.path.join(path, '.' + key) if os.path.isdir(dirname): result.append(key) continue # We still need to check if it's not bare repository as # bare repos don't have working directories try: get_backend(key)(path) result.append(key) continue except RepositoryError: # Wrong backend pass except VCSError: # No backend at all pass return result
def get_udiff(filenode_old, filenode_new, show_whitespace=True): """ Returns unified diff between given ``filenode_old`` and ``filenode_new``. """ try: filenode_old_date = filenode_old.changeset.date except NodeError: filenode_old_date = None try: filenode_new_date = filenode_new.changeset.date except NodeError: filenode_new_date = None for filenode in (filenode_old, filenode_new): if not isinstance(filenode, FileNode): raise VCSError("Given object should be FileNode object, not %s" % filenode.__class__) if filenode_old_date and filenode_new_date: if not filenode_old_date < filenode_new_date: logging.debug("Generating udiff for filenodes with not increasing " "dates") vcs_udiff = unified_diff(filenode_old.content.splitlines(True), filenode_new.content.splitlines(True), filenode_old.name, filenode_new.name, filenode_old_date, filenode_old_date) return vcs_udiff
def __init__(self, repo_path, create=False, baseui=None, src_url=None, update_after_clone=False): """ Raises RepositoryError if repository could not be find at the given ``repo_path``. :param repo_path: local path of the repository :param create=False: if set to True, would try to create repository if it does not exist rather than raising exception :param baseui=None: user data :param src_url=None: would try to clone repository from given location :param update_after_clone=False: sets update of working copy after making a clone """ if not isinstance(repo_path, str): raise VCSError('Mercurial backend requires repository path to ' 'be instance of <str> got %s instead' % type(repo_path)) self.path = abspath(repo_path) self.baseui = baseui or ui.ui() # We've set path and ui, now we can set _repo itself self._repo = self._get_repo(create, src_url, update_after_clone)
def get_backend(alias): """ Returns ``Repository`` class identified by the given alias or raises VCSError if alias is not recognized or backend class cannot be imported. """ if alias not in settings.BACKENDS: raise VCSError("Given alias '%s' is not recognized! Allowed aliases:\n" "%s" % (alias, pformat(settings.BACKENDS.keys()))) backend_path = settings.BACKENDS[alias] klass = import_class(backend_path) return klass
def get_scm(path, search_up=False, explicit_alias=None): """ Returns one of alias from ``ALIASES`` (in order of precedence same as shortcuts given in ``ALIASES``) and top working dir path for the given argument. If no scm-specific directory is found or more than one scm is found at that directory, ``VCSError`` is raised. :param search_up: if set to ``True``, this function would try to move up to parent directory every time no scm is recognized for the currently checked path. Default: ``False``. :param explicit_alias: can be one of available backend aliases, when given it will return given explicit alias in repositories under more than one version control, if explicit_alias is different than found it will raise VCSError """ if not os.path.isdir(path): raise VCSError("Given path %s is not a directory" % path) def get_scms(path): return [(scm, path) for scm in get_scms_for_path(path)] found_scms = get_scms(path) while not found_scms and search_up: newpath = abspath(path, '..') if newpath == path: break path = newpath found_scms = get_scms(path) if len(found_scms) > 1: for scm in found_scms: if scm[0] == explicit_alias: return scm raise VCSError('More than one [%s] scm found at given path %s' % (','.join((x[0] for x in found_scms)), path)) if len(found_scms) is 0: raise VCSError('No scm found at given path %s' % path) return found_scms[0]
def next(self, branch=None): if branch and self.branch != branch: raise VCSError('Branch option used on changeset not belonging ' 'to that branch') def _next(changeset, branch): try: next_ = changeset.revision + 1 next_rev = changeset.repository.revisions[next_] except IndexError: raise ChangesetDoesNotExistError cs = changeset.repository.get_changeset(next_rev) if branch and branch != cs.branch: return _next(cs, branch) return cs return _next(self, branch)
def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True): """ Returns git style diff between given ``filenode_old`` and ``filenode_new``. :param ignore_whitespace: ignore whitespaces in diff """ for filenode in (filenode_old, filenode_new): if not isinstance(filenode, FileNode): raise VCSError("Given object should be FileNode object, not %s" % filenode.__class__) old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40) new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40) repo = filenode_new.changeset.repository vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path, ignore_whitespace) return vcs_gitdiff
def import_class(class_path): """ Returns class from the given path. For example, in order to get class located at ``vcs.backends.hg.MercurialRepository``: try: hgrepo = import_class('vcs.backends.hg.MercurialRepository') except VCSError: # hadle error """ splitted = class_path.split('.') mod_path = '.'.join(splitted[:-1]) class_name = splitted[-1] try: class_mod = __import__(mod_path, {}, {}, [class_name]) except ImportError, err: msg = "There was problem while trying to import backend class. "\ "Original error was:\n%s" % err raise VCSError(msg)
def prev(self, branch=None): if branch and self.branch != branch: raise VCSError('Branch option used on changeset not belonging ' 'to that branch') def _prev(changeset, branch): try: prev_ = changeset.revision - 1 if prev_ < 0: raise IndexError prev_rev = changeset.repository.revisions[prev_] except IndexError: raise ChangesetDoesNotExistError cs = changeset.repository.get_changeset(prev_rev) if branch and branch != cs.branch: return _prev(cs, branch) return cs return _prev(self, branch)
def __init__(self, filenode, annotate_from_changeset_func=None, order=None, **options): """ If ``annotate_from_changeset_func`` is passed it should be a function which returns string from the given changeset. For example, we may pass following function as ``annotate_from_changeset_func``:: def changeset_to_anchor(changeset): return '<a href="/changesets/%s/">%s</a>\n' %\ (changeset.id, changeset.id) :param annotate_from_changeset_func: see above :param order: (default: ``['ls', 'annotate', 'code']``); order of columns; :param options: standard pygment's HtmlFormatter options, there is extra option tough, ``headers``. For instance we can pass:: formatter = AnnotateHtmlFormatter(filenode, headers={ 'ls': '#', 'annotate': 'Annotate', 'code': 'Code', }) """ super(AnnotateHtmlFormatter, self).__init__(**options) self.annotate_from_changeset_func = annotate_from_changeset_func self.order = order or ('ls', 'annotate', 'code') headers = options.pop('headers', None) if headers and not ('ls' in headers and 'annotate' in headers and 'code' in headers): raise ValueError("If headers option dict is specified it must " "all 'ls', 'annotate' and 'code' keys") self.headers = headers if isinstance(filenode, FileNode): self.filenode = filenode else: raise VCSError("This formatter expect FileNode parameter, not %r" % type(filenode))