##// END OF EJS Templates
wireproto: fix handling of '*' args for HTTP and SSH
Peter Arrenbrecht -
r13721:3458c15a default
parent child Browse files
Show More
@@ -1,75 +1,75 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import cStringIO, zlib, sys, urllib
9 9 from mercurial import util, wireproto
10 10 from common import HTTP_OK
11 11
12 12 HGTYPE = 'application/mercurial-0.1'
13 13
14 14 class webproto(object):
15 15 def __init__(self, req):
16 16 self.req = req
17 17 self.response = ''
18 18 def getargs(self, args):
19 19 data = {}
20 20 keys = args.split()
21 21 for k in keys:
22 22 if k == '*':
23 23 star = {}
24 24 for key in self.req.form.keys():
25 if key not in keys:
25 if key != 'cmd' and key not in keys:
26 26 star[key] = self.req.form[key][0]
27 27 data['*'] = star
28 28 else:
29 29 data[k] = self.req.form[k][0]
30 30 return [data[k] for k in keys]
31 31 def getfile(self, fp):
32 32 length = int(self.req.env['CONTENT_LENGTH'])
33 33 for s in util.filechunkiter(self.req, limit=length):
34 34 fp.write(s)
35 35 def redirect(self):
36 36 self.oldio = sys.stdout, sys.stderr
37 37 sys.stderr = sys.stdout = cStringIO.StringIO()
38 38 def groupchunks(self, cg):
39 39 z = zlib.compressobj()
40 40 while 1:
41 41 chunk = cg.read(4096)
42 42 if not chunk:
43 43 break
44 44 yield z.compress(chunk)
45 45 yield z.flush()
46 46 def _client(self):
47 47 return 'remote:%s:%s:%s' % (
48 48 self.req.env.get('wsgi.url_scheme') or 'http',
49 49 urllib.quote(self.req.env.get('REMOTE_HOST', '')),
50 50 urllib.quote(self.req.env.get('REMOTE_USER', '')))
51 51
52 52 def iscmd(cmd):
53 53 return cmd in wireproto.commands
54 54
55 55 def call(repo, req, cmd):
56 56 p = webproto(req)
57 57 rsp = wireproto.dispatch(repo, p, cmd)
58 58 if isinstance(rsp, str):
59 59 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
60 60 return [rsp]
61 61 elif isinstance(rsp, wireproto.streamres):
62 62 req.respond(HTTP_OK, HGTYPE)
63 63 return rsp.gen
64 64 elif isinstance(rsp, wireproto.pushres):
65 65 val = sys.stdout.getvalue()
66 66 sys.stdout, sys.stderr = p.oldio
67 67 req.respond(HTTP_OK, HGTYPE)
68 68 return ['%d\n%s' % (rsp.res, val)]
69 69 elif isinstance(rsp, wireproto.pusherr):
70 70 # drain the incoming bundle
71 71 req.drain()
72 72 sys.stdout, sys.stderr = p.oldio
73 73 rsp = '0\n%s\n' % rsp.res
74 74 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
75 75 return [rsp]
@@ -1,200 +1,215 b''
1 1 # sshrepo.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import util, error, wireproto
10 10 import re
11 11
12 12 class remotelock(object):
13 13 def __init__(self, repo):
14 14 self.repo = repo
15 15 def release(self):
16 16 self.repo.unlock()
17 17 self.repo = None
18 18 def __del__(self):
19 19 if self.repo:
20 20 self.release()
21 21
22 22 class sshrepository(wireproto.wirerepository):
23 23 def __init__(self, ui, path, create=0):
24 24 self._url = path
25 25 self.ui = ui
26 26
27 27 m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
28 28 if not m:
29 29 self._abort(error.RepoError(_("couldn't parse location %s") % path))
30 30
31 31 self.user = m.group(2)
32 32 if self.user and ':' in self.user:
33 33 self._abort(error.RepoError(_("password in URL not supported")))
34 34 self.host = m.group(3)
35 35 self.port = m.group(5)
36 36 self.path = m.group(7) or "."
37 37
38 38 sshcmd = self.ui.config("ui", "ssh", "ssh")
39 39 remotecmd = self.ui.config("ui", "remotecmd", "hg")
40 40
41 41 args = util.sshargs(sshcmd, self.host, self.user, self.port)
42 42
43 43 if create:
44 44 cmd = '%s %s "%s init %s"'
45 45 cmd = cmd % (sshcmd, args, remotecmd, self.path)
46 46
47 47 ui.note(_('running %s\n') % cmd)
48 48 res = util.system(cmd)
49 49 if res != 0:
50 50 self._abort(error.RepoError(_("could not create remote repo")))
51 51
52 52 self.validate_repo(ui, sshcmd, args, remotecmd)
53 53
54 54 def url(self):
55 55 return self._url
56 56
57 57 def validate_repo(self, ui, sshcmd, args, remotecmd):
58 58 # cleanup up previous run
59 59 self.cleanup()
60 60
61 61 cmd = '%s %s "%s -R %s serve --stdio"'
62 62 cmd = cmd % (sshcmd, args, remotecmd, self.path)
63 63
64 64 cmd = util.quotecommand(cmd)
65 65 ui.note(_('running %s\n') % cmd)
66 66 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
67 67
68 68 # skip any noise generated by remote shell
69 69 self._callstream("hello")
70 70 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
71 71 lines = ["", "dummy"]
72 72 max_noise = 500
73 73 while lines[-1] and max_noise:
74 74 l = r.readline()
75 75 self.readerr()
76 76 if lines[-1] == "1\n" and l == "\n":
77 77 break
78 78 if l:
79 79 ui.debug("remote: ", l)
80 80 lines.append(l)
81 81 max_noise -= 1
82 82 else:
83 83 self._abort(error.RepoError(_("no suitable response from remote hg")))
84 84
85 85 self.capabilities = set()
86 86 for l in reversed(lines):
87 87 if l.startswith("capabilities:"):
88 88 self.capabilities.update(l[:-1].split(":")[1].split())
89 89 break
90 90
91 91 def readerr(self):
92 92 while 1:
93 93 size = util.fstat(self.pipee).st_size
94 94 if size == 0:
95 95 break
96 96 s = self.pipee.read(size)
97 97 if not s:
98 98 break
99 99 for l in s.splitlines():
100 100 self.ui.status(_("remote: "), l, '\n')
101 101
102 102 def _abort(self, exception):
103 103 self.cleanup()
104 104 raise exception
105 105
106 106 def cleanup(self):
107 107 try:
108 108 self.pipeo.close()
109 109 self.pipei.close()
110 110 # read the error descriptor until EOF
111 111 for l in self.pipee:
112 112 self.ui.status(_("remote: "), l)
113 113 self.pipee.close()
114 114 except:
115 115 pass
116 116
117 117 __del__ = cleanup
118 118
119 119 def _callstream(self, cmd, **args):
120 120 self.ui.debug("sending %s command\n" % cmd)
121 121 self.pipeo.write("%s\n" % cmd)
122 for k, v in sorted(args.iteritems()):
122 _func, names = wireproto.commands[cmd]
123 keys = names.split()
124 wireargs = {}
125 for k in keys:
126 if k == '*':
127 wireargs['*'] = args
128 break
129 else:
130 wireargs[k] = args[k]
131 del args[k]
132 for k, v in sorted(wireargs.iteritems()):
123 133 self.pipeo.write("%s %d\n" % (k, len(v)))
124 self.pipeo.write(v)
134 if isinstance(v, dict):
135 for dk, dv in v.iteritems():
136 self.pipeo.write("%s %d\n" % (dk, len(dv)))
137 self.pipeo.write(dv)
138 else:
139 self.pipeo.write(v)
125 140 self.pipeo.flush()
126 141
127 142 return self.pipei
128 143
129 144 def _call(self, cmd, **args):
130 145 self._callstream(cmd, **args)
131 146 return self._recv()
132 147
133 148 def _callpush(self, cmd, fp, **args):
134 149 r = self._call(cmd, **args)
135 150 if r:
136 151 return '', r
137 152 while 1:
138 153 d = fp.read(4096)
139 154 if not d:
140 155 break
141 156 self._send(d)
142 157 self._send("", flush=True)
143 158 r = self._recv()
144 159 if r:
145 160 return '', r
146 161 return self._recv(), ''
147 162
148 163 def _decompress(self, stream):
149 164 return stream
150 165
151 166 def _recv(self):
152 167 l = self.pipei.readline()
153 168 self.readerr()
154 169 try:
155 170 l = int(l)
156 171 except:
157 172 self._abort(error.ResponseError(_("unexpected response:"), l))
158 173 return self.pipei.read(l)
159 174
160 175 def _send(self, data, flush=False):
161 176 self.pipeo.write("%d\n" % len(data))
162 177 if data:
163 178 self.pipeo.write(data)
164 179 if flush:
165 180 self.pipeo.flush()
166 181 self.readerr()
167 182
168 183 def lock(self):
169 184 self._call("lock")
170 185 return remotelock(self)
171 186
172 187 def unlock(self):
173 188 self._call("unlock")
174 189
175 190 def addchangegroup(self, cg, source, url):
176 191 '''Send a changegroup to the remote server. Return an integer
177 192 similar to unbundle(). DEPRECATED, since it requires locking the
178 193 remote.'''
179 194 d = self._call("addchangegroup")
180 195 if d:
181 196 self._abort(error.RepoError(_("push refused: %s") % d))
182 197 while 1:
183 198 d = cg.read(4096)
184 199 if not d:
185 200 break
186 201 self.pipeo.write(d)
187 202 self.readerr()
188 203
189 204 self.pipeo.flush()
190 205
191 206 self.readerr()
192 207 r = self._recv()
193 208 if not r:
194 209 return 1
195 210 try:
196 211 return int(r)
197 212 except:
198 213 self._abort(error.ResponseError(_("unexpected response:"), r))
199 214
200 215 instance = sshrepository
@@ -1,143 +1,144 b''
1 1 # sshserver.py - ssh protocol server support for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import util, hook, wireproto, changegroup
10 10 import os, sys
11 11
12 12 class sshserver(object):
13 13 def __init__(self, ui, repo):
14 14 self.ui = ui
15 15 self.repo = repo
16 16 self.lock = None
17 17 self.fin = sys.stdin
18 18 self.fout = sys.stdout
19 19
20 20 hook.redirect(True)
21 21 sys.stdout = sys.stderr
22 22
23 23 # Prevent insertion/deletion of CRs
24 24 util.set_binary(self.fin)
25 25 util.set_binary(self.fout)
26 26
27 27 def getargs(self, args):
28 28 data = {}
29 29 keys = args.split()
30 30 for n in xrange(len(keys)):
31 31 argline = self.fin.readline()[:-1]
32 32 arg, l = argline.split()
33 val = self.fin.read(int(l))
34 33 if arg not in keys:
35 34 raise util.Abort("unexpected parameter %r" % arg)
36 35 if arg == '*':
37 36 star = {}
38 for n in xrange(int(l)):
37 for k in xrange(int(l)):
38 argline = self.fin.readline()[:-1]
39 39 arg, l = argline.split()
40 40 val = self.fin.read(int(l))
41 41 star[arg] = val
42 42 data['*'] = star
43 43 else:
44 val = self.fin.read(int(l))
44 45 data[arg] = val
45 46 return [data[k] for k in keys]
46 47
47 48 def getarg(self, name):
48 49 return self.getargs(name)[0]
49 50
50 51 def getfile(self, fpout):
51 52 self.sendresponse('')
52 53 count = int(self.fin.readline())
53 54 while count:
54 55 fpout.write(self.fin.read(count))
55 56 count = int(self.fin.readline())
56 57
57 58 def redirect(self):
58 59 pass
59 60
60 61 def groupchunks(self, changegroup):
61 62 while True:
62 63 d = changegroup.read(4096)
63 64 if not d:
64 65 break
65 66 yield d
66 67
67 68 def sendresponse(self, v):
68 69 self.fout.write("%d\n" % len(v))
69 70 self.fout.write(v)
70 71 self.fout.flush()
71 72
72 73 def sendstream(self, source):
73 74 for chunk in source.gen:
74 75 self.fout.write(chunk)
75 76 self.fout.flush()
76 77
77 78 def sendpushresponse(self, rsp):
78 79 self.sendresponse('')
79 80 self.sendresponse(str(rsp.res))
80 81
81 82 def sendpusherror(self, rsp):
82 83 self.sendresponse(rsp.res)
83 84
84 85 def serve_forever(self):
85 86 try:
86 87 while self.serve_one():
87 88 pass
88 89 finally:
89 90 if self.lock is not None:
90 91 self.lock.release()
91 92 sys.exit(0)
92 93
93 94 handlers = {
94 95 str: sendresponse,
95 96 wireproto.streamres: sendstream,
96 97 wireproto.pushres: sendpushresponse,
97 98 wireproto.pusherr: sendpusherror,
98 99 }
99 100
100 101 def serve_one(self):
101 102 cmd = self.fin.readline()[:-1]
102 103 if cmd and cmd in wireproto.commands:
103 104 rsp = wireproto.dispatch(self.repo, self, cmd)
104 105 self.handlers[rsp.__class__](self, rsp)
105 106 elif cmd:
106 107 impl = getattr(self, 'do_' + cmd, None)
107 108 if impl:
108 109 r = impl()
109 110 if r is not None:
110 111 self.sendresponse(r)
111 112 else: self.sendresponse("")
112 113 return cmd != ''
113 114
114 115 def do_lock(self):
115 116 '''DEPRECATED - allowing remote client to lock repo is not safe'''
116 117
117 118 self.lock = self.repo.lock()
118 119 return ""
119 120
120 121 def do_unlock(self):
121 122 '''DEPRECATED'''
122 123
123 124 if self.lock:
124 125 self.lock.release()
125 126 self.lock = None
126 127 return ""
127 128
128 129 def do_addchangegroup(self):
129 130 '''DEPRECATED'''
130 131
131 132 if not self.lock:
132 133 self.sendresponse("not locked")
133 134 return
134 135
135 136 self.sendresponse("")
136 137 cg = changegroup.unbundle10(self.fin, "UN")
137 138 r = self.repo.addchangegroup(cg, 'serve', self._client(),
138 139 lock=self.lock)
139 140 return str(r)
140 141
141 142 def _client(self):
142 143 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
143 144 return 'remote:ssh:' + client
@@ -1,366 +1,379 b''
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import urllib, tempfile, os, sys
9 9 from i18n import _
10 10 from node import bin, hex
11 11 import changegroup as changegroupmod
12 12 import repo, error, encoding, util, store
13 13 import pushkey as pushkeymod
14 14
15 15 # list of nodes encoding / decoding
16 16
17 17 def decodelist(l, sep=' '):
18 18 return map(bin, l.split(sep))
19 19
20 20 def encodelist(l, sep=' '):
21 21 return sep.join(map(hex, l))
22 22
23 23 # client side
24 24
25 25 class wirerepository(repo.repository):
26 26 def lookup(self, key):
27 27 self.requirecap('lookup', _('look up remote revision'))
28 28 d = self._call("lookup", key=encoding.fromlocal(key))
29 29 success, data = d[:-1].split(" ", 1)
30 30 if int(success):
31 31 return bin(data)
32 32 self._abort(error.RepoError(data))
33 33
34 34 def heads(self):
35 35 d = self._call("heads")
36 36 try:
37 37 return decodelist(d[:-1])
38 38 except:
39 39 self._abort(error.ResponseError(_("unexpected response:"), d))
40 40
41 41 def branchmap(self):
42 42 d = self._call("branchmap")
43 43 try:
44 44 branchmap = {}
45 45 for branchpart in d.splitlines():
46 46 branchname, branchheads = branchpart.split(' ', 1)
47 47 branchname = encoding.tolocal(urllib.unquote(branchname))
48 48 branchheads = decodelist(branchheads)
49 49 branchmap[branchname] = branchheads
50 50 return branchmap
51 51 except TypeError:
52 52 self._abort(error.ResponseError(_("unexpected response:"), d))
53 53
54 54 def branches(self, nodes):
55 55 n = encodelist(nodes)
56 56 d = self._call("branches", nodes=n)
57 57 try:
58 58 br = [tuple(decodelist(b)) for b in d.splitlines()]
59 59 return br
60 60 except:
61 61 self._abort(error.ResponseError(_("unexpected response:"), d))
62 62
63 63 def between(self, pairs):
64 64 batch = 8 # avoid giant requests
65 65 r = []
66 66 for i in xrange(0, len(pairs), batch):
67 67 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
68 68 d = self._call("between", pairs=n)
69 69 try:
70 70 r.extend(l and decodelist(l) or [] for l in d.splitlines())
71 71 except:
72 72 self._abort(error.ResponseError(_("unexpected response:"), d))
73 73 return r
74 74
75 75 def pushkey(self, namespace, key, old, new):
76 76 if not self.capable('pushkey'):
77 77 return False
78 78 d = self._call("pushkey",
79 79 namespace=encoding.fromlocal(namespace),
80 80 key=encoding.fromlocal(key),
81 81 old=encoding.fromlocal(old),
82 82 new=encoding.fromlocal(new))
83 83 try:
84 84 d = bool(int(d))
85 85 except ValueError:
86 86 raise error.ResponseError(
87 87 _('push failed (unexpected response):'), d)
88 88 return d
89 89
90 90 def listkeys(self, namespace):
91 91 if not self.capable('pushkey'):
92 92 return {}
93 93 d = self._call("listkeys", namespace=encoding.fromlocal(namespace))
94 94 r = {}
95 95 for l in d.splitlines():
96 96 k, v = l.split('\t')
97 97 r[encoding.tolocal(k)] = encoding.tolocal(v)
98 98 return r
99 99
100 100 def stream_out(self):
101 101 return self._callstream('stream_out')
102 102
103 103 def changegroup(self, nodes, kind):
104 104 n = encodelist(nodes)
105 105 f = self._callstream("changegroup", roots=n)
106 106 return changegroupmod.unbundle10(self._decompress(f), 'UN')
107 107
108 108 def changegroupsubset(self, bases, heads, kind):
109 109 self.requirecap('changegroupsubset', _('look up remote changes'))
110 110 bases = encodelist(bases)
111 111 heads = encodelist(heads)
112 112 f = self._callstream("changegroupsubset",
113 113 bases=bases, heads=heads)
114 114 return changegroupmod.unbundle10(self._decompress(f), 'UN')
115 115
116 116 def unbundle(self, cg, heads, source):
117 117 '''Send cg (a readable file-like object representing the
118 118 changegroup to push, typically a chunkbuffer object) to the
119 119 remote server as a bundle. Return an integer indicating the
120 120 result of the push (see localrepository.addchangegroup()).'''
121 121
122 122 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
123 123 if ret == "":
124 124 raise error.ResponseError(
125 125 _('push failed:'), output)
126 126 try:
127 127 ret = int(ret)
128 128 except ValueError:
129 129 raise error.ResponseError(
130 130 _('push failed (unexpected response):'), ret)
131 131
132 132 for l in output.splitlines(True):
133 133 self.ui.status(_('remote: '), l)
134 134 return ret
135 135
136 136 def debugwireargs(self, one, two, three=None, four=None):
137 137 # don't pass optional arguments left at their default value
138 138 opts = {}
139 139 if three is not None:
140 140 opts['three'] = three
141 141 if four is not None:
142 142 opts['four'] = four
143 143 return self._call('debugwireargs', one=one, two=two, **opts)
144 144
145 145 # server side
146 146
147 147 class streamres(object):
148 148 def __init__(self, gen):
149 149 self.gen = gen
150 150
151 151 class pushres(object):
152 152 def __init__(self, res):
153 153 self.res = res
154 154
155 155 class pusherr(object):
156 156 def __init__(self, res):
157 157 self.res = res
158 158
159 159 def dispatch(repo, proto, command):
160 160 func, spec = commands[command]
161 161 args = proto.getargs(spec)
162 162 return func(repo, proto, *args)
163 163
164 def options(cmd, keys, others):
165 opts = {}
166 for k in keys:
167 if k in others:
168 opts[k] = others[k]
169 del others[k]
170 if others:
171 sys.stderr.write("abort: %s got unexpected arguments %s\n"
172 % (cmd, ",".join(others)))
173 return opts
174
164 175 def between(repo, proto, pairs):
165 176 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
166 177 r = []
167 178 for b in repo.between(pairs):
168 179 r.append(encodelist(b) + "\n")
169 180 return "".join(r)
170 181
171 182 def branchmap(repo, proto):
172 183 branchmap = repo.branchmap()
173 184 heads = []
174 185 for branch, nodes in branchmap.iteritems():
175 186 branchname = urllib.quote(encoding.fromlocal(branch))
176 187 branchnodes = encodelist(nodes)
177 188 heads.append('%s %s' % (branchname, branchnodes))
178 189 return '\n'.join(heads)
179 190
180 191 def branches(repo, proto, nodes):
181 192 nodes = decodelist(nodes)
182 193 r = []
183 194 for b in repo.branches(nodes):
184 195 r.append(encodelist(b) + "\n")
185 196 return "".join(r)
186 197
187 198 def capabilities(repo, proto):
188 199 caps = 'lookup changegroupsubset branchmap pushkey'.split()
189 200 if _allowstream(repo.ui):
190 201 requiredformats = repo.requirements & repo.supportedformats
191 202 # if our local revlogs are just revlogv1, add 'stream' cap
192 203 if not requiredformats - set(('revlogv1',)):
193 204 caps.append('stream')
194 205 # otherwise, add 'streamreqs' detailing our local revlog format
195 206 else:
196 207 caps.append('streamreqs=%s' % ','.join(requiredformats))
197 208 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
198 209 return ' '.join(caps)
199 210
200 211 def changegroup(repo, proto, roots):
201 212 nodes = decodelist(roots)
202 213 cg = repo.changegroup(nodes, 'serve')
203 214 return streamres(proto.groupchunks(cg))
204 215
205 216 def changegroupsubset(repo, proto, bases, heads):
206 217 bases = decodelist(bases)
207 218 heads = decodelist(heads)
208 219 cg = repo.changegroupsubset(bases, heads, 'serve')
209 220 return streamres(proto.groupchunks(cg))
210 221
211 def debugwireargs(repo, proto, one, two):
212 return repo.debugwireargs(one, two)
222 def debugwireargs(repo, proto, one, two, others):
223 # only accept optional args from the known set
224 opts = options('debugwireargs', ['three', 'four'], others)
225 return repo.debugwireargs(one, two, **opts)
213 226
214 227 def heads(repo, proto):
215 228 h = repo.heads()
216 229 return encodelist(h) + "\n"
217 230
218 231 def hello(repo, proto):
219 232 '''the hello command returns a set of lines describing various
220 233 interesting things about the server, in an RFC822-like format.
221 234 Currently the only one defined is "capabilities", which
222 235 consists of a line in the form:
223 236
224 237 capabilities: space separated list of tokens
225 238 '''
226 239 return "capabilities: %s\n" % (capabilities(repo, proto))
227 240
228 241 def listkeys(repo, proto, namespace):
229 242 d = pushkeymod.list(repo, encoding.tolocal(namespace)).items()
230 243 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
231 244 for k, v in d])
232 245 return t
233 246
234 247 def lookup(repo, proto, key):
235 248 try:
236 249 r = hex(repo.lookup(encoding.tolocal(key)))
237 250 success = 1
238 251 except Exception, inst:
239 252 r = str(inst)
240 253 success = 0
241 254 return "%s %s\n" % (success, r)
242 255
243 256 def pushkey(repo, proto, namespace, key, old, new):
244 257 # compatibility with pre-1.8 clients which were accidentally
245 258 # sending raw binary nodes rather than utf-8-encoded hex
246 259 if len(new) == 20 and new.encode('string-escape') != new:
247 260 # looks like it could be a binary node
248 261 try:
249 262 u = new.decode('utf-8')
250 263 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
251 264 except UnicodeDecodeError:
252 265 pass # binary, leave unmodified
253 266 else:
254 267 new = encoding.tolocal(new) # normal path
255 268
256 269 r = pushkeymod.push(repo,
257 270 encoding.tolocal(namespace), encoding.tolocal(key),
258 271 encoding.tolocal(old), new)
259 272 return '%s\n' % int(r)
260 273
261 274 def _allowstream(ui):
262 275 return ui.configbool('server', 'uncompressed', True, untrusted=True)
263 276
264 277 def stream(repo, proto):
265 278 '''If the server supports streaming clone, it advertises the "stream"
266 279 capability with a value representing the version and flags of the repo
267 280 it is serving. Client checks to see if it understands the format.
268 281
269 282 The format is simple: the server writes out a line with the amount
270 283 of files, then the total amount of bytes to be transfered (separated
271 284 by a space). Then, for each file, the server first writes the filename
272 285 and filesize (separated by the null character), then the file contents.
273 286 '''
274 287
275 288 if not _allowstream(repo.ui):
276 289 return '1\n'
277 290
278 291 entries = []
279 292 total_bytes = 0
280 293 try:
281 294 # get consistent snapshot of repo, lock during scan
282 295 lock = repo.lock()
283 296 try:
284 297 repo.ui.debug('scanning\n')
285 298 for name, ename, size in repo.store.walk():
286 299 entries.append((name, size))
287 300 total_bytes += size
288 301 finally:
289 302 lock.release()
290 303 except error.LockError:
291 304 return '2\n' # error: 2
292 305
293 306 def streamer(repo, entries, total):
294 307 '''stream out all metadata files in repository.'''
295 308 yield '0\n' # success
296 309 repo.ui.debug('%d files, %d bytes to transfer\n' %
297 310 (len(entries), total_bytes))
298 311 yield '%d %d\n' % (len(entries), total_bytes)
299 312 for name, size in entries:
300 313 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
301 314 # partially encode name over the wire for backwards compat
302 315 yield '%s\0%d\n' % (store.encodedir(name), size)
303 316 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
304 317 yield chunk
305 318
306 319 return streamres(streamer(repo, entries, total_bytes))
307 320
308 321 def unbundle(repo, proto, heads):
309 322 their_heads = decodelist(heads)
310 323
311 324 def check_heads():
312 325 heads = repo.heads()
313 326 return their_heads == ['force'] or their_heads == heads
314 327
315 328 proto.redirect()
316 329
317 330 # fail early if possible
318 331 if not check_heads():
319 332 return pusherr('unsynced changes')
320 333
321 334 # write bundle data to temporary file because it can be big
322 335 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
323 336 fp = os.fdopen(fd, 'wb+')
324 337 r = 0
325 338 try:
326 339 proto.getfile(fp)
327 340 lock = repo.lock()
328 341 try:
329 342 if not check_heads():
330 343 # someone else committed/pushed/unbundled while we
331 344 # were transferring data
332 345 return pusherr('unsynced changes')
333 346
334 347 # push can proceed
335 348 fp.seek(0)
336 349 gen = changegroupmod.readbundle(fp, None)
337 350
338 351 try:
339 352 r = repo.addchangegroup(gen, 'serve', proto._client(),
340 353 lock=lock)
341 354 except util.Abort, inst:
342 355 sys.stderr.write("abort: %s\n" % inst)
343 356 finally:
344 357 lock.release()
345 358 return pushres(r)
346 359
347 360 finally:
348 361 fp.close()
349 362 os.unlink(tempname)
350 363
351 364 commands = {
352 365 'between': (between, 'pairs'),
353 366 'branchmap': (branchmap, ''),
354 367 'branches': (branches, 'nodes'),
355 368 'capabilities': (capabilities, ''),
356 369 'changegroup': (changegroup, 'roots'),
357 370 'changegroupsubset': (changegroupsubset, 'bases heads'),
358 'debugwireargs': (debugwireargs, 'one two'),
371 'debugwireargs': (debugwireargs, 'one two *'),
359 372 'heads': (heads, ''),
360 373 'hello': (hello, ''),
361 374 'listkeys': (listkeys, 'namespace'),
362 375 'lookup': (lookup, 'key'),
363 376 'pushkey': (pushkey, 'namespace key old new'),
364 377 'stream_out': (stream, ''),
365 378 'unbundle': (unbundle, 'heads'),
366 379 }
@@ -1,42 +1,60 b''
1 1
2 2 Test wire protocol argument passing
3 3
4 4 Setup repo:
5 5
6 6 $ hg init repo
7 7
8 8 Local:
9 9
10 $ hg debugwireargs repo eins zwei --three drei --four vier
11 eins zwei drei vier
12 $ hg debugwireargs repo eins zwei --four vier
13 eins zwei None vier
10 14 $ hg debugwireargs repo eins zwei
11 15 eins zwei None None
12 16
13 17 HTTP:
14 18
15 19 $ hg serve -R repo -p $HGPORT -d --pid-file=hg1.pid -E error.log -A access.log
16 20 $ cat hg1.pid >> $DAEMON_PIDS
17 21
22 $ hg debugwireargs http://localhost:$HGPORT/ un deux trois quatre
23 un deux trois quatre
24 $ hg debugwireargs http://localhost:$HGPORT/ eins zwei --four vier
25 eins zwei None vier
18 26 $ hg debugwireargs http://localhost:$HGPORT/ eins zwei
19 27 eins zwei None None
20 28 $ cat access.log
21 29 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
30 * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob)
31 * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - (glob)
32 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
33 * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob)
34 * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - (glob)
35 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
22 36 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
23 37 * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - (glob)
24 38
25 39 SSH (try to exercise the ssh functionality with a dummy script):
26 40
27 41 $ cat <<EOF > dummyssh
28 42 > import sys
29 43 > import os
30 44 > os.chdir(os.path.dirname(sys.argv[0]))
31 45 > if sys.argv[1] != "user@dummy":
32 46 > sys.exit(-1)
33 47 > if not os.path.exists("dummyssh"):
34 48 > sys.exit(-1)
35 49 > os.environ["SSH_CLIENT"] = "127.0.0.1 1 2"
36 50 > r = os.system(sys.argv[2])
37 51 > sys.exit(bool(r))
38 52 > EOF
39 53
54 $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo uno due tre quattro
55 uno due tre quattro
56 $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei --four vier
57 eins zwei None vier
40 58 $ hg debugwireargs --ssh "python ./dummyssh" ssh://user@dummy/repo eins zwei
41 59 eins zwei None None
42 60
General Comments 0
You need to be logged in to leave comments. Login now