anyvc / anyvc / remote /


    run packends in different processes and on different computers
    also circumvent the gpl

    :license: lgpl2
    :copyright: 2009 by Ronny Pfannschmidt

from execnet import makegateway
from anyvc.util import cachedproperty
from .object import RemoteCaller
from anyvc.common.commit_builder import RevisionBuilderPath
import time
import datetime
from py.path import local

class RemoteCommit(object):
    def __init__(self, repo, id):
        self.repo = repo = id

    def get_parent_diff(self):
        return self.repo.commit_diff(

    def exists(self, path):
        return self.repo.commit_exists(, path)

    def file_content(self, path):
        data = self.repo.commit_file_content(, path)
        if data is None:
            raise IOError('%r not found' % path)
        return data

    def parents(self):
        return [RemoteCommit(self.repo, id)
                for id in self.repo.commit_parents(]

    def author(self):
        return self.repo.commit_author(

    def message(self):
        return self.repo.commit_message(

    def __enter__(self):
        from anyvc.common.repository import RevisionView
        return RevisionView(self, '')

    def time(self):
        return datetime.datetime.fromtimestamp(self.repo.commit_time(

    def __exit__(self, et, ev, tb):

class RemoteRepository(RemoteCaller):

    def path(self):
        return local(self._call_remote('path'))

    def get_commit(self, id):
        return RemoteCommit(self, id)

    def get_default_head(self):
        id = self._call_remote('get_default_head')
        return self.get_commit(id)

    def transaction(self, *k, **kw):
        t = kw.get('time')
        if isinstance(t, datetime.datetime):  # XXX: fragile
            t = time.mktime(t.timetuple())
            kw['time'] = t
        channel = self._call_remote('transaction', *k, **kw)
        return RemoteTransaction(channel)

    def __len__(self):
        return self._call_remote('count_revisions')

class RemoteWorkdir(RemoteCaller):

    def status(self, **kw):
        from anyvc.common.workdir import StatedPath
        items = self._call_remote('status', **kw)
        for path, base, state in items:
            yield StatedPath(path, state, base)

    def repository(self):
        #XXX: this one shouldnt be
        channel = self._call_remote('get_local_repo')
        if channel is not None:
            return RemoteRepository(channel)

    def path(self):
        return local(self._call_remote('get_path'))

class RemoteTransaction(RemoteCaller):
    def __enter__(self):
        #XXX: take remote commit into account
        return RevisionBuilderPath(None, '', self)

    def __exit__(self, etype,  eval, tb):
        if etype is None:

class RemoteBackend(object):
    def __init__(self, backend, module, spec):
        self.spec = spec = backend
        self.module_name = module
        self.gateway = makegateway(spec)
        channel = self.gateway.remote_exec("""
            from anyvc.remote.slave import start_controller
        self._channel = channel.receive()
        if self._channel is None:
            raise ImportError('module %s not found on remote' % module)
        self._caller = RemoteCaller(self._channel) = True

    def stop(self):
        #XXX: propperly shutdown the slave?
        self.gateway.exit() = False

    def features(self):
        return self._caller.features()

    def is_repository(self, path):
        return self._caller.is_repository(path=path)

    def is_workdir(self, path):
        return self._caller.is_workdir(path=path)

    def Repository(self, **kw):
        newchan = self._caller.open_repo(**kw)
        return RemoteRepository(newchan)

    def Workdir(self, path, **kw):
        kw['path'] = str(path)
        if kw.get('source'):  # might be none
            kw['source'] = str(kw['source'])
        newchan = self._caller.open_workdir(**kw)
        return RemoteWorkdir(newchan)