class NothingToGraftException(Exception):
    """Not found any tree to graft.""" class Changeset(Changeset):
    @classmethod
    def from_git_commit(cls, git_commit):
        if not isinstance(git_commit, GitCommit):
            git_commit = GitCommit(git_commit)

        changeset = cls()

        (changeset.author, changeset.timestamp, changeset.utcoffset) = \
            Authorship.from_git_str(git_commit.author).to_hg()

        if git_commit.committer != git_commit.author:
            changeset.committer = Authorship.from_git_str(
                git_commit.committer).to_hg_str()

        changeset.body = git_commit.body

        return changeset class GitHgHelper(BaseHelper):
    VERSION = 3003
    MODE = 'import'
    _helper = False

    @classmethod
    def reload(self):
        with self.query(b'reload'):
            pass
        self.git2hg.invalidate()
        self.hg2git.invalidate()
        self._cat_commit.invalidate()
        # Force this method to be synchronous with the helper, using the
        # synchronous `version` command.
        with self.query(b'version %d' % self.VERSION) as stdout:
            stdout.readline()

    @classmethod
    def _cat_file(self, typ, sha1):
        with self.query(b'cat-file', sha1) as stdout:
            return self._read_file(typ, stdout)

    @classmethod
    @lrucache(16)
    def _cat_commit(self, sha1):
        return self._cat_file(b'commit', sha1)

    @classmethod
    def cat_file(self, typ, sha1):
        if typ == b'commit':
            return self._cat_commit(sha1)
        return self._cat_file(typ, sha1)

    @classmethod
    @lrucache(16)
    def git2hg(self, sha1):
        assert sha1 != b'changeset'
        with self.query(b'git2hg', sha1) as stdout:
            return self._read_file(b'blob', stdout)

    @classmethod
    def file_meta(self, sha1):
        with self.query(b'file-meta', sha1) as stdout:
            return self._read_file(b'blob', stdout)

    @classmethod
    @lrucache(16)
    def hg2git(self, hg_sha1):
        with self.query(b'hg2git', hg_sha1) as stdout:
            sha1 = stdout.read(41)
            assert sha1[-1:] == b'\n'
            return sha1[:40]

    @classmethod
    def manifest(self, hg_sha1):
        with self.query(b'manifest', hg_sha1) as stdout:
            return self._read_data(stdout)

    @classmethod
    def check_manifest(self, hg_sha1):
        with self.query(b'check-manifest', hg_sha1) as stdout:
            return stdout.readline().strip() == b'ok'

    @classmethod
    def check_file(self, hg_sha1, *parents):
        with self.query(b'check-file', hg_sha1, *parents) as stdout:
            return stdout.readline().strip() == b'ok'

    @classmethod
    def ls_tree(self, sha1, recursive=False):
        extra = () if not recursive else (b'-r',)
        with self.query(b'ls-tree', sha1, *extra) as stdout:
            for line in self._read_data(stdout).split(b'\0'):
                if line:
                    yield split_ls_tree(line)

    @classmethod
    def rev_list(self, *args):
        with self.query(b'rev-list', *args) as stdout:
            for line in self._read_data(stdout).splitlines():
                parents = line.split()
                commit = parents.pop(0)
                tree = parents.pop(0)
                yield commit, tree, parents

    @classmethod
    def diff_tree(self, rev1, rev2, detect_copy=False):
        extra = () if not detect_copy else (b'-C', b'-C')
        extra = extra + (b'--ignore-submodules=dirty', b'--')
        with self.query(b'diff-tree', rev1, rev2, *extra) as stdout:
            data = self._read_data(stdout)
            off = 0
            while off < len(data):
                tab = data.find(b'\t', off)
                assert tab != -1
                (mode_before, mode_after, sha1_before, sha1_after,
                 status) = data[off:tab].split(b' ')
                if detect_copy and status[:1] in b'RC':
                    orig = data.find(b'\0', tab + 1)
                    status = status[:1] + data[tab + 1:orig]
                    tab = orig
                end = data.find(b'\0', tab + 1)
                path = data[tab + 1:end]
                off = end + 1
                yield (mode_before, mode_after, sha1_before, sha1_after,
                       status, path)

    @classmethod
    def set(self, *args):
        if args[0] == b'changeset-metadata':
            self.git2hg.invalidate(self, self.hg2git(args[1]))
        elif args[0] != b'file-meta':
            self.hg2git.invalidate(self, args[1])
        with self.query(b'set', *args):
            pass

    @classmethod
    def store(self, what, *args):
        if what == b'metadata':
            with self.query(b'store', what, *args) as stdout:
                sha1 = stdout.read(41)
                assert sha1[-1:] == b'\n'
                return sha1[:40]
        elif what in (b'file', b'manifest'):
            obj = args[0]
            if isinstance(obj, RawRevChunk01):
                delta_node = obj.delta_node
            elif isinstance(obj, RawRevChunk02):
                delta_node = b'cg2'
            else:
                assert False
            with self.query(b'store', what, delta_node, b'%d' % len(obj)):
                self._helper.stdin.write(obj)
                self._helper.stdin.flush()
        elif what == b'manifest_changegroup':
            with self.query(b'store', what, *args):
                return self._helper.stdin
        else:
            assert False

    @classmethod
    @contextmanager
    def store_changegroup(self, version):
        with self.query(b'store', b'changegroup', b'%d' % version):
            yield self._helper.stdin

    @classmethod
    def heads(self, what):
        with self.query(b'heads', what) as stdout:
            data = self._read_data(stdout)
            return data.split()

    @classmethod
    def reset_heads(self, what):
        with self.query(b'reset-heads', what):
            pass

    @classmethod
    def upgrade(self):
        with self.query(b'upgrade') as stdout:
            return stdout.readline().strip() == b'ok'

    @classmethod
    def create_git_tree(self, manifest_sha1, ref_commit=None):
        extra_arg = (ref_commit,) if ref_commit else ()
        with self.query(b'create-git-tree', manifest_sha1,
                        *extra_arg) as stdout:
            sha1 = stdout.read(41)
            assert sha1[-1:] == b'\n'
            return sha1[:40]

    @classmethod
    def seen(self, typ, sha1):
        with self.query(b'seen', typ, sha1) as stdout:
            return stdout.readline().strip() == b'yes'

    @classmethod
    def dangling(self, typ):
        with self.query(b'dangling', typ) as stdout:
            data = self._read_data(stdout)
            return data.splitlines()

    @classmethod
    def update_ref(self, ref, newvalue):
        with self.query(b'reset', b'%s\nfrom %s\n' % (ref, newvalue)):
            self._helper.stdin.flush()

    @classmethod
    def cat_blob(self, ref):
        with self.query(b'cat-blob', ref) as stdout:
            sha1, blob, size = stdout.readline().split()
            assert blob == b'blob'
            size = int(size)
            content = stdout.read(size)
            lf = stdout.read(1)
            assert lf == b'\n'
            return content

    @classmethod
    def _get_last(self):
        with self.query(b'get-mark', b':1') as stdout:
            sha1 = stdout.read(41)
            assert sha1[-1:] == b'\n'
            return sha1[:40]

    @classmethod
    def put_blob(self, data=b'', want_sha1=True):
        with self.query(b'blob') as io:
            io.write(b'mark :1\n')
            io.write(b'data %d\n' % len(data))
            io.write(data)
            io.write(b'\n')
            io.flush()
            if want_sha1:
                return self._get_last()

    @classmethod
    @contextmanager
    def commit(self, ref, committer=b'<cinnabar@git> 0 +0000', author=None,
               message=b'', from_commit=None, parents=(), pseudo_mark=None):
        if isinstance(parents, GeneratorType):
            parents = tuple(parents)
        from_tree = None
        if parents and parents[0] == from_commit:
            _from = parents[0]
            merges = parents[1:]
        else:
            _from = NULL_NODE_ID
            merges = parents
            if from_commit:
                with self.query(b'ls', from_commit, b'') as stdout:
                    line = stdout.readline()
                    if line.startswith(b'missing '):
                        from_tree = None
                    else:
                        mode, typ, from_tree, path = split_ls_tree(line[:-1])

        helper = CommitHelper()
        helper.write(b'mark :1\n')
        # TODO: properly handle errors, like from the committer being badly
        # formatted.
        if author:
            helper.write(b'author %s\n' % author)
        helper.write(b'committer %s\n' % committer)
        helper.cmd_data(message)

        helper.write(b'from %s\n' % _from)
        for merge in merges:
            helper.write(b'merge %s\n' % merge)
        if from_tree:
            helper.write(b'M 040000 %s \n' % from_tree)

        yield helper
        helper.write(b'\n')

        with self.query(b'commit', ref) as stdout:
            stdout.write(helper.flush_buffer())
            stdout.flush()

        if not pseudo_mark:
            helper.sha1 = self._get_last()

    @classmethod
    def close(self, rollback=True):
        if not rollback and self._helper != self:
            with self.query(b'done'):
                pass
        super(GitHgHelper, self).close() class HgRepoHelper(BaseHelper):
    VERSION = 3003
    MODE = 'wire'
    _helper = False

    @classmethod
    def connect(self, url):
        with self.query(b'connect', url) as stdout:
            resp = stdout.readline().rstrip()
            if resp == b'bundle':
                return stdout
            if resp != b'ok':
                raise Exception(resp.decode('ascii'))

    @classmethod
    def state(self):
        with self.query(b'state') as stdout:
            return {
                'branchmap': self._read_data(stdout),
                'heads': self._read_data(stdout),
                'bookmarks': self._read_data(stdout),
            }

    @classmethod
    def capable(self, name):
        with self.query(b'capable', name) as stdout:
            return self._read_data(stdout)

    @classmethod
    def known(self, nodes):
        with self.query(b'known', *nodes) as stdout:
            return self._read_data(stdout)

    @classmethod
    def listkeys(self, namespace):
        with self.query(b'listkeys', namespace) as stdout:
            return self._read_data(stdout)

    @classmethod
    def getbundle(self, heads, common, bundle2caps=False):
        with self.query(b'getbundle', b','.join(heads), b','.join(common),
                        bundle2caps) as stdout:
            return stdout

    @classmethod
    def unbundle(self, input_iterator, heads):
        with self.query(b'unbundle', *heads) as stdout:
            for data in input_iterator:
                self._helper.stdin.write(data)
            self._helper.stdin.flush()
            ret = self._read_data(stdout)
            try:
                return int(ret)
            except ValueError:
                return ret

    @classmethod
    def pushkey(self, namespace, key, old, new):
        with self.query(b'pushkey', namespace, key, old, new) as stdout:
            ret = self._read_data(stdout).rstrip()
            try:
                return bool(int(ret))
            except ValueError:
                return ret

    @classmethod
    def lookup(self, key):
        with self.query(b'lookup', key) as stdout:
            success, data = self._read_data(stdout).rstrip().split(b' ', 1)
            return data if int(success) else None

    @classmethod
    def clonebundles(self):
        with self.query(b'clonebundles') as stdout:
            return self._read_data(stdout)

    @classmethod
    def cinnabarclone(self):
        with self.query(b'cinnabarclone') as stdout:
            return self._read_data(stdout) class BundleHelper(HgRepoHelper):
    _helper = False class gitdag(object):
    __slots__ = "_parents", "_children", "_tags"

    def __init__(self, revlist=[]):
        self._parents = OrderedDefaultDict(set)
        self._children = defaultdict(set)
        for node, parents in revlist:
            self._parents[node] |= set(parents)
            for p in parents:
                self._children[p].add(node)
        self._tags = {}

    def add(self, node, parents, tag=None):
        self._parents[node] |= set(parents)
        for p in parents:
            self._children[p].add(node)
        if tag:
            self._tags[node] = tag

    def roots(self, tag=None):
        for node, parents in iteritems(self._parents):
            if self._tags.get(node) == tag:
                if all(p not in self._parents or self._tags.get(p) != tag
                       for p in parents):
                    yield node

    def heads(self, tag=None):
        for node in self._parents:
            if self._tags.get(node) == tag:
                if (node not in self._children or
                        all(self._tags.get(c) != tag
                            for c in self._children[node])):
                    yield node

    def all_heads(self, with_tags=True):
        if with_tags:
            for node in self._parents:
                tag = self._tags.get(node)
                if (node not in self._children or
                        all(self._tags.get(c) != tag
                            for c in self._children[node])):
                    yield tag, node
        else:
            for node in self._parents:
                if node not in self._children:
                    yield node

    def tag_nodes_and_parents(self, nodes, tag):
        self._tag_nodes_and_other(self._parents, nodes, tag)

    def tag_nodes_and_children(self, nodes, tag):
        self._tag_nodes_and_other(self._children, nodes, tag)

    def _tag_nodes_and_other(self, other, nodes, tag):
        assert tag
        queue = deque(nodes)
        while queue:
            node = queue.popleft()
            if node in self._tags:
                continue
            self._tags[node] = tag
            for o in other.get(node, ()):
                queue.append(o)

    def iternodes(self, tag=None):
        if tag is None:
            for n in self._parents:
                if n not in self._tags:
                    yield n
        else:
            for n, t in iteritems(self._tags):
                if t == tag:
                    yield n

    def __len__(self):
        return len(self._parents)

    def __contains__(self, node):
        return node in self._parents

    def tags(self):
        return set(itervalues(self._tags)) class Git(object):
    _notes_depth = {}
    _config = None
    _replace = {}

    @classmethod
    def iter(self, *args, **kwargs):
        start = time.time()

        if args[0] == 'config':
            self._config = None

        proc = GitProcess(*args, **kwargs)
        try:
            for line in proc.stdout or ():
                line = line.rstrip(b'\n')
                yield line

        finally:
            proc.wait()
            logging.getLogger(args[0]).info('[%d] wall time: %.3fs',
                                            proc.pid, time.time() - start)

    @classmethod
    def run(self, *args, **kwargs):
        stdout = kwargs.pop('stdout', None)
        return tuple(self.iter(*args, stdout=stdout, **kwargs))

    @classmethod
    def for_each_ref(self, *patterns):
        if not patterns:
            return
        # Ideally, this would not actually call for-each-ref if all refs
        # matching the given patterns are already known.
        for line in self.iter('for-each-ref', '--format',
                              '%(objectname) %(refname)', *patterns,
                              stderr=open(os.devnull, 'wb')):
            yield line.split(b' ', 1)

    @classmethod
    def resolve_ref(self, ref):
        return one(Git.iter('rev-parse', '--revs-only', ref,
                            stderr=open(os.devnull, 'wb')))

    @classmethod
    def ls_tree(self, treeish, path=b'', recursive=False):
        from .helper import GitHgHelper
        assert not treeish.startswith(b'refs/')

        if path.endswith(b'/') or recursive or path == b'':
            path = path.rstrip(b'/')
            for line in GitHgHelper.ls_tree(b'%s:%s' % (treeish, path),
                                            recursive):
                mode, typ, sha1, p = line
                if path:
                    yield mode, typ, sha1, posixpath.join(path, p)
                else:
                    yield mode, typ, sha1, p
        else:
            with GitHgHelper.query(b'ls', treeish, path) as stdout:
                line = stdout.readline()
                if not line.startswith(b'missing '):
                    yield split_ls_tree(line[:-1])

    @classmethod
    def update_ref(self, ref, newvalue):
        assert not newvalue.startswith(b'refs/')
        from .helper import GitHgHelper
        GitHgHelper.update_ref(ref, newvalue)

    @classmethod
    def delete_ref(self, ref):
        self.update_ref(ref, NULL_NODE_ID)

    @classmethod
    def config(self, name, remote=None, values={}, multiple=False):
        assert not (values and multiple)
        if self._config is None:
            proc = GitProcess('config', '-l', '-z')
            data = proc.stdout.read()
            proc.wait()
            self._config = {}
            for l in data.split(b'\0'):
                if l:
                    k, v = l.split(b'\n', 1)
                    if k in self._config:
                        self._config[k] += b'\0' + v
                    else:
                        self._config[k] = v
        var = name.encode('ascii')
        value = None
        if name.startswith('cinnabar.'):
            var = ('GIT_%s' % name.replace('.', '_').upper()).encode('ascii')
            value = environ(var)
            if value is None and remote:
                var = b'remote.%s.%s' % (
                    remote, name.replace('.', '-').encode('ascii'))
                value = self._config.get(var.lower())
        elif name == 'fetch.prune' and remote:
            var = b'remote.%s.prune' % remote
            value = self._config.get(var.lower())
        if value is None:
            var = name.encode('ascii')
            value = self._config.get(var.lower())
        if value:
            value = value.split(b'\0')
            if not multiple:
                value = value[-1]
        logging.getLogger('config').info('%s = %r', var, value)
        if values:
            if value in values:
                if isinstance(values, dict):
                    value = values[value]
            else:
                values = ', '.join(sorted('"%s"' % v.decode('ascii')
                                          for v in values
                                          if v is not None))
                if value is None:
                    raise InvalidConfig(
                        '%s must be set to one of %s' % (
                            var.decode('ascii'), values))
                else:
                    raise InvalidConfig(
                        'Invalid value for %s: "%s". Valid values: %s' % (
                            var.decode('ascii'), fsdecode(value), values))
        return value class InvalidConfig(Exception):
    pass NULL_NODE_ID = b'0' * 40 class StreamHandler(logging.StreamHandler):
