示例#1
0
    def _get_revision(self, revision):
        """
        Given any revision identifier, returns a 40 char string with revision hash.

        :param revision: str or int or None
        """
        if self._empty:
            raise EmptyRepositoryError("There are no changesets yet")

        if revision in [-1, None]:
            revision = b'tip'
        elif isinstance(revision, str):
            revision = safe_bytes(revision)

        try:
            if isinstance(revision, int):
                return ascii_str(self._repo[revision].hex())
            return ascii_str(
                mercurial.scmutil.revsymbol(self._repo, revision).hex())
        except (IndexError, ValueError, mercurial.error.RepoLookupError,
                TypeError):
            msg = "Revision %r does not exist for %s" % (safe_str(revision),
                                                         self.name)
            raise ChangesetDoesNotExistError(msg)
        except (LookupError, ):
            msg = "Ambiguous identifier `%s` for %s" % (safe_str(revision),
                                                        self.name)
            raise ChangesetDoesNotExistError(msg)
示例#2
0
    def get_nodes(self, path):
        """
        Returns combined ``DirNode`` and ``FileNode`` objects list representing
        state of changeset at the given ``path``. If node at the given ``path``
        is not instance of ``DirNode``, ChangesetError would be raised.
        """

        if self._get_kind(path) != NodeKind.DIR:
            raise ChangesetError("Directory does not exist for revision %s at "
                                 " '%s'" % (self.revision, path))
        path = path.rstrip('/')
        id = self._get_id_for_path(path)
        tree = self.repository._repo[id]
        dirnodes = []
        filenodes = []
        als = self.repository.alias
        for name, stat, id in tree.items():
            obj_path = safe_str(name)
            if path != '':
                obj_path = '/'.join((path, obj_path))
            if objects.S_ISGITLINK(stat):
                root_tree = self.repository._repo[self._tree_id]
                cf = ConfigFile.from_file(
                    BytesIO(
                        self.repository._repo.get_object(
                            root_tree[b'.gitmodules'][1]).data))
                url = ascii_str(cf.get(('submodule', obj_path), 'url'))
                dirnodes.append(
                    SubModuleNode(obj_path,
                                  url=url,
                                  changeset=ascii_str(id),
                                  alias=als))
                continue

            obj = self.repository._repo.get_object(id)
            if obj_path not in self._stat_modes:
                self._stat_modes[obj_path] = stat
            if isinstance(obj, objects.Tree):
                dirnodes.append(DirNode(obj_path, changeset=self))
            elif isinstance(obj, objects.Blob):
                filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
            else:
                raise ChangesetError("Requested object should be Tree "
                                     "or Blob, is %r" % type(obj))
        nodes = dirnodes + filenodes
        for node in nodes:
            if node.path not in self.nodes:
                self.nodes[node.path] = node
        nodes.sort()
        return nodes
示例#3
0
 def __init__(self, repository, revision):
     self.repository = repository
     assert isinstance(revision, str), repr(revision)
     self._ctx = repository._repo[ascii_bytes(revision)]
     self.raw_id = ascii_str(self._ctx.hex())
     self.revision = self._ctx._rev
     self.nodes = {}
示例#4
0
 def _get_all_revisions2(self):
     # alternate implementation using dulwich
     includes = [
         ascii_str(sha) for key, (sha, type_) in self._parsed_refs.items()
         if type_ != b'T'
     ]
     return [c.commit.id for c in self._repo.get_walker(include=includes)]
