##// END OF EJS Templates
wireproto: fix decodelist to properly return empty list...
Peter Arrenbrecht -
r13722:f4a85ace default
parent child Browse files
Show More
@@ -1,379 +1,381 b''
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 if l:
19 return map(bin, l.split(sep))
20 return []
19
21
20 def encodelist(l, sep=' '):
22 def encodelist(l, sep=' '):
21 return sep.join(map(hex, l))
23 return sep.join(map(hex, l))
22
24
23 # client side
25 # client side
24
26
25 class wirerepository(repo.repository):
27 class wirerepository(repo.repository):
26 def lookup(self, key):
28 def lookup(self, key):
27 self.requirecap('lookup', _('look up remote revision'))
29 self.requirecap('lookup', _('look up remote revision'))
28 d = self._call("lookup", key=encoding.fromlocal(key))
30 d = self._call("lookup", key=encoding.fromlocal(key))
29 success, data = d[:-1].split(" ", 1)
31 success, data = d[:-1].split(" ", 1)
30 if int(success):
32 if int(success):
31 return bin(data)
33 return bin(data)
32 self._abort(error.RepoError(data))
34 self._abort(error.RepoError(data))
33
35
34 def heads(self):
36 def heads(self):
35 d = self._call("heads")
37 d = self._call("heads")
36 try:
38 try:
37 return decodelist(d[:-1])
39 return decodelist(d[:-1])
38 except:
40 except:
39 self._abort(error.ResponseError(_("unexpected response:"), d))
41 self._abort(error.ResponseError(_("unexpected response:"), d))
40
42
41 def branchmap(self):
43 def branchmap(self):
42 d = self._call("branchmap")
44 d = self._call("branchmap")
43 try:
45 try:
44 branchmap = {}
46 branchmap = {}
45 for branchpart in d.splitlines():
47 for branchpart in d.splitlines():
46 branchname, branchheads = branchpart.split(' ', 1)
48 branchname, branchheads = branchpart.split(' ', 1)
47 branchname = encoding.tolocal(urllib.unquote(branchname))
49 branchname = encoding.tolocal(urllib.unquote(branchname))
48 branchheads = decodelist(branchheads)
50 branchheads = decodelist(branchheads)
49 branchmap[branchname] = branchheads
51 branchmap[branchname] = branchheads
50 return branchmap
52 return branchmap
51 except TypeError:
53 except TypeError:
52 self._abort(error.ResponseError(_("unexpected response:"), d))
54 self._abort(error.ResponseError(_("unexpected response:"), d))
53
55
54 def branches(self, nodes):
56 def branches(self, nodes):
55 n = encodelist(nodes)
57 n = encodelist(nodes)
56 d = self._call("branches", nodes=n)
58 d = self._call("branches", nodes=n)
57 try:
59 try:
58 br = [tuple(decodelist(b)) for b in d.splitlines()]
60 br = [tuple(decodelist(b)) for b in d.splitlines()]
59 return br
61 return br
60 except:
62 except:
61 self._abort(error.ResponseError(_("unexpected response:"), d))
63 self._abort(error.ResponseError(_("unexpected response:"), d))
62
64
63 def between(self, pairs):
65 def between(self, pairs):
64 batch = 8 # avoid giant requests
66 batch = 8 # avoid giant requests
65 r = []
67 r = []
66 for i in xrange(0, len(pairs), batch):
68 for i in xrange(0, len(pairs), batch):
67 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
69 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
68 d = self._call("between", pairs=n)
70 d = self._call("between", pairs=n)
69 try:
71 try:
70 r.extend(l and decodelist(l) or [] for l in d.splitlines())
72 r.extend(l and decodelist(l) or [] for l in d.splitlines())
71 except:
73 except:
72 self._abort(error.ResponseError(_("unexpected response:"), d))
74 self._abort(error.ResponseError(_("unexpected response:"), d))
73 return r
75 return r
74
76
75 def pushkey(self, namespace, key, old, new):
77 def pushkey(self, namespace, key, old, new):
76 if not self.capable('pushkey'):
78 if not self.capable('pushkey'):
77 return False
79 return False
78 d = self._call("pushkey",
80 d = self._call("pushkey",
79 namespace=encoding.fromlocal(namespace),
81 namespace=encoding.fromlocal(namespace),
80 key=encoding.fromlocal(key),
82 key=encoding.fromlocal(key),
81 old=encoding.fromlocal(old),
83 old=encoding.fromlocal(old),
82 new=encoding.fromlocal(new))
84 new=encoding.fromlocal(new))
83 try:
85 try:
84 d = bool(int(d))
86 d = bool(int(d))
85 except ValueError:
87 except ValueError:
86 raise error.ResponseError(
88 raise error.ResponseError(
87 _('push failed (unexpected response):'), d)
89 _('push failed (unexpected response):'), d)
88 return d
90 return d
89
91
90 def listkeys(self, namespace):
92 def listkeys(self, namespace):
91 if not self.capable('pushkey'):
93 if not self.capable('pushkey'):
92 return {}
94 return {}
93 d = self._call("listkeys", namespace=encoding.fromlocal(namespace))
95 d = self._call("listkeys", namespace=encoding.fromlocal(namespace))
94 r = {}
96 r = {}
95 for l in d.splitlines():
97 for l in d.splitlines():
96 k, v = l.split('\t')
98 k, v = l.split('\t')
97 r[encoding.tolocal(k)] = encoding.tolocal(v)
99 r[encoding.tolocal(k)] = encoding.tolocal(v)
98 return r
100 return r
99
101
100 def stream_out(self):
102 def stream_out(self):
101 return self._callstream('stream_out')
103 return self._callstream('stream_out')
102
104
103 def changegroup(self, nodes, kind):
105 def changegroup(self, nodes, kind):
104 n = encodelist(nodes)
106 n = encodelist(nodes)
105 f = self._callstream("changegroup", roots=n)
107 f = self._callstream("changegroup", roots=n)
106 return changegroupmod.unbundle10(self._decompress(f), 'UN')
108 return changegroupmod.unbundle10(self._decompress(f), 'UN')
107
109
108 def changegroupsubset(self, bases, heads, kind):
110 def changegroupsubset(self, bases, heads, kind):
109 self.requirecap('changegroupsubset', _('look up remote changes'))
111 self.requirecap('changegroupsubset', _('look up remote changes'))
110 bases = encodelist(bases)
112 bases = encodelist(bases)
111 heads = encodelist(heads)
113 heads = encodelist(heads)
112 f = self._callstream("changegroupsubset",
114 f = self._callstream("changegroupsubset",
113 bases=bases, heads=heads)
115 bases=bases, heads=heads)
114 return changegroupmod.unbundle10(self._decompress(f), 'UN')
116 return changegroupmod.unbundle10(self._decompress(f), 'UN')
115
117
116 def unbundle(self, cg, heads, source):
118 def unbundle(self, cg, heads, source):
117 '''Send cg (a readable file-like object representing the
119 '''Send cg (a readable file-like object representing the
118 changegroup to push, typically a chunkbuffer object) to the
120 changegroup to push, typically a chunkbuffer object) to the
119 remote server as a bundle. Return an integer indicating the
121 remote server as a bundle. Return an integer indicating the
120 result of the push (see localrepository.addchangegroup()).'''
122 result of the push (see localrepository.addchangegroup()).'''
121
123
122 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
124 ret, output = self._callpush("unbundle", cg, heads=encodelist(heads))
123 if ret == "":
125 if ret == "":
124 raise error.ResponseError(
126 raise error.ResponseError(
125 _('push failed:'), output)
127 _('push failed:'), output)
126 try:
128 try:
127 ret = int(ret)
129 ret = int(ret)
128 except ValueError:
130 except ValueError:
129 raise error.ResponseError(
131 raise error.ResponseError(
130 _('push failed (unexpected response):'), ret)
132 _('push failed (unexpected response):'), ret)
131
133
132 for l in output.splitlines(True):
134 for l in output.splitlines(True):
133 self.ui.status(_('remote: '), l)
135 self.ui.status(_('remote: '), l)
134 return ret
136 return ret
135
137
136 def debugwireargs(self, one, two, three=None, four=None):
138 def debugwireargs(self, one, two, three=None, four=None):
137 # don't pass optional arguments left at their default value
139 # don't pass optional arguments left at their default value
138 opts = {}
140 opts = {}
139 if three is not None:
141 if three is not None:
140 opts['three'] = three
142 opts['three'] = three
141 if four is not None:
143 if four is not None:
142 opts['four'] = four
144 opts['four'] = four
143 return self._call('debugwireargs', one=one, two=two, **opts)
145 return self._call('debugwireargs', one=one, two=two, **opts)
144
146
145 # server side
147 # server side
146
148
147 class streamres(object):
149 class streamres(object):
148 def __init__(self, gen):
150 def __init__(self, gen):
149 self.gen = gen
151 self.gen = gen
150
152
151 class pushres(object):
153 class pushres(object):
152 def __init__(self, res):
154 def __init__(self, res):
153 self.res = res
155 self.res = res
154
156
155 class pusherr(object):
157 class pusherr(object):
156 def __init__(self, res):
158 def __init__(self, res):
157 self.res = res
159 self.res = res
158
160
159 def dispatch(repo, proto, command):
161 def dispatch(repo, proto, command):
160 func, spec = commands[command]
162 func, spec = commands[command]
161 args = proto.getargs(spec)
163 args = proto.getargs(spec)
162 return func(repo, proto, *args)
164 return func(repo, proto, *args)
163
165
164 def options(cmd, keys, others):
166 def options(cmd, keys, others):
165 opts = {}
167 opts = {}
166 for k in keys:
168 for k in keys:
167 if k in others:
169 if k in others:
168 opts[k] = others[k]
170 opts[k] = others[k]
169 del others[k]
171 del others[k]
170 if others:
172 if others:
171 sys.stderr.write("abort: %s got unexpected arguments %s\n"
173 sys.stderr.write("abort: %s got unexpected arguments %s\n"
172 % (cmd, ",".join(others)))
174 % (cmd, ",".join(others)))
173 return opts
175 return opts
174
176
175 def between(repo, proto, pairs):
177 def between(repo, proto, pairs):
176 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
178 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
177 r = []
179 r = []
178 for b in repo.between(pairs):
180 for b in repo.between(pairs):
179 r.append(encodelist(b) + "\n")
181 r.append(encodelist(b) + "\n")
180 return "".join(r)
182 return "".join(r)
181
183
182 def branchmap(repo, proto):
184 def branchmap(repo, proto):
183 branchmap = repo.branchmap()
185 branchmap = repo.branchmap()
184 heads = []
186 heads = []
185 for branch, nodes in branchmap.iteritems():
187 for branch, nodes in branchmap.iteritems():
186 branchname = urllib.quote(encoding.fromlocal(branch))
188 branchname = urllib.quote(encoding.fromlocal(branch))
187 branchnodes = encodelist(nodes)
189 branchnodes = encodelist(nodes)
188 heads.append('%s %s' % (branchname, branchnodes))
190 heads.append('%s %s' % (branchname, branchnodes))
189 return '\n'.join(heads)
191 return '\n'.join(heads)
190
192
191 def branches(repo, proto, nodes):
193 def branches(repo, proto, nodes):
192 nodes = decodelist(nodes)
194 nodes = decodelist(nodes)
193 r = []
195 r = []
194 for b in repo.branches(nodes):
196 for b in repo.branches(nodes):
195 r.append(encodelist(b) + "\n")
197 r.append(encodelist(b) + "\n")
196 return "".join(r)
198 return "".join(r)
197
199
198 def capabilities(repo, proto):
200 def capabilities(repo, proto):
199 caps = 'lookup changegroupsubset branchmap pushkey'.split()
201 caps = 'lookup changegroupsubset branchmap pushkey'.split()
200 if _allowstream(repo.ui):
202 if _allowstream(repo.ui):
201 requiredformats = repo.requirements & repo.supportedformats
203 requiredformats = repo.requirements & repo.supportedformats
202 # if our local revlogs are just revlogv1, add 'stream' cap
204 # if our local revlogs are just revlogv1, add 'stream' cap
203 if not requiredformats - set(('revlogv1',)):
205 if not requiredformats - set(('revlogv1',)):
204 caps.append('stream')
206 caps.append('stream')
205 # otherwise, add 'streamreqs' detailing our local revlog format
207 # otherwise, add 'streamreqs' detailing our local revlog format
206 else:
208 else:
207 caps.append('streamreqs=%s' % ','.join(requiredformats))
209 caps.append('streamreqs=%s' % ','.join(requiredformats))
208 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
210 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
209 return ' '.join(caps)
211 return ' '.join(caps)
210
212
211 def changegroup(repo, proto, roots):
213 def changegroup(repo, proto, roots):
212 nodes = decodelist(roots)
214 nodes = decodelist(roots)
213 cg = repo.changegroup(nodes, 'serve')
215 cg = repo.changegroup(nodes, 'serve')
214 return streamres(proto.groupchunks(cg))
216 return streamres(proto.groupchunks(cg))
215
217
216 def changegroupsubset(repo, proto, bases, heads):
218 def changegroupsubset(repo, proto, bases, heads):
217 bases = decodelist(bases)
219 bases = decodelist(bases)
218 heads = decodelist(heads)
220 heads = decodelist(heads)
219 cg = repo.changegroupsubset(bases, heads, 'serve')
221 cg = repo.changegroupsubset(bases, heads, 'serve')
220 return streamres(proto.groupchunks(cg))
222 return streamres(proto.groupchunks(cg))
221
223
222 def debugwireargs(repo, proto, one, two, others):
224 def debugwireargs(repo, proto, one, two, others):
223 # only accept optional args from the known set
225 # only accept optional args from the known set
224 opts = options('debugwireargs', ['three', 'four'], others)
226 opts = options('debugwireargs', ['three', 'four'], others)
225 return repo.debugwireargs(one, two, **opts)
227 return repo.debugwireargs(one, two, **opts)
226
228
227 def heads(repo, proto):
229 def heads(repo, proto):
228 h = repo.heads()
230 h = repo.heads()
229 return encodelist(h) + "\n"
231 return encodelist(h) + "\n"
230
232
231 def hello(repo, proto):
233 def hello(repo, proto):
232 '''the hello command returns a set of lines describing various
234 '''the hello command returns a set of lines describing various
233 interesting things about the server, in an RFC822-like format.
235 interesting things about the server, in an RFC822-like format.
234 Currently the only one defined is "capabilities", which
236 Currently the only one defined is "capabilities", which
235 consists of a line in the form:
237 consists of a line in the form:
236
238
237 capabilities: space separated list of tokens
239 capabilities: space separated list of tokens
238 '''
240 '''
239 return "capabilities: %s\n" % (capabilities(repo, proto))
241 return "capabilities: %s\n" % (capabilities(repo, proto))
240
242
241 def listkeys(repo, proto, namespace):
243 def listkeys(repo, proto, namespace):
242 d = pushkeymod.list(repo, encoding.tolocal(namespace)).items()
244 d = pushkeymod.list(repo, encoding.tolocal(namespace)).items()
243 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
245 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
244 for k, v in d])
246 for k, v in d])
245 return t
247 return t
246
248
247 def lookup(repo, proto, key):
249 def lookup(repo, proto, key):
248 try:
250 try:
249 r = hex(repo.lookup(encoding.tolocal(key)))
251 r = hex(repo.lookup(encoding.tolocal(key)))
250 success = 1
252 success = 1
251 except Exception, inst:
253 except Exception, inst:
252 r = str(inst)
254 r = str(inst)
253 success = 0
255 success = 0
254 return "%s %s\n" % (success, r)
256 return "%s %s\n" % (success, r)
255
257
256 def pushkey(repo, proto, namespace, key, old, new):
258 def pushkey(repo, proto, namespace, key, old, new):
257 # compatibility with pre-1.8 clients which were accidentally
259 # compatibility with pre-1.8 clients which were accidentally
258 # sending raw binary nodes rather than utf-8-encoded hex
260 # sending raw binary nodes rather than utf-8-encoded hex
259 if len(new) == 20 and new.encode('string-escape') != new:
261 if len(new) == 20 and new.encode('string-escape') != new:
260 # looks like it could be a binary node
262 # looks like it could be a binary node
261 try:
263 try:
262 u = new.decode('utf-8')
264 u = new.decode('utf-8')
263 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
265 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
264 except UnicodeDecodeError:
266 except UnicodeDecodeError:
265 pass # binary, leave unmodified
267 pass # binary, leave unmodified
266 else:
268 else:
267 new = encoding.tolocal(new) # normal path
269 new = encoding.tolocal(new) # normal path
268
270
269 r = pushkeymod.push(repo,
271 r = pushkeymod.push(repo,
270 encoding.tolocal(namespace), encoding.tolocal(key),
272 encoding.tolocal(namespace), encoding.tolocal(key),
271 encoding.tolocal(old), new)
273 encoding.tolocal(old), new)
272 return '%s\n' % int(r)
274 return '%s\n' % int(r)
273
275
274 def _allowstream(ui):
276 def _allowstream(ui):
275 return ui.configbool('server', 'uncompressed', True, untrusted=True)
277 return ui.configbool('server', 'uncompressed', True, untrusted=True)
276
278
277 def stream(repo, proto):
279 def stream(repo, proto):
278 '''If the server supports streaming clone, it advertises the "stream"
280 '''If the server supports streaming clone, it advertises the "stream"
279 capability with a value representing the version and flags of the repo
281 capability with a value representing the version and flags of the repo
280 it is serving. Client checks to see if it understands the format.
282 it is serving. Client checks to see if it understands the format.
281
283
282 The format is simple: the server writes out a line with the amount
284 The format is simple: the server writes out a line with the amount
283 of files, then the total amount of bytes to be transfered (separated
285 of files, then the total amount of bytes to be transfered (separated
284 by a space). Then, for each file, the server first writes the filename
286 by a space). Then, for each file, the server first writes the filename
285 and filesize (separated by the null character), then the file contents.
287 and filesize (separated by the null character), then the file contents.
286 '''
288 '''
287
289
288 if not _allowstream(repo.ui):
290 if not _allowstream(repo.ui):
289 return '1\n'
291 return '1\n'
290
292
291 entries = []
293 entries = []
292 total_bytes = 0
294 total_bytes = 0
293 try:
295 try:
294 # get consistent snapshot of repo, lock during scan
296 # get consistent snapshot of repo, lock during scan
295 lock = repo.lock()
297 lock = repo.lock()
296 try:
298 try:
297 repo.ui.debug('scanning\n')
299 repo.ui.debug('scanning\n')
298 for name, ename, size in repo.store.walk():
300 for name, ename, size in repo.store.walk():
299 entries.append((name, size))
301 entries.append((name, size))
300 total_bytes += size
302 total_bytes += size
301 finally:
303 finally:
302 lock.release()
304 lock.release()
303 except error.LockError:
305 except error.LockError:
304 return '2\n' # error: 2
306 return '2\n' # error: 2
305
307
306 def streamer(repo, entries, total):
308 def streamer(repo, entries, total):
307 '''stream out all metadata files in repository.'''
309 '''stream out all metadata files in repository.'''
308 yield '0\n' # success
310 yield '0\n' # success
309 repo.ui.debug('%d files, %d bytes to transfer\n' %
311 repo.ui.debug('%d files, %d bytes to transfer\n' %
310 (len(entries), total_bytes))
312 (len(entries), total_bytes))
311 yield '%d %d\n' % (len(entries), total_bytes)
313 yield '%d %d\n' % (len(entries), total_bytes)
312 for name, size in entries:
314 for name, size in entries:
313 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
315 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
314 # partially encode name over the wire for backwards compat
316 # partially encode name over the wire for backwards compat
315 yield '%s\0%d\n' % (store.encodedir(name), size)
317 yield '%s\0%d\n' % (store.encodedir(name), size)
316 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
318 for chunk in util.filechunkiter(repo.sopener(name), limit=size):
317 yield chunk
319 yield chunk
318
320
319 return streamres(streamer(repo, entries, total_bytes))
321 return streamres(streamer(repo, entries, total_bytes))
320
322
321 def unbundle(repo, proto, heads):
323 def unbundle(repo, proto, heads):
322 their_heads = decodelist(heads)
324 their_heads = decodelist(heads)
323
325
324 def check_heads():
326 def check_heads():
325 heads = repo.heads()
327 heads = repo.heads()
326 return their_heads == ['force'] or their_heads == heads
328 return their_heads == ['force'] or their_heads == heads
327
329
328 proto.redirect()
330 proto.redirect()
329
331
330 # fail early if possible
332 # fail early if possible
331 if not check_heads():
333 if not check_heads():
332 return pusherr('unsynced changes')
334 return pusherr('unsynced changes')
333
335
334 # write bundle data to temporary file because it can be big
336 # write bundle data to temporary file because it can be big
335 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
337 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
336 fp = os.fdopen(fd, 'wb+')
338 fp = os.fdopen(fd, 'wb+')
337 r = 0
339 r = 0
338 try:
340 try:
339 proto.getfile(fp)
341 proto.getfile(fp)
340 lock = repo.lock()
342 lock = repo.lock()
341 try:
343 try:
342 if not check_heads():
344 if not check_heads():
343 # someone else committed/pushed/unbundled while we
345 # someone else committed/pushed/unbundled while we
344 # were transferring data
346 # were transferring data
345 return pusherr('unsynced changes')
347 return pusherr('unsynced changes')
346
348
347 # push can proceed
349 # push can proceed
348 fp.seek(0)
350 fp.seek(0)
349 gen = changegroupmod.readbundle(fp, None)
351 gen = changegroupmod.readbundle(fp, None)
350
352
351 try:
353 try:
352 r = repo.addchangegroup(gen, 'serve', proto._client(),
354 r = repo.addchangegroup(gen, 'serve', proto._client(),
353 lock=lock)
355 lock=lock)
354 except util.Abort, inst:
356 except util.Abort, inst:
355 sys.stderr.write("abort: %s\n" % inst)
357 sys.stderr.write("abort: %s\n" % inst)
356 finally:
358 finally:
357 lock.release()
359 lock.release()
358 return pushres(r)
360 return pushres(r)
359
361
360 finally:
362 finally:
361 fp.close()
363 fp.close()
362 os.unlink(tempname)
364 os.unlink(tempname)
363
365
364 commands = {
366 commands = {
365 'between': (between, 'pairs'),
367 'between': (between, 'pairs'),
366 'branchmap': (branchmap, ''),
368 'branchmap': (branchmap, ''),
367 'branches': (branches, 'nodes'),
369 'branches': (branches, 'nodes'),
368 'capabilities': (capabilities, ''),
370 'capabilities': (capabilities, ''),
369 'changegroup': (changegroup, 'roots'),
371 'changegroup': (changegroup, 'roots'),
370 'changegroupsubset': (changegroupsubset, 'bases heads'),
372 'changegroupsubset': (changegroupsubset, 'bases heads'),
371 'debugwireargs': (debugwireargs, 'one two *'),
373 'debugwireargs': (debugwireargs, 'one two *'),
372 'heads': (heads, ''),
374 'heads': (heads, ''),
373 'hello': (hello, ''),
375 'hello': (hello, ''),
374 'listkeys': (listkeys, 'namespace'),
376 'listkeys': (listkeys, 'namespace'),
375 'lookup': (lookup, 'key'),
377 'lookup': (lookup, 'key'),
376 'pushkey': (pushkey, 'namespace key old new'),
378 'pushkey': (pushkey, 'namespace key old new'),
377 'stream_out': (stream, ''),
379 'stream_out': (stream, ''),
378 'unbundle': (unbundle, 'heads'),
380 'unbundle': (unbundle, 'heads'),
379 }
381 }
General Comments 0
You need to be logged in to leave comments. Login now