protocol.py
197 lines
| 6.2 KiB
| text/x-python
|
PythonLexer
|
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. | ||||
|
r6212 | import cStringIO, zlib, tempfile, errno, os, sys | ||
|
r5598 | from mercurial import util, streamclone | ||
|
r6211 | from mercurial.node import bin, hex | ||
|
r6154 | from mercurial import changegroup as changegroupmod | ||
|
r5993 | from common import HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR | ||
|
r5598 | |||
|
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', | ||||
] | ||||
|
r5993 | HGTYPE = 'application/mercurial-0.1' | ||
|
r6781 | def lookup(repo, req): | ||
|
r5598 | try: | ||
|
r6781 | r = hex(repo.lookup(req.form['key'][0])) | ||
|
r5598 | success = 1 | ||
except Exception,inst: | ||||
r = str(inst) | ||||
success = 0 | ||||
resp = "%s %s\n" % (success, r) | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
r6784 | yield resp | ||
|
r5598 | |||
|
r6781 | def heads(repo, req): | ||
resp = " ".join(map(hex, repo.heads())) + "\n" | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
r6784 | yield resp | ||
|
r5598 | |||
|
r6781 | def branches(repo, req): | ||
|
r5598 | nodes = [] | ||
|
r5915 | if 'nodes' in req.form: | ||
|
r5598 | nodes = map(bin, req.form['nodes'][0].split(" ")) | ||
resp = cStringIO.StringIO() | ||||
|
r6781 | for b in repo.branches(nodes): | ||
|
r5598 | resp.write(" ".join(map(hex, b)) + "\n") | ||
resp = resp.getvalue() | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
r6784 | yield resp | ||
|
r5598 | |||
|
r6781 | def between(repo, req): | ||
|
r5915 | if 'pairs' in req.form: | ||
|
r5598 | pairs = [map(bin, p.split("-")) | ||
for p in req.form['pairs'][0].split(" ")] | ||||
resp = cStringIO.StringIO() | ||||
|
r6781 | for b in repo.between(pairs): | ||
|
r5598 | resp.write(" ".join(map(hex, b)) + "\n") | ||
resp = resp.getvalue() | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
r6784 | yield resp | ||
|
r5598 | |||
|
r6781 | def changegroup(repo, req): | ||
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
|
r5598 | nodes = [] | ||
|
r5915 | if 'roots' in req.form: | ||
|
r5598 | nodes = map(bin, req.form['roots'][0].split(" ")) | ||
z = zlib.compressobj() | ||||
|
r6781 | f = repo.changegroup(nodes, 'serve') | ||
|
r5598 | while 1: | ||
chunk = f.read(4096) | ||||
if not chunk: | ||||
break | ||||
|
r6784 | yield z.compress(chunk) | ||
|
r5598 | |||
|
r6784 | yield z.flush() | ||
|
r5598 | |||
|
r6781 | def changegroupsubset(repo, req): | ||
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
|
r5598 | bases = [] | ||
heads = [] | ||||
|
r5915 | if 'bases' in req.form: | ||
|
r5598 | bases = [bin(x) for x in req.form['bases'][0].split(' ')] | ||
|
r5915 | if 'heads' in req.form: | ||
|
r5598 | heads = [bin(x) for x in req.form['heads'][0].split(' ')] | ||
z = zlib.compressobj() | ||||
|
r6781 | f = repo.changegroupsubset(bases, heads, 'serve') | ||
|
r5598 | while 1: | ||
chunk = f.read(4096) | ||||
if not chunk: | ||||
break | ||||
|
r6784 | yield z.compress(chunk) | ||
|
r5598 | |||
|
r6784 | yield z.flush() | ||
|
r5598 | |||
|
r6781 | def capabilities(repo, req): | ||
|
r6780 | caps = ['lookup', 'changegroupsubset'] | ||
|
r6781 | if repo.ui.configbool('server', 'uncompressed', untrusted=True): | ||
caps.append('stream=%d' % repo.changelog.version) | ||||
|
r6780 | if changegroupmod.bundlepriority: | ||
caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority)) | ||||
rsp = ' '.join(caps) | ||||
req.respond(HTTP_OK, HGTYPE, length=len(rsp)) | ||||
|
r6784 | yield rsp | ||
|
r5598 | |||
|
r6781 | def unbundle(repo, req): | ||
|
r6335 | |||
|
r6784 | errorfmt = '0\n%s\n' | ||
|
r6779 | proto = req.env.get('wsgi.url_scheme') or 'http' | ||
|
r5598 | their_heads = req.form['heads'][0].split(' ') | ||
def check_heads(): | ||||
|
r6781 | heads = map(hex, repo.heads()) | ||
|
r5598 | return their_heads == [hex('force')] or their_heads == heads | ||
# fail early if possible | ||||
if not check_heads(): | ||||
|
r6784 | length = int(req.env.get('CONTENT_LENGTH', 0)) | ||
for s in util.filechunkiter(req, limit=length): | ||||
# drain incoming bundle, else client will not see | ||||
# response when run outside cgi script | ||||
pass | ||||
req.respond(HTTP_OK, HGTYPE) | ||||
|
r6788 | return errorfmt % 'unsynced changes', | ||
|
r5598 | |||
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
|
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: | ||||
|
r6781 | lock = repo.lock() | ||
|
r5598 | try: | ||
if not check_heads(): | ||||
|
r6788 | return errorfmt % 'unsynced changes', | ||
|
r5598 | |||
fp.seek(0) | ||||
header = fp.read(6) | ||||
|
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) | ||||
|
r5598 | |||
# send addchangegroup output to client | ||||
|
r6265 | oldio = sys.stdout, sys.stderr | ||
sys.stderr = sys.stdout = cStringIO.StringIO() | ||||
|
r5598 | |||
try: | ||||
url = 'remote:%s:%s' % (proto, | ||||
req.env.get('REMOTE_HOST', '')) | ||||
try: | ||||
|
r6781 | ret = repo.addchangegroup(gen, 'serve', url) | ||
|
r5598 | except util.Abort, inst: | ||
sys.stdout.write("abort: %s\n" % inst) | ||||
ret = 0 | ||||
finally: | ||||
val = sys.stdout.getvalue() | ||||
|
r6265 | sys.stdout, sys.stderr = oldio | ||
|
r6788 | return '%d\n%s' % (ret, val), | ||
|
r5598 | finally: | ||
del lock | ||||
|
r6154 | except ValueError, inst: | ||
|
r6788 | return errorfmt % inst, | ||
|
r5598 | except (OSError, IOError), inst: | ||
filename = getattr(inst, 'filename', '') | ||||
# Don't send our filesystem layout to the client | ||||
|
r6781 | if filename.startswith(repo.root): | ||
filename = filename[len(repo.root)+1:] | ||||
|
r5598 | else: | ||
filename = '' | ||||
error = getattr(inst, 'strerror', 'Unknown error') | ||||
if inst.errno == errno.ENOENT: | ||||
|
r5993 | code = HTTP_NOT_FOUND | ||
|
r5598 | else: | ||
|
r5993 | code = HTTP_SERVER_ERROR | ||
req.respond(code) | ||||
|
r6788 | return '0\n%s: %s\n' % (error, filename), | ||
|
r5598 | finally: | ||
fp.close() | ||||
os.unlink(tempname) | ||||
|
r6781 | def stream_out(repo, req): | ||
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
|
r6794 | streamclone.stream_out(repo, req, untrusted=True) | ||
|
r6795 | return [] | ||