コード例 #1
0
ファイル: repository.py プロジェクト: NexMirror/Kallithea
    def get_changesets(self,
                       start=None,
                       end=None,
                       start_date=None,
                       end_date=None,
                       branch_name=None,
                       reverse=False,
                       max_revisions=None):
        """
        Returns iterator of ``MercurialChangeset`` objects from start to end
        (both are inclusive)

        :param start: None, str, int or mercurial lookup format
        :param end:  None, str, int or mercurial lookup format
        :param start_date:
        :param end_date:
        :param branch_name:
        :param reversed: return changesets in reversed order
        """
        start_raw_id = self._get_revision(start)
        start_pos = None if start is None else self.revisions.index(
            start_raw_id)
        end_raw_id = self._get_revision(end)
        end_pos = None if end is None else self.revisions.index(end_raw_id)

        if start_pos is not None and end_pos is not None and start_pos > end_pos:
            raise RepositoryError("Start revision '%s' cannot be "
                                  "after end revision '%s'" % (start, end))

        if branch_name and branch_name not in self.allbranches:
            msg = "Branch %r not found in %s" % (branch_name, self.name)
            raise BranchDoesNotExistError(msg)
        if end_pos is not None:
            end_pos += 1
        # filter branches
        filter_ = []
        if branch_name:
            filter_.append(b'branch("%s")' % safe_bytes(branch_name))
        if start_date:
            filter_.append(b'date(">%s")' % safe_bytes(str(start_date)))
        if end_date:
            filter_.append(b'date("<%s")' % safe_bytes(str(end_date)))
        if filter_ or max_revisions:
            if filter_:
                revspec = b' and '.join(filter_)
            else:
                revspec = b'all()'
            if max_revisions:
                revspec = b'limit(%s, %d)' % (revspec, max_revisions)
            revisions = mercurial.scmutil.revrange(self._repo, [revspec])
        else:
            revisions = self.revisions

        # this is very much a hack to turn this into a list; a better solution
        # would be to get rid of this function entirely and use revsets
        revs = list(revisions)[start_pos:end_pos]
        if reverse:
            revs.reverse()

        return CollectionGenerator(self, revs)
コード例 #2
0
ファイル: repository.py プロジェクト: NexMirror/Kallithea
    def remove_tag(self, name, user, message=None, date=None):
        """
        Removes tag with the given ``name``.

        :param name: name of the tag to be removed
        :param user: full username, i.e.: "Joe Doe <*****@*****.**>"
        :param message: message of the tag's removal commit
        :param date: date of tag's removal commit

        :raises TagDoesNotExistError: if tag with given name does not exists
        """
        if name not in self.tags:
            raise TagDoesNotExistError("Tag %s does not exist" % name)
        if message is None:
            message = "Removed tag %s" % name
        if date is None:
            date = safe_bytes(
                datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S'))
        local = False

        try:
            mercurial.tags.tag(self._repo,
                               safe_bytes(name), mercurial.commands.nullid,
                               safe_bytes(message), local, safe_bytes(user),
                               date)
            self.tags = self._get_tags()
        except mercurial.error.Abort as e:
            raise RepositoryError(e.args[0])
コード例 #3
0
ファイル: repository.py プロジェクト: NexMirror/Kallithea
    def _get_repo(self, create, src_url=None, update_after_clone=False):
        """
        Function will check for mercurial repository in given path and return
        a localrepo object. If there is no repository in that path it will
        raise an exception unless ``create`` parameter is set to True - in
        that case repository would be created and returned.
        If ``src_url`` is given, would try to clone repository from the
        location at given clone_point. Additionally it'll make update to
        working copy accordingly to ``update_after_clone`` flag
        """
        try:
            if src_url:
                url = safe_bytes(self._get_url(src_url))
                opts = {}
                if not update_after_clone:
                    opts.update({'noupdate': True})
                MercurialRepository._check_url(url, self.baseui)
                mercurial.commands.clone(self.baseui, url,
                                         safe_bytes(self.path), **opts)

                # Don't try to create if we've already cloned repo
                create = False
            return mercurial.localrepo.instance(self.baseui,
                                                safe_bytes(self.path),
                                                create=create)
        except (mercurial.error.Abort, mercurial.error.RepoError) as err:
            if create:
                msg = "Cannot create repository at %s. Original error was %s" \
                    % (self.name, err)
            else:
                msg = "Not valid repository at %s. Original error was %s" \
                    % (self.name, err)
            raise RepositoryError(msg)
コード例 #4
0
ファイル: repository.py プロジェクト: NexMirror/Kallithea
    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)
コード例 #5
0
    def tag(self,
            name,
            user,
            revision=None,
            message=None,
            date=None,
            **kwargs):
        """
        Creates and returns a tag for the given ``revision``.

        :param name: name for new tag
        :param user: full username, i.e.: "Joe Doe <*****@*****.**>"
        :param revision: changeset id for which new tag would be created
        :param message: message of the tag's commit
        :param date: date of tag's commit

        :raises TagAlreadyExistError: if tag with same name already exists
        """
        if name in self.tags:
            raise TagAlreadyExistError("Tag %s already exists" % name)
        changeset = self.get_changeset(revision)
        message = message or "Added tag %s for commit %s" % (name,
                                                             changeset.raw_id)
        self._repo.refs[b"refs/tags/%s" %
                        safe_bytes(name)] = changeset._commit.id

        self._parsed_refs = self._get_parsed_refs()
        self.tags = self._get_tags()
        return changeset
