##// END OF EJS Templates
listkey: display the size of the listkey payload in a debug message...
Pierre-Yves David -
r25339:c50655b9 default
parent child Browse files
Show More
@@ -1,833 +1,835 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, bundle2, pushkey as pushkeymod
12 12 import peer, error, encoding, util, exchange
13 13
14 14
15 15 class abstractserverproto(object):
16 16 """abstract class that summarizes the protocol API
17 17
18 18 Used as reference and documentation.
19 19 """
20 20
21 21 def getargs(self, args):
22 22 """return the value for arguments in <args>
23 23
24 24 returns a list of values (same order as <args>)"""
25 25 raise NotImplementedError()
26 26
27 27 def getfile(self, fp):
28 28 """write the whole content of a file into a file like object
29 29
30 30 The file is in the form::
31 31
32 32 (<chunk-size>\n<chunk>)+0\n
33 33
34 34 chunk size is the ascii version of the int.
35 35 """
36 36 raise NotImplementedError()
37 37
38 38 def redirect(self):
39 39 """may setup interception for stdout and stderr
40 40
41 41 See also the `restore` method."""
42 42 raise NotImplementedError()
43 43
44 44 # If the `redirect` function does install interception, the `restore`
45 45 # function MUST be defined. If interception is not used, this function
46 46 # MUST NOT be defined.
47 47 #
48 48 # left commented here on purpose
49 49 #
50 50 #def restore(self):
51 51 # """reinstall previous stdout and stderr and return intercepted stdout
52 52 # """
53 53 # raise NotImplementedError()
54 54
55 55 def groupchunks(self, cg):
56 56 """return 4096 chunks from a changegroup object
57 57
58 58 Some protocols may have compressed the contents."""
59 59 raise NotImplementedError()
60 60
61 61 # abstract batching support
62 62
63 63 class future(object):
64 64 '''placeholder for a value to be set later'''
65 65 def set(self, value):
66 66 if util.safehasattr(self, 'value'):
67 67 raise error.RepoError("future is already set")
68 68 self.value = value
69 69
70 70 class batcher(object):
71 71 '''base class for batches of commands submittable in a single request
72 72
73 73 All methods invoked on instances of this class are simply queued and
74 74 return a a future for the result. Once you call submit(), all the queued
75 75 calls are performed and the results set in their respective futures.
76 76 '''
77 77 def __init__(self):
78 78 self.calls = []
79 79 def __getattr__(self, name):
80 80 def call(*args, **opts):
81 81 resref = future()
82 82 self.calls.append((name, args, opts, resref,))
83 83 return resref
84 84 return call
85 85 def submit(self):
86 86 pass
87 87
88 88 class localbatch(batcher):
89 89 '''performs the queued calls directly'''
90 90 def __init__(self, local):
91 91 batcher.__init__(self)
92 92 self.local = local
93 93 def submit(self):
94 94 for name, args, opts, resref in self.calls:
95 95 resref.set(getattr(self.local, name)(*args, **opts))
96 96
97 97 class remotebatch(batcher):
98 98 '''batches the queued calls; uses as few roundtrips as possible'''
99 99 def __init__(self, remote):
100 100 '''remote must support _submitbatch(encbatch) and
101 101 _submitone(op, encargs)'''
102 102 batcher.__init__(self)
103 103 self.remote = remote
104 104 def submit(self):
105 105 req, rsp = [], []
106 106 for name, args, opts, resref in self.calls:
107 107 mtd = getattr(self.remote, name)
108 108 batchablefn = getattr(mtd, 'batchable', None)
109 109 if batchablefn is not None:
110 110 batchable = batchablefn(mtd.im_self, *args, **opts)
111 111 encargsorres, encresref = batchable.next()
112 112 if encresref:
113 113 req.append((name, encargsorres,))
114 114 rsp.append((batchable, encresref, resref,))
115 115 else:
116 116 resref.set(encargsorres)
117 117 else:
118 118 if req:
119 119 self._submitreq(req, rsp)
120 120 req, rsp = [], []
121 121 resref.set(mtd(*args, **opts))
122 122 if req:
123 123 self._submitreq(req, rsp)
124 124 def _submitreq(self, req, rsp):
125 125 encresults = self.remote._submitbatch(req)
126 126 for encres, r in zip(encresults, rsp):
127 127 batchable, encresref, resref = r
128 128 encresref.set(encres)
129 129 resref.set(batchable.next())
130 130
131 131 def batchable(f):
132 132 '''annotation for batchable methods
133 133
134 134 Such methods must implement a coroutine as follows:
135 135
136 136 @batchable
137 137 def sample(self, one, two=None):
138 138 # Handle locally computable results first:
139 139 if not one:
140 140 yield "a local result", None
141 141 # Build list of encoded arguments suitable for your wire protocol:
142 142 encargs = [('one', encode(one),), ('two', encode(two),)]
143 143 # Create future for injection of encoded result:
144 144 encresref = future()
145 145 # Return encoded arguments and future:
146 146 yield encargs, encresref
147 147 # Assuming the future to be filled with the result from the batched
148 148 # request now. Decode it:
149 149 yield decode(encresref.value)
150 150
151 151 The decorator returns a function which wraps this coroutine as a plain
152 152 method, but adds the original method as an attribute called "batchable",
153 153 which is used by remotebatch to split the call into separate encoding and
154 154 decoding phases.
155 155 '''
156 156 def plain(*args, **opts):
157 157 batchable = f(*args, **opts)
158 158 encargsorres, encresref = batchable.next()
159 159 if not encresref:
160 160 return encargsorres # a local result in this case
161 161 self = args[0]
162 162 encresref.set(self._submitone(f.func_name, encargsorres))
163 163 return batchable.next()
164 164 setattr(plain, 'batchable', f)
165 165 return plain
166 166
167 167 # list of nodes encoding / decoding
168 168
169 169 def decodelist(l, sep=' '):
170 170 if l:
171 171 return map(bin, l.split(sep))
172 172 return []
173 173
174 174 def encodelist(l, sep=' '):
175 175 try:
176 176 return sep.join(map(hex, l))
177 177 except TypeError:
178 178 print l
179 179 raise
180 180
181 181 # batched call argument encoding
182 182
183 183 def escapearg(plain):
184 184 return (plain
185 185 .replace(':', '::')
186 186 .replace(',', ':,')
187 187 .replace(';', ':;')
188 188 .replace('=', ':='))
189 189
190 190 def unescapearg(escaped):
191 191 return (escaped
192 192 .replace(':=', '=')
193 193 .replace(':;', ';')
194 194 .replace(':,', ',')
195 195 .replace('::', ':'))
196 196
197 197 # mapping of options accepted by getbundle and their types
198 198 #
199 199 # Meant to be extended by extensions. It is extensions responsibility to ensure
200 200 # such options are properly processed in exchange.getbundle.
201 201 #
202 202 # supported types are:
203 203 #
204 204 # :nodes: list of binary nodes
205 205 # :csv: list of comma-separated values
206 206 # :plain: string with no transformation needed.
207 207 gboptsmap = {'heads': 'nodes',
208 208 'common': 'nodes',
209 209 'obsmarkers': 'boolean',
210 210 'bundlecaps': 'csv',
211 211 'listkeys': 'csv',
212 212 'cg': 'boolean'}
213 213
214 214 # client side
215 215
216 216 class wirepeer(peer.peerrepository):
217 217
218 218 def batch(self):
219 219 return remotebatch(self)
220 220 def _submitbatch(self, req):
221 221 cmds = []
222 222 for op, argsdict in req:
223 223 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
224 224 cmds.append('%s %s' % (op, args))
225 225 rsp = self._call("batch", cmds=';'.join(cmds))
226 226 return rsp.split(';')
227 227 def _submitone(self, op, args):
228 228 return self._call(op, **args)
229 229
230 230 @batchable
231 231 def lookup(self, key):
232 232 self.requirecap('lookup', _('look up remote revision'))
233 233 f = future()
234 234 yield {'key': encoding.fromlocal(key)}, f
235 235 d = f.value
236 236 success, data = d[:-1].split(" ", 1)
237 237 if int(success):
238 238 yield bin(data)
239 239 self._abort(error.RepoError(data))
240 240
241 241 @batchable
242 242 def heads(self):
243 243 f = future()
244 244 yield {}, f
245 245 d = f.value
246 246 try:
247 247 yield decodelist(d[:-1])
248 248 except ValueError:
249 249 self._abort(error.ResponseError(_("unexpected response:"), d))
250 250
251 251 @batchable
252 252 def known(self, nodes):
253 253 f = future()
254 254 yield {'nodes': encodelist(nodes)}, f
255 255 d = f.value
256 256 try:
257 257 yield [bool(int(b)) for b in d]
258 258 except ValueError:
259 259 self._abort(error.ResponseError(_("unexpected response:"), d))
260 260
261 261 @batchable
262 262 def branchmap(self):
263 263 f = future()
264 264 yield {}, f
265 265 d = f.value
266 266 try:
267 267 branchmap = {}
268 268 for branchpart in d.splitlines():
269 269 branchname, branchheads = branchpart.split(' ', 1)
270 270 branchname = encoding.tolocal(urllib.unquote(branchname))
271 271 branchheads = decodelist(branchheads)
272 272 branchmap[branchname] = branchheads
273 273 yield branchmap
274 274 except TypeError:
275 275 self._abort(error.ResponseError(_("unexpected response:"), d))
276 276
277 277 def branches(self, nodes):
278 278 n = encodelist(nodes)
279 279 d = self._call("branches", nodes=n)
280 280 try:
281 281 br = [tuple(decodelist(b)) for b in d.splitlines()]
282 282 return br
283 283 except ValueError:
284 284 self._abort(error.ResponseError(_("unexpected response:"), d))
285 285
286 286 def between(self, pairs):
287 287 batch = 8 # avoid giant requests
288 288 r = []
289 289 for i in xrange(0, len(pairs), batch):
290 290 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
291 291 d = self._call("between", pairs=n)
292 292 try:
293 293 r.extend(l and decodelist(l) or [] for l in d.splitlines())
294 294 except ValueError:
295 295 self._abort(error.ResponseError(_("unexpected response:"), d))
296 296 return r
297 297
298 298 @batchable
299 299 def pushkey(self, namespace, key, old, new):
300 300 if not self.capable('pushkey'):
301 301 yield False, None
302 302 f = future()
303 303 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
304 304 yield {'namespace': encoding.fromlocal(namespace),
305 305 'key': encoding.fromlocal(key),
306 306 'old': encoding.fromlocal(old),
307 307 'new': encoding.fromlocal(new)}, f
308 308 d = f.value
309 309 d, output = d.split('\n', 1)
310 310 try:
311 311 d = bool(int(d))
312 312 except ValueError:
313 313 raise error.ResponseError(
314 314 _('push failed (unexpected response):'), d)
315 315 for l in output.splitlines(True):
316 316 self.ui.status(_('remote: '), l)
317 317 yield d
318 318
319 319 @batchable
320 320 def listkeys(self, namespace):
321 321 if not self.capable('pushkey'):
322 322 yield {}, None
323 323 f = future()
324 324 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
325 325 yield {'namespace': encoding.fromlocal(namespace)}, f
326 326 d = f.value
327 self.ui.debug('received listkey for "%s": %i bytes\n'
328 % (namespace, len(d)))
327 329 yield pushkeymod.decodekeys(d)
328 330
329 331 def stream_out(self):
330 332 return self._callstream('stream_out')
331 333
332 334 def changegroup(self, nodes, kind):
333 335 n = encodelist(nodes)
334 336 f = self._callcompressable("changegroup", roots=n)
335 337 return changegroupmod.cg1unpacker(f, 'UN')
336 338
337 339 def changegroupsubset(self, bases, heads, kind):
338 340 self.requirecap('changegroupsubset', _('look up remote changes'))
339 341 bases = encodelist(bases)
340 342 heads = encodelist(heads)
341 343 f = self._callcompressable("changegroupsubset",
342 344 bases=bases, heads=heads)
343 345 return changegroupmod.cg1unpacker(f, 'UN')
344 346
345 347 def getbundle(self, source, **kwargs):
346 348 self.requirecap('getbundle', _('look up remote changes'))
347 349 opts = {}
348 350 bundlecaps = kwargs.get('bundlecaps')
349 351 if bundlecaps is not None:
350 352 kwargs['bundlecaps'] = sorted(bundlecaps)
351 353 else:
352 354 bundlecaps = () # kwargs could have it to None
353 355 for key, value in kwargs.iteritems():
354 356 if value is None:
355 357 continue
356 358 keytype = gboptsmap.get(key)
357 359 if keytype is None:
358 360 assert False, 'unexpected'
359 361 elif keytype == 'nodes':
360 362 value = encodelist(value)
361 363 elif keytype == 'csv':
362 364 value = ','.join(value)
363 365 elif keytype == 'boolean':
364 366 value = '%i' % bool(value)
365 367 elif keytype != 'plain':
366 368 raise KeyError('unknown getbundle option type %s'
367 369 % keytype)
368 370 opts[key] = value
369 371 f = self._callcompressable("getbundle", **opts)
370 372 if any((cap.startswith('HG2') for cap in bundlecaps)):
371 373 return bundle2.getunbundler(self.ui, f)
372 374 else:
373 375 return changegroupmod.cg1unpacker(f, 'UN')
374 376
375 377 def unbundle(self, cg, heads, source):
376 378 '''Send cg (a readable file-like object representing the
377 379 changegroup to push, typically a chunkbuffer object) to the
378 380 remote server as a bundle.
379 381
380 382 When pushing a bundle10 stream, return an integer indicating the
381 383 result of the push (see localrepository.addchangegroup()).
382 384
383 385 When pushing a bundle20 stream, return a bundle20 stream.'''
384 386
385 387 if heads != ['force'] and self.capable('unbundlehash'):
386 388 heads = encodelist(['hashed',
387 389 util.sha1(''.join(sorted(heads))).digest()])
388 390 else:
389 391 heads = encodelist(heads)
390 392
391 393 if util.safehasattr(cg, 'deltaheader'):
392 394 # this a bundle10, do the old style call sequence
393 395 ret, output = self._callpush("unbundle", cg, heads=heads)
394 396 if ret == "":
395 397 raise error.ResponseError(
396 398 _('push failed:'), output)
397 399 try:
398 400 ret = int(ret)
399 401 except ValueError:
400 402 raise error.ResponseError(
401 403 _('push failed (unexpected response):'), ret)
402 404
403 405 for l in output.splitlines(True):
404 406 self.ui.status(_('remote: '), l)
405 407 else:
406 408 # bundle2 push. Send a stream, fetch a stream.
407 409 stream = self._calltwowaystream('unbundle', cg, heads=heads)
408 410 ret = bundle2.getunbundler(self.ui, stream)
409 411 return ret
410 412
411 413 def debugwireargs(self, one, two, three=None, four=None, five=None):
412 414 # don't pass optional arguments left at their default value
413 415 opts = {}
414 416 if three is not None:
415 417 opts['three'] = three
416 418 if four is not None:
417 419 opts['four'] = four
418 420 return self._call('debugwireargs', one=one, two=two, **opts)
419 421
420 422 def _call(self, cmd, **args):
421 423 """execute <cmd> on the server
422 424
423 425 The command is expected to return a simple string.
424 426
425 427 returns the server reply as a string."""
426 428 raise NotImplementedError()
427 429
428 430 def _callstream(self, cmd, **args):
429 431 """execute <cmd> on the server
430 432
431 433 The command is expected to return a stream.
432 434
433 435 returns the server reply as a file like object."""
434 436 raise NotImplementedError()
435 437
436 438 def _callcompressable(self, cmd, **args):
437 439 """execute <cmd> on the server
438 440
439 441 The command is expected to return a stream.
440 442
441 443 The stream may have been compressed in some implementations. This
442 444 function takes care of the decompression. This is the only difference
443 445 with _callstream.
444 446
445 447 returns the server reply as a file like object.
446 448 """
447 449 raise NotImplementedError()
448 450
449 451 def _callpush(self, cmd, fp, **args):
450 452 """execute a <cmd> on server
451 453
452 454 The command is expected to be related to a push. Push has a special
453 455 return method.
454 456
455 457 returns the server reply as a (ret, output) tuple. ret is either
456 458 empty (error) or a stringified int.
457 459 """
458 460 raise NotImplementedError()
459 461
460 462 def _calltwowaystream(self, cmd, fp, **args):
461 463 """execute <cmd> on server
462 464
463 465 The command will send a stream to the server and get a stream in reply.
464 466 """
465 467 raise NotImplementedError()
466 468
467 469 def _abort(self, exception):
468 470 """clearly abort the wire protocol connection and raise the exception
469 471 """
470 472 raise NotImplementedError()
471 473
472 474 # server side
473 475
474 476 # wire protocol command can either return a string or one of these classes.
475 477 class streamres(object):
476 478 """wireproto reply: binary stream
477 479
478 480 The call was successful and the result is a stream.
479 481 Iterate on the `self.gen` attribute to retrieve chunks.
480 482 """
481 483 def __init__(self, gen):
482 484 self.gen = gen
483 485
484 486 class pushres(object):
485 487 """wireproto reply: success with simple integer return
486 488
487 489 The call was successful and returned an integer contained in `self.res`.
488 490 """
489 491 def __init__(self, res):
490 492 self.res = res
491 493
492 494 class pusherr(object):
493 495 """wireproto reply: failure
494 496
495 497 The call failed. The `self.res` attribute contains the error message.
496 498 """
497 499 def __init__(self, res):
498 500 self.res = res
499 501
500 502 class ooberror(object):
501 503 """wireproto reply: failure of a batch of operation
502 504
503 505 Something failed during a batch call. The error message is stored in
504 506 `self.message`.
505 507 """
506 508 def __init__(self, message):
507 509 self.message = message
508 510
509 511 def dispatch(repo, proto, command):
510 512 repo = repo.filtered("served")
511 513 func, spec = commands[command]
512 514 args = proto.getargs(spec)
513 515 return func(repo, proto, *args)
514 516
515 517 def options(cmd, keys, others):
516 518 opts = {}
517 519 for k in keys:
518 520 if k in others:
519 521 opts[k] = others[k]
520 522 del others[k]
521 523 if others:
522 524 sys.stderr.write("warning: %s ignored unexpected arguments %s\n"
523 525 % (cmd, ",".join(others)))
524 526 return opts
525 527
526 528 # list of commands
527 529 commands = {}
528 530
529 531 def wireprotocommand(name, args=''):
530 532 """decorator for wire protocol command"""
531 533 def register(func):
532 534 commands[name] = (func, args)
533 535 return func
534 536 return register
535 537
536 538 @wireprotocommand('batch', 'cmds *')
537 539 def batch(repo, proto, cmds, others):
538 540 repo = repo.filtered("served")
539 541 res = []
540 542 for pair in cmds.split(';'):
541 543 op, args = pair.split(' ', 1)
542 544 vals = {}
543 545 for a in args.split(','):
544 546 if a:
545 547 n, v = a.split('=')
546 548 vals[n] = unescapearg(v)
547 549 func, spec = commands[op]
548 550 if spec:
549 551 keys = spec.split()
550 552 data = {}
551 553 for k in keys:
552 554 if k == '*':
553 555 star = {}
554 556 for key in vals.keys():
555 557 if key not in keys:
556 558 star[key] = vals[key]
557 559 data['*'] = star
558 560 else:
559 561 data[k] = vals[k]
560 562 result = func(repo, proto, *[data[k] for k in keys])
561 563 else:
562 564 result = func(repo, proto)
563 565 if isinstance(result, ooberror):
564 566 return result
565 567 res.append(escapearg(result))
566 568 return ';'.join(res)
567 569
568 570 @wireprotocommand('between', 'pairs')
569 571 def between(repo, proto, pairs):
570 572 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
571 573 r = []
572 574 for b in repo.between(pairs):
573 575 r.append(encodelist(b) + "\n")
574 576 return "".join(r)
575 577
576 578 @wireprotocommand('branchmap')
577 579 def branchmap(repo, proto):
578 580 branchmap = repo.branchmap()
579 581 heads = []
580 582 for branch, nodes in branchmap.iteritems():
581 583 branchname = urllib.quote(encoding.fromlocal(branch))
582 584 branchnodes = encodelist(nodes)
583 585 heads.append('%s %s' % (branchname, branchnodes))
584 586 return '\n'.join(heads)
585 587
586 588 @wireprotocommand('branches', 'nodes')
587 589 def branches(repo, proto, nodes):
588 590 nodes = decodelist(nodes)
589 591 r = []
590 592 for b in repo.branches(nodes):
591 593 r.append(encodelist(b) + "\n")
592 594 return "".join(r)
593 595
594 596
595 597 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
596 598 'known', 'getbundle', 'unbundlehash', 'batch']
597 599
598 600 def _capabilities(repo, proto):
599 601 """return a list of capabilities for a repo
600 602
601 603 This function exists to allow extensions to easily wrap capabilities
602 604 computation
603 605
604 606 - returns a lists: easy to alter
605 607 - change done here will be propagated to both `capabilities` and `hello`
606 608 command without any other action needed.
607 609 """
608 610 # copy to prevent modification of the global list
609 611 caps = list(wireprotocaps)
610 612 if _allowstream(repo.ui):
611 613 if repo.ui.configbool('server', 'preferuncompressed', False):
612 614 caps.append('stream-preferred')
613 615 requiredformats = repo.requirements & repo.supportedformats
614 616 # if our local revlogs are just revlogv1, add 'stream' cap
615 617 if not requiredformats - set(('revlogv1',)):
616 618 caps.append('stream')
617 619 # otherwise, add 'streamreqs' detailing our local revlog format
618 620 else:
619 621 caps.append('streamreqs=%s' % ','.join(requiredformats))
620 622 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
621 623 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
622 624 caps.append('bundle2=' + urllib.quote(capsblob))
623 625 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
624 626 caps.append('httpheader=1024')
625 627 return caps
626 628
627 629 # If you are writing an extension and consider wrapping this function. Wrap
628 630 # `_capabilities` instead.
629 631 @wireprotocommand('capabilities')
630 632 def capabilities(repo, proto):
631 633 return ' '.join(_capabilities(repo, proto))
632 634
633 635 @wireprotocommand('changegroup', 'roots')
634 636 def changegroup(repo, proto, roots):
635 637 nodes = decodelist(roots)
636 638 cg = changegroupmod.changegroup(repo, nodes, 'serve')
637 639 return streamres(proto.groupchunks(cg))
638 640
639 641 @wireprotocommand('changegroupsubset', 'bases heads')
640 642 def changegroupsubset(repo, proto, bases, heads):
641 643 bases = decodelist(bases)
642 644 heads = decodelist(heads)
643 645 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
644 646 return streamres(proto.groupchunks(cg))
645 647
646 648 @wireprotocommand('debugwireargs', 'one two *')
647 649 def debugwireargs(repo, proto, one, two, others):
648 650 # only accept optional args from the known set
649 651 opts = options('debugwireargs', ['three', 'four'], others)
650 652 return repo.debugwireargs(one, two, **opts)
651 653
652 654 # List of options accepted by getbundle.
653 655 #
654 656 # Meant to be extended by extensions. It is the extension's responsibility to
655 657 # ensure such options are properly processed in exchange.getbundle.
656 658 gboptslist = ['heads', 'common', 'bundlecaps']
657 659
658 660 @wireprotocommand('getbundle', '*')
659 661 def getbundle(repo, proto, others):
660 662 opts = options('getbundle', gboptsmap.keys(), others)
661 663 for k, v in opts.iteritems():
662 664 keytype = gboptsmap[k]
663 665 if keytype == 'nodes':
664 666 opts[k] = decodelist(v)
665 667 elif keytype == 'csv':
666 668 opts[k] = set(v.split(','))
667 669 elif keytype == 'boolean':
668 670 opts[k] = bool(v)
669 671 elif keytype != 'plain':
670 672 raise KeyError('unknown getbundle option type %s'
671 673 % keytype)
672 674 cg = exchange.getbundle(repo, 'serve', **opts)
673 675 return streamres(proto.groupchunks(cg))
674 676
675 677 @wireprotocommand('heads')
676 678 def heads(repo, proto):
677 679 h = repo.heads()
678 680 return encodelist(h) + "\n"
679 681
680 682 @wireprotocommand('hello')
681 683 def hello(repo, proto):
682 684 '''the hello command returns a set of lines describing various
683 685 interesting things about the server, in an RFC822-like format.
684 686 Currently the only one defined is "capabilities", which
685 687 consists of a line in the form:
686 688
687 689 capabilities: space separated list of tokens
688 690 '''
689 691 return "capabilities: %s\n" % (capabilities(repo, proto))
690 692
691 693 @wireprotocommand('listkeys', 'namespace')
692 694 def listkeys(repo, proto, namespace):
693 695 d = repo.listkeys(encoding.tolocal(namespace)).items()
694 696 return pushkeymod.encodekeys(d)
695 697
696 698 @wireprotocommand('lookup', 'key')
697 699 def lookup(repo, proto, key):
698 700 try:
699 701 k = encoding.tolocal(key)
700 702 c = repo[k]
701 703 r = c.hex()
702 704 success = 1
703 705 except Exception, inst:
704 706 r = str(inst)
705 707 success = 0
706 708 return "%s %s\n" % (success, r)
707 709
708 710 @wireprotocommand('known', 'nodes *')
709 711 def known(repo, proto, nodes, others):
710 712 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
711 713
712 714 @wireprotocommand('pushkey', 'namespace key old new')
713 715 def pushkey(repo, proto, namespace, key, old, new):
714 716 # compatibility with pre-1.8 clients which were accidentally
715 717 # sending raw binary nodes rather than utf-8-encoded hex
716 718 if len(new) == 20 and new.encode('string-escape') != new:
717 719 # looks like it could be a binary node
718 720 try:
719 721 new.decode('utf-8')
720 722 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
721 723 except UnicodeDecodeError:
722 724 pass # binary, leave unmodified
723 725 else:
724 726 new = encoding.tolocal(new) # normal path
725 727
726 728 if util.safehasattr(proto, 'restore'):
727 729
728 730 proto.redirect()
729 731
730 732 try:
731 733 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
732 734 encoding.tolocal(old), new) or False
733 735 except util.Abort:
734 736 r = False
735 737
736 738 output = proto.restore()
737 739
738 740 return '%s\n%s' % (int(r), output)
739 741
740 742 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
741 743 encoding.tolocal(old), new)
742 744 return '%s\n' % int(r)
743 745
744 746 def _allowstream(ui):
745 747 return ui.configbool('server', 'uncompressed', True, untrusted=True)
746 748
747 749 @wireprotocommand('stream_out')
748 750 def stream(repo, proto):
749 751 '''If the server supports streaming clone, it advertises the "stream"
750 752 capability with a value representing the version and flags of the repo
751 753 it is serving. Client checks to see if it understands the format.
752 754 '''
753 755 if not _allowstream(repo.ui):
754 756 return '1\n'
755 757
756 758 def getstream(it):
757 759 yield '0\n'
758 760 for chunk in it:
759 761 yield chunk
760 762
761 763 try:
762 764 # LockError may be raised before the first result is yielded. Don't
763 765 # emit output until we're sure we got the lock successfully.
764 766 it = exchange.generatestreamclone(repo)
765 767 return streamres(getstream(it))
766 768 except error.LockError:
767 769 return '2\n'
768 770
769 771 @wireprotocommand('unbundle', 'heads')
770 772 def unbundle(repo, proto, heads):
771 773 their_heads = decodelist(heads)
772 774
773 775 try:
774 776 proto.redirect()
775 777
776 778 exchange.check_heads(repo, their_heads, 'preparing changes')
777 779
778 780 # write bundle data to temporary file because it can be big
779 781 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
780 782 fp = os.fdopen(fd, 'wb+')
781 783 r = 0
782 784 try:
783 785 proto.getfile(fp)
784 786 fp.seek(0)
785 787 gen = exchange.readbundle(repo.ui, fp, None)
786 788 r = exchange.unbundle(repo, gen, their_heads, 'serve',
787 789 proto._client())
788 790 if util.safehasattr(r, 'addpart'):
789 791 # The return looks streamable, we are in the bundle2 case and
790 792 # should return a stream.
791 793 return streamres(r.getchunks())
792 794 return pushres(r)
793 795
794 796 finally:
795 797 fp.close()
796 798 os.unlink(tempname)
797 799
798 800 except (error.BundleValueError, util.Abort, error.PushRaced), exc:
799 801 # handle non-bundle2 case first
800 802 if not getattr(exc, 'duringunbundle2', False):
801 803 try:
802 804 raise
803 805 except util.Abort:
804 806 # The old code we moved used sys.stderr directly.
805 807 # We did not change it to minimise code change.
806 808 # This need to be moved to something proper.
807 809 # Feel free to do it.
808 810 sys.stderr.write("abort: %s\n" % exc)
809 811 return pushres(0)
810 812 except error.PushRaced:
811 813 return pusherr(str(exc))
812 814
813 815 bundler = bundle2.bundle20(repo.ui)
814 816 for out in getattr(exc, '_bundle2salvagedoutput', ()):
815 817 bundler.addpart(out)
816 818 try:
817 819 raise
818 820 except error.BundleValueError, exc:
819 821 errpart = bundler.newpart('error:unsupportedcontent')
820 822 if exc.parttype is not None:
821 823 errpart.addparam('parttype', exc.parttype)
822 824 if exc.params:
823 825 errpart.addparam('params', '\0'.join(exc.params))
824 826 except util.Abort, exc:
825 827 manargs = [('message', str(exc))]
826 828 advargs = []
827 829 if exc.hint is not None:
828 830 advargs.append(('hint', exc.hint))
829 831 bundler.addpart(bundle2.bundlepart('error:abort',
830 832 manargs, advargs))
831 833 except error.PushRaced, exc:
832 834 bundler.newpart('error:pushraced', [('message', str(exc))])
833 835 return streamres(bundler.getchunks())
@@ -1,504 +1,507 b''
1 1
2 2
3 3 This test tries to exercise the ssh functionality with a dummy script
4 4
5 5 creating 'remote' repo
6 6
7 7 $ hg init remote
8 8 $ cd remote
9 9 $ echo this > foo
10 10 $ echo this > fooO
11 11 $ hg ci -A -m "init" foo fooO
12 12
13 13 insert a closed branch (issue4428)
14 14
15 15 $ hg up null
16 16 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
17 17 $ hg branch closed
18 18 marked working directory as branch closed
19 19 (branches are permanent and global, did you want a bookmark?)
20 20 $ hg ci -mc0
21 21 $ hg ci --close-branch -mc1
22 22 $ hg up -q default
23 23
24 24 configure for serving
25 25
26 26 $ cat <<EOF > .hg/hgrc
27 27 > [server]
28 28 > uncompressed = True
29 29 >
30 30 > [hooks]
31 31 > changegroup = python "$TESTDIR/printenv.py" changegroup-in-remote 0 ../dummylog
32 32 > EOF
33 33 $ cd ..
34 34
35 35 repo not found error
36 36
37 37 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/nonexistent local
38 38 remote: abort: there is no Mercurial repository here (.hg not found)!
39 39 abort: no suitable response from remote hg!
40 40 [255]
41 41
42 42 non-existent absolute path
43 43
44 44 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
45 45 remote: abort: there is no Mercurial repository here (.hg not found)!
46 46 abort: no suitable response from remote hg!
47 47 [255]
48 48
49 49 clone remote via stream
50 50
51 51 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
52 52 streaming all changes
53 53 4 files to transfer, 615 bytes of data
54 54 transferred 615 bytes in * seconds (*) (glob)
55 55 searching for changes
56 56 no changes found
57 57 updating to branch default
58 58 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 59 $ cd local-stream
60 60 $ hg verify
61 61 checking changesets
62 62 checking manifests
63 63 crosschecking files in changesets and manifests
64 64 checking files
65 65 2 files, 3 changesets, 2 total revisions
66 66 $ hg branches
67 67 default 0:1160648e36ce
68 68 $ cd ..
69 69
70 70 clone bookmarks via stream
71 71
72 72 $ hg -R local-stream book mybook
73 73 $ hg clone -e "python \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
74 74 streaming all changes
75 75 4 files to transfer, 615 bytes of data
76 76 transferred 615 bytes in * seconds (*) (glob)
77 77 searching for changes
78 78 no changes found
79 79 updating to branch default
80 80 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
81 81 $ cd stream2
82 82 $ hg book
83 83 mybook 0:1160648e36ce
84 84 $ cd ..
85 85 $ rm -rf local-stream stream2
86 86
87 87 clone remote via pull
88 88
89 89 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local
90 90 requesting all changes
91 91 adding changesets
92 92 adding manifests
93 93 adding file changes
94 94 added 3 changesets with 2 changes to 2 files
95 95 updating to branch default
96 96 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 97
98 98 verify
99 99
100 100 $ cd local
101 101 $ hg verify
102 102 checking changesets
103 103 checking manifests
104 104 crosschecking files in changesets and manifests
105 105 checking files
106 106 2 files, 3 changesets, 2 total revisions
107 107 $ echo '[hooks]' >> .hg/hgrc
108 108 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup-in-local 0 ../dummylog" >> .hg/hgrc
109 109
110 110 empty default pull
111 111
112 112 $ hg paths
113 113 default = ssh://user@dummy/remote
114 114 $ hg pull -e "python \"$TESTDIR/dummyssh\""
115 115 pulling from ssh://user@dummy/remote
116 116 searching for changes
117 117 no changes found
118 118
119 119 pull from wrong ssh URL
120 120
121 121 $ hg pull -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/doesnotexist
122 122 pulling from ssh://user@dummy/doesnotexist
123 123 remote: abort: there is no Mercurial repository here (.hg not found)!
124 124 abort: no suitable response from remote hg!
125 125 [255]
126 126
127 127 local change
128 128
129 129 $ echo bleah > foo
130 130 $ hg ci -m "add"
131 131
132 132 updating rc
133 133
134 134 $ echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc
135 135 $ echo "[ui]" >> .hg/hgrc
136 136 $ echo "ssh = python \"$TESTDIR/dummyssh\"" >> .hg/hgrc
137 137
138 138 find outgoing
139 139
140 140 $ hg out ssh://user@dummy/remote
141 141 comparing with ssh://user@dummy/remote
142 142 searching for changes
143 143 changeset: 3:a28a9d1a809c
144 144 tag: tip
145 145 parent: 0:1160648e36ce
146 146 user: test
147 147 date: Thu Jan 01 00:00:00 1970 +0000
148 148 summary: add
149 149
150 150
151 151 find incoming on the remote side
152 152
153 153 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/local
154 154 comparing with ssh://user@dummy/local
155 155 searching for changes
156 156 changeset: 3:a28a9d1a809c
157 157 tag: tip
158 158 parent: 0:1160648e36ce
159 159 user: test
160 160 date: Thu Jan 01 00:00:00 1970 +0000
161 161 summary: add
162 162
163 163
164 164 find incoming on the remote side (using absolute path)
165 165
166 166 $ hg incoming -R ../remote -e "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/`pwd`"
167 167 comparing with ssh://user@dummy/$TESTTMP/local
168 168 searching for changes
169 169 changeset: 3:a28a9d1a809c
170 170 tag: tip
171 171 parent: 0:1160648e36ce
172 172 user: test
173 173 date: Thu Jan 01 00:00:00 1970 +0000
174 174 summary: add
175 175
176 176
177 177 push
178 178
179 179 $ hg push
180 180 pushing to ssh://user@dummy/remote
181 181 searching for changes
182 182 remote: adding changesets
183 183 remote: adding manifests
184 184 remote: adding file changes
185 185 remote: added 1 changesets with 1 changes to 1 files
186 186 $ cd ../remote
187 187
188 188 check remote tip
189 189
190 190 $ hg tip
191 191 changeset: 3:a28a9d1a809c
192 192 tag: tip
193 193 parent: 0:1160648e36ce
194 194 user: test
195 195 date: Thu Jan 01 00:00:00 1970 +0000
196 196 summary: add
197 197
198 198 $ hg verify
199 199 checking changesets
200 200 checking manifests
201 201 crosschecking files in changesets and manifests
202 202 checking files
203 203 2 files, 4 changesets, 3 total revisions
204 204 $ hg cat -r tip foo
205 205 bleah
206 206 $ echo z > z
207 207 $ hg ci -A -m z z
208 208 created new head
209 209
210 210 test pushkeys and bookmarks
211 211
212 212 $ cd ../local
213 213 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote namespaces
214 214 bookmarks
215 215 namespaces
216 216 phases
217 217 $ hg book foo -r 0
218 218 $ hg out -B
219 219 comparing with ssh://user@dummy/remote
220 220 searching for changed bookmarks
221 221 foo 1160648e36ce
222 222 $ hg push -B foo
223 223 pushing to ssh://user@dummy/remote
224 224 searching for changes
225 225 no changes found
226 226 exporting bookmark foo
227 227 [1]
228 228 $ hg debugpushkey --config ui.ssh="python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote bookmarks
229 229 foo 1160648e36cec0054048a7edc4110c6f84fde594
230 230 $ hg book -f foo
231 231 $ hg push --traceback
232 232 pushing to ssh://user@dummy/remote
233 233 searching for changes
234 234 no changes found
235 235 updating bookmark foo
236 236 [1]
237 237 $ hg book -d foo
238 238 $ hg in -B
239 239 comparing with ssh://user@dummy/remote
240 240 searching for changed bookmarks
241 241 foo a28a9d1a809c
242 242 $ hg book -f -r 0 foo
243 243 $ hg pull -B foo
244 244 pulling from ssh://user@dummy/remote
245 245 no changes found
246 246 updating bookmark foo
247 247 $ hg book -d foo
248 248 $ hg push -B foo
249 249 pushing to ssh://user@dummy/remote
250 250 searching for changes
251 251 no changes found
252 252 deleting remote bookmark foo
253 253 [1]
254 254
255 255 a bad, evil hook that prints to stdout
256 256
257 257 $ cat <<EOF > $TESTTMP/badhook
258 258 > import sys
259 259 > sys.stdout.write("KABOOM\n")
260 260 > EOF
261 261
262 262 $ echo '[hooks]' >> ../remote/.hg/hgrc
263 263 $ echo "changegroup.stdout = python $TESTTMP/badhook" >> ../remote/.hg/hgrc
264 264 $ echo r > r
265 265 $ hg ci -A -m z r
266 266
267 267 push should succeed even though it has an unexpected response
268 268
269 269 $ hg push
270 270 pushing to ssh://user@dummy/remote
271 271 searching for changes
272 272 remote has heads on branch 'default' that are not known locally: 6c0482d977a3
273 273 remote: adding changesets
274 274 remote: adding manifests
275 275 remote: adding file changes
276 276 remote: added 1 changesets with 1 changes to 1 files
277 277 remote: KABOOM
278 278 $ hg -R ../remote heads
279 279 changeset: 5:1383141674ec
280 280 tag: tip
281 281 parent: 3:a28a9d1a809c
282 282 user: test
283 283 date: Thu Jan 01 00:00:00 1970 +0000
284 284 summary: z
285 285
286 286 changeset: 4:6c0482d977a3
287 287 parent: 0:1160648e36ce
288 288 user: test
289 289 date: Thu Jan 01 00:00:00 1970 +0000
290 290 summary: z
291 291
292 292
293 293 clone bookmarks
294 294
295 295 $ hg -R ../remote bookmark test
296 296 $ hg -R ../remote bookmarks
297 297 * test 4:6c0482d977a3
298 298 $ hg clone -e "python \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote local-bookmarks
299 299 requesting all changes
300 300 adding changesets
301 301 adding manifests
302 302 adding file changes
303 303 added 6 changesets with 5 changes to 4 files (+1 heads)
304 304 updating to branch default
305 305 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 306 $ hg -R local-bookmarks bookmarks
307 307 test 4:6c0482d977a3
308 308
309 309 passwords in ssh urls are not supported
310 310 (we use a glob here because different Python versions give different
311 311 results here)
312 312
313 313 $ hg push ssh://user:erroneouspwd@dummy/remote
314 314 pushing to ssh://user:*@dummy/remote (glob)
315 315 abort: password in URL not supported!
316 316 [255]
317 317
318 318 $ cd ..
319 319
320 320 hide outer repo
321 321 $ hg init
322 322
323 323 Test remote paths with spaces (issue2983):
324 324
325 325 $ hg init --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
326 326 $ touch "$TESTTMP/a repo/test"
327 327 $ hg -R 'a repo' commit -A -m "test"
328 328 adding test
329 329 $ hg -R 'a repo' tag tag
330 330 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
331 331 73649e48688a
332 332
333 333 $ hg id --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo#noNoNO"
334 334 abort: unknown revision 'noNoNO'!
335 335 [255]
336 336
337 337 Test (non-)escaping of remote paths with spaces when cloning (issue3145):
338 338
339 339 $ hg clone --ssh "python \"$TESTDIR/dummyssh\"" "ssh://user@dummy/a repo"
340 340 destination directory: a repo
341 341 abort: destination 'a repo' is not empty
342 342 [255]
343 343
344 344 Test hg-ssh using a helper script that will restore PYTHONPATH (which might
345 345 have been cleared by a hg.exe wrapper) and invoke hg-ssh with the right
346 346 parameters:
347 347
348 348 $ cat > ssh.sh << EOF
349 349 > userhost="\$1"
350 350 > SSH_ORIGINAL_COMMAND="\$2"
351 351 > export SSH_ORIGINAL_COMMAND
352 352 > PYTHONPATH="$PYTHONPATH"
353 353 > export PYTHONPATH
354 354 > python "$TESTDIR/../contrib/hg-ssh" "$TESTTMP/a repo"
355 355 > EOF
356 356
357 357 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a repo"
358 358 73649e48688a
359 359
360 360 $ hg id --ssh "sh ssh.sh" "ssh://user@dummy/a'repo"
361 361 remote: Illegal repository "$TESTTMP/a'repo" (glob)
362 362 abort: no suitable response from remote hg!
363 363 [255]
364 364
365 365 $ hg id --ssh "sh ssh.sh" --remotecmd hacking "ssh://user@dummy/a'repo"
366 366 remote: Illegal command "hacking -R 'a'\''repo' serve --stdio"
367 367 abort: no suitable response from remote hg!
368 368 [255]
369 369
370 370 $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" python "$TESTDIR/../contrib/hg-ssh"
371 371 Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
372 372 [255]
373 373
374 374 Test hg-ssh in read-only mode:
375 375
376 376 $ cat > ssh.sh << EOF
377 377 > userhost="\$1"
378 378 > SSH_ORIGINAL_COMMAND="\$2"
379 379 > export SSH_ORIGINAL_COMMAND
380 380 > PYTHONPATH="$PYTHONPATH"
381 381 > export PYTHONPATH
382 382 > python "$TESTDIR/../contrib/hg-ssh" --read-only "$TESTTMP/remote"
383 383 > EOF
384 384
385 385 $ hg clone --ssh "sh ssh.sh" "ssh://user@dummy/$TESTTMP/remote" read-only-local
386 386 requesting all changes
387 387 adding changesets
388 388 adding manifests
389 389 adding file changes
390 390 added 6 changesets with 5 changes to 4 files (+1 heads)
391 391 updating to branch default
392 392 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 393
394 394 $ cd read-only-local
395 395 $ echo "baz" > bar
396 396 $ hg ci -A -m "unpushable commit" bar
397 397 $ hg push --ssh "sh ../ssh.sh"
398 398 pushing to ssh://user@dummy/*/remote (glob)
399 399 searching for changes
400 400 remote: Permission denied
401 401 remote: abort: pretxnopen.hg-ssh hook failed
402 402 remote: Permission denied
403 403 remote: pushkey-abort: prepushkey.hg-ssh hook failed
404 404 updating 6c0482d977a3 to public failed!
405 405 [1]
406 406
407 407 $ cd ..
408 408
409 409 stderr from remote commands should be printed before stdout from local code (issue4336)
410 410
411 411 $ hg clone remote stderr-ordering
412 412 updating to branch default
413 413 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 414 $ cd stderr-ordering
415 415 $ cat >> localwrite.py << EOF
416 416 > from mercurial import exchange, extensions
417 417 >
418 418 > def wrappedpush(orig, repo, *args, **kwargs):
419 419 > res = orig(repo, *args, **kwargs)
420 420 > repo.ui.write('local stdout\n')
421 421 > return res
422 422 >
423 423 > def extsetup(ui):
424 424 > extensions.wrapfunction(exchange, 'push', wrappedpush)
425 425 > EOF
426 426
427 427 $ cat >> .hg/hgrc << EOF
428 428 > [paths]
429 429 > default-push = ssh://user@dummy/remote
430 430 > [ui]
431 431 > ssh = python "$TESTDIR/dummyssh"
432 432 > [extensions]
433 433 > localwrite = localwrite.py
434 434 > EOF
435 435
436 436 $ echo localwrite > foo
437 437 $ hg commit -m 'testing localwrite'
438 438 $ hg push
439 439 pushing to ssh://user@dummy/remote
440 440 searching for changes
441 441 remote: adding changesets
442 442 remote: adding manifests
443 443 remote: adding file changes
444 444 remote: added 1 changesets with 1 changes to 1 files
445 445 remote: KABOOM
446 446 local stdout
447 447
448 448 debug output
449 449
450 450 $ hg pull --debug ssh://user@dummy/remote
451 451 pulling from ssh://user@dummy/remote
452 452 running python "*/dummyssh" user@dummy 'hg -R remote serve --stdio' (glob)
453 453 sending hello command
454 454 sending between command
455 455 remote: 271
456 456 remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
457 457 remote: 1
458 458 preparing listkeys for "bookmarks"
459 459 sending listkeys command
460 received listkey for "bookmarks": 45 bytes
460 461 preparing listkeys for "bookmarks"
461 462 sending listkeys command
463 received listkey for "bookmarks": 45 bytes
462 464 query 1; heads
463 465 sending batch command
464 466 searching for changes
465 467 all remote heads known locally
466 468 no changes found
467 469 preparing listkeys for "phases"
468 470 sending listkeys command
471 received listkey for "phases": 15 bytes
469 472 checking for updated bookmarks
470 473
471 474 $ cd ..
472 475
473 476 $ cat dummylog
474 477 Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
475 478 Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
476 479 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
477 480 Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio
478 481 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
479 482 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
480 483 Got arguments 1:user@dummy 2:hg -R doesnotexist serve --stdio
481 484 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
482 485 Got arguments 1:user@dummy 2:hg -R local serve --stdio
483 486 Got arguments 1:user@dummy 2:hg -R $TESTTMP/local serve --stdio
484 487 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
485 488 changegroup-in-remote hook: HG_NODE=a28a9d1a809cab7d4e2fde4bee738a9ede948b60 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
486 489 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
487 490 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
488 491 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
489 492 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
490 493 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
491 494 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
492 495 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
493 496 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
494 497 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
495 498 changegroup-in-remote hook: HG_NODE=1383141674ec756a6056f6a9097618482fe0f4a6 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
496 499 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
497 500 Got arguments 1:user@dummy 2:hg init 'a repo'
498 501 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
499 502 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
500 503 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
501 504 Got arguments 1:user@dummy 2:hg -R 'a repo' serve --stdio
502 505 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
503 506 changegroup-in-remote hook: HG_NODE=65c38f4125f9602c8db4af56530cc221d93b8ef8 HG_SOURCE=serve HG_TXNID=TXN:* HG_URL=remote:ssh:127.0.0.1 (glob)
504 507 Got arguments 1:user@dummy 2:hg -R remote serve --stdio
General Comments 0
You need to be logged in to leave comments. Login now