separate the wire protocol commands from the user interface commands
authorDirkjan Ochtman <dirkjan@ochtman.nl>
Mon, 03 Dec 2007 12:27:11 +0100
changeset 5598 d534ba1c4eb4
parent 5597 e7f99a3ed008
child 5599 3de66c2a9734
separate the wire protocol commands from the user interface commands
mercurial/hgweb/hgweb_mod.py
mercurial/hgweb/protocol.py
mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/hgweb_mod.py	Mon Dec 03 12:06:21 2007 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Mon Dec 03 12:27:11 2007 +0100
@@ -12,7 +12,7 @@
 from mercurial import revlog, templater
 from common import ErrorResponse, get_mtime, style_map, paritygen
 from request import wsgirequest
-import webcommands
+import webcommands, protocol
 
 shortcuts = {
     'cl': [('cmd', ['changelog']), ('rev', None)],
@@ -177,7 +177,7 @@
                 cmd = cmd[style+1:]
 
             # avoid accepting e.g. style parameter as command
-            if hasattr(webcommands, cmd):
+            if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
                 req.form['cmd'] = [cmd]
 
             if args and args[0]:
@@ -276,7 +276,10 @@
             cmd = req.form['cmd'][0]
 
             try:
-                method = getattr(webcommands, cmd)
+                if hasattr(protocol, cmd):
+                    method = getattr(protocol, cmd)
+                else:
+                    method = getattr(webcommands, cmd)
                 method(self, req)
             except revlog.LookupError, err:
                 req.respond(404, self.t(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/protocol.py	Mon Dec 03 12:27:11 2007 +0100
@@ -0,0 +1,237 @@
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import cStringIO, zlib, bz2, tempfile, errno, os, sys
+from mercurial import util, streamclone
+from mercurial.i18n import gettext as _
+from mercurial.node import *
+
+def lookup(web, req):
+    try:
+        r = hex(web.repo.lookup(req.form['key'][0]))
+        success = 1
+    except Exception,inst:
+        r = str(inst)
+        success = 0
+    resp = "%s %s\n" % (success, r)
+    req.httphdr("application/mercurial-0.1", length=len(resp))
+    req.write(resp)
+
+def heads(web, req):
+    resp = " ".join(map(hex, web.repo.heads())) + "\n"
+    req.httphdr("application/mercurial-0.1", length=len(resp))
+    req.write(resp)
+
+def branches(web, req):
+    nodes = []
+    if req.form.has_key('nodes'):
+        nodes = map(bin, req.form['nodes'][0].split(" "))
+    resp = cStringIO.StringIO()
+    for b in web.repo.branches(nodes):
+        resp.write(" ".join(map(hex, b)) + "\n")
+    resp = resp.getvalue()
+    req.httphdr("application/mercurial-0.1", length=len(resp))
+    req.write(resp)
+
+def between(web, req):
+    if req.form.has_key('pairs'):
+        pairs = [map(bin, p.split("-"))
+                 for p in req.form['pairs'][0].split(" ")]
+    resp = cStringIO.StringIO()
+    for b in web.repo.between(pairs):
+        resp.write(" ".join(map(hex, b)) + "\n")
+    resp = resp.getvalue()
+    req.httphdr("application/mercurial-0.1", length=len(resp))
+    req.write(resp)
+
+def changegroup(web, req):
+    req.httphdr("application/mercurial-0.1")
+    nodes = []
+    if not web.allowpull:
+        return
+
+    if req.form.has_key('roots'):
+        nodes = map(bin, req.form['roots'][0].split(" "))
+
+    z = zlib.compressobj()
+    f = web.repo.changegroup(nodes, 'serve')
+    while 1:
+        chunk = f.read(4096)
+        if not chunk:
+            break
+        req.write(z.compress(chunk))
+
+    req.write(z.flush())
+
+def changegroupsubset(web, req):
+    req.httphdr("application/mercurial-0.1")
+    bases = []
+    heads = []
+    if not web.allowpull:
+        return
+
+    if req.form.has_key('bases'):
+        bases = [bin(x) for x in req.form['bases'][0].split(' ')]
+    if req.form.has_key('heads'):
+        heads = [bin(x) for x in req.form['heads'][0].split(' ')]
+
+    z = zlib.compressobj()
+    f = web.repo.changegroupsubset(bases, heads, 'serve')
+    while 1:
+        chunk = f.read(4096)
+        if not chunk:
+            break
+        req.write(z.compress(chunk))
+
+    req.write(z.flush())
+
+def capabilities(web, req):
+    caps = ['lookup', 'changegroupsubset']
+    if web.configbool('server', 'uncompressed'):
+        caps.append('stream=%d' % web.repo.changelog.version)
+    # XXX: make configurable and/or share code with do_unbundle:
+    unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
+    if unbundleversions:
+        caps.append('unbundle=%s' % ','.join(unbundleversions))
+    resp = ' '.join(caps)
+    req.httphdr("application/mercurial-0.1", length=len(resp))
+    req.write(resp)
+
+def unbundle(web, req):
+    def bail(response, headers={}):
+        length = int(req.env['CONTENT_LENGTH'])
+        for s in util.filechunkiter(req, limit=length):
+            # drain incoming bundle, else client will not see
+            # response when run outside cgi script
+            pass
+        req.httphdr("application/mercurial-0.1", headers=headers)
+        req.write('0\n')
+        req.write(response)
+
+    # require ssl by default, auth info cannot be sniffed and
+    # replayed
+    ssl_req = web.configbool('web', 'push_ssl', True)
+    if ssl_req:
+        if req.env.get('wsgi.url_scheme') != 'https':
+            bail(_('ssl required\n'))
+            return
+        proto = 'https'
+    else:
+        proto = 'http'
+
+    # do not allow push unless explicitly allowed
+    if not web.check_perm(req, 'push', False):
+        bail(_('push not authorized\n'),
+             headers={'status': '401 Unauthorized'})
+        return
+
+    their_heads = req.form['heads'][0].split(' ')
+
+    def check_heads():
+        heads = map(hex, web.repo.heads())
+        return their_heads == [hex('force')] or their_heads == heads
+
+    # fail early if possible
+    if not check_heads():
+        bail(_('unsynced changes\n'))
+        return
+
+    req.httphdr("application/mercurial-0.1")
+
+    # do not lock repo until all changegroup data is
+    # streamed. save to temporary file.
+
+    fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
+    fp = os.fdopen(fd, 'wb+')
+    try:
+        length = int(req.env['CONTENT_LENGTH'])
+        for s in util.filechunkiter(req, limit=length):
+            fp.write(s)
+
+        try:
+            lock = web.repo.lock()
+            try:
+                if not check_heads():
+                    req.write('0\n')
+                    req.write(_('unsynced changes\n'))
+                    return
+
+                fp.seek(0)
+                header = fp.read(6)
+                if not header.startswith("HG"):
+                    # old client with uncompressed bundle
+                    def generator(f):
+                        yield header
+                        for chunk in f:
+                            yield chunk
+                elif not header.startswith("HG10"):
+                    req.write("0\n")
+                    req.write(_("unknown bundle version\n"))
+                    return
+                elif header == "HG10GZ":
+                    def generator(f):
+                        zd = zlib.decompressobj()
+                        for chunk in f:
+                            yield zd.decompress(chunk)
+                elif header == "HG10BZ":
+                    def generator(f):
+                        zd = bz2.BZ2Decompressor()
+                        zd.decompress("BZ")
+                        for chunk in f:
+                            yield zd.decompress(chunk)
+                elif header == "HG10UN":
+                    def generator(f):
+                        for chunk in f:
+                            yield chunk
+                else:
+                    req.write("0\n")
+                    req.write(_("unknown bundle compression type\n"))
+                    return
+                gen = generator(util.filechunkiter(fp, 4096))
+
+                # send addchangegroup output to client
+
+                old_stdout = sys.stdout
+                sys.stdout = cStringIO.StringIO()
+
+                try:
+                    url = 'remote:%s:%s' % (proto,
+                                            req.env.get('REMOTE_HOST', ''))
+                    try:
+                        ret = web.repo.addchangegroup(
+                                    util.chunkbuffer(gen), 'serve', url)
+                    except util.Abort, inst:
+                        sys.stdout.write("abort: %s\n" % inst)
+                        ret = 0
+                finally:
+                    val = sys.stdout.getvalue()
+                    sys.stdout = old_stdout
+                req.write('%d\n' % ret)
+                req.write(val)
+            finally:
+                del lock
+        except (OSError, IOError), inst:
+            req.write('0\n')
+            filename = getattr(inst, 'filename', '')
+            # Don't send our filesystem layout to the client
+            if filename.startswith(web.repo.root):
+                filename = filename[len(web.repo.root)+1:]
+            else:
+                filename = ''
+            error = getattr(inst, 'strerror', 'Unknown error')
+            if inst.errno == errno.ENOENT:
+                code = 404
+            else:
+                code = 500
+            req.respond(code, '%s: %s\n' % (error, filename))
+    finally:
+        fp.close()
+        os.unlink(tempname)
+
+def stream_out(web, req):
+    req.httphdr("application/mercurial-0.1")
+    streamclone.stream_out(web.repo, req, untrusted=True)
--- a/mercurial/hgweb/webcommands.py	Mon Dec 03 12:06:21 2007 +0100
+++ b/mercurial/hgweb/webcommands.py	Mon Dec 03 12:27:11 2007 +0100
@@ -5,10 +5,8 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import cStringIO, zlib, bz2, tempfile, errno, os, sys
-from mercurial import revlog, util, streamclone
-from mercurial.i18n import gettext as _
-from mercurial.node import *
+import os
+from mercurial import revlog
 from common import staticfile
 
 def log(web, req):
@@ -73,85 +71,6 @@
 def filelog(web, req):
     req.write(web.filelog(web.filectx(req)))
 
-def lookup(web, req):
-    try:
-        r = hex(web.repo.lookup(req.form['key'][0]))
-        success = 1
-    except Exception,inst:
-        r = str(inst)
-        success = 0
-    resp = "%s %s\n" % (success, r)
-    req.httphdr("application/mercurial-0.1", length=len(resp))
-    req.write(resp)
-
-def heads(web, req):
-    resp = " ".join(map(hex, web.repo.heads())) + "\n"
-    req.httphdr("application/mercurial-0.1", length=len(resp))
-    req.write(resp)
-
-def branches(web, req):
-    nodes = []
-    if req.form.has_key('nodes'):
-        nodes = map(bin, req.form['nodes'][0].split(" "))
-    resp = cStringIO.StringIO()
-    for b in web.repo.branches(nodes):
-        resp.write(" ".join(map(hex, b)) + "\n")
-    resp = resp.getvalue()
-    req.httphdr("application/mercurial-0.1", length=len(resp))
-    req.write(resp)
-
-def between(web, req):
-    if req.form.has_key('pairs'):
-        pairs = [map(bin, p.split("-"))
-                 for p in req.form['pairs'][0].split(" ")]
-    resp = cStringIO.StringIO()
-    for b in web.repo.between(pairs):
-        resp.write(" ".join(map(hex, b)) + "\n")
-    resp = resp.getvalue()
-    req.httphdr("application/mercurial-0.1", length=len(resp))
-    req.write(resp)
-
-def changegroup(web, req):
-    req.httphdr("application/mercurial-0.1")
-    nodes = []
-    if not web.allowpull:
-        return
-
-    if req.form.has_key('roots'):
-        nodes = map(bin, req.form['roots'][0].split(" "))
-
-    z = zlib.compressobj()
-    f = web.repo.changegroup(nodes, 'serve')
-    while 1:
-        chunk = f.read(4096)
-        if not chunk:
-            break
-        req.write(z.compress(chunk))
-
-    req.write(z.flush())
-
-def changegroupsubset(web, req):
-    req.httphdr("application/mercurial-0.1")
-    bases = []
-    heads = []
-    if not web.allowpull:
-        return
-
-    if req.form.has_key('bases'):
-        bases = [bin(x) for x in req.form['bases'][0].split(' ')]
-    if req.form.has_key('heads'):
-        heads = [bin(x) for x in req.form['heads'][0].split(' ')]
-
-    z = zlib.compressobj()
-    f = web.repo.changegroupsubset(bases, heads, 'serve')
-    while 1:
-        chunk = f.read(4096)
-        if not chunk:
-            break
-        req.write(z.compress(chunk))
-
-    req.write(z.flush())
-
 def archive(web, req):
     type_ = req.form['type'][0]
     allowed = web.configlist("web", "allow_archive")
@@ -171,150 +90,3 @@
                         os.path.join(web.templatepath, "static"),
                         untrusted=False)
     req.write(staticfile(static, fname, req))
-
-def capabilities(web, req):
-    caps = ['lookup', 'changegroupsubset']
-    if web.configbool('server', 'uncompressed'):
-        caps.append('stream=%d' % web.repo.changelog.version)
-    # XXX: make configurable and/or share code with do_unbundle:
-    unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
-    if unbundleversions:
-        caps.append('unbundle=%s' % ','.join(unbundleversions))
-    resp = ' '.join(caps)
-    req.httphdr("application/mercurial-0.1", length=len(resp))
-    req.write(resp)
-
-def unbundle(web, req):
-    def bail(response, headers={}):
-        length = int(req.env['CONTENT_LENGTH'])
-        for s in util.filechunkiter(req, limit=length):
-            # drain incoming bundle, else client will not see
-            # response when run outside cgi script
-            pass
-        req.httphdr("application/mercurial-0.1", headers=headers)
-        req.write('0\n')
-        req.write(response)
-
-    # require ssl by default, auth info cannot be sniffed and
-    # replayed
-    ssl_req = web.configbool('web', 'push_ssl', True)
-    if ssl_req:
-        if req.env.get('wsgi.url_scheme') != 'https':
-            bail(_('ssl required\n'))
-            return
-        proto = 'https'
-    else:
-        proto = 'http'
-
-    # do not allow push unless explicitly allowed
-    if not web.check_perm(req, 'push', False):
-        bail(_('push not authorized\n'),
-             headers={'status': '401 Unauthorized'})
-        return
-
-    their_heads = req.form['heads'][0].split(' ')
-
-    def check_heads():
-        heads = map(hex, web.repo.heads())
-        return their_heads == [hex('force')] or their_heads == heads
-
-    # fail early if possible
-    if not check_heads():
-        bail(_('unsynced changes\n'))
-        return
-
-    req.httphdr("application/mercurial-0.1")
-
-    # do not lock repo until all changegroup data is
-    # streamed. save to temporary file.
-
-    fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
-    fp = os.fdopen(fd, 'wb+')
-    try:
-        length = int(req.env['CONTENT_LENGTH'])
-        for s in util.filechunkiter(req, limit=length):
-            fp.write(s)
-
-        try:
-            lock = web.repo.lock()
-            try:
-                if not check_heads():
-                    req.write('0\n')
-                    req.write(_('unsynced changes\n'))
-                    return
-
-                fp.seek(0)
-                header = fp.read(6)
-                if not header.startswith("HG"):
-                    # old client with uncompressed bundle
-                    def generator(f):
-                        yield header
-                        for chunk in f:
-                            yield chunk
-                elif not header.startswith("HG10"):
-                    req.write("0\n")
-                    req.write(_("unknown bundle version\n"))
-                    return
-                elif header == "HG10GZ":
-                    def generator(f):
-                        zd = zlib.decompressobj()
-                        for chunk in f:
-                            yield zd.decompress(chunk)
-                elif header == "HG10BZ":
-                    def generator(f):
-                        zd = bz2.BZ2Decompressor()
-                        zd.decompress("BZ")
-                        for chunk in f:
-                            yield zd.decompress(chunk)
-                elif header == "HG10UN":
-                    def generator(f):
-                        for chunk in f:
-                            yield chunk
-                else:
-                    req.write("0\n")
-                    req.write(_("unknown bundle compression type\n"))
-                    return
-                gen = generator(util.filechunkiter(fp, 4096))
-
-                # send addchangegroup output to client
-
-                old_stdout = sys.stdout
-                sys.stdout = cStringIO.StringIO()
-
-                try:
-                    url = 'remote:%s:%s' % (proto,
-                                            req.env.get('REMOTE_HOST', ''))
-                    try:
-                        ret = web.repo.addchangegroup(
-                                    util.chunkbuffer(gen), 'serve', url)
-                    except util.Abort, inst:
-                        sys.stdout.write("abort: %s\n" % inst)
-                        ret = 0
-                finally:
-                    val = sys.stdout.getvalue()
-                    sys.stdout = old_stdout
-                req.write('%d\n' % ret)
-                req.write(val)
-            finally:
-                del lock
-        except (OSError, IOError), inst:
-            req.write('0\n')
-            filename = getattr(inst, 'filename', '')
-            # Don't send our filesystem layout to the client
-            if filename.startswith(web.repo.root):
-                filename = filename[len(web.repo.root)+1:]
-            else:
-                filename = ''
-            error = getattr(inst, 'strerror', 'Unknown error')
-            if inst.errno == errno.ENOENT:
-                code = 404
-            else:
-                code = 500
-            req.respond(code, '%s: %s\n' % (error, filename))
-    finally:
-        fp.close()
-        os.unlink(tempname)
-
-def stream_out(web, req):
-    req.httphdr("application/mercurial-0.1")
-    streamclone.stream_out(web.repo, req, untrusted=True)