コード例 #6
0
    def _check_url(cls, url):
        """
        Function will check given url and try to verify if it's a valid
        link. Sometimes it may happened that git will issue basic
        auth request that can cause whole API to hang when used from python
        or other external calls.

        On failures it'll raise urllib2.HTTPError, exception is also thrown
        when the return code is non 200
        """
        # check first if it's not an local url
        if os.path.isdir(url) or url.startswith('file:'):
            return True

        if url.startswith('git://'):
            return True

        if '+' in url[:url.find('://')]:
            url = url[url.find('+') + 1:]

        handlers = []
        url_obj = mercurial.util.url(safe_bytes(url))
        test_uri, authinfo = url_obj.authinfo()
        if not test_uri.endswith(b'info/refs'):
            test_uri = test_uri.rstrip(b'/') + b'/info/refs'

        url_obj.passwd = b'*****'
        cleaned_uri = str(url_obj)

        if authinfo:
            # create a password manager
            passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
            passmgr.add_password(*authinfo)

            handlers.extend((mercurial.url.httpbasicauthhandler(passmgr),
                             mercurial.url.httpdigestauthhandler(passmgr)))

        o = urllib.request.build_opener(*handlers)
        o.addheaders = [('User-Agent', 'git/1.7.8.0')]  # fake some git

        req = urllib.request.Request(
            "%s?%s" % (safe_str(test_uri),
                       urllib.parse.urlencode({"service": 'git-upload-pack'})))

        try:
            resp = o.open(req)
            if resp.code != 200:
                raise Exception('Return Code is not 200')
        except Exception as e:
            # means it cannot be cloned
            raise urllib.error.URLError("[%s] org_exc: %s" % (cleaned_uri, e))

        # now detect if it's proper git repo
        gitdata = resp.read()
        if b'service=git-upload-pack' not in gitdata:
            raise urllib.error.URLError("url [%s] does not look like an git" %
                                        cleaned_uri)

        return True
コード例 #7
0
ファイル: repository.py プロジェクト: NexMirror/Kallithea
    def tag(self,
            name,
            user,
            revision=None,
            message=None,
            date=None,
            **kwargs):
        """
        Creates and returns a tag for the given ``revision``.

        :param name: name for new tag
        :param user: full username, i.e.: "Joe Doe <*****@*****.**>"
        :param revision: changeset id for which new tag would be created
        :param message: message of the tag's commit
        :param date: date of tag's commit

        :raises TagAlreadyExistError: if tag with same name already exists
        """
        if name in self.tags:
            raise TagAlreadyExistError("Tag %s already exists" % name)
        changeset = self.get_changeset(revision)
        local = kwargs.setdefault('local', False)

        if message is None:
            message = "Added tag %s for changeset %s" % (name,
                                                         changeset.short_id)

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

        try:
            mercurial.tags.tag(self._repo, safe_bytes(name),
                               changeset._ctx.node(), safe_bytes(message),
                               local, safe_bytes(user), date)
        except mercurial.error.Abort as e:
            raise RepositoryError(e.args[0])

        # Reinitialize tags
        self.tags = self._get_tags()
        tag_id = self.tags[name]

        return self.get_changeset(revision=tag_id)
コード例 #8
0
ファイル: repository.py プロジェクト: NexMirror/Kallithea
 def pull(self, url):
     """
     Tries to pull changes from external location.
     """
     other = mercurial.hg.peer(self._repo, {},
                               safe_bytes(self._get_url(url)))
     try:
         mercurial.exchange.pull(self._repo, other, heads=None, force=None)
     except mercurial.error.Abort as err:
         # Propagate error but with vcs's type
         raise RepositoryError(str(err))
