protocol.py
230 lines
| 7.1 KiB
| text/x-python
|
PythonLexer
Dirkjan Ochtman
|
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. | ||||
Joel Rosdahl
|
r6212 | import cStringIO, zlib, tempfile, errno, os, sys | ||
Dirkjan Ochtman
|
r5598 | from mercurial import util, streamclone | ||
Joel Rosdahl
|
r6211 | from mercurial.node import bin, hex | ||
Dirkjan Ochtman
|
r6154 | from mercurial import changegroup as changegroupmod | ||
Dirkjan Ochtman
|
r5993 | from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR | ||
Dirkjan Ochtman
|
r5598 | |||
Dirkjan Ochtman
|
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
|
r5993 | HGTYPE = 'application/mercurial-0.1' | ||
Dirkjan Ochtman
|
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
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
Dirkjan Ochtman
|
r5598 | req.write(resp) | ||
def heads(web, req): | ||||
resp = " ".join(map(hex, web.repo.heads())) + "\n" | ||||
Dirkjan Ochtman
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
Dirkjan Ochtman
|
r5598 | req.write(resp) | ||
def branches(web, req): | ||||
nodes = [] | ||||
Christian Ebert
|
r5915 | if 'nodes' in req.form: | ||
Dirkjan Ochtman
|
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
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
Dirkjan Ochtman
|
r5598 | req.write(resp) | ||
def between(web, req): | ||||
Christian Ebert
|
r5915 | if 'pairs' in req.form: | ||
Dirkjan Ochtman
|
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
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
Dirkjan Ochtman
|
r5598 | req.write(resp) | ||
def changegroup(web, req): | ||||
Dirkjan Ochtman
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
Dirkjan Ochtman
|
r5598 | nodes = [] | ||
if not web.allowpull: | ||||
return | ||||
Christian Ebert
|
r5915 | if 'roots' in req.form: | ||
Dirkjan Ochtman
|
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
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
Dirkjan Ochtman
|
r5598 | bases = [] | ||
heads = [] | ||||
if not web.allowpull: | ||||
return | ||||
Christian Ebert
|
r5915 | if 'bases' in req.form: | ||
Dirkjan Ochtman
|
r5598 | bases = [bin(x) for x in req.form['bases'][0].split(' ')] | ||
Christian Ebert
|
r5915 | if 'heads' in req.form: | ||
Dirkjan Ochtman
|
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): | ||||
Dirkjan Ochtman
|
r6152 | resp = ' '.join(web.capabilities()) | ||
Dirkjan Ochtman
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
Dirkjan Ochtman
|
r5598 | req.write(resp) | ||
def unbundle(web, req): | ||||
Dirkjan Ochtman
|
r6335 | |||
Dirkjan Ochtman
|
r5598 | def bail(response, headers={}): | ||
Dirkjan Ochtman
|
r6335 | length = int(req.env.get('CONTENT_LENGTH', 0)) | ||
Dirkjan Ochtman
|
r5598 | 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
|
r6335 | |||
status = headers.pop('status', HTTP_OK) | ||||
Dirkjan Ochtman
|
r5993 | req.header(headers.items()) | ||
Dirkjan Ochtman
|
r6335 | req.respond(status, HGTYPE) | ||
Dirkjan Ochtman
|
r5598 | req.write('0\n') | ||
req.write(response) | ||||
Dirkjan Ochtman
|
r6335 | # enforce that you can only unbundle with POST requests | ||
if req.env['REQUEST_METHOD'] != 'POST': | ||||
headers = {'status': '405 Method Not Allowed'} | ||||
bail('unbundle requires POST request\n', headers) | ||||
return | ||||
Dirkjan Ochtman
|
r5598 | # 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': | ||||
Dirkjan Ochtman
|
r6155 | bail('ssl required\n') | ||
Dirkjan Ochtman
|
r5598 | return | ||
proto = 'https' | ||||
else: | ||||
proto = 'http' | ||||
# do not allow push unless explicitly allowed | ||||
if not web.check_perm(req, 'push', False): | ||||
Dirkjan Ochtman
|
r6335 | bail('push not authorized\n', headers={'status': '401 Unauthorized'}) | ||
Dirkjan Ochtman
|
r5598 | 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(): | ||||
Dirkjan Ochtman
|
r6155 | bail('unsynced changes\n') | ||
Dirkjan Ochtman
|
r5598 | return | ||
Dirkjan Ochtman
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
Dirkjan Ochtman
|
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') | ||||
Dirkjan Ochtman
|
r6155 | req.write('unsynced changes\n') | ||
Dirkjan Ochtman
|
r5598 | return | ||
fp.seek(0) | ||||
header = fp.read(6) | ||||
Dirkjan Ochtman
|
r6154 | if header.startswith('HG') and not header.startswith('HG10'): | ||
raise ValueError('unknown bundle version') | ||||
elif header not in changegroupmod.bundletypes: | ||||
raise ValueError('unknown bundle compression type') | ||||
gen = changegroupmod.unbundle(header, fp) | ||||
Dirkjan Ochtman
|
r5598 | |||
# send addchangegroup output to client | ||||
Jesse Glick
|
r6265 | oldio = sys.stdout, sys.stderr | ||
sys.stderr = sys.stdout = cStringIO.StringIO() | ||||
Dirkjan Ochtman
|
r5598 | |||
try: | ||||
url = 'remote:%s:%s' % (proto, | ||||
req.env.get('REMOTE_HOST', '')) | ||||
try: | ||||
Dirkjan Ochtman
|
r6154 | ret = web.repo.addchangegroup(gen, 'serve', url) | ||
Dirkjan Ochtman
|
r5598 | except util.Abort, inst: | ||
sys.stdout.write("abort: %s\n" % inst) | ||||
ret = 0 | ||||
finally: | ||||
val = sys.stdout.getvalue() | ||||
Jesse Glick
|
r6265 | sys.stdout, sys.stderr = oldio | ||
Dirkjan Ochtman
|
r5598 | req.write('%d\n' % ret) | ||
req.write(val) | ||||
finally: | ||||
del lock | ||||
Dirkjan Ochtman
|
r6154 | except ValueError, inst: | ||
req.write('0\n') | ||||
req.write(str(inst) + '\n') | ||||
Dirkjan Ochtman
|
r5598 | 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
|
r5993 | code = HTTP_NOT_FOUND | ||
Dirkjan Ochtman
|
r5598 | else: | ||
Dirkjan Ochtman
|
r5993 | code = HTTP_SERVER_ERROR | ||
req.respond(code) | ||||
req.write('%s: %s\n' % (error, filename)) | ||||
Dirkjan Ochtman
|
r5598 | finally: | ||
fp.close() | ||||
os.unlink(tempname) | ||||
def stream_out(web, req): | ||||
Benoit Boissinot
|
r6630 | if not web.allowpull: | ||
return | ||||
Dirkjan Ochtman
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
Dirkjan Ochtman
|
r5598 | streamclone.stream_out(web.repo, req, untrusted=True) | ||