示例#5
0
    def get_node(self, path):
        """
        Returns ``Node`` object from the given ``path``. If there is no node at
        the given ``path``, ``ChangesetError`` would be raised.
        """
        path = path.rstrip('/')
        if path not in self.nodes:
            try:
                id_ = self._get_id_for_path(path)
            except ChangesetError:
                raise NodeDoesNotExistError(
                    "Cannot find one of parents' "
                    "directories for a given path: %s" % path)

            stat = self._stat_modes.get(path)
            if stat and objects.S_ISGITLINK(stat):
                tree = self.repository._repo[self._tree_id]
                cf = ConfigFile.from_file(
                    BytesIO(
                        self.repository._repo.get_object(
                            tree[b'.gitmodules'][1]).data))
                url = ascii_str(cf.get(('submodule', path), 'url'))
                node = SubModuleNode(path,
                                     url=url,
                                     changeset=ascii_str(id_),
                                     alias=self.repository.alias)
            else:
                obj = self.repository._repo.get_object(id_)

                if isinstance(obj, objects.Tree):
                    if path == '':
                        node = RootNode(changeset=self)
                    else:
                        node = DirNode(path, changeset=self)
                    node._tree = obj
                elif isinstance(obj, objects.Blob):
                    node = FileNode(path, changeset=self)
                    node._blob = obj
                else:
                    raise NodeDoesNotExistError(
                        "There is no file nor directory "
                        "at the given path: '%s' at revision %s" %
                        (path, self.short_id))
            # cache node
            self.nodes[path] = node
        return self.nodes[path]
示例#6
0
 def _get_tags(self):
     if not self.revisions:
         return {}
     _tags = [(safe_str(key), ascii_str(sha))
              for key, (sha, type_) in self._parsed_refs.items()
              if type_ == b'T']
     return OrderedDict(
         sorted(_tags, key=(lambda ctx: ctx[0]), reverse=True))
示例#7
0
 def parents(self):
     """
     Returns list of parents changesets.
     """
     return [
         self.repository.get_changeset(ascii_str(parent_id))
         for parent_id in self._commit.parents
     ]
示例#8
0
 def branches(self):
     if not self.revisions:
         return {}
     _branches = [(safe_str(key), ascii_str(sha))
                  for key, (sha, type_) in self._parsed_refs.items()
                  if type_ == b'H']
     return OrderedDict(
         sorted(_branches, key=(lambda ctx: ctx[0]), reverse=False))
示例#9
0
    def _get_tags(self):
        if self._empty:
            return {}

        return OrderedDict(
            sorted(
                ((safe_str(n), ascii_str(mercurial.node.hex(h)))
                 for n, h in self._repo.tags().items()),
                reverse=True,
                key=lambda x: x[0],  # sort by name
            ))
示例#10
0
    def _get_bookmarks(self):
        if self._empty:
            return {}

        return OrderedDict(
            sorted(
                ((safe_str(n), ascii_str(h))
                 for n, h in self._repo._bookmarks.items()),
                reverse=True,
                key=lambda x: x[0],  # sort by name
            ))
示例#11
0
 def get_file_annotate(self, path):
     """
     Returns a generator of four element tuples with
         lineno, sha, changeset lazy loader and line
     """
     annotations = self._get_filectx(path).annotate()
     annotation_lines = [(annotateline.fctx, annotateline.text)
                         for annotateline in annotations]
     for i, (fctx, line) in enumerate(annotation_lines):
         sha = ascii_str(fctx.hex())
         yield (i + 1, sha,
                lambda sha=sha: self.repository.get_changeset(sha), line)
示例#12
0
def generate_api_key():
    """
    Generates a random (presumably unique) API key.

    This value is used in URLs and "Bearer" HTTP Authorization headers,
    which in practice means it should only contain URL-safe characters
    (RFC 3986):

        unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
    """
    # Hexadecimal certainly qualifies as URL-safe.
    return ascii_str(binascii.hexlify(os.urandom(20)))
示例#13
0
    def _get_branches(self, normal=True, closed=False):
        """
        Gets branches for this repository
        Returns only not closed branches by default

        :param closed: return also closed branches for mercurial
        :param normal: return also normal branches
        """

        if self._empty:
            return {}

        bt = OrderedDict()
        for bn, _heads, node, isclosed in sorted(
                self._repo.branchmap().iterbranches()):
            if isclosed:
                if closed:
                    bt[safe_str(bn)] = ascii_str(mercurial.node.hex(node))
            else:
                if normal:
                    bt[safe_str(bn)] = ascii_str(mercurial.node.hex(node))
        return bt