コード例 #9
0
ファイル: changeset.py プロジェクト: NexMirror/Kallithea
    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
        if kind not in allowed_kinds:
            raise ImproperArchiveTypeError('Archive kind not supported use one'
                                           'of %s' % ' '.join(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")

        mercurial.archival.archive(self.repository._repo,
                                   stream,
                                   ascii_bytes(self.raw_id),
                                   safe_bytes(kind),
                                   prefix=safe_bytes(prefix),
                                   subrepos=subrepos)
コード例 #10
0
ファイル: repository.py プロジェクト: NexMirror/Kallithea
    def get_config_value(self, section, name=None, config_file=None):
        """
        Returns configuration value for a given [``section``] and ``name``.

        :param section: Section we want to retrieve value from
        :param name: Name of configuration we want to retrieve
        :param config_file: A path to file which should be used to retrieve
          configuration from (might also be a list of file paths)
        """
        if config_file is None:
            config_file = []
        elif isinstance(config_file, str):
            config_file = [config_file]

        config = self._repo.ui
        if config_file:
            config = mercurial.ui.ui()
            for path in config_file:
                config.readconfig(safe_bytes(path))
        value = config.config(safe_bytes(section), safe_bytes(name))
        return value if value is None else safe_str(value)
コード例 #11
0
    def _serve(self):
        # Note: we want a repo with config based on .hg/hgrc and can thus not use self.db_repo.scm_instance._repo.ui
        baseui = make_ui(repo_path=self.db_repo.repo_full_path)
        if not self.allow_push:
            baseui.setconfig(b'hooks', b'pretxnopen._ssh_reject',
                             b'python:kallithea.lib.hooks.rejectpush')
            baseui.setconfig(b'hooks', b'prepushkey._ssh_reject',
                             b'python:kallithea.lib.hooks.rejectpush')

        repo = mercurial.hg.repository(baseui,
                                       safe_bytes(self.db_repo.repo_full_path))
        log.debug("Starting Mercurial sshserver for %s",
                  self.db_repo.repo_full_path)
        mercurial.wireprotoserver.sshserver(baseui, repo).serve_forever()
コード例 #12
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)
コード例 #13
0
ファイル: nodes.py プロジェクト: NexMirror/Kallithea
    def __init__(self, path, content=None, changeset=None, mode=None):
        """
        Only one of ``content`` and ``changeset`` may be given. Passing both
        would raise ``NodeError`` exception.

        :param path: relative path to the node
        :param content: content may be passed to constructor
        :param changeset: if given, will use it to lazily fetch content
        :param mode: octal representation of ST_MODE (i.e. 0100644)
        """

        if content and changeset:
            raise NodeError("Cannot use both content and changeset")
        super(FileNode, self).__init__(path, kind=NodeKind.FILE)
        self.changeset = changeset
        if not isinstance(content, bytes) and content is not None:
            # File content is one thing that inherently must be bytes ... but
            # VCS module tries to be "user friendly" and support unicode ...
            content = safe_bytes(content)
        self._content = content
        self._mode = mode or 0o100644
コード例 #14
0
ファイル: changeset.py プロジェクト: NexMirror/Kallithea
 def _get_filectx(self, path):
     path = path.rstrip('/')
     if self._get_kind(path) != NodeKind.FILE:
         raise ChangesetError("File does not exist for revision %s at "
                              " '%s'" % (self.raw_id, path))
     return self._ctx.filectx(safe_bytes(path))
コード例 #15
0
ファイル: inmemory.py プロジェクト: NexMirror/Kallithea
    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
コード例 #16
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
コード例 #17
0
ファイル: repository.py プロジェクト: NexMirror/Kallithea
    def _check_url(cls, url, repoui=None):
        """
        Function will check given url and try to verify if it's a valid
        link. Sometimes it may happened that mercurial will issue basic
        auth request that can cause whole API to hang when used from python
        or other external calls.

        On failures it'll raise urllib2.HTTPError, exception is also thrown
        when the return code is non 200
        """
        # check first if it's not an local url
        url = safe_bytes(url)
        if os.path.isdir(url) or url.startswith(b'file:'):
            return True

        if url.startswith(b'ssh:'):
            # in case of invalid uri or authentication issues, sshpeer will
            # throw an exception.
            mercurial.sshpeer.instance(repoui or mercurial.ui.ui(), url,
                                       False).lookup(b'tip')
            return True

        url_prefix = None
        if b'+' in url[:url.find(b'://')]:
            url_prefix, url = url.split(b'+', 1)

        handlers = []
        url_obj = mercurial.util.url(url)
        test_uri, authinfo = url_obj.authinfo()
        url_obj.passwd = b'*****'
        cleaned_uri = str(url_obj)

        if authinfo:
            # create a password manager
            passmgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
            passmgr.add_password(*authinfo)

            handlers.extend((mercurial.url.httpbasicauthhandler(passmgr),
                             mercurial.url.httpdigestauthhandler(passmgr)))

        o = urllib.request.build_opener(*handlers)
        o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
                        ('Accept', 'application/mercurial-0.1')]

        req = urllib.request.Request(
            "%s?%s" %
            (test_uri,
             urllib.parse.urlencode({
                 'cmd': 'between',
                 'pairs': "%s-%s" % ('0' * 40, '0' * 40),
             })))

        try:
            resp = o.open(req)
            if resp.code != 200:
                raise Exception('Return Code is not 200')
        except Exception as e:
            # means it cannot be cloned
            raise urllib.error.URLError("[%s] org_exc: %s" % (cleaned_uri, e))

        if not url_prefix:  # skip svn+http://... (and git+... too)
            # now check if it's a proper hg repo
            try:
                mercurial.httppeer.instance(repoui or mercurial.ui.ui(), url,
                                            False).lookup(b'tip')
            except Exception as e:
                raise urllib.error.URLError(
                    "url [%s] does not look like an hg repo org_exc: %s" %
                    (cleaned_uri, e))

        return True