##// END OF EJS Templates
wireproto: introduce pusherr() to deal with "unsynced changes" error...
Benoit Boissinot -
r12703:40bb5853 default
parent child Browse files
Show More
@@ -1,68 +1,73
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import cStringIO, zlib, sys, urllib
8 import cStringIO, zlib, sys, urllib
9 from mercurial import util, wireproto
9 from mercurial import util, wireproto
10 from common import HTTP_OK
10 from common import HTTP_OK
11
11
12 HGTYPE = 'application/mercurial-0.1'
12 HGTYPE = 'application/mercurial-0.1'
13
13
14 class webproto(object):
14 class webproto(object):
15 def __init__(self, req):
15 def __init__(self, req):
16 self.req = req
16 self.req = req
17 self.response = ''
17 self.response = ''
18 def getargs(self, args):
18 def getargs(self, args):
19 data = {}
19 data = {}
20 keys = args.split()
20 keys = args.split()
21 for k in keys:
21 for k in keys:
22 if k == '*':
22 if k == '*':
23 star = {}
23 star = {}
24 for key in self.req.form.keys():
24 for key in self.req.form.keys():
25 if key not in keys:
25 if key not in keys:
26 star[key] = self.req.form[key][0]
26 star[key] = self.req.form[key][0]
27 data['*'] = star
27 data['*'] = star
28 else:
28 else:
29 data[k] = self.req.form[k][0]
29 data[k] = self.req.form[k][0]
30 return [data[k] for k in keys]
30 return [data[k] for k in keys]
31 def getfile(self, fp):
31 def getfile(self, fp):
32 length = int(self.req.env['CONTENT_LENGTH'])
32 length = int(self.req.env['CONTENT_LENGTH'])
33 for s in util.filechunkiter(self.req, limit=length):
33 for s in util.filechunkiter(self.req, limit=length):
34 fp.write(s)
34 fp.write(s)
35 def redirect(self):
35 def redirect(self):
36 self.oldio = sys.stdout, sys.stderr
36 self.oldio = sys.stdout, sys.stderr
37 sys.stderr = sys.stdout = cStringIO.StringIO()
37 sys.stderr = sys.stdout = cStringIO.StringIO()
38 def groupchunks(self, cg):
38 def groupchunks(self, cg):
39 z = zlib.compressobj()
39 z = zlib.compressobj()
40 while 1:
40 while 1:
41 chunk = cg.read(4096)
41 chunk = cg.read(4096)
42 if not chunk:
42 if not chunk:
43 break
43 break
44 yield z.compress(chunk)
44 yield z.compress(chunk)
45 yield z.flush()
45 yield z.flush()
46 def _client(self):
46 def _client(self):
47 return 'remote:%s:%s:%s' % (
47 return 'remote:%s:%s:%s' % (
48 self.req.env.get('wsgi.url_scheme') or 'http',
48 self.req.env.get('wsgi.url_scheme') or 'http',
49 urllib.quote(self.req.env.get('REMOTE_HOST', '')),
49 urllib.quote(self.req.env.get('REMOTE_HOST', '')),
50 urllib.quote(self.req.env.get('REMOTE_USER', '')))
50 urllib.quote(self.req.env.get('REMOTE_USER', '')))
51
51
52 def iscmd(cmd):
52 def iscmd(cmd):
53 return cmd in wireproto.commands
53 return cmd in wireproto.commands
54
54
55 def call(repo, req, cmd):
55 def call(repo, req, cmd):
56 p = webproto(req)
56 p = webproto(req)
57 rsp = wireproto.dispatch(repo, p, cmd)
57 rsp = wireproto.dispatch(repo, p, cmd)
58 if isinstance(rsp, str):
58 if isinstance(rsp, str):
59 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
59 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
60 return [rsp]
60 return [rsp]
61 elif isinstance(rsp, wireproto.streamres):
61 elif isinstance(rsp, wireproto.streamres):
62 req.respond(HTTP_OK, HGTYPE)
62 req.respond(HTTP_OK, HGTYPE)
63 return rsp.gen
63 return rsp.gen
64 elif isinstance(rsp, wireproto.pushres):
64 elif isinstance(rsp, wireproto.pushres):
65 val = sys.stdout.getvalue()
65 val = sys.stdout.getvalue()
66 sys.stdout, sys.stderr = p.oldio
66 sys.stdout, sys.stderr = p.oldio
67 req.respond(HTTP_OK, HGTYPE)
67 req.respond(HTTP_OK, HGTYPE)
68 return ['%d\n%s' % (rsp.res, val)]
68 return ['%d\n%s' % (rsp.res, val)]
69 elif isinstance(rsp, wireproto.pusherr):
70 sys.stdout, sys.stderr = p.oldio
71 rsp = '0\n%s\n' % rsp.res
72 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
73 return [rsp]
@@ -1,140 +1,144
1 # sshserver.py - ssh protocol server support for mercurial
1 # sshserver.py - ssh protocol server support for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import util, hook, wireproto, changegroup
9 import util, hook, wireproto, changegroup
10 import os, sys
10 import os, sys
11
11
12 class sshserver(object):
12 class sshserver(object):
13 def __init__(self, ui, repo):
13 def __init__(self, ui, repo):
14 self.ui = ui
14 self.ui = ui
15 self.repo = repo
15 self.repo = repo
16 self.lock = None
16 self.lock = None
17 self.fin = sys.stdin
17 self.fin = sys.stdin
18 self.fout = sys.stdout
18 self.fout = sys.stdout
19
19
20 hook.redirect(True)
20 hook.redirect(True)
21 sys.stdout = sys.stderr
21 sys.stdout = sys.stderr
22
22
23 # Prevent insertion/deletion of CRs
23 # Prevent insertion/deletion of CRs
24 util.set_binary(self.fin)
24 util.set_binary(self.fin)
25 util.set_binary(self.fout)
25 util.set_binary(self.fout)
26
26
27 def getargs(self, args):
27 def getargs(self, args):
28 data = {}
28 data = {}
29 keys = args.split()
29 keys = args.split()
30 count = len(keys)
30 count = len(keys)
31 for n in xrange(len(keys)):
31 for n in xrange(len(keys)):
32 argline = self.fin.readline()[:-1]
32 argline = self.fin.readline()[:-1]
33 arg, l = argline.split()
33 arg, l = argline.split()
34 val = self.fin.read(int(l))
34 val = self.fin.read(int(l))
35 if arg not in keys:
35 if arg not in keys:
36 raise util.Abort("unexpected parameter %r" % arg)
36 raise util.Abort("unexpected parameter %r" % arg)
37 if arg == '*':
37 if arg == '*':
38 star = {}
38 star = {}
39 for n in xrange(int(l)):
39 for n in xrange(int(l)):
40 arg, l = argline.split()
40 arg, l = argline.split()
41 val = self.fin.read(int(l))
41 val = self.fin.read(int(l))
42 star[arg] = val
42 star[arg] = val
43 data['*'] = star
43 data['*'] = star
44 else:
44 else:
45 data[arg] = val
45 data[arg] = val
46 return [data[k] for k in keys]
46 return [data[k] for k in keys]
47
47
48 def getarg(self, name):
48 def getarg(self, name):
49 return self.getargs(name)[0]
49 return self.getargs(name)[0]
50
50
51 def getfile(self, fpout):
51 def getfile(self, fpout):
52 self.sendresponse('')
52 self.sendresponse('')
53 count = int(self.fin.readline())
53 count = int(self.fin.readline())
54 while count:
54 while count:
55 fpout.write(self.fin.read(count))
55 fpout.write(self.fin.read(count))
56 count = int(self.fin.readline())
56 count = int(self.fin.readline())
57
57
58 def redirect(self):
58 def redirect(self):
59 pass
59 pass
60
60
61 def groupchunks(self, changegroup):
61 def groupchunks(self, changegroup):
62 while True:
62 while True:
63 d = changegroup.read(4096)
63 d = changegroup.read(4096)
64 if not d:
64 if not d:
65 break
65 break
66 yield d
66 yield d
67
67
68 def sendresponse(self, v):
68 def sendresponse(self, v):
69 self.fout.write("%d\n" % len(v))
69 self.fout.write("%d\n" % len(v))
70 self.fout.write(v)
70 self.fout.write(v)
71 self.fout.flush()
71 self.fout.flush()
72
72
73 def sendstream(self, source):
73 def sendstream(self, source):
74 for chunk in source.gen:
74 for chunk in source.gen:
75 self.fout.write(chunk)
75 self.fout.write(chunk)
76 self.fout.flush()
76 self.fout.flush()
77
77
78 def sendpushresponse(self, rsp):
78 def sendpushresponse(self, rsp):
79 self.sendresponse('')
79 self.sendresponse('')
80 self.sendresponse(str(rsp.res))
80 self.sendresponse(str(rsp.res))
81
81
82 def sendpusherror(self, rsp):
83 self.sendresponse(rsp.res)
84
82 def serve_forever(self):
85 def serve_forever(self):
83 try:
86 try:
84 while self.serve_one():
87 while self.serve_one():
85 pass
88 pass
86 finally:
89 finally:
87 if self.lock is not None:
90 if self.lock is not None:
88 self.lock.release()
91 self.lock.release()
89 sys.exit(0)
92 sys.exit(0)
90
93
91 handlers = {
94 handlers = {
92 str: sendresponse,
95 str: sendresponse,
93 wireproto.streamres: sendstream,
96 wireproto.streamres: sendstream,
94 wireproto.pushres: sendpushresponse,
97 wireproto.pushres: sendpushresponse,
98 wireproto.pusherr: sendpusherror,
95 }
99 }
96
100
97 def serve_one(self):
101 def serve_one(self):
98 cmd = self.fin.readline()[:-1]
102 cmd = self.fin.readline()[:-1]
99 if cmd and cmd in wireproto.commands:
103 if cmd and cmd in wireproto.commands:
100 rsp = wireproto.dispatch(self.repo, self, cmd)
104 rsp = wireproto.dispatch(self.repo, self, cmd)
101 self.handlers[rsp.__class__](self, rsp)
105 self.handlers[rsp.__class__](self, rsp)
102 elif cmd:
106 elif cmd:
103 impl = getattr(self, 'do_' + cmd, None)
107 impl = getattr(self, 'do_' + cmd, None)
104 if impl:
108 if impl:
105 r = impl()
109 r = impl()
106 if r is not None:
110 if r is not None:
107 self.sendresponse(r)
111 self.sendresponse(r)
108 else: self.sendresponse("")
112 else: self.sendresponse("")
109 return cmd != ''
113 return cmd != ''
110
114
111 def do_lock(self):
115 def do_lock(self):
112 '''DEPRECATED - allowing remote client to lock repo is not safe'''
116 '''DEPRECATED - allowing remote client to lock repo is not safe'''
113
117
114 self.lock = self.repo.lock()
118 self.lock = self.repo.lock()
115 return ""
119 return ""
116
120
117 def do_unlock(self):
121 def do_unlock(self):
118 '''DEPRECATED'''
122 '''DEPRECATED'''
119
123
120 if self.lock:
124 if self.lock:
121 self.lock.release()
125 self.lock.release()
122 self.lock = None
126 self.lock = None
123 return ""
127 return ""
124
128
125 def do_addchangegroup(self):
129 def do_addchangegroup(self):
126 '''DEPRECATED'''
130 '''DEPRECATED'''
127
131
128 if not self.lock:
132 if not self.lock:
129 self.sendresponse("not locked")
133 self.sendresponse("not locked")
130 return
134 return
131
135
132 self.sendresponse("")
136 self.sendresponse("")
133 cg = changegroup.unbundle10(self.fin, "UN")
137 cg = changegroup.unbundle10(self.fin, "UN")
134 r = self.repo.addchangegroup(cg, 'serve', self._client(),
138 r = self.repo.addchangegroup(cg, 'serve', self._client(),
135 lock=self.lock)
139 lock=self.lock)
136 return str(r)
140 return str(r)
137
141
138 def _client(self):
142 def _client(self):
139 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
143 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
140 return 'remote:ssh:' + client
144 return 'remote:ssh:' + client
@@ -1,334 +1,338
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import urllib, tempfile, os, sys
8 import urllib, tempfile, os, sys
9 from i18n import _
9 from i18n import _
10 from node import bin, hex
10 from node import bin, hex
11 import changegroup as changegroupmod
11 import changegroup as changegroupmod
12 import repo, error, encoding, util, store
12 import repo, error, encoding, util, store
13 import pushkey as pushkeymod
13 import pushkey as pushkeymod
14
14
15 # list of nodes encoding / decoding
15 # list of nodes encoding / decoding
16
16
17 def decodelist(l, sep=' '):
17 def decodelist(l, sep=' '):
18 return map(bin, l.split(sep))
18 return map(bin, l.split(sep))
19
19
20 def encodelist(l, sep=' '):
20 def encodelist(l, sep=' '):
21 return sep.join(map(hex, l))
21 return sep.join(map(hex, l))
22
22
23 # client side
23 # client side
24
24
25 class wirerepository(repo.repository):
25 class wirerepository(repo.repository):
26 def lookup(self, key):
26 def lookup(self, key):
27 self.requirecap('lookup', _('look up remote revision'))
27 self.requirecap('lookup', _('look up remote revision'))
28 d = self._call("lookup", key=key)
28 d = self._call("lookup", key=key)
29 success, data = d[:-1].split(" ", 1)
29 success, data = d[:-1].split(" ", 1)
30 if int(success):
30 if int(success):
31 return bin(data)
31 return bin(data)
32 self._abort(error.RepoError(data))
32 self._abort(error.RepoError(data))
33
33
34 def heads(self):
34 def heads(self):
35 d = self._call("heads")
35 d = self._call("heads")
36 try:
36 try:
37 return decodelist(d[:-1])
37 return decodelist(d[:-1])
38 except:
38 except:
39 self._abort(error.ResponseError(_("unexpected response:"), d))
39 self._abort(error.ResponseError(_("unexpected response:"), d))
40
40
41 def branchmap(self):
41 def branchmap(self):
42 d = self._call("branchmap")
42 d = self._call("branchmap")
43 try:
43 try:
44 branchmap = {}
44 branchmap = {}
45 for branchpart in d.splitlines():
45 for branchpart in d.splitlines():
46 branchname, branchheads = branchpart.split(' ', 1)
46 branchname, branchheads = branchpart.split(' ', 1)
47 branchname = urllib.unquote(branchname)
47 branchname = urllib.unquote(branchname)
48 # Earlier servers (1.3.x) send branch names in (their) local
48 # Earlier servers (1.3.x) send branch names in (their) local
49 # charset. The best we can do is assume it's identical to our
49 # charset. The best we can do is assume it's identical to our
50 # own local charset, in case it's not utf-8.
50 # own local charset, in case it's not utf-8.
51 try:
51 try:
52 branchname.decode('utf-8')
52 branchname.decode('utf-8')
53 except UnicodeDecodeError:
53 except UnicodeDecodeError:
54 branchname = encoding.fromlocal(branchname)
54 branchname = encoding.fromlocal(branchname)
55 branchheads = decodelist(branchheads)
55 branchheads = decodelist(branchheads)
56 branchmap[branchname] = branchheads
56 branchmap[branchname] = branchheads
57 return branchmap
57 return branchmap
58 except TypeError:
58 except TypeError:
59 self._abort(error.ResponseError(_("unexpected response:"), d))
59 self._abort(error.ResponseError(_("unexpected response:"), d))
60
60
61 def branches(self, nodes):
61 def branches(self, nodes):
62 n = encodelist(nodes)
62 n = encodelist(nodes)
63 d = self._call("branches", nodes=n)
63 d = self._call("branches", nodes=n)
64 try:
64 try:
65 br = [tuple(decodelist(b)) for b in d.splitlines()]
65 br = [tuple(decodelist(b)) for b in d.splitlines()]
66 return br
66 return br
67 except:
67 except:
68 self._abort(error.ResponseError(_("unexpected response:"), d))
68 self._abort(error.ResponseError(_("unexpected response:"), d))
69
69
70 def between(self, pairs):
70 def between(self, pairs):
71 batch = 8 # avoid giant requests
71 batch = 8 # avoid giant requests
72 r = []
72 r = []
73 for i in xrange(0, len(pairs), batch):
73 for i in xrange(0, len(pairs), batch):
74 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
74 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
75 d = self._call("between", pairs=n)
75 d = self._call("between", pairs=n)
76 try:
76 try:
77 r.extend(l and decodelist(l) or [] for l in d.splitlines())
77 r.extend(l and decodelist(l) or [] for l in d.splitlines())
78 except:
78 except:
79 self._abort(error.ResponseError(_("unexpected response:"), d))
79 self._abort(error.ResponseError(_("unexpected response:"), d))
80 return r
80 return r
81
81
82 def pushkey(self, namespace, key, old, new):
82 def pushkey(self, namespace, key, old, new):
83 if not self.capable('pushkey'):
83 if not self.capable('pushkey'):
84 return False
84 return False
85 d = self._call("pushkey",
85 d = self._call("pushkey",
86 namespace=namespace, key=key, old=old, new=new)
86 namespace=namespace, key=key, old=old, new=new)
87 return bool(int(d))
87 return bool(int(d))
88
88
89 def listkeys(self, namespace):
89 def listkeys(self, namespace):
90 if not self.capable('pushkey'):
90 if not self.capable('pushkey'):
91 return {}
91 return {}
92 d = self._call("listkeys", namespace=namespace)
92 d = self._call("listkeys", namespace=namespace)
93 r = {}
93 r = {}
94 for l in d.splitlines():
94 for l in d.splitlines():
95 k, v = l.split('\t')
95 k, v = l.split('\t')
96 r[k.decode('string-escape')] = v.decode('string-escape')
96 r[k.decode('string-escape')] = v.decode('string-escape')
97 return r
97 return r
98
98
99 def stream_out(self):
99 def stream_out(self):
100 return self._callstream('stream_out')
100 return self._callstream('stream_out')
101
101
102 def changegroup(self, nodes, kind):
102 def changegroup(self, nodes, kind):
103 n = encodelist(nodes)
103 n = encodelist(nodes)
104 f = self._callstream("changegroup", roots=n)
104 f = self._callstream("changegroup", roots=n)
105 return changegroupmod.unbundle10(self._decompress(f), 'UN')
105 return changegroupmod.unbundle10(self._decompress(f), 'UN')
106
106
107 def changegroupsubset(self, bases, heads, kind):
107 def changegroupsubset(self, bases, heads, kind):
108 self.requirecap('changegroupsubset', _('look up remote changes'))
108 self.requirecap('changegroupsubset', _('look up remote changes'))
109 bases = encodelist(bases)
109 bases = encodelist(bases)
110 heads = encodelist(heads)
110 heads = encodelist(heads)
111 f = self._callstream("changegroupsubset",
111 f = self._callstream("changegroupsubset",
112 bases=bases, heads=heads)
112 bases=bases, heads=heads)
113 return changegroupmod.unbundle10(self._decompress(f), 'UN')
113 return changegroupmod.unbundle10(self._decompress(f), 'UN')
114
114
115 def unbundle(self, cg, heads, source):
115 def unbundle(self, cg, heads, source):
116 '''Send cg (a readable file-like object representing the
116 '''Send cg (a readable file-like object representing the
117 changegroup to push, typically a chunkbuffer object) to the
117 changegroup to push, typically a chunkbuffer object) to the
118 remote server as a bundle. Return an integer indicating the
118 remote server as a bundle. Return an integer indicating the
119 result of the push (see localrepository.addchangegroup()).'''
119 result of the push (see localrepository.addchangegroup()).'''
120
120
121 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
121 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
122 if ret == "":
122 if ret == "":
123 raise error.ResponseError(
123 raise error.ResponseError(
124 _('push failed:'), output)
124 _('push failed:'), output)
125 try:
125 try:
126 ret = int(ret)
126 ret = int(ret)
127 except ValueError:
127 except ValueError:
128 raise error.ResponseError(
128 raise error.ResponseError(
129 _('push failed (unexpected response):'), ret)
129 _('push failed (unexpected response):'), ret)
130
130
131 for l in output.splitlines(True):
131 for l in output.splitlines(True):
132 self.ui.status(_('remote: '), l)
132 self.ui.status(_('remote: '), l)
133 return ret
133 return ret
134
134
135 # server side
135 # server side
136
136
137 class streamres(object):
137 class streamres(object):
138 def __init__(self, gen):
138 def __init__(self, gen):
139 self.gen = gen
139 self.gen = gen
140
140
141 class pushres(object):
141 class pushres(object):
142 def __init__(self, res):
142 def __init__(self, res):
143 self.res = res
143 self.res = res
144
144
145 class pusherr(object):
146 def __init__(self, res):
147 self.res = res
148
145 def dispatch(repo, proto, command):
149 def dispatch(repo, proto, command):
146 func, spec = commands[command]
150 func, spec = commands[command]
147 args = proto.getargs(spec)
151 args = proto.getargs(spec)
148 return func(repo, proto, *args)
152 return func(repo, proto, *args)
149
153
150 def between(repo, proto, pairs):
154 def between(repo, proto, pairs):
151 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
155 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
152 r = []
156 r = []
153 for b in repo.between(pairs):
157 for b in repo.between(pairs):
154 r.append(encodelist(b) + "\n")
158 r.append(encodelist(b) + "\n")
155 return "".join(r)
159 return "".join(r)
156
160
157 def branchmap(repo, proto):
161 def branchmap(repo, proto):
158 branchmap = repo.branchmap()
162 branchmap = repo.branchmap()
159 heads = []
163 heads = []
160 for branch, nodes in branchmap.iteritems():
164 for branch, nodes in branchmap.iteritems():
161 branchname = urllib.quote(branch)
165 branchname = urllib.quote(branch)
162 branchnodes = encodelist(nodes)
166 branchnodes = encodelist(nodes)
163 heads.append('%s %s' % (branchname, branchnodes))
167 heads.append('%s %s' % (branchname, branchnodes))
164 return '\n'.join(heads)
168 return '\n'.join(heads)
165
169
166 def branches(repo, proto, nodes):
170 def branches(repo, proto, nodes):
167 nodes = decodelist(nodes)
171 nodes = decodelist(nodes)
168 r = []
172 r = []
169 for b in repo.branches(nodes):
173 for b in repo.branches(nodes):
170 r.append(encodelist(b) + "\n")
174 r.append(encodelist(b) + "\n")
171 return "".join(r)
175 return "".join(r)
172
176
173 def capabilities(repo, proto):
177 def capabilities(repo, proto):
174 caps = 'lookup changegroupsubset branchmap pushkey'.split()
178 caps = 'lookup changegroupsubset branchmap pushkey'.split()
175 if _allowstream(repo.ui):
179 if _allowstream(repo.ui):
176 requiredformats = repo.requirements & repo.supportedformats
180 requiredformats = repo.requirements & repo.supportedformats
177 # if our local revlogs are just revlogv1, add 'stream' cap
181 # if our local revlogs are just revlogv1, add 'stream' cap
178 if not requiredformats - set(('revlogv1',)):
182 if not requiredformats - set(('revlogv1',)):
179 caps.append('stream')
183 caps.append('stream')
180 # otherwise, add 'streamreqs' detailing our local revlog format
184 # otherwise, add 'streamreqs' detailing our local revlog format
181 else:
185 else:
182 caps.append('streamreqs=%s' % ','.join(requiredformats))
186 caps.append('streamreqs=%s' % ','.join(requiredformats))
183 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
187 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
184 return ' '.join(caps)
188 return ' '.join(caps)
185
189
186 def changegroup(repo, proto, roots):
190 def changegroup(repo, proto, roots):
187 nodes = decodelist(roots)
191 nodes = decodelist(roots)
188 cg = repo.changegroup(nodes, 'serve')
192 cg = repo.changegroup(nodes, 'serve')
189 return streamres(proto.groupchunks(cg))
193 return streamres(proto.groupchunks(cg))
190
194
191 def changegroupsubset(repo, proto, bases, heads):
195 def changegroupsubset(repo, proto, bases, heads):
192 bases = decodelist(bases)
196 bases = decodelist(bases)
193 heads = decodelist(heads)
197 heads = decodelist(heads)
194 cg = repo.changegroupsubset(bases, heads, 'serve')
198 cg = repo.changegroupsubset(bases, heads, 'serve')
195 return streamres(proto.groupchunks(cg))
199 return streamres(proto.groupchunks(cg))
196
200
197 def heads(repo, proto):
201 def heads(repo, proto):
198 h = repo.heads()
202 h = repo.heads()
199 return encodelist(h) + "\n"
203 return encodelist(h) + "\n"
200
204
201 def hello(repo, proto):
205 def hello(repo, proto):
202 '''the hello command returns a set of lines describing various
206 '''the hello command returns a set of lines describing various
203 interesting things about the server, in an RFC822-like format.
207 interesting things about the server, in an RFC822-like format.
204 Currently the only one defined is "capabilities", which
208 Currently the only one defined is "capabilities", which
205 consists of a line in the form:
209 consists of a line in the form:
206
210
207 capabilities: space separated list of tokens
211 capabilities: space separated list of tokens
208 '''
212 '''
209 return "capabilities: %s\n" % (capabilities(repo, proto))
213 return "capabilities: %s\n" % (capabilities(repo, proto))
210
214
211 def listkeys(repo, proto, namespace):
215 def listkeys(repo, proto, namespace):
212 d = pushkeymod.list(repo, namespace).items()
216 d = pushkeymod.list(repo, namespace).items()
213 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
217 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
214 v.encode('string-escape')) for k, v in d])
218 v.encode('string-escape')) for k, v in d])
215 return t
219 return t
216
220
217 def lookup(repo, proto, key):
221 def lookup(repo, proto, key):
218 try:
222 try:
219 r = hex(repo.lookup(key))
223 r = hex(repo.lookup(key))
220 success = 1
224 success = 1
221 except Exception, inst:
225 except Exception, inst:
222 r = str(inst)
226 r = str(inst)
223 success = 0
227 success = 0
224 return "%s %s\n" % (success, r)
228 return "%s %s\n" % (success, r)
225
229
226 def pushkey(repo, proto, namespace, key, old, new):
230 def pushkey(repo, proto, namespace, key, old, new):
227 r = pushkeymod.push(repo, namespace, key, old, new)
231 r = pushkeymod.push(repo, namespace, key, old, new)
228 return '%s\n' % int(r)
232 return '%s\n' % int(r)
229
233
230 def _allowstream(ui):
234 def _allowstream(ui):
231 return ui.configbool('server', 'uncompressed', True, untrusted=True)
235 return ui.configbool('server', 'uncompressed', True, untrusted=True)
232
236
233 def stream(repo, proto):
237 def stream(repo, proto):
234 '''If the server supports streaming clone, it advertises the "stream"
238 '''If the server supports streaming clone, it advertises the "stream"
235 capability with a value representing the version and flags of the repo
239 capability with a value representing the version and flags of the repo
236 it is serving. Client checks to see if it understands the format.
240 it is serving. Client checks to see if it understands the format.
237
241
238 The format is simple: the server writes out a line with the amount
242 The format is simple: the server writes out a line with the amount
239 of files, then the total amount of bytes to be transfered (separated
243 of files, then the total amount of bytes to be transfered (separated
240 by a space). Then, for each file, the server first writes the filename
244 by a space). Then, for each file, the server first writes the filename
241 and filesize (separated by the null character), then the file contents.
245 and filesize (separated by the null character), then the file contents.
242 '''
246 '''
243
247
244 if not _allowstream(repo.ui):
248 if not _allowstream(repo.ui):
245 return '1\n'
249 return '1\n'
246
250
247 entries = []
251 entries = []
248 total_bytes = 0
252 total_bytes = 0
249 try:
253 try:
250 # get consistent snapshot of repo, lock during scan
254 # get consistent snapshot of repo, lock during scan
251 lock = repo.lock()
255 lock = repo.lock()
252 try:
256 try:
253 repo.ui.debug('scanning\n')
257 repo.ui.debug('scanning\n')
254 for name, ename, size in repo.store.walk():
258 for name, ename, size in repo.store.walk():
255 entries.append((name, size))
259 entries.append((name, size))
256 total_bytes += size
260 total_bytes += size
257 finally:
261 finally:
258 lock.release()
262 lock.release()
259 except error.LockError:
263 except error.LockError:
260 return '2\n' # error: 2
264 return '2\n' # error: 2
261
265
262 def streamer(repo, entries, total):
266 def streamer(repo, entries, total):
263 '''stream out all metadata files in repository.'''
267 '''stream out all metadata files in repository.'''
264 yield '0\n' # success
268 yield '0\n' # success
265 repo.ui.debug('%d files, %d bytes to transfer\n' %
269 repo.ui.debug('%d files, %d bytes to transfer\n' %
266 (len(entries), total_bytes))
270 (len(entries), total_bytes))
267 yield '%d %d\n' % (len(entries), total_bytes)
271 yield '%d %d\n' % (len(entries), total_bytes)
268 for name, size in entries:
272 for name, size in entries:
269 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
273 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
270 # partially encode name over the wire for backwards compat
274 # partially encode name over the wire for backwards compat
271 yield '%s\0%d\n' % (store.encodedir(name), size)
275 yield '%s\0%d\n' % (store.encodedir(name), size)
272 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
276 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
273 yield chunk
277 yield chunk
274
278
275 return streamres(streamer(repo, entries, total_bytes))
279 return streamres(streamer(repo, entries, total_bytes))
276
280
277 def unbundle(repo, proto, heads):
281 def unbundle(repo, proto, heads):
278 their_heads = decodelist(heads)
282 their_heads = decodelist(heads)
279
283
280 def check_heads():
284 def check_heads():
281 heads = repo.heads()
285 heads = repo.heads()
282 return their_heads == ['force'] or their_heads == heads
286 return their_heads == ['force'] or their_heads == heads
283
287
284 proto.redirect()
288 proto.redirect()
285
289
286 # fail early if possible
290 # fail early if possible
287 if not check_heads():
291 if not check_heads():
288 return 'unsynced changes'
292 return pusherr('unsynced changes')
289
293
290 # write bundle data to temporary file because it can be big
294 # write bundle data to temporary file because it can be big
291 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
295 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
292 fp = os.fdopen(fd, 'wb+')
296 fp = os.fdopen(fd, 'wb+')
293 r = 0
297 r = 0
294 try:
298 try:
295 proto.getfile(fp)
299 proto.getfile(fp)
296 lock = repo.lock()
300 lock = repo.lock()
297 try:
301 try:
298 if not check_heads():
302 if not check_heads():
299 # someone else committed/pushed/unbundled while we
303 # someone else committed/pushed/unbundled while we
300 # were transferring data
304 # were transferring data
301 return 'unsynced changes'
305 return pusherr('unsynced changes')
302
306
303 # push can proceed
307 # push can proceed
304 fp.seek(0)
308 fp.seek(0)
305 gen = changegroupmod.readbundle(fp, None)
309 gen = changegroupmod.readbundle(fp, None)
306
310
307 try:
311 try:
308 r = repo.addchangegroup(gen, 'serve', proto._client(),
312 r = repo.addchangegroup(gen, 'serve', proto._client(),
309 lock=lock)
313 lock=lock)
310 except util.Abort, inst:
314 except util.Abort, inst:
311 sys.stderr.write("abort: %s\n" % inst)
315 sys.stderr.write("abort: %s\n" % inst)
312 finally:
316 finally:
313 lock.release()
317 lock.release()
314 return pushres(r)
318 return pushres(r)
315
319
316 finally:
320 finally:
317 fp.close()
321 fp.close()
318 os.unlink(tempname)
322 os.unlink(tempname)
319
323
320 commands = {
324 commands = {
321 'between': (between, 'pairs'),
325 'between': (between, 'pairs'),
322 'branchmap': (branchmap, ''),
326 'branchmap': (branchmap, ''),
323 'branches': (branches, 'nodes'),
327 'branches': (branches, 'nodes'),
324 'capabilities': (capabilities, ''),
328 'capabilities': (capabilities, ''),
325 'changegroup': (changegroup, 'roots'),
329 'changegroup': (changegroup, 'roots'),
326 'changegroupsubset': (changegroupsubset, 'bases heads'),
330 'changegroupsubset': (changegroupsubset, 'bases heads'),
327 'heads': (heads, ''),
331 'heads': (heads, ''),
328 'hello': (hello, ''),
332 'hello': (hello, ''),
329 'listkeys': (listkeys, 'namespace'),
333 'listkeys': (listkeys, 'namespace'),
330 'lookup': (lookup, 'key'),
334 'lookup': (lookup, 'key'),
331 'pushkey': (pushkey, 'namespace key old new'),
335 'pushkey': (pushkey, 'namespace key old new'),
332 'stream_out': (stream, ''),
336 'stream_out': (stream, ''),
333 'unbundle': (unbundle, 'heads'),
337 'unbundle': (unbundle, 'heads'),
334 }
338 }
General Comments 0
You need to be logged in to leave comments. Login now