示例#14
0
    def _get_revision(self, revision):
        """
        Given any revision identifier, returns a 40 char string with revision hash.
        """
        if self._empty:
            raise EmptyRepositoryError("There are no changesets yet")

        if revision in (None, '', 'tip', 'HEAD', 'head', -1):
            revision = -1

        if isinstance(revision, int):
            try:
                return self.revisions[revision]
            except IndexError:
                msg = "Revision %r does not exist for %s" % (revision,
                                                             self.name)
                raise ChangesetDoesNotExistError(msg)

        if isinstance(revision, str):
            if revision.isdigit() and (len(revision) < 12 or len(revision)
                                       == revision.count('0')):
                try:
                    return self.revisions[int(revision)]
                except IndexError:
                    msg = "Revision %r does not exist for %s" % (revision,
                                                                 self)
                    raise ChangesetDoesNotExistError(msg)

            # get by branch/tag name
            _ref_revision = self._parsed_refs.get(safe_bytes(revision))
            if _ref_revision:  # and _ref_revision[1] in [b'H', b'RH', b'T']:
                return ascii_str(_ref_revision[0])

            if revision in self.revisions:
                return revision

            # maybe it's a tag ? we don't have them in self.revisions
            if revision in self.tags.values():
                return revision

            if SHA_PATTERN.match(revision):
                msg = "Revision %r does not exist for %s" % (revision,
                                                             self.name)
                raise ChangesetDoesNotExistError(msg)

        raise ChangesetDoesNotExistError("Given revision %r not recognized" %
                                         revision)
示例#15
0
    def get_file_history_2(self, path):
        """
        Returns history of file as reversed list of ``Changeset`` objects for
        which file at given ``path`` has been modified.

        """
        self._get_filectx(path)
        from dulwich.walk import Walker
        include = [self.raw_id]
        walker = Walker(self.repository._repo.object_store,
                        include,
                        paths=[path],
                        max_entries=1)
        return [
            self.repository.get_changeset(ascii_str(x.commit.id.decode))
            for x in walker
        ]
示例#16
0
    def __init__(self, repository, revision):
        self._stat_modes = {}
        self.repository = repository
        try:
            commit = self.repository._repo[ascii_bytes(revision)]
            if isinstance(commit, objects.Tag):
                revision = safe_str(commit.object[1])
                commit = self.repository._repo.get_object(commit.object[1])
        except KeyError:
            raise RepositoryError("Cannot get object with id %s" % revision)
        self.raw_id = ascii_str(commit.id)
        self.short_id = self.raw_id[:12]
        self._commit = commit  # a Dulwich Commmit with .id
        self._tree_id = commit.tree
        self._committer_property = 'committer'
        self._author_property = 'author'
        self._date_property = 'commit_time'
        self._date_tz_property = 'commit_timezone'
        self.revision = repository.revisions.index(self.raw_id)

        self.nodes = {}
        self._paths = {}
示例#17
0
 def _get_all_revisions(self):
     return [
         ascii_str(self._repo[x].hex())
         for x in self._repo.filtered(b'visible').changelog.revs()
     ]
示例#18
0
 def get_changeset(self):
     wk_dir_id = ascii_str(self.repository._repo[None].parents()[0].hex())
     return self.repository.get_changeset(wk_dir_id)