class Formatter(logging.Formatter):
class ConfigSetFunc(object):
class Progress(object):
class IOLogger(object):
class OrderedDefaultDict(OrderedDict):
class VersionedDict(object):
class lrucache(object):
    class node(object):
class chunkbuffer(object):
class HTTPReader(object):
class Seekable(object):
class Process(object):
class TypedProperty(object):
class MemoryCPUReporter(Thread):
class VersionCheck(Thread):
    def __init__(self):
    def emit(self, record):
    def __init__(self):
    def format(self, record):
def init_logging():
    def __init__(self, key, values, extra_values=(), default='', remote=None):
    def __call__(self, name):
def interval_expired(config_key, interval, globl=False):
    def __init__(self, fmt):
    def progress(self, count=None):
    def _print_count(self, count, t1=None):
    def finish(self, count=None):
def progress_iter(fmt, iter):
def progress_enum(fmt, enum_iter):
    def __init__(self, logger, reader, writer=None, prefix=''):
    def read(self, length=None, level=logging.INFO):
    def readline(self, level=logging.INFO):
    def write(self, data, level=logging.INFO):
    def flush(self):
    def __iter__(self):
def one(iterable):
def strip_suffix(s, suffix):
    def __init__(self, default_factory, *args, **kwargs):
    def __missing__(self, key):
    def __init__(self, content=None, **kwargs):
    def update(self, content=None, **kwargs):
    def __getitem__(self, key):
    def get(self, key, default=None):
    def __contains__(self, key):
    def __delitem__(self, key):
    def __setitem__(self, key, value):
    def __len__(self):
    def keys(self):
    def values(self):
    def __iter__(self):
    def items(self):
    def iteritems(self):
    def iterchanges(self):
    def flattened(self):
