protocol.py
225 lines
| 7.3 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> | ||||
# | ||||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r5598 | |||
|
r9713 | import cStringIO, zlib, tempfile, errno, os, sys, urllib, copy | ||
|
r11370 | from mercurial import util, streamclone, pushkey | ||
|
r6211 | from mercurial.node import bin, hex | ||
|
r6154 | from mercurial import changegroup as changegroupmod | ||
|
r7281 | from common import ErrorResponse, 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', | ||||
|
r11370 | 'branchmap', 'pushkey', 'listkeys' | ||
|
r5963 | ] | ||
|
r5993 | HGTYPE = 'application/mercurial-0.1' | ||
|
r11370 | basecaps = 'lookup changegroupsubset branchmap pushkey'.split() | ||
|
r5993 | |||
|
r6781 | def lookup(repo, req): | ||
|
r5598 | try: | ||
|
r6781 | r = hex(repo.lookup(req.form['key'][0])) | ||
|
r5598 | success = 1 | ||
|
r9198 | except Exception, inst: | ||
|
r5598 | 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 | |||
|
r8562 | def branchmap(repo, req): | ||
branches = repo.branchmap() | ||||
heads = [] | ||||
for branch, nodes in branches.iteritems(): | ||||
branchname = urllib.quote(branch) | ||||
branchnodes = [hex(node) for node in nodes] | ||||
heads.append('%s %s' % (branchname, ' '.join(branchnodes))) | ||||
resp = '\n'.join(heads) | ||||
req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||||
yield resp | ||||
|
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): | ||
|
r10530 | pairs = [map(bin, p.split("-")) | ||
for p in req.form['pairs'][0].split(" ")] | ||||
|
r10531 | resp = ''.join(" ".join(map(hex, b)) + "\n" for b in repo.between(pairs)) | ||
|
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): | ||
|
r9713 | caps = copy.copy(basecaps) | ||
|
r10377 | if streamclone.allowed(repo.ui): | ||
|
r6781 | 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 | |||
|
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(): | ||||
|
r7180 | req.drain() | ||
|
r6926 | raise ErrorResponse(HTTP_OK, 'unsynced changes') | ||
|
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(): | ||||
|
r6926 | raise ErrorResponse(HTTP_OK, '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: | ||||
|
r8846 | url = 'remote:%s:%s:%s' % ( | ||
proto, | ||||
urllib.quote(req.env.get('REMOTE_HOST', '')), | ||||
urllib.quote(req.env.get('REMOTE_USER', ''))) | ||||
|
r5598 | 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 | ||
|
r6926 | req.respond(HTTP_OK, HGTYPE) | ||
|
r6788 | return '%d\n%s' % (ret, val), | ||
|
r5598 | finally: | ||
|
r8109 | lock.release() | ||
|
r6154 | except ValueError, inst: | ||
|
r6926 | raise ErrorResponse(HTTP_OK, inst) | ||
|
r5598 | except (OSError, IOError), inst: | ||
error = getattr(inst, 'strerror', 'Unknown error') | ||||
|
r10951 | if not isinstance(error, str): | ||
error = 'Error: %s' % str(error) | ||||
|
r5598 | if inst.errno == errno.ENOENT: | ||
|
r5993 | code = HTTP_NOT_FOUND | ||
|
r5598 | else: | ||
|
r5993 | code = HTTP_SERVER_ERROR | ||
|
r9694 | filename = getattr(inst, 'filename', '') | ||
# Don't send our filesystem layout to the client | ||||
if filename and filename.startswith(repo.root): | ||||
filename = filename[len(repo.root)+1:] | ||||
text = '%s: %s' % (error, filename) | ||||
else: | ||||
text = error.replace(repo.root + os.path.sep, '') | ||||
raise ErrorResponse(code, text) | ||||
|
r5598 | finally: | ||
fp.close() | ||||
os.unlink(tempname) | ||||
|
r6781 | def stream_out(repo, req): | ||
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
|
r6925 | try: | ||
|
r10377 | for chunk in streamclone.stream_out(repo): | ||
|
r6925 | yield chunk | ||
except streamclone.StreamException, inst: | ||||
yield str(inst) | ||||
|
r11370 | |||
def pushkey(repo, req): | ||||
namespace = req.form['namespace'][0] | ||||
key = req.form['key'][0] | ||||
old = req.form['old'][0] | ||||
new = req.form['new'][0] | ||||
r = repo.pushkey(namespace, key, old, new) | ||||
r = '%d\n' % int(r) | ||||
req.respond(HTTP_OK, HGTYPE, length=len(r)) | ||||
yield r | ||||
def listkeys(repo, req): | ||||
namespace = req.form['namespace'][0] | ||||
d = repo.listkeys(namespace).items() | ||||
t = '\n'.join(['%s\t%s' % (k.encode('string-escape'), | ||||
v.encode('string-escape')) for k, v in d]) | ||||
req.respond(HTTP_OK, HGTYPE, length=len(t)) | ||||
yield t | ||||