class SourceControl(object):
    def __init__(self):
        pass

    def start(self, wiki):
        pass

    def init(self, wiki):
        pass

    def postInit(self):
        pass

    def getSpecialFilenames(self):
        raise NotImplementedError()

    def getHistory(self, path=None, limit=10, after_rev=None):
        raise NotImplementedError()

    def getState(self, path):
        raise NotImplementedError()

    def getRevision(self, path, rev):
        raise NotImplementedError()

    def diff(self, path, rev1, rev2):
        raise NotImplementedError()

    def commit(self, paths, op_meta):
        pass

    def revert(self, paths=None):
        pass class Author(object):
    def __init__(self, name=None, email=None):
        self.name = name
        self.email = email
        if name is not None and email is None:
            m = re.match(r'(?P<name>.+)\s+\<(?P<email>.+@.+\.\w+)\>', name)
            if m is not None:
                self.name = str(m.group('name'))
                self.email = str(m.group('email'))

    def __str__(self):
        return '%s <%s>' % (self.name, self.email) class Revision(object):
    def __init__(self, rev_id=-1):
        self.rev_id = rev_id
        self.rev_name = rev_id
        self.author = None
        self.timestamp = 0
        self.description = None
        self.files = []

    @property
    def is_local(self):
        return self.rev_id == -1

    @property
    def is_committed(self):
        return self.rev_id != -1

    def addFile(self, path, action=None):
        self.files.append(FileRevision(path, action)) class SourceControlError(Exception):
    def __init__(self, operation, message, command, output, *args):
        Exception.__init__(self, *args)
        self.operation = operation
        self.message = message
        self.command = command
        self.output = output

    def __str__(self):
        return "Error running '%s': %s\nCommand: %s\nOutput: %s" % (
                self.operation, self.message, self.command, self.output) ACTION_ADD = 0 ACTION_EDIT = 2 ACTION_DELETE = 1 STATE_NEW = 2 STATE_MODIFIED = 1 STATE_COMMITTED = 0
import re
import os
import os.path
import time
import logging
import tempfile
import threading
import subprocess
        import shutil
    import hglib
    import atexit
        import signal
from hglib.error import CommandError
from hglib.util import cmdbuilder
from .base import (
        SourceControl, Author, Revision, SourceControlError,
        ACTION_ADD, ACTION_EDIT, ACTION_DELETE,
        STATE_NEW, STATE_MODIFIED, STATE_COMMITTED)

        return rev

    def _run(self, cmd, *args, **kwargs):
        exe = [self.hg]
        if 'norepo' not in kwargs or not kwargs['norepo']:
            exe += ['-R', self.root]
        exe.append(cmd)
        exe += args
        logger.debug("Running Mercurial: " + str(exe))
        out = subprocess.check_output(exe)
        return _s(out)


hg_client = None
cl_lock = threading.Lock()


def _b(strs):
    """ Convert a list of strings to binary UTF8 arrays. """
    if strs is None:
        return None
    if isinstance(strs, str):
        return strs.encode('utf8')
    return list([s.encode('utf8') if s is not None else None for s in strs])


def _s(strs):
    """ Convert a byte array to string using UTF8 encoding. """
    if strs is None:
        return None
    if isinstance(strs, bytes):
        return strs.decode('utf8')
    return list([s.decode('utf8') if s is not None else None for s in strs])


def create_hg_client(root):
    logger.debug("Spawning Mercurial command server at: %s" % root)
    global hg_client
    hg_client = hglib.open(root)

    def shutdown_commandserver(num, frame):
        global hg_client
        if hg_client is not None:
            with cl_lock:
                if hg_client is not None:
                    logger.debug("Shutting down Mercurial command server.")
                    hg_client.close()
                    hg_client = None
    atexit.register(shutdown_commandserver, None, None)
    try:
        signal.signal(signal.SIGTERM, shutdown_commandserver)
    except:  # NOQA
        # `mod_wsgi` prevents adding stuff to `SIGTERM`
        # so let's not make a big deal if this doesn't
        # go through.
        pass


class MercurialCommandServerSourceControl(MercurialBaseSourceControl):
    def __init__(self, root, client=None):
        MercurialBaseSourceControl.__init__(self, root)
        self._client = client

    def _initRepo(self, root):
        exe = ['hg', 'init', root]
        logger.debug("Running Mercurial: " + str(exe))
        return subprocess.check_output(exe)

    @property
    def client(self):
        if self._client is None:
            if hg_client is None:
                with cl_lock:
                    if hg_client is None:
                        create_hg_client(self.root)
                self._client = hg_client
            else:
                logger.debug("Re-using existing Mercurial command server.")
                self._client = hg_client
        return self._client

    def getHistory(self, path=None, limit=10, after_rev=None):
        if path is not None:
            status = _s(self.client.status(include=_b([path])))
            if len(status) > 0 and status[0] == '?':
                return []

        rev = None
        if after_rev:
            if not re_rev.match(after_rev):
                raise ValueError("Invalid revision ID: %s" % after_rev)
            rev = 'reverse(:%s^)' % after_rev

        needs_files = False
        if path is not None:
            repo_revs = self.client.log(files=_b([path]), follow=True,
                                        limit=limit, revrange=_b(rev))
        else:
            needs_files = True
            repo_revs = self.client.log(follow=True, limit=limit,
                                        revrange=_b(rev))
        revisions = []
        for rev in repo_revs:
            r = Revision(_s(rev.node))
            r.rev_name = _s(rev.node[:12])
            r.author = Author(_s(rev.author))
            r.timestamp = time.mktime(rev.date.timetuple())
            r.description = _s(rev.desc)
            if needs_files:
                rev_statuses = self.client.status(change=rev.node)
                for rev_status in rev_statuses:
                    r.addFile(_s(rev_status[1]),
                              self.actions.get(_s(rev_status[0])))
            revisions.append(r)
        return revisions

    def getState(self, path):
        statuses = self.client.status(include=_b([path]))
        if len(statuses) == 0:

            return STATE_COMMITTED