def _iter_diff_blocks(a, b):
def byte_diff(a, b):
def sorted_merge(iter_a, iter_b, key=lambda i: i[0], non_key=lambda i: i[1:]):
        def __init__(self):
        def insert(self, after):
        def detach(self):
    def __init__(self, size):
    def __call__(self, func):
        def wrapper(*args):
    def invalidate(self, *args):
    def __getitem__(self, key):
    def __setitem__(self, key, value):
    def __delitem__(self, key):
    def __len__(self):
    def __init__(self, in_iter):
        def splitbig(chunks):
    def read(self, l=None):
    def __init__(self, url):
    def read(self, size):
    def _reopen(self):
    def readable(self):
    def readinto(self, b):
    def __init__(self, reader, length):
    def read(self, length):
    def tell(self):
    def seek(self, pos, how=os.SEEK_SET):
    def __init__(self, *args, **kwargs):
    def _env_strings(self, env):
    def _popen(self, cmd, env, **kwargs):
    def wait(self):
    def pid(self):
    def stdin(self):
    def stdout(self):
    def stderr(self):
    def __init__(self, cls):
    def __get__(self, obj, cls=None):
    def __set__(self, obj, value):
    def __init__(self, memory=False, cpu=False):
    def _report(self, proc):
    def run(self):
    def shutdown(self):
    def __init__(self):
    def run(self):
    def join(self):