示例#19
0
    def commit(self,
               message,
               author,
               parents=None,
               branch=None,
               date=None,
               **kwargs):
        """
        Performs in-memory commit (doesn't check workdir in any way) and
        returns newly created ``Changeset``. Updates repository's
        ``revisions``.

        :param message: message of the commit
        :param author: full username, i.e. "Joe Doe <*****@*****.**>"
        :param parents: single parent or sequence of parents from which commit
          would be derived
        :param date: ``datetime.datetime`` instance. Defaults to
          ``datetime.datetime.now()``.
        :param branch: branch name, as string. If none given, default backend's
          branch would be used.

        :raises ``CommitError``: if any error occurs while committing
        """
        self.check_integrity(parents)

        if not isinstance(message, str):
            raise RepositoryError('message must be a str - got %r' %
                                  type(message))
        if not isinstance(author, str):
            raise RepositoryError('author must be a str - got %r' %
                                  type(author))

        from .repository import MercurialRepository
        if branch is None:
            branch = MercurialRepository.DEFAULT_BRANCH_NAME
        kwargs[b'branch'] = safe_bytes(branch)

        def filectxfn(_repo, memctx, bytes_path):
            """
            Callback from Mercurial, returning ctx to commit for the given
            path.
            """
            path = safe_str(bytes_path)

            # check if this path is removed
            if path in (node.path for node in self.removed):
                return None

            # check if this path is added
            for node in self.added:
                if node.path == path:
                    return mercurial.context.memfilectx(
                        _repo,
                        memctx,
                        path=bytes_path,
                        data=node.content,
                        islink=False,
                        isexec=node.is_executable,
                        copysource=False)

            # or changed
            for node in self.changed:
                if node.path == path:
                    return mercurial.context.memfilectx(
                        _repo,
                        memctx,
                        path=bytes_path,
                        data=node.content,
                        islink=False,
                        isexec=node.is_executable,
                        copysource=False)

            raise RepositoryError("Given path haven't been marked as added, "
                                  "changed or removed (%s)" % path)

        parents = [None, None]
        for i, parent in enumerate(self.parents):
            if parent is not None:
                parents[i] = parent._ctx.node()

        if date and isinstance(date, datetime.datetime):
            date = safe_bytes(date.strftime('%a, %d %b %Y %H:%M:%S'))

        commit_ctx = mercurial.context.memctx(
            repo=self.repository._repo,
            parents=parents,
            text=b'',
            files=[safe_bytes(x) for x in self.get_paths()],
            filectxfn=filectxfn,
            user=safe_bytes(author),
            date=date,
            extra=kwargs)

        # injecting given _repo params
        commit_ctx._text = safe_bytes(message)
        commit_ctx._user = safe_bytes(author)
        commit_ctx._date = date

        # TODO: Catch exceptions!
        n = self.repository._repo.commitctx(commit_ctx)
        # Returns mercurial node
        self._commit_ctx = commit_ctx  # For reference
        # Update vcs repository object & recreate mercurial _repo
        # new_ctx = self.repository._repo[node]
        # new_tip = ascii_str(self.repository.get_changeset(new_ctx.hex()))
        self.repository.revisions.append(ascii_str(mercurial.node.hex(n)))
        self._repo = self.repository._get_repo(create=False)
        self.repository.branches = self.repository._get_branches()
        tip = self.repository.get_changeset()
        self.reset()
        return tip
