##// END OF EJS Templates
hgweb: separate protocol calls from interface calls (issue996)...
hgweb: separate protocol calls from interface calls (issue996) The protocol functions are already pretty careful about not raising exceptions to the caller, and have their own error handling. We can formalize this a little bit to make it clearer (before, the exception handlers for a limited number of exceptions coming from the interface bits would blow up because some variables aren't instantiated for the protocol calls).

File last commit:

r5993:948a41e7 default
r6149:b023915a default
Show More
protocol.py
250 lines | 8.0 KiB | text/x-python | PythonLexer
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 #
# 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 *
Dirkjan Ochtman
hgweb: explicit response status
r5993 from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598
Dirkjan Ochtman
hgweb: explicitly check if requested command exists
r5963 # __all__ is populated with the allowed commands. Be sure to add to it if
# you're adding a new command, or the new command won't work.
__all__ = [
'lookup', 'heads', 'branches', 'between', 'changegroup',
'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
]
Dirkjan Ochtman
hgweb: explicit response status
r5993 HGTYPE = 'application/mercurial-0.1'
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 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)
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE, length=len(resp))
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 req.write(resp)
def heads(web, req):
resp = " ".join(map(hex, web.repo.heads())) + "\n"
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE, length=len(resp))
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 req.write(resp)
def branches(web, req):
nodes = []
Christian Ebert
Prefer i in d over d.has_key(i)
r5915 if 'nodes' in req.form:
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 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()
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE, length=len(resp))
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 req.write(resp)
def between(web, req):
Christian Ebert
Prefer i in d over d.has_key(i)
r5915 if 'pairs' in req.form:
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 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()
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE, length=len(resp))
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 req.write(resp)
def changegroup(web, req):
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE)
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 nodes = []
if not web.allowpull:
return
Christian Ebert
Prefer i in d over d.has_key(i)
r5915 if 'roots' in req.form:
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 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):
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE)
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 bases = []
heads = []
if not web.allowpull:
return
Christian Ebert
Prefer i in d over d.has_key(i)
r5915 if 'bases' in req.form:
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
Christian Ebert
Prefer i in d over d.has_key(i)
r5915 if 'heads' in req.form:
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 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)
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE, length=len(resp))
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 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
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.header(headers.items())
req.respond(HTTP_OK, HGTYPE)
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 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
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE)
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598
# 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:
Dirkjan Ochtman
hgweb: explicit response status
r5993 code = HTTP_NOT_FOUND
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 else:
Dirkjan Ochtman
hgweb: explicit response status
r5993 code = HTTP_SERVER_ERROR
req.respond(code)
req.write('%s: %s\n' % (error, filename))
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 finally:
fp.close()
os.unlink(tempname)
def stream_out(web, req):
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, HGTYPE)
Dirkjan Ochtman
separate the wire protocol commands from the user interface commands
r5598 streamclone.stream_out(web.repo, req, untrusted=True)