protocol.py
221 lines
| 6.8 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. | ||||
import cStringIO, zlib, bz2, tempfile, errno, os, sys | ||||
from mercurial import util, streamclone | ||||
from mercurial.i18n import gettext as _ | ||||
from mercurial.node import * | ||||
|
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' | ||
|
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) | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
r5598 | req.write(resp) | ||
def heads(web, req): | ||||
resp = " ".join(map(hex, web.repo.heads())) + "\n" | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
r5598 | req.write(resp) | ||
def branches(web, req): | ||||
nodes = [] | ||||
|
r5915 | if 'nodes' in req.form: | ||
|
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() | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
r5598 | req.write(resp) | ||
def between(web, req): | ||||
|
r5915 | if 'pairs' in req.form: | ||
|
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() | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
r5598 | req.write(resp) | ||
def changegroup(web, req): | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
|
r5598 | nodes = [] | ||
if not web.allowpull: | ||||
return | ||||
|
r5915 | if 'roots' in req.form: | ||
|
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): | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
|
r5598 | bases = [] | ||
heads = [] | ||||
if not web.allowpull: | ||||
return | ||||
|
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() | ||||
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): | ||||
|
r6152 | resp = ' '.join(web.capabilities()) | ||
|
r5993 | req.respond(HTTP_OK, HGTYPE, length=len(resp)) | ||
|
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 | ||||
|
r5993 | req.header(headers.items()) | ||
req.respond(HTTP_OK, HGTYPE) | ||||
|
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 | ||||
|
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: | ||||
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) | ||||
|
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 | ||||
old_stdout = sys.stdout | ||||
sys.stdout = cStringIO.StringIO() | ||||
try: | ||||
url = 'remote:%s:%s' % (proto, | ||||
req.env.get('REMOTE_HOST', '')) | ||||
try: | ||||
|
r6154 | ret = web.repo.addchangegroup(gen, 'serve', url) | ||
|
r5598 | 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 | ||||
|
r6154 | except ValueError, inst: | ||
req.write('0\n') | ||||
req.write(str(inst) + '\n') | ||||
|
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: | ||||
|
r5993 | code = HTTP_NOT_FOUND | ||
|
r5598 | else: | ||
|
r5993 | code = HTTP_SERVER_ERROR | ||
req.respond(code) | ||||
req.write('%s: %s\n' % (error, filename)) | ||||
|
r5598 | finally: | ||
fp.close() | ||||
os.unlink(tempname) | ||||
def stream_out(web, req): | ||||
|
r5993 | req.respond(HTTP_OK, HGTYPE) | ||
|
r5598 | streamclone.stream_out(web.repo, req, untrusted=True) | ||