示例#20
0
    def commit(self,
               message,
               author,
               parents=None,
               branch=None,
               date=None,
               **kwargs):
        """
        Performs in-memory commit (doesn't check workdir in any way) and
        returns newly created ``Changeset``. Updates repository's
        ``revisions``.

        :param message: message of the commit
        :param author: full username, i.e. "Joe Doe <*****@*****.**>"
        :param parents: single parent or sequence of parents from which commit
          would be derived
        :param date: ``datetime.datetime`` instance. Defaults to
          ``datetime.datetime.now()``.
        :param branch: branch name, as string. If none given, default backend's
          branch would be used.

        :raises ``CommitError``: if any error occurs while committing
        """
        self.check_integrity(parents)

        from .repository import GitRepository
        if branch is None:
            branch = GitRepository.DEFAULT_BRANCH_NAME

        repo = self.repository._repo
        object_store = repo.object_store

        ENCODING = b"UTF-8"  # TODO: should probably be kept in sync with safe_str/safe_bytes and vcs/conf/settings.py DEFAULT_ENCODINGS

        # Create tree and populates it with blobs
        commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or \
            objects.Tree()
        for node in self.added + self.changed:
            # Compute subdirs if needed
            dirpath, nodename = posixpath.split(node.path)
            dirnames = safe_bytes(dirpath).split(b'/') if dirpath else []
            parent = commit_tree
            ancestors = [('', parent)]

            # Tries to dig for the deepest existing tree
            while dirnames:
                curdir = dirnames.pop(0)
                try:
                    dir_id = parent[curdir][1]
                except KeyError:
                    # put curdir back into dirnames and stops
                    dirnames.insert(0, curdir)
                    break
                else:
                    # If found, updates parent
                    parent = self.repository._repo[dir_id]
                    ancestors.append((curdir, parent))
            # Now parent is deepest existing tree and we need to create subtrees
            # for dirnames (in reverse order) [this only applies for nodes from added]
            new_trees = []

            blob = objects.Blob.from_string(node.content)

            node_path = safe_bytes(node.name)
            if dirnames:
                # If there are trees which should be created we need to build
                # them now (in reverse order)
                reversed_dirnames = list(reversed(dirnames))
                curtree = objects.Tree()
                curtree[node_path] = node.mode, blob.id
                new_trees.append(curtree)
                for dirname in reversed_dirnames[:-1]:
                    newtree = objects.Tree()
                    #newtree.add(stat.S_IFDIR, dirname, curtree.id)
                    newtree[dirname] = stat.S_IFDIR, curtree.id
                    new_trees.append(newtree)
                    curtree = newtree
                parent[reversed_dirnames[-1]] = stat.S_IFDIR, curtree.id
            else:
                parent.add(name=node_path, mode=node.mode, hexsha=blob.id)

            new_trees.append(parent)
            # Update ancestors
            for parent, tree, path in reversed([
                (a[1], b[1], b[0]) for a, b in zip(ancestors, ancestors[1:])
            ]):
                parent[path] = stat.S_IFDIR, tree.id
                object_store.add_object(tree)

            object_store.add_object(blob)
            for tree in new_trees:
                object_store.add_object(tree)
        for node in self.removed:
            paths = safe_bytes(node.path).split(b'/')
            tree = commit_tree
            trees = [tree]
            # Traverse deep into the forest...
            for path in paths:
                try:
                    obj = self.repository._repo[tree[path][1]]
                    if isinstance(obj, objects.Tree):
                        trees.append(obj)
                        tree = obj
                except KeyError:
                    break
            # Cut down the blob and all rotten trees on the way back...
            for path, tree in reversed(list(zip(paths, trees))):
                del tree[path]
                if tree:
                    # This tree still has elements - don't remove it or any
                    # of it's parents
                    break

        object_store.add_object(commit_tree)

        # Create commit
        commit = objects.Commit()
        commit.tree = commit_tree.id
        commit.parents = [p._commit.id for p in self.parents if p]
        commit.author = commit.committer = safe_bytes(author)
        commit.encoding = ENCODING
        commit.message = safe_bytes(message)

        # Compute date
        if date is None:
            date = time.time()
        elif isinstance(date, datetime.datetime):
            date = time.mktime(date.timetuple())

        author_time = kwargs.pop('author_time', date)
        commit.commit_time = int(date)
        commit.author_time = int(author_time)
        tz = time.timezone
        author_tz = kwargs.pop('author_timezone', tz)
        commit.commit_timezone = tz
        commit.author_timezone = author_tz

        object_store.add_object(commit)

        # Update vcs repository object & recreate dulwich repo
        ref = b'refs/heads/%s' % safe_bytes(branch)
        repo.refs[ref] = commit.id
        self.repository.revisions.append(ascii_str(commit.id))
        # invalidate parsed refs after commit
        self.repository._parsed_refs = self.repository._get_parsed_refs()
        tip = self.repository.get_changeset()
        self.reset()
        return tip