Example #1
0
    def __init__(self,
                 remote=None,
                 userpass='******',
                 prefixes=[sys.prefix],
                 hook=False,
                 evt_mgr=None,
                 verbose=False):
        if remote is None:
            self.remote = get_default_kvs()
        else:
            self.remote = remote
        if userpass == '<config>':
            import config
            self.userpass = config.get_auth()
        else:
            self.userpass = userpass
        self.prefixes = prefixes
        self.hook = hook
        self.evt_mgr = evt_mgr
        self.verbose = verbose

        self.ec = JoinedEggCollection([
            EggCollection(prefix, self.hook, self.evt_mgr)
            for prefix in self.prefixes
        ])
        self.local_dir = join(self.prefixes[0], 'LOCAL-REPO')
        self._connected = False
Example #2
0
    def __init__(self, remote=None, userpass='******', prefixes=[sys.prefix],
                 hook=False, evt_mgr=None, verbose=False, config=None):
        if config is None:
            self.config = Configuration._get_default_config()
        else:
            self.config = config

        self.local_dir = get_writable_local_dir(self.config)
        if remote is None:
            self.remote = get_default_remote(self.config)
        else:
            self.remote = remote
        if userpass == '<config>':
            self.userpass = self.config.get_auth()
        else:
            self.userpass = userpass

        self.prefixes = prefixes
        self.hook = hook
        self.evt_mgr = evt_mgr
        self.verbose = verbose

        self.ec = JoinedEggCollection([
                EggCollection(prefix, self.hook, self.evt_mgr)
                for prefix in self.prefixes])
        self._execution_aborted = threading.Event()
Example #3
0
    def __init__(self, urls, userpass=None,
                 prefixes=[sys.prefix], hook=False, verbose=False):
        self.remote = create_joined_store(urls)
        self.userpass = userpass
        self.prefixes = prefixes
        self.hook = hook
        self.verbose = verbose

        self.ec = JoinedEggCollection([EggCollection(prefix, self.hook)
                                       for prefix in self.prefixes])
        self.local_dir = join(self.prefixes[0], 'LOCAL-REPO')
Example #4
0
    def __init__(
        self, remote=None, userpass="******", prefixes=[sys.prefix], hook=False, evt_mgr=None, verbose=False
    ):
        if remote is None:
            self.remote = get_default_kvs()
        else:
            self.remote = remote
        if userpass == "<config>":
            import config

            self.userpass = config.get_auth()
        else:
            self.userpass = userpass
        self.prefixes = prefixes
        self.hook = hook
        self.evt_mgr = evt_mgr
        self.verbose = verbose

        self.ec = JoinedEggCollection([EggCollection(prefix, self.hook, self.evt_mgr) for prefix in self.prefixes])
        self.local_dir = join(self.prefixes[0], "LOCAL-REPO")
        self._connected = False
Example #5
0
    def __init__(self, remote=None, userpass='******', prefixes=[sys.prefix],
                 hook=False, evt_mgr=None, verbose=False):
        self.local_dir = get_writable_local_dir(prefixes[0])
        if remote is None:
            self.remote = get_default_remote(prefixes)
        else:
            self.remote = remote
        if userpass == '<config>':
            import config
            self.userpass = config.get_auth()
        else:
            self.userpass = userpass

        check_prefixes(prefixes)
        self.prefixes = prefixes
        self.hook = hook
        self.evt_mgr = evt_mgr
        self.verbose = verbose

        self.ec = JoinedEggCollection([
                EggCollection(prefix, self.hook, self.evt_mgr)
                for prefix in self.prefixes])
        self._connected = False
        self._execution_aborted = threading.Event()
