[PATCH 1 of 5 RFC] serve: add --cmdserver option to communicate with hg over a pipe

Idan Kamara idankk86 at gmail.com
Fri Jun 3 15:04:40 CDT 2011


# HG changeset patch
# User Idan Kamara <idankk86 at gmail.com>
# Date 1307111261 -10800
# Node ID 5b9901ab0f4c60450ecd5c112fd937493a043667
# Parent  a67e866f46f9919a963afb4e4f1bb4a2c56a3c42
serve: add --cmdserver option to communicate with hg over a pipe

diff -r a67e866f46f9 -r 5b9901ab0f4c mercurial/commands.py
--- a/mercurial/commands.py	Thu Jun 02 00:33:33 2011 +0200
+++ b/mercurial/commands.py	Fri Jun 03 17:27:41 2011 +0300
@@ -11,7 +11,8 @@
 import os, re, sys, difflib, time, tempfile, errno
 import hg, scmutil, util, revlog, extensions, copies, error, bookmarks
 import patch, help, url, encoding, templatekw, discovery
-import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
+import archival, changegroup, cmdutil, hbisect
+import sshserver, hgweb, hgweb.server, commandserver
 import merge as mergemod
 import minirst, revset, fileset
 import dagparser, context, simplemerge
@@ -4375,6 +4376,7 @@
      _('FILE')),
     ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
     ('', 'stdio', None, _('for remote clients')),
+    ('', 'cmdserver', '', _('for remote clients'), _('MODE')),
     ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
     ('', 'style', '', _('template style to use'), _('STYLE')),
     ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
@@ -4405,13 +4407,24 @@
     Returns 0 on success.
     """
 
-    if opts["stdio"]:
+    if opts["stdio"] and opts["cmdserver"]:
+        raise util.Abort(_("cannot use --stdio with --cmdserver"))
+
+    def checkrepo():
         if repo is None:
             raise error.RepoError(_("There is no Mercurial repository here"
                               " (.hg not found)"))
+
+    if opts["stdio"]:
+        checkrepo()
         s = sshserver.sshserver(ui, repo)
         s.serve_forever()
 
+    if opts["cmdserver"]:
+        checkrepo()
+        s = commandserver.server(ui, repo, opts["cmdserver"])
+        return s.serve()
+
     # this way we can check if something was given in the command-line
     if opts.get('port'):
         opts['port'] = util.getport(opts.get('port'))
diff -r a67e866f46f9 -r 5b9901ab0f4c mercurial/commandserver.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/commandserver.py	Fri Jun 03 17:27:41 2011 +0300
@@ -0,0 +1,91 @@
+# commandserver.py - communicate with Mercurial's API over a pipe
+#
+#  Copyright Matt Mackall <mpm at selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from i18n import _
+import struct
+import sys
+import dispatch, util
+
+class channeledoutput(object):
+    """
+    Write data from in_ to out in the following format:
+
+    channel identifier - 'e' for stderr, 'o' for stdout (1 byte),
+    data length (int),
+    data
+    """
+    def __init__(self, in_, out, channel):
+        self.in_ = in_
+        self.out = out
+        self.channel = channel
+    def write(self, data):
+        if not data:
+            return
+        self.out.write(struct.pack('>cI', self.channel, len(data)))
+        self.out.write(data)
+    def __getattr__(self, attr):
+        return getattr(self.in_, attr)
+
+class server(object):
+    """
+    Listens for commands on stdin, runs them and writes the output to stdout
+    using 2 channels for stdout/err
+    """
+    def __init__(self, ui, repo, mode):
+        self.ui = ui
+        self.ui.setconfig('ui', 'interactive', 'off')
+        self.repo = repo
+
+        if mode == 'pipe':
+            # stream all output to stdout using 2 channels to distinguish them
+            sys.stderr = channeledoutput(sys.stderr, sys.stdout, 'e')
+            sys.stdout = channeledoutput(sys.stdout, sys.stdout, 'o')
+            self.fin = sys.stdin
+            self.fout = sys.stdout
+        else:
+            raise util.Abort(_('unknown mode %s') % mode)
+
+        # Prevent insertion/deletion of CRs
+        util.setbinary(self.fin)
+        util.setbinary(self.fout)
+
+    def readargs(self):
+        """ returns a list of arguments to execute, format:
+
+        length in bytes of args (int),
+        NUL terminated list of strings
+        """
+        length = self.fin.read(4)
+
+        # is the other end closed?
+        if not length:
+            return None
+
+        length = struct.unpack('>I', length)[0]
+        args = self.fin.read(length)
+        return args.split('\0')
+
+    def writereturn(self, ret):
+        """ write the return code - \0 followed by an integer """
+        # ret could be None
+        ret = ret or 0
+        self.fout.write('\0' + struct.pack('>i', int(ret)))
+        self.fout.flush()
+
+    def serve(self):
+        while True:
+            args = self.readargs()
+            if not args:
+                break
+
+            # copy the ui so changes to it don't persist between requests
+            req = dispatch.request(args, self.ui.copy(), self.repo)
+            ret = dispatch.dispatch(req)
+
+            self.writereturn(ret)
+
+        return None
diff -r a67e866f46f9 -r 5b9901ab0f4c tests/test-debugcomplete.t
--- a/tests/test-debugcomplete.t	Thu Jun 02 00:33:33 2011 +0200
+++ b/tests/test-debugcomplete.t	Fri Jun 03 17:27:41 2011 +0300
@@ -137,6 +137,7 @@
   --accesslog
   --address
   --certificate
+  --cmdserver
   --config
   --cwd
   --daemon
@@ -199,7 +200,7 @@
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
   push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
   remove: after, force, include, exclude
-  serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, templates, style, ipv6, certificate
+  serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
   summary: remote
   update: clean, check, date, rev
diff -r a67e866f46f9 -r 5b9901ab0f4c tests/test-http-branchmap.t
--- a/tests/test-http-branchmap.t	Thu Jun 02 00:33:33 2011 +0200
+++ b/tests/test-http-branchmap.t	Fri Jun 03 17:27:41 2011 +0300
@@ -79,7 +79,7 @@
   > 
   > myui = ui.ui()
   > repo = hg.repository(myui, 'a')
-  > commands.serve(myui, repo, stdio=True)
+  > commands.serve(myui, repo, stdio=True, cmdserver=False)
   > EOF
   $ echo baz >> b/foo
   $ hg -R b ci -m baz


More information about the Mercurial-devel mailing list