def run(func, args):
    def iteritems(d):
    def itervalues(d):
    def environ(k=None):
    def iteritems(d):
    def itervalues(d):
    def fsencode(s):
    def fsdecode(s):
    def environ(k=None):
    CREATED = 1
    MODIFIED = 2
    REMOVED = 3
        REPO = 'https://github.com/glandium/git-cinnabar' def create_bundle(store, commits, bundle2caps={}):
    version = b'01'
    chunk_type = RawRevChunk01
    if bundle2caps:
        versions = bundle2caps.get(b'changegroup')
        if versions:
            if b'02' in versions:
                chunk_type = RawRevChunk02
                version = b'02'
    cg = create_changegroup(store, bundle_data(store, commits), chunk_type)
    if bundle2caps:
        yield b'HG20'
        yield b'\0' * 4  # bundle parameters length: no params
        replycaps = bundle2caps.get(b'replycaps')
        if replycaps:
            for chunk in bundlepart(b'REPLYCAPS',
                                    data=chunkbuffer([replycaps])):
                yield chunk
        for chunk in bundlepart(b'CHANGEGROUP',
                                advisoryparams=((b'version', version),),
                                data=chunkbuffer(cg)):
            yield chunk
        yield b'\0' * 4  # End of bundle
    else:
        for chunk in cg:
            yield chunk def encodecaps(caps):
    return b'\n'.join(
        b'%s=%s' % (
            quote_from_bytes(k).encode('ascii'),
            b','.join(quote_from_bytes(v).encode('ascii') for v in values))
        if values else quote_from_bytes(k).encode('ascii')
        for k, values in sorted(caps.items())
    ) def decodecaps(caps):
    return {
        unquote_to_bytes(key): [unquote_to_bytes(v)
                                for v in val.split(b',')] if val else []
        for key, eq, val in (l.partition(b'=') for l in caps.splitlines())
    } class RawRevChunk01(RawRevChunk):
    __slots__ = ('__weakref__',)

    node = RawRevChunk._field(0, 20, hexlify)
    parent1 = RawRevChunk._field(20, 20, hexlify)
    parent2 = RawRevChunk._field(40, 20, hexlify)
    changeset = RawRevChunk._field(60, 20, hexlify)
    data = RawRevChunk._field(80)
    patch = RawRevChunk._field(80, filter=RevDiff)

    # Because we keep so many instances of this class on hold, the overhead
    # of having a __dict__ per instance is a deal breaker.
    _delta_nodes = {}

    @property
    def delta_node(self):
        return self._delta_nodes.get(self.node, NULL_NODE_ID)

    @delta_node.setter
    def delta_node(self, value):
        self._delta_nodes[self.node] = value class RawRevChunk02(RawRevChunk):
    __slots__ = ()

    node = RawRevChunk._field(0, 20, hexlify)
    parent1 = RawRevChunk._field(20, 20, hexlify)
    parent2 = RawRevChunk._field(40, 20, hexlify)
    delta_node = RawRevChunk._field(60, 20, hexlify)
    changeset = RawRevChunk._field(80, 20, hexlify)
    data = RawRevChunk._field(100)
    patch = RawRevChunk._field(100, filter=RevDiff)
import os
import re
import ssl
import sys
import logging
import struct
import random
    from urllib.parse import quote_from_bytes, unquote_to_bytes
    from urllib import quote as quote_from_bytes
    from urllib import unquote as unquote_to_bytes
    from urllib2 import HTTPError
    from urllib.error import HTTPError
from cinnabar.exceptions import NothingToGraftException
from cinnabar.githg import Changeset
from cinnabar.helper import (
    GitHgHelper,
    HgRepoHelper,
    BundleHelper,
)
from binascii import (
    hexlify,
    unhexlify,
)
from itertools import chain
    from itertools import izip as zip
from io import BytesIO
    from urlparse import (
        ParseResult,
        urlparse,
        urlunparse,
    )
    from urllib.parse import (
        ParseResult,
        urlparse,
        urlunparse,
    )
from cinnabar.dag import gitdag
from cinnabar.git import (
    Git,
    InvalidConfig,
    NULL_NODE_ID,
)
from cinnabar.util import (
    HTTPReader,
    check_enabled,
    chunkbuffer,
    environ,
    experiment,
    fsdecode,
    progress_enum,
    progress_iter,
)
from collections import (
    defaultdict,
    deque,
    OrderedDict,
)
from .bundle import (
    create_bundle,
    encodecaps,
    decodecaps,
)
from .changegroup import (
    RawRevChunk01,
    RawRevChunk02,
)
    from mercurial import (
        changegroup,
        error,
        hg,
        ui,
        url,
    )
        from mercurial.sshpeer import instance as sshpeer
        from mercurial.sshrepo import instance as sshpeer
        from mercurial.utils import procutil
        from mercurial import util as procutil
        from mercurial.utils import urlutil
        from mercurial import util as urlutil
        from mercurial.changegroup import cg1unpacker
        from mercurial.changegroup import unbundle10 as cg1unpacker
        from mercurial.bundle2 import capabilities
        from mercurial.bundle2 import unbundle20
        if self._heads is None:
            self.init_state()
        return self._heads

    def branchmap(self):
        if self._branchmap is None:
            self.init_state()
        return self._branchmap

    def listkeys(self, namespace):
        if namespace == b'bookmarks':
            if self._bookmarks is None:
                self.init_state()
            return self._bookmarks
        return self._decode_keys(HgRepoHelper.listkeys(namespace))

    def known(self, nodes):
        result = HgRepoHelper.known(hexlify(n) for n in nodes)
        return [b == b'1'[0] for b in result]

    def getbundle(self, name, heads, common, *args, **kwargs):
        heads = [hexlify(h) for h in heads]
        common = [hexlify(c) for c in common]
        bundlecaps = b','.join(kwargs.get('bundlecaps', ()))
        getbundle_params["heads"] = [
            h.decode('ascii', 'replace') for h in heads]
        getbundle_params["common"] = [
            c.decode('ascii', 'replace') for c in common]
        getbundle_params["bundlecaps"] = bundlecaps.decode('utf-8', 'replace')
        data = HgRepoHelper.getbundle(heads, common, bundlecaps)
        header = readexactly(data, 4)
        if header == b'HG20':
            return unbundle20(self.ui, data)

        class Reader(object):
            def __init__(self, header, data):
                self.header = header
                self.data = data

            def read(self, length):
                result = self.header[:length]
                self.header = self.header[length:]
                if length > len(result):
                    result += self.data.read(length - len(result))
                return result

        if header == b'err\n':
            return Reader(b'', BytesIO())
        return Reader(header, data)

    def pushkey(self, namespace, key, old, new):
        return HgRepoHelper.pushkey(namespace, key, old, new)

    def unbundle(self, cg, heads, *args, **kwargs):
        data = HgRepoHelper.unbundle(cg, (hexlify(h) if h != b'force' else h
                                          for h in heads))
        if isinstance(data, str) and data.startswith(b'HG20'):
            data = unbundle20(self.ui, BytesIO(data[4:]))
        return data

    def local(self):
        return None

    def lookup(self, key):
        data = HgRepoHelper.lookup(key)
        if data:
            return unhexlify(data)
        raise Exception('Unknown revision %s' % fsdecode(key))


def unbundle_fh(fh, path):
    header = readexactly(fh, 4)
    magic, version = header[0:2], header[2:4]
    if magic != b'HG':
        raise Exception('%s: not a Mercurial bundle' % fsdecode(path))
    if version == b'10':
        alg = readexactly(fh, 2)
        return cg1unpacker(fh, alg)
    elif unbundle20 and version.startswith(b'2'):
        return unbundle20(get_ui(), fh)
    else:
        raise Exception('%s: unsupported bundle version %s' % (fsdecode(path),
                        version.decode('ascii')))


# Mercurial's bundlerepo completely unwraps bundles in $TMPDIR but we can be
# smarter than that.
class bundlerepo(object):
    def __init__(self, path, fh=None):
        self._url = path
        if fh is None:
            fh = open(path, 'rb')
        self._bundle = unbundle_fh(fh, path)
        self._file = os.path.basename(path)

    def url(self):
        return self._url

    def init(self, store):
        self._store = store

    def _ensure_ready(self):
        assert hasattr(self, '_store')
        if self._store is None:
            return
        store = self._store
        self._store = None

        raw_unbundler = unbundler(self._bundle)
        self._dag = gitdag()
        branches = set()

        chunks = []

        def iter_and_store(iterator):
            for item in iterator:
                chunks.append(item)
                yield item

        changeset_chunks = ChunksCollection(progress_iter(

            'Analyzing {} changesets from ' + fsdecode(self._file),