sshserver.py
177 lines
| 5.0 KiB
| text/x-python
|
PythonLexer
/ mercurial / sshserver.py
|
r2399 | # sshserver.py - ssh protocol server support for mercurial | ||
|
r2396 | # | ||
# Copyright 2005 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. | ||||
from demandload import demandload | ||||
from i18n import gettext as _ | ||||
from node import * | ||||
|
r2612 | demandload(globals(), "os streamclone sys tempfile util") | ||
|
r2396 | |||
class sshserver(object): | ||||
def __init__(self, ui, repo): | ||||
self.ui = ui | ||||
self.repo = repo | ||||
self.lock = None | ||||
self.fin = sys.stdin | ||||
self.fout = sys.stdout | ||||
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): | ||||
while self.serve_one(): pass | ||||
sys.exit(0) | ||||
def serve_one(self): | ||||
cmd = self.fin.readline()[:-1] | ||||
if cmd: | ||||
impl = getattr(self, 'do_' + cmd, None) | ||||
if impl: impl() | ||||
|
r2397 | else: self.respond("") | ||
|
r2396 | return cmd != '' | ||
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 | ||||
''' | ||||
|
r2621 | caps = ['unbundle'] | ||
|
r2622 | if self.ui.configbool('server', 'uncompressed'): | ||
|
r2621 | caps.append('stream=%d' % self.repo.revlogversion) | ||
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() | ||||
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 | ||||
|
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 | ||||
try: | ||||
fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-') | ||||
fp = os.fdopen(fd, 'wb+') | ||||
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: | ||||
fp.close() | ||||
os.unlink(tempname) | ||||
|
r2612 | def do_stream_out(self): | ||
streamclone.stream_out(self.repo, self.fout) | ||||