class Enpkg(object):
    """
    This is main interface for using enpkg, it is used by the CLI.
    Arguments for object creation:

    remote: key-value store (KVS) instance
        This is the KVS which enpkg will try to connect to for querying
        and fetching eggs.

    All remaining arguments are optional.

    userpass: tuple(username, password) -- default, see below
        these credentials are used when the remote KVS instance is being
        connected.
        By default the credentials are obtained from config.get_auth(),
        which might use the keyring package.

    prefixes: list of path -- default: [sys.prefix]
        Each path, is an install "prefix" (such as, e.g. /usr/local)
        in which things get installed.
        Eggs are installed or removed from the first prefix in the list.

    hook: boolean -- default: False
        Usually eggs are installed into the site-packages directory of the
        corresponding prefix (e.g. /usr/local/lib/python2.7/site-packages).
        When hook is set to True, eggs are installed into "versioned" egg
        directories, for special usage with import hooks (hence the name).

    evt_mgr: encore event manager instance -- default: None
        Various progress events (e.g. for download, install, ...) are being
        emitted to the event manager.  By default, a simple progress bar
        is displayed on the console (which does not use the event manager
        at all).
    """
    def __init__(self, remote=None, userpass='******', prefixes=[sys.prefix],
                 hook=False, evt_mgr=None, verbose=False):
        self.local_dir = get_writable_local_dir(prefixes[0])
        if remote is None:
            self.remote = get_default_remote(prefixes)
        else:
            self.remote = remote
        if userpass == '<config>':
            import config
            self.userpass = config.get_auth()
        else:
            self.userpass = userpass

        check_prefixes(prefixes)
        self.prefixes = prefixes
        self.hook = hook
        self.evt_mgr = evt_mgr
        self.verbose = verbose

        self.ec = JoinedEggCollection([
                EggCollection(prefix, self.hook, self.evt_mgr)
                for prefix in self.prefixes])
        self._execution_aborted = threading.Event()

    # ============= methods which relate to remove store =================

    def reconnect(self):
        """
        Normally it is not necessary to call this method, it is only there
        to offer a convenient way to (re)connect the key-value store.
        This is necessary to update to changes which have occured in the
        store, as the remote store might create a cache during connecting.
        """
        self._connect(force=True)

    def _connect(self, force=False):
        if not self.remote.is_connected or force:
            self.remote.connect(self.userpass)

    def query_remote(self, **kwargs):
        """
        Query the (usually remote) KVS for egg packages.
        """
        self._connect()
        kwargs['type'] = 'egg'
        return self.remote.query(**kwargs)

    def info_list_name(self, name):
        """
        return (sorted by versions (when possible)), a list of metadata
        dictionaries which are available on the remote KVS for a given name
        """
        req = Req(name)
        info_list = []
        for key, info in self.query_remote(name=name):
            if req.matches(info):
                info_list.append(dict(info))
        try:
            return sorted(info_list, key=comparable_info)
        except TypeError:
            return info_list

    # ============= methods which relate to local installation ===========

    def query_installed(self, **kwargs):
        """
        Query installed packages.  In addition to the remote metadata the
        following attributes are added:

        ctime: creation (install) time (string representing local time)

        hook: boolean -- whether installed into "versioned" egg directory

        installed: True (always)

        meta_dir: the path to the egg metadata directory on the local system
        """
        return self.ec.query(**kwargs)

    def find(self, egg):
        """
        Return the local egg metadata (see ``query_installed``) for a given
        egg (key) or None is the egg is not installed
        """
        return self.ec.find(egg)

    def execute(self, actions):
        """
        Execute actions, which is an iterable over tuples(action, egg_name),
        where action is one of 'fetch', 'remote', or 'install' and egg_name
        is the filename of the egg.
        This method is only meant to be called with actions created by the
        *_actions methods below.
        """
        if self.verbose:
            print "Enpkg.execute:", len(actions)
            for item in actions:
                print '\t' + str(item)

        if len(actions) == 0:
            return

        if self.evt_mgr:
            from encore.events.api import ProgressManager
        else:
            from egginst.console import ProgressManager

        self.super_id = uuid4()
        for c in self.ec.collections:
            c.super_id = self.super_id

        progress = ProgressManager(
                self.evt_mgr, source=self,
                operation_id=self.super_id,
                message="super",
                steps=len(actions),
                # ---
                progress_type="super", filename=actions[-1][1],
                disp_amount=len(actions), super_id=None)

        with History(None if self.hook else self.prefixes[0]):
            with progress:
                for n, (opcode, egg) in enumerate(actions):
                    if self._execution_aborted.is_set():
                        self._execution_aborted.clear()
                        break
                    if opcode.startswith('fetch_'):
                        self.fetch(egg, force=int(opcode[-1]))
                    elif opcode == 'remove':
                        self.ec.remove(egg)
                    elif opcode == 'install':
                        if self.remote.is_connected:
                            extra_info = self.remote.get_metadata(egg)
                        else:
                            extra_info = None
                        self.ec.install(egg, self.local_dir, extra_info)
                    else:
                        raise Exception("unknown opcode: %r" % opcode)
                    progress(step=n)

        self.super_id = None
        for c in self.ec.collections:
            c.super_id = self.super_id

    def abort_execution(self):
        self._execution_aborted.set()

    def _install_actions_enstaller(self, installed_version=None):
        # installed_version is only useful for testing
        if installed_version is None:
            installed_version = enstaller.__version__

        mode = 'recur'
        self._connect()
        req = req_from_anything("enstaller")
        eggs = Resolve(self.remote, self.verbose).install_sequence(req, mode)
        if eggs is None:
            raise EnpkgError("No egg found for requirement '%s'." % req)
        elif not len(eggs) == 1:
            raise EnpkgError("No egg found to update enstaller, aborting...")
        else:
            name, version, build = split_eggname(eggs[0])
            if version == installed_version:
                return []
            else:
                return self._install_actions(eggs, mode, False, False)

    def install_actions(self, arg, mode='recur', force=False, forceall=False):
        """
        Create a list of actions which are required for installing, which
        includes updating, a package (without actually doing anything).

        The first argument may be any of:
          * the KVS key, i.e. the egg filename
          * a requirement object (enstaller.resolve.Req)
          * the requirement as a string
        """
        req = req_from_anything(arg)
        # resolve the list of eggs that need to be installed
        self._connect()
        eggs = Resolve(self.remote, self.verbose).install_sequence(req, mode)
        if eggs is None:
             raise EnpkgError("No egg found for requirement '%s'." % req)
        return self._install_actions(eggs, mode, force, forceall)

    def _install_actions(self, eggs, mode, force, forceall):
        if not forceall:
            # remove already installed eggs from egg list
            if force:
                eggs = self._filter_installed_eggs(eggs[:-1]) + [eggs[-1]]
            else:
                eggs = self._filter_installed_eggs(eggs)

        res = []
        for egg in eggs:
            res.append(('fetch_%d' % bool(forceall or force), egg))

        if not self.hook:
            # remove packages with the same name (from first egg collection
            # only, in reverse install order)
            for egg in reversed(eggs):
                name = split_eggname(egg)[0].lower()
                index = dict(self.ec.collections[0].query(name=name))
                assert len(index) < 2
                if len(index) == 1:
                    res.append(('remove', index.keys()[0]))
        for egg in eggs:
            res.append(('install', egg))
        return res

    def _filter_installed_eggs(self, eggs):
        """ Filter out already installed eggs from the given egg list.

        Note that only visible eggs are filtered.
        For example, if in multiple prefixes, a lower prefix has an egg
        which is overridden by a different version in a higher prefix,
        then only the top-most egg is considered and the egg in lower prefix
        is not considered.
        """
        filtered_eggs = []
        for egg in eggs:
            for installed in self.ec.query(name=split_eggname(egg)[0].lower()):
                if installed[0] == egg:
                    break
            else:
                filtered_eggs.append(egg)
        return filtered_eggs

    def remove_actions(self, arg):
        """
        Create the action necessary to remove an egg.  The argument, may be
        one of ..., see above.
        """
        req = req_from_anything(arg)
        assert req.name
        index = dict(self.ec.collections[0].query(**req.as_dict()))
        if len(index) == 0:
            raise EnpkgError("package %s not installed in: %r" %
                             (req, self.prefixes[0]))
        if len(index) > 1:
            assert self.hook
            versions = ['%(version)s-%(build)d' % d
                        for d in index.itervalues()]
            raise EnpkgError("package %s installed more than once: %s" %
                             (req.name, ', '.join(versions)))
        return [('remove', index.keys()[0])]

    def revert_actions(self, arg):
        """
        Calculate the actions necessary to revert to a given state, the
        argument may be one of:
          * complete set of eggs, i.e. a set of egg file names
          * revision number (negative numbers allowed)
        """
        if self.hook:
            raise NotImplementedError
        h = History(self.prefixes[0])
        h.update()
        if isinstance(arg, set):
            state = arg
        else:
            try:
                rev = int(arg)
            except ValueError:
                raise EnpkgError("Error: integer expected, got: %r" % arg)
            try:
                state = h.get_state(rev)
            except IndexError:
                raise EnpkgError("Error: no such revision: %r" % arg)

        curr = h.get_state()
        if state == curr:
            return []

        res = []
        for egg in curr - state:
            if egg.startswith('enstaller'):
                continue
            res.append(('remove', egg))

        for egg in state - curr:
            if egg.startswith('enstaller'):
                continue
            if not isfile(join(self.local_dir, egg)):
                self._connect()
                if self.remote.exists(egg):
                    res.append(('fetch_0', egg))
                else:
                    raise EnpkgError("cannot revert -- missing %r" % egg)
            res.append(('install', egg))
        return res

    def get_history(self):
        """
        return a history (h) object:

        h = Enpkg().get_history()
        h.parse() -> list of tuples(datetime strings, set of eggs/diffs)
        h.construct_states() -> list of tuples(datetime strings, set of eggs)
        """
        if self.hook:
            raise NotImplementedError
        return History(self.prefixes[0])

    # == methods which relate to both (remote store and local installation) ==

    def query(self, **kwargs):
        index = dict(self.query_remote(**kwargs))
        for key, info in self.query_installed(**kwargs):
            if key in index:
                index[key].update(info)
            else:
                index[key] = info
        return index.iteritems()

    def fetch(self, egg, force=False):
        self._connect()
        f = FetchAPI(self.remote, self.local_dir, self.evt_mgr)
        f.super_id = getattr(self, 'super_id', None)
        f.verbose = self.verbose
        f.fetch_egg(egg, force, self._execution_aborted)
Example #7
0
class Enpkg(object):

    def __init__(self, urls, userpass=None,
                 prefixes=[sys.prefix], hook=False, verbose=False):
        self.remote = create_joined_store(urls)
        self.userpass = userpass
        self.prefixes = prefixes
        self.hook = hook
        self.verbose = verbose

        self.ec = JoinedEggCollection([EggCollection(prefix, self.hook)
                                       for prefix in self.prefixes])
        self.local_dir = join(self.prefixes[0], 'LOCAL-REPO')

    # ============= methods which relate to remove store =================

    def _connect(self):
        if getattr(self, '_connected', None):
            return
        self.remote.connect(self.userpass)
        self._connected = True

    def query_remote(self, **kwargs):
        self._connect()
        kwargs['type'] = 'egg'
        return self.remote.query(**kwargs)

    def info_list_name(self, name):
        req = Req(name)
        info_list = []
        for key, info in self.query_remote(name=name):
            if req.matches(info):
                repo = self.remote.where_from(key)
                info['repo_dispname'] = repo.info()['dispname']
                info_list.append(dict(info))
        try:
            return sorted(info_list,
                      key=lambda info: comparable_version(info['version']))
        except TypeError:
            return info_list

    # ============= methods which relate to local installation ===========

    def query_installed(self, **kwargs):
        return self.ec.query(**kwargs)

    def find(self, egg):
        return self.ec.find(egg)

    def install(self, arg, mode='recur', force=False, forceall=False):
        req = req_from_anything(arg)
        # resolve the list of eggs that need to be installed
        self._connect()
        resolver = Resolve(self.remote, self.verbose)
        eggs = resolver.install_sequence(req, mode)
        if eggs is None:
             raise EnpkgError("No egg found for requirement '%s'." % req)

        if not forceall:
            # remove already installed eggs from egg list
            rm = lambda eggs: [e for e in eggs if self.find(e) is None]
            if force:
                eggs = rm(eggs[:-1]) + [eggs[-1]]
            else:
                eggs = rm(eggs)

        # fetch eggs
        for egg in eggs:
            self.fetch(egg, force or forceall)

        if not self.hook:
            # remove packages with the same name (from first egg collection
            # only, in reverse install order)
            for egg in reversed(eggs):
                try:
                    self.remove(Req(name_egg(egg)))
                except EnpkgError:
                    pass

        # install eggs
        for egg in eggs:
            extra_info = {}
            repo = self.remote.where_from(egg)
            if repo:
                extra_info['repo_dispname'] = repo.info()['dispname']
            self.ec.install(egg, self.local_dir, extra_info)
        return len(eggs)

    def remove(self, req):
        assert req.name
        index = dict(self.ec.collections[0].query(**req.as_dict()))
        if len(index) == 0:
            raise EnpkgError("Package %s not installed in: %r" %
                              (req, self.prefixes[0]))
        if len(index) > 1:
            assert self.hook
            versions = ['%(version)s-%(build)d' % d
                        for d in index.itervalues()]
            raise EnpkgError("Package %s installed more than once: %s" %
                              (req.name, ', '.join(versions)))
        egg = index.keys()[0]
        self.ec.remove(egg)

    # == methods which relate to both (remote store / local installation ==

    def query(self, **kwargs):
        index = dict(self.query_remote(**kwargs))
        index.update(self.query_installed(**kwargs))
        return index.iteritems()

    def fetch(self, egg, force=False):
        self._connect()
        f = FetchAPI(self.remote, self.local_dir)
        f.verbose = self.verbose
        f.fetch_egg(egg, force)
Example #8
0
class Enpkg(object):
    """
    This is main interface for using enpkg, it is used by the CLI.
    Arguments for object creation:

    remote: key-value store (KVS) instance
        This is the KVS which enpkg will try to connect to for querying
        and fetching eggs.

    All remaining arguments are optional.

    userpass: tuple(username, password) -- default, see below
        these credentials are used when the remote KVS instance is being
        connected.
        By default the credentials are obtained from config.get_auth(),
        which might use the keyring package.

    prefixes: list of path -- default: [sys.prefix]
        Each path, is an install "prefix" (such as, e.g. /usr/local)
        in which things get installed.
        Eggs are installed or removed from the first prefix in the list.

    hook: boolean -- default: False
        Usually eggs are installed into the site-packages directory of the
        corresponding prefix (e.g. /usr/local/lib/python2.7/site-packages).
        When hook is set to True, eggs are installed into "versioned" egg
        directories, for special usage with import hooks (hence the name).

    evt_mgr: encore event manager instance -- default: None
        Various progress events (e.g. for download, install, ...) are being
        emitted to the event manager.  By default, a simple progress bar
        is displayed on the console (which does not use the event manager
        at all).
    """
    def __init__(self,
                 remote=None,
                 userpass='******',
                 prefixes=[sys.prefix],
                 hook=False,
                 evt_mgr=None,
                 verbose=False):
        if remote is None:
            self.remote = get_default_kvs()
        else:
            self.remote = remote
        if userpass == '<config>':
            import config
            self.userpass = config.get_auth()
        else:
            self.userpass = userpass
        self.prefixes = prefixes
        self.hook = hook
        self.evt_mgr = evt_mgr
        self.verbose = verbose

        self.ec = JoinedEggCollection([
            EggCollection(prefix, self.hook, self.evt_mgr)
            for prefix in self.prefixes
        ])
        self.local_dir = join(self.prefixes[0], 'LOCAL-REPO')
        self._connected = False

    # ============= methods which relate to remove store =================

    def reconnect(self):
        """
        Normally it is not necessary to call this method, it is only there
        to offer a convenient way to (re)connect the key-value store.
        This is necessary to update to changes which have occured in the
        store, as the remove store might create a cache during connecting.
        """
        self._connected = False
        self._connect()

    def _connect(self):
        if self._connected:
            return
        self.remote.connect(self.userpass)
        self._connected = True

    def query_remote(self, **kwargs):
        """
        Query the (usually remote) KVS for egg packages.
        """
        self._connect()
        kwargs['type'] = 'egg'
        return self.remote.query(**kwargs)

    def info_list_name(self, name):
        """
        return (sorted by versions (when possible)), a list of metadata
        dictionaries which are available on the remote KVS for a given name
        """
        req = Req(name)
        info_list = []
        for key, info in self.query_remote(name=name):
            if req.matches(info):
                info_list.append(dict(info))
        try:
            return sorted(info_list, key=comparable_info)
        except TypeError:
            return info_list

    # ============= methods which relate to local installation ===========

    def query_installed(self, **kwargs):
        """
        Query installed packages.  In addition to the remote metadata the
        following attributes are added:

        ctime: creation (install) time (string representing local time)

        hook: boolean -- whether installed into "versioned" egg directory

        installed: True (always)

        meta_dir: the path to the egg metadata directory on the local system
        """
        return self.ec.query(**kwargs)

    def find(self, egg):
        """
        Return the local egg metadata (see ``query_installed``) for a given
        egg (key) or None is the egg is not installed
        """
        return self.ec.find(egg)

    def execute(self, actions):
        """
        Execute actions, which is an iterable over tuples(action, egg_name),
        where action is one of 'fetch', 'remote', or 'install' and egg_name
        is the filename of the egg.
        This method is only meant to be called with actions created by the
        *_actions methods below.
        """
        if self.verbose:
            print "Enpkg.execute:", len(actions)
            for item in actions:
                print '\t' + str(item)

        if len(actions) == 0:
            return

        if self.evt_mgr:
            from encore.events.api import ProgressManager
        else:
            from egginst.console import ProgressManager

        self.super_id = uuid4()
        for c in self.ec.collections:
            c.super_id = self.super_id

        progress = ProgressManager(
            self.evt_mgr,
            source=self,
            operation_id=self.super_id,
            message="super",
            steps=len(actions),
            # ---
            progress_type="super",
            filename=actions[-1][1],
            disp_amount=len(actions),
            super_id=None)

        with History(None if self.hook else self.prefixes[0]):
            with progress:
                for n, (opcode, egg) in enumerate(actions):
                    if opcode.startswith('fetch_'):
                        self.fetch(egg, force=int(opcode[-1]))
                    elif opcode == 'remove':
                        self.ec.remove(egg)
                    elif opcode == 'install':
                        if self._connected:
                            extra_info = self.remote.get_metadata(egg)
                        else:
                            extra_info = None
                        self.ec.install(egg, self.local_dir, extra_info)
                    else:
                        raise Exception("unknown opcode: %r" % opcode)
                    progress(step=n)

        self.super_id = None
        for c in self.ec.collections:
            c.super_id = self.super_id

    def install_actions(self, arg, mode='recur', force=False, forceall=False):
        """
        Create a list of actions which are required for installing, which
        includes updating, a package (without actually doing anything).

        The first argument may be any of:
          * the KVS key, i.e. the egg filename
          * a requirement object (enstaller.resolve.Req)
          * the requirement as a string
        """
        req = req_from_anything(arg)
        # resolve the list of eggs that need to be installed
        self._connect()
        eggs = Resolve(self.remote, self.verbose).install_sequence(req, mode)
        if eggs is None:
            raise EnpkgError("No egg found for requirement '%s'." % req)

        if not forceall:
            # remove already installed eggs from egg list
            rm = lambda eggs: [e for e in eggs if self.find(e) is None]
            if force:
                eggs = rm(eggs[:-1]) + [eggs[-1]]
            else:
                eggs = rm(eggs)

        res = []
        for egg in eggs:
            res.append(('fetch_%d' % bool(forceall or force), egg))

        if not self.hook:
            # remove packages with the same name (from first egg collection
            # only, in reverse install order)
            for egg in reversed(eggs):
                name = split_eggname(egg)[0].lower()
                index = dict(self.ec.collections[0].query(name=name))
                assert len(index) < 2
                if len(index) == 1:
                    res.append(('remove', index.keys()[0]))
        for egg in eggs:
            res.append(('install', egg))
        return res

    def remove_actions(self, arg):
        """
        Create the action necessary to remove an egg.  The argument, may be
        one of ..., see above.
        """
        req = req_from_anything(arg)
        assert req.name
        index = dict(self.ec.collections[0].query(**req.as_dict()))
        if len(index) == 0:
            raise EnpkgError("package %s not installed in: %r" %
                             (req, self.prefixes[0]))
        if len(index) > 1:
            assert self.hook
            versions = [
                '%(version)s-%(build)d' % d for d in index.itervalues()
            ]
            raise EnpkgError("package %s installed more than once: %s" %
                             (req.name, ', '.join(versions)))
        return [('remove', index.keys()[0])]

    def revert_actions(self, arg):
        """
        Calculate the actions necessary to revert to a given state, the
        argument may be one of:
          * complete set of eggs, i.e. a set of egg file names
          * revision number (negative numbers allowed)
        """
        if self.hook:
            raise NotImplementedError
        h = History(self.prefixes[0])
        h.update()
        if isinstance(arg, set):
            state = arg
        else:
            try:
                rev = int(arg)
            except ValueError:
                raise EnpkgError("Error: integer expected, got: %r" % arg)
            try:
                state = h.get_state(rev)
            except IndexError:
                raise EnpkgError("Error: no such revision: %r" % arg)

        curr = h.get_state()
        if state == curr:
            return []

        res = []
        for egg in curr - state:
            res.append(('remove', egg))

        for egg in state - curr:
            if not isfile(join(self.local_dir, egg)):
                self._connect()
                if self.remote.exists(egg):
                    res.append(('fetch_0', egg))
                else:
                    raise EnpkgError("cannot revert -- missing %r" % egg)
            res.append(('install', egg))
        return res

    def get_history(self):
        """
        return a history (h) object:

        h = Enpkg().get_history()
        h.parse() -> list of tuples(datetime strings, set of eggs/diffs)
        h.construct_states() -> list of tuples(datetime strings, set of eggs)
        """
        if self.hook:
            raise NotImplementedError
        return History(self.prefixes[0])

    # == methods which relate to both (remote store and local installation) ==

    def query(self, **kwargs):
        index = dict(self.query_remote(**kwargs))
        for key, info in self.query_installed(**kwargs):
            if key in index:
                index[key].update(info)
            else:
                index[key] = info
        return index.iteritems()

    def fetch(self, egg, force=False):
        self._connect()
        f = FetchAPI(self.remote, self.local_dir, self.evt_mgr)
        f.super_id = getattr(self, 'super_id', None)
        f.verbose = self.verbose
        f.fetch_egg(egg, force)