[PATCH 10 of 12 RFC v2] hglib: a wrapper around hg's cmdserver, written in Python

Idan Kamara idankk86 at gmail.com
Wed Jun 15 16:09:22 CDT 2011


# HG changeset patch
# User Idan Kamara <idankk86 at gmail.com>
# Date 1307111464 -10800
# Node ID 06f8e17e7af4b21df2ee211cf8b65ef348f0e323
# Parent  2aa1aab45314c38f5636360b3a9136eed6329bcb
hglib: a wrapper around hg's cmdserver, written in Python

diff -r 2aa1aab45314 -r 06f8e17e7af4 contrib/hglib/hglib.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/hglib/hglib.py	Fri Jun 03 17:31:04 2011 +0300
@@ -0,0 +1,216 @@
+import subprocess
+import struct
+import cStringIO
+
+HGPATH = 'hg'
+
+def connect(path, fin, out, err, extraconfigs=None):
+    return hgclient(path, fin, out, err, extraconfigs)
+
+class CommandError(Exception):
+    pass
+
+class ServerError(Exception):
+    pass
+
+class ResponseError(ServerError, ValueError):
+    pass
+
+class CapabilityError(ServerError):
+    pass
+
+class hgclient(object):
+    inputfmt = '>I'
+    outputfmt = '>cI'
+    outputfmtsize = struct.calcsize(outputfmt)
+    retfmt = '>i'
+
+    def __init__(self, path, fin, out, err, extraconfigs):
+        self.fin = fin
+        self.ochannels = {'o' : out, 'e' : err, 'd' : err}
+        cmdline = [HGPATH, '-R', path, 'serve', '--cmdserver', 'pipe']
+
+        if extraconfigs:
+            cmdline += ['--config'] + extraconfigs
+
+        self.server = subprocess.Popen(cmdline, stdin=subprocess.PIPE,
+                                       stdout=subprocess.PIPE)
+
+        self._readhello()
+
+    @property
+    def encoding(self):
+        """ get the servers encoding """
+        if not 'getencoding' in self.capabilities:
+            raise CapabilityError('getencoding')
+
+        if not self._encoding:
+            self.server.stdin.write('getencoding\n')
+            self._encoding = self._readfromchannel('r')
+
+        return self._encoding
+
+    def _readfromchannel(self, channel, discardoptinal=True):
+        ch, length = self._readheader()
+
+        # read-loop until data on the given channel arrives
+        while ch != channel:
+            if ch in self.ochannels:
+                data = self.server.stdout.read(length)
+                self.ochannels[ch].write(data)
+                self.ochannels[ch].flush()
+            elif ch.isupper(): # is this a required channel?
+                raise ResponseError("cannot discard required channel '%s'" % ch)
+            elif not discardoptinal:
+                raise ResponseError("expected data on channel '%s', "
+                                    "got '%s'" % (channel, ch))
+            else:
+                # discard this channels data
+                self.server.stdout.read(length)
+
+            ch, length = self._readheader()
+
+        return self.server.stdout.read(length)
+
+    def _readhello(self):
+        """ read the hello message the server sends when started """
+
+        self.capabilities = self._readfromchannel('o')[len('capabilities: '):]
+        if not self.capabilities:
+            raise ResponseError("expected 'capabilities: '")
+
+        self.capabilities = set(self.capabilities.split())
+
+        # at the very least the server should be able to run commands
+        assert 'runcommand' in self.capabilities
+
+        self._encoding = self._readfromchannel('o')[len('encoding: '):]
+        if not self._encoding:
+            raise ResponseError("expected 'encoding: '")
+
+    def _readheader(self):
+        data = self.server.stdout.read(hgclient.outputfmtsize)
+        return struct.unpack(hgclient.outputfmt, data)
+
+    def runcommand(self, args):
+        if not self.server or self.terminated():
+            raise ValueError("server not connected")
+
+        self.server.stdin.write('runcommand\n')
+
+        args = '\0'.join(args)
+        self.server.stdin.write(struct.pack(hgclient.inputfmt, len(args)))
+        self.server.stdin.write(args)
+        self.server.stdin.flush()
+
+        while True:
+            channel, length = self._readheader()
+
+            # input channels
+            if channel in 'IL':
+                assert length > 0
+
+                if channel == 'I':
+                    s = self.fin.read(length)
+                else:
+                    s = self.fin.readline(length)
+
+                self.server.stdin.write(struct.pack(hgclient.inputfmt, len(s)))
+
+                if s:
+                    self.server.stdin.write(s)
+            # output channels
+            elif channel in self.ochannels:
+                data = self.server.stdout.read(length)
+                self.ochannels[channel].write(data)
+                self.ochannels[channel].flush()
+            # result channel, command finished
+            elif channel == 'r':
+                # this is the return code
+                data = self.server.stdout.read(length)
+                break
+            # a channel that we don't know and can't ignore
+            elif channel.isupper():
+                raise ResponseError("unexpected data on required channel '%s'"
+                                    % channel)
+            # optional channel
+            else:
+                self.server.stdout.read(length)
+
+        ret = struct.unpack(hgclient.retfmt, data)[0]
+        return ret
+
+    def outputruncommand(self, args):
+        """ run the command specified by args, returning (ret, output, error) """
+        self.redirect()
+        try:
+            ret = self.runcommand(args)
+        finally:
+            out, err = self.restore()
+
+        return (ret, out, err)
+
+    def inputruncommand(self, args, fin):
+        old = self.fin
+        self.fin = fin
+        try:
+            return self.outputruncommand(args)
+        finally:
+            self.fin = old
+
+    def close(self):
+        self.server.stdin.close()
+        self.server.wait()
+        ret = self.server.returncode
+        self.server = None
+
+        return ret
+
+    def terminated(self):
+        if self.server.poll() is not None:
+            return self.server.returncode
+        else:
+            return None
+
+    def redirect(self):
+        self.old = self.ochannels['o'], self.ochannels['e']
+        self.ochannels['o'], self.ochannels['e'] = \
+            cStringIO.StringIO(), cStringIO.StringIO()
+
+    def restore(self):
+        out, err = self.ochannels['o'].getvalue(), self.ochannels['e'].getvalue()
+        self.ochannels['o'], self.ochannels['e'] = self.old
+        return out, err
+
+    def status(self):
+        ret, out, err = self.outputruncommand(['status', '-0'])
+
+        if ret:
+            raise CommandError(ret, out, err)
+
+        d = dict((c, []) for c in 'MARC!?I ')
+
+        for entry in out.split('\0'):
+            if entry:
+                t, f = entry.split(' ', 1)
+                d[t].append(f)
+
+        return d
+
+    def import_(self, patch):
+        if isinstance(patch, str):
+            fp = open(patch)
+        else:
+            assert hasattr(patch, 'read')
+            assert hasattr(patch, 'readline')
+
+            fp = patch
+
+        try:
+            ret, out, err = self.inputruncommand(['import', '-'], fp)
+
+            if ret:
+                raise CommandError(ret, out, err)
+        finally:
+            if fp != patch:
+                fp.close()


More information about the Mercurial-devel mailing list