sshserver.py
225 lines
| 6.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / sshserver.py
|
r2399 | # sshserver.py - ssh protocol server support for mercurial | ||
|
r2396 | # | ||
|
r4635 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||
|
r2859 | # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> | ||
|
r2396 | # | ||
|
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. | ||
|
r2396 | |||
|
r3891 | from i18n import _ | ||
|
r6211 | from node import bin, hex | ||
|
r8312 | import streamclone, util, hook | ||
|
r9713 | import os, sys, tempfile, urllib, copy | ||
|
r2396 | |||
class sshserver(object): | ||||
|
r9713 | |||
caps = 'unbundle lookup changegroupsubset branchmap'.split() | ||||
|
r2396 | def __init__(self, ui, repo): | ||
self.ui = ui | ||||
self.repo = repo | ||||
self.lock = None | ||||
self.fin = sys.stdin | ||||
self.fout = sys.stdout | ||||
|
r5833 | hook.redirect(True) | ||
|
r2396 | sys.stdout = sys.stderr | ||
# Prevent insertion/deletion of CRs | ||||
util.set_binary(self.fin) | ||||
util.set_binary(self.fout) | ||||
def getarg(self): | ||||
argline = self.fin.readline()[:-1] | ||||
arg, l = argline.split() | ||||
val = self.fin.read(int(l)) | ||||
return arg, val | ||||
def respond(self, v): | ||||
self.fout.write("%d\n" % len(v)) | ||||
self.fout.write(v) | ||||
self.fout.flush() | ||||
def serve_forever(self): | ||||
|
r8109 | try: | ||
|
r10282 | while self.serve_one(): | ||
pass | ||||
|
r8109 | finally: | ||
if self.lock is not None: | ||||
self.lock.release() | ||||
|
r2396 | sys.exit(0) | ||
def serve_one(self): | ||||
cmd = self.fin.readline()[:-1] | ||||
if cmd: | ||||
impl = getattr(self, 'do_' + cmd, None) | ||||
|
r10282 | if impl: | ||
impl() | ||||
|
r2397 | else: self.respond("") | ||
|
r2396 | return cmd != '' | ||
|
r3446 | def do_lookup(self): | ||
arg, key = self.getarg() | ||||
assert arg == 'key' | ||||
|
r3447 | try: | ||
r = hex(self.repo.lookup(key)) | ||||
success = 1 | ||||
|
r9198 | except Exception, inst: | ||
|
r3447 | r = str(inst) | ||
success = 0 | ||||
self.respond("%s %s\n" % (success, r)) | ||||
|
r3446 | |||
|
r8562 | def do_branchmap(self): | ||
branchmap = self.repo.branchmap() | ||||
heads = [] | ||||
for branch, nodes in branchmap.iteritems(): | ||||
branchname = urllib.quote(branch) | ||||
branchnodes = [hex(node) for node in nodes] | ||||
heads.append('%s %s' % (branchname, ' '.join(branchnodes))) | ||||
self.respond('\n'.join(heads)) | ||||
|
r2396 | def do_heads(self): | ||
h = self.repo.heads() | ||||
self.respond(" ".join(map(hex, h)) + "\n") | ||||
|
r2419 | def do_hello(self): | ||
'''the hello command returns a set of lines describing various | ||||
interesting things about the server, in an RFC822-like format. | ||||
Currently the only one defined is "capabilities", which | ||||
consists of a line in the form: | ||||
capabilities: space separated list of tokens | ||||
''' | ||||
|
r9713 | caps = copy.copy(self.caps) | ||
|
r2622 | if self.ui.configbool('server', 'uncompressed'): | ||
|
r4258 | caps.append('stream=%d' % self.repo.changelog.version) | ||
|
r2621 | self.respond("capabilities: %s\n" % (' '.join(caps),)) | ||
|
r2419 | |||
|
r2396 | def do_lock(self): | ||
|
r2439 | '''DEPRECATED - allowing remote client to lock repo is not safe''' | ||
|
r2396 | self.lock = self.repo.lock() | ||
self.respond("") | ||||
def do_unlock(self): | ||||
|
r2439 | '''DEPRECATED''' | ||
|
r2396 | if self.lock: | ||
self.lock.release() | ||||
self.lock = None | ||||
self.respond("") | ||||
def do_branches(self): | ||||
arg, nodes = self.getarg() | ||||
nodes = map(bin, nodes.split(" ")) | ||||
r = [] | ||||
for b in self.repo.branches(nodes): | ||||
r.append(" ".join(map(hex, b)) + "\n") | ||||
self.respond("".join(r)) | ||||
def do_between(self): | ||||
arg, pairs = self.getarg() | ||||
pairs = [map(bin, p.split("-")) for p in pairs.split(" ")] | ||||
r = [] | ||||
for b in self.repo.between(pairs): | ||||
r.append(" ".join(map(hex, b)) + "\n") | ||||
self.respond("".join(r)) | ||||
def do_changegroup(self): | ||||
nodes = [] | ||||
arg, roots = self.getarg() | ||||
nodes = map(bin, roots.split(" ")) | ||||
cg = self.repo.changegroup(nodes, 'serve') | ||||
while True: | ||||
d = cg.read(4096) | ||||
if not d: | ||||
break | ||||
self.fout.write(d) | ||||
self.fout.flush() | ||||
|
r3446 | def do_changegroupsubset(self): | ||
argmap = dict([self.getarg(), self.getarg()]) | ||||
bases = [bin(n) for n in argmap['bases'].split(' ')] | ||||
heads = [bin(n) for n in argmap['heads'].split(' ')] | ||||
cg = self.repo.changegroupsubset(bases, heads, 'serve') | ||||
while True: | ||||
d = cg.read(4096) | ||||
if not d: | ||||
break | ||||
self.fout.write(d) | ||||
self.fout.flush() | ||||
|
r2396 | def do_addchangegroup(self): | ||
|
r2439 | '''DEPRECATED''' | ||
|
r2396 | if not self.lock: | ||
self.respond("not locked") | ||||
return | ||||
self.respond("") | ||||
|
r2673 | r = self.repo.addchangegroup(self.fin, 'serve', self.client_url()) | ||
|
r2396 | self.respond(str(r)) | ||
|
r2439 | |||
|
r2673 | def client_url(self): | ||
client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0] | ||||
return 'remote:ssh:' + client | ||||
|
r3223 | |||
|
r2439 | def do_unbundle(self): | ||
their_heads = self.getarg()[1].split() | ||||
def check_heads(): | ||||
heads = map(hex, self.repo.heads()) | ||||
return their_heads == [hex('force')] or their_heads == heads | ||||
# fail early if possible | ||||
if not check_heads(): | ||||
self.respond(_('unsynced changes')) | ||||
return | ||||
self.respond('') | ||||
# write bundle data to temporary file because it can be big | ||||
|
r9742 | fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') | ||
fp = os.fdopen(fd, 'wb+') | ||||
|
r2439 | try: | ||
count = int(self.fin.readline()) | ||||
while count: | ||||
fp.write(self.fin.read(count)) | ||||
count = int(self.fin.readline()) | ||||
was_locked = self.lock is not None | ||||
if not was_locked: | ||||
self.lock = self.repo.lock() | ||||
try: | ||||
if not check_heads(): | ||||
# someone else committed/pushed/unbundled while we | ||||
# were transferring data | ||||
self.respond(_('unsynced changes')) | ||||
return | ||||
self.respond('') | ||||
# push can proceed | ||||
fp.seek(0) | ||||
|
r2673 | r = self.repo.addchangegroup(fp, 'serve', self.client_url()) | ||
|
r2439 | self.respond(str(r)) | ||
finally: | ||||
if not was_locked: | ||||
self.lock.release() | ||||
self.lock = None | ||||
finally: | ||||
|
r9742 | fp.close() | ||
os.unlink(tempname) | ||||
|
r2439 | |||
|
r2612 | def do_stream_out(self): | ||
|
r6925 | try: | ||
for chunk in streamclone.stream_out(self.repo): | ||||
self.fout.write(chunk) | ||||
self.fout.flush() | ||||
except streamclone.StreamException, inst: | ||||
self.fout.write(str(inst)) | ||||
self.fout.flush() | ||||