##// END OF EJS Templates
bundle2: support for push over the wire...
Pierre-Yves David -
r21075:438803e4 default
parent child Browse files
Show More
@@ -1,797 +1,811 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
12 12 import peer, error, encoding, util, store, 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 return sep.join(map(hex, l))
176 176
177 177 # batched call argument encoding
178 178
179 179 def escapearg(plain):
180 180 return (plain
181 181 .replace(':', '::')
182 182 .replace(',', ':,')
183 183 .replace(';', ':;')
184 184 .replace('=', ':='))
185 185
186 186 def unescapearg(escaped):
187 187 return (escaped
188 188 .replace(':=', '=')
189 189 .replace(':;', ';')
190 190 .replace(':,', ',')
191 191 .replace('::', ':'))
192 192
193 193 # client side
194 194
195 195 class wirepeer(peer.peerrepository):
196 196
197 197 def batch(self):
198 198 return remotebatch(self)
199 199 def _submitbatch(self, req):
200 200 cmds = []
201 201 for op, argsdict in req:
202 202 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
203 203 cmds.append('%s %s' % (op, args))
204 204 rsp = self._call("batch", cmds=';'.join(cmds))
205 205 return rsp.split(';')
206 206 def _submitone(self, op, args):
207 207 return self._call(op, **args)
208 208
209 209 @batchable
210 210 def lookup(self, key):
211 211 self.requirecap('lookup', _('look up remote revision'))
212 212 f = future()
213 213 yield {'key': encoding.fromlocal(key)}, f
214 214 d = f.value
215 215 success, data = d[:-1].split(" ", 1)
216 216 if int(success):
217 217 yield bin(data)
218 218 self._abort(error.RepoError(data))
219 219
220 220 @batchable
221 221 def heads(self):
222 222 f = future()
223 223 yield {}, f
224 224 d = f.value
225 225 try:
226 226 yield decodelist(d[:-1])
227 227 except ValueError:
228 228 self._abort(error.ResponseError(_("unexpected response:"), d))
229 229
230 230 @batchable
231 231 def known(self, nodes):
232 232 f = future()
233 233 yield {'nodes': encodelist(nodes)}, f
234 234 d = f.value
235 235 try:
236 236 yield [bool(int(f)) for f in d]
237 237 except ValueError:
238 238 self._abort(error.ResponseError(_("unexpected response:"), d))
239 239
240 240 @batchable
241 241 def branchmap(self):
242 242 f = future()
243 243 yield {}, f
244 244 d = f.value
245 245 try:
246 246 branchmap = {}
247 247 for branchpart in d.splitlines():
248 248 branchname, branchheads = branchpart.split(' ', 1)
249 249 branchname = encoding.tolocal(urllib.unquote(branchname))
250 250 branchheads = decodelist(branchheads)
251 251 branchmap[branchname] = branchheads
252 252 yield branchmap
253 253 except TypeError:
254 254 self._abort(error.ResponseError(_("unexpected response:"), d))
255 255
256 256 def branches(self, nodes):
257 257 n = encodelist(nodes)
258 258 d = self._call("branches", nodes=n)
259 259 try:
260 260 br = [tuple(decodelist(b)) for b in d.splitlines()]
261 261 return br
262 262 except ValueError:
263 263 self._abort(error.ResponseError(_("unexpected response:"), d))
264 264
265 265 def between(self, pairs):
266 266 batch = 8 # avoid giant requests
267 267 r = []
268 268 for i in xrange(0, len(pairs), batch):
269 269 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
270 270 d = self._call("between", pairs=n)
271 271 try:
272 272 r.extend(l and decodelist(l) or [] for l in d.splitlines())
273 273 except ValueError:
274 274 self._abort(error.ResponseError(_("unexpected response:"), d))
275 275 return r
276 276
277 277 @batchable
278 278 def pushkey(self, namespace, key, old, new):
279 279 if not self.capable('pushkey'):
280 280 yield False, None
281 281 f = future()
282 282 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
283 283 yield {'namespace': encoding.fromlocal(namespace),
284 284 'key': encoding.fromlocal(key),
285 285 'old': encoding.fromlocal(old),
286 286 'new': encoding.fromlocal(new)}, f
287 287 d = f.value
288 288 d, output = d.split('\n', 1)
289 289 try:
290 290 d = bool(int(d))
291 291 except ValueError:
292 292 raise error.ResponseError(
293 293 _('push failed (unexpected response):'), d)
294 294 for l in output.splitlines(True):
295 295 self.ui.status(_('remote: '), l)
296 296 yield d
297 297
298 298 @batchable
299 299 def listkeys(self, namespace):
300 300 if not self.capable('pushkey'):
301 301 yield {}, None
302 302 f = future()
303 303 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
304 304 yield {'namespace': encoding.fromlocal(namespace)}, f
305 305 d = f.value
306 306 r = {}
307 307 for l in d.splitlines():
308 308 k, v = l.split('\t')
309 309 r[encoding.tolocal(k)] = encoding.tolocal(v)
310 310 yield r
311 311
312 312 def stream_out(self):
313 313 return self._callstream('stream_out')
314 314
315 315 def changegroup(self, nodes, kind):
316 316 n = encodelist(nodes)
317 317 f = self._callcompressable("changegroup", roots=n)
318 318 return changegroupmod.unbundle10(f, 'UN')
319 319
320 320 def changegroupsubset(self, bases, heads, kind):
321 321 self.requirecap('changegroupsubset', _('look up remote changes'))
322 322 bases = encodelist(bases)
323 323 heads = encodelist(heads)
324 324 f = self._callcompressable("changegroupsubset",
325 325 bases=bases, heads=heads)
326 326 return changegroupmod.unbundle10(f, 'UN')
327 327
328 328 def getbundle(self, source, heads=None, common=None, bundlecaps=None):
329 329 self.requirecap('getbundle', _('look up remote changes'))
330 330 opts = {}
331 331 if heads is not None:
332 332 opts['heads'] = encodelist(heads)
333 333 if common is not None:
334 334 opts['common'] = encodelist(common)
335 335 if bundlecaps is not None:
336 336 opts['bundlecaps'] = ','.join(bundlecaps)
337 337 f = self._callcompressable("getbundle", **opts)
338 338 if bundlecaps is not None and 'HG20' in bundlecaps:
339 339 return bundle2.unbundle20(self.ui, f)
340 340 else:
341 341 return changegroupmod.unbundle10(f, 'UN')
342 342
343 343 def unbundle(self, cg, heads, source):
344 344 '''Send cg (a readable file-like object representing the
345 345 changegroup to push, typically a chunkbuffer object) to the
346 remote server as a bundle. Return an integer indicating the
347 result of the push (see localrepository.addchangegroup()).'''
346 remote server as a bundle.
347
348 When pushing a bundle10 stream, return an integer indicating the
349 result of the push (see localrepository.addchangegroup()).
350
351 When pushing a bundle20 stream, return a bundle20 stream.'''
348 352
349 353 if heads != ['force'] and self.capable('unbundlehash'):
350 354 heads = encodelist(['hashed',
351 355 util.sha1(''.join(sorted(heads))).digest()])
352 356 else:
353 357 heads = encodelist(heads)
354 358
355 ret, output = self._callpush("unbundle", cg, heads=heads)
356 if ret == "":
357 raise error.ResponseError(
358 _('push failed:'), output)
359 try:
360 ret = int(ret)
361 except ValueError:
362 raise error.ResponseError(
363 _('push failed (unexpected response):'), ret)
359 if util.safehasattr(cg, 'deltaheader'):
360 # this a bundle10, do the old style call sequence
361 ret, output = self._callpush("unbundle", cg, heads=heads)
362 if ret == "":
363 raise error.ResponseError(
364 _('push failed:'), output)
365 try:
366 ret = int(ret)
367 except ValueError:
368 raise error.ResponseError(
369 _('push failed (unexpected response):'), ret)
364 370
365 for l in output.splitlines(True):
366 self.ui.status(_('remote: '), l)
371 for l in output.splitlines(True):
372 self.ui.status(_('remote: '), l)
373 else:
374 # bundle2 push. Send a stream, fetch a stream.
375 stream = self._calltwowaystream('unbundle', cg, heads=heads)
376 ret = bundle2.unbundle20(self.ui, stream)
367 377 return ret
368 378
369 379 def debugwireargs(self, one, two, three=None, four=None, five=None):
370 380 # don't pass optional arguments left at their default value
371 381 opts = {}
372 382 if three is not None:
373 383 opts['three'] = three
374 384 if four is not None:
375 385 opts['four'] = four
376 386 return self._call('debugwireargs', one=one, two=two, **opts)
377 387
378 388 def _call(self, cmd, **args):
379 389 """execute <cmd> on the server
380 390
381 391 The command is expected to return a simple string.
382 392
383 393 returns the server reply as a string."""
384 394 raise NotImplementedError()
385 395
386 396 def _callstream(self, cmd, **args):
387 397 """execute <cmd> on the server
388 398
389 399 The command is expected to return a stream.
390 400
391 401 returns the server reply as a file like object."""
392 402 raise NotImplementedError()
393 403
394 404 def _callcompressable(self, cmd, **args):
395 405 """execute <cmd> on the server
396 406
397 407 The command is expected to return a stream.
398 408
399 409 The stream may have been compressed in some implementations. This
400 410 function takes care of the decompression. This is the only difference
401 411 with _callstream.
402 412
403 413 returns the server reply as a file like object.
404 414 """
405 415 raise NotImplementedError()
406 416
407 417 def _callpush(self, cmd, fp, **args):
408 418 """execute a <cmd> on server
409 419
410 420 The command is expected to be related to a push. Push has a special
411 421 return method.
412 422
413 423 returns the server reply as a (ret, output) tuple. ret is either
414 424 empty (error) or a stringified int.
415 425 """
416 426 raise NotImplementedError()
417 427
418 428 def _calltwowaystream(self, cmd, fp, **args):
419 429 """execute <cmd> on server
420 430
421 431 The command will send a stream to the server and get a stream in reply.
422 432 """
423 433 raise NotImplementedError()
424 434
425 435 def _abort(self, exception):
426 436 """clearly abort the wire protocol connection and raise the exception
427 437 """
428 438 raise NotImplementedError()
429 439
430 440 # server side
431 441
432 442 # wire protocol command can either return a string or one of these classes.
433 443 class streamres(object):
434 444 """wireproto reply: binary stream
435 445
436 446 The call was successful and the result is a stream.
437 447 Iterate on the `self.gen` attribute to retrieve chunks.
438 448 """
439 449 def __init__(self, gen):
440 450 self.gen = gen
441 451
442 452 class pushres(object):
443 453 """wireproto reply: success with simple integer return
444 454
445 455 The call was successful and returned an integer contained in `self.res`.
446 456 """
447 457 def __init__(self, res):
448 458 self.res = res
449 459
450 460 class pusherr(object):
451 461 """wireproto reply: failure
452 462
453 463 The call failed. The `self.res` attribute contains the error message.
454 464 """
455 465 def __init__(self, res):
456 466 self.res = res
457 467
458 468 class ooberror(object):
459 469 """wireproto reply: failure of a batch of operation
460 470
461 471 Something failed during a batch call. The error message is stored in
462 472 `self.message`.
463 473 """
464 474 def __init__(self, message):
465 475 self.message = message
466 476
467 477 def dispatch(repo, proto, command):
468 478 repo = repo.filtered("served")
469 479 func, spec = commands[command]
470 480 args = proto.getargs(spec)
471 481 return func(repo, proto, *args)
472 482
473 483 def options(cmd, keys, others):
474 484 opts = {}
475 485 for k in keys:
476 486 if k in others:
477 487 opts[k] = others[k]
478 488 del others[k]
479 489 if others:
480 490 sys.stderr.write("abort: %s got unexpected arguments %s\n"
481 491 % (cmd, ",".join(others)))
482 492 return opts
483 493
484 494 # list of commands
485 495 commands = {}
486 496
487 497 def wireprotocommand(name, args=''):
488 498 """decorator for wire protocol command"""
489 499 def register(func):
490 500 commands[name] = (func, args)
491 501 return func
492 502 return register
493 503
494 504 @wireprotocommand('batch', 'cmds *')
495 505 def batch(repo, proto, cmds, others):
496 506 repo = repo.filtered("served")
497 507 res = []
498 508 for pair in cmds.split(';'):
499 509 op, args = pair.split(' ', 1)
500 510 vals = {}
501 511 for a in args.split(','):
502 512 if a:
503 513 n, v = a.split('=')
504 514 vals[n] = unescapearg(v)
505 515 func, spec = commands[op]
506 516 if spec:
507 517 keys = spec.split()
508 518 data = {}
509 519 for k in keys:
510 520 if k == '*':
511 521 star = {}
512 522 for key in vals.keys():
513 523 if key not in keys:
514 524 star[key] = vals[key]
515 525 data['*'] = star
516 526 else:
517 527 data[k] = vals[k]
518 528 result = func(repo, proto, *[data[k] for k in keys])
519 529 else:
520 530 result = func(repo, proto)
521 531 if isinstance(result, ooberror):
522 532 return result
523 533 res.append(escapearg(result))
524 534 return ';'.join(res)
525 535
526 536 @wireprotocommand('between', 'pairs')
527 537 def between(repo, proto, pairs):
528 538 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
529 539 r = []
530 540 for b in repo.between(pairs):
531 541 r.append(encodelist(b) + "\n")
532 542 return "".join(r)
533 543
534 544 @wireprotocommand('branchmap')
535 545 def branchmap(repo, proto):
536 546 branchmap = repo.branchmap()
537 547 heads = []
538 548 for branch, nodes in branchmap.iteritems():
539 549 branchname = urllib.quote(encoding.fromlocal(branch))
540 550 branchnodes = encodelist(nodes)
541 551 heads.append('%s %s' % (branchname, branchnodes))
542 552 return '\n'.join(heads)
543 553
544 554 @wireprotocommand('branches', 'nodes')
545 555 def branches(repo, proto, nodes):
546 556 nodes = decodelist(nodes)
547 557 r = []
548 558 for b in repo.branches(nodes):
549 559 r.append(encodelist(b) + "\n")
550 560 return "".join(r)
551 561
552 562
553 563 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
554 564 'known', 'getbundle', 'unbundlehash', 'batch']
555 565
556 566 def _capabilities(repo, proto):
557 567 """return a list of capabilities for a repo
558 568
559 569 This function exists to allow extensions to easily wrap capabilities
560 570 computation
561 571
562 572 - returns a lists: easy to alter
563 573 - change done here will be propagated to both `capabilities` and `hello`
564 574 command without any other action needed.
565 575 """
566 576 # copy to prevent modification of the global list
567 577 caps = list(wireprotocaps)
568 578 if _allowstream(repo.ui):
569 579 if repo.ui.configbool('server', 'preferuncompressed', False):
570 580 caps.append('stream-preferred')
571 581 requiredformats = repo.requirements & repo.supportedformats
572 582 # if our local revlogs are just revlogv1, add 'stream' cap
573 583 if not requiredformats - set(('revlogv1',)):
574 584 caps.append('stream')
575 585 # otherwise, add 'streamreqs' detailing our local revlog format
576 586 else:
577 587 caps.append('streamreqs=%s' % ','.join(requiredformats))
578 588 if repo.ui.configbool('server', 'bundle2', False):
579 589 caps.append('bundle2')
580 590 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
581 591 caps.append('httpheader=1024')
582 592 return caps
583 593
584 594 # If you are writing an extension and consider wrapping this function. Wrap
585 595 # `_capabilities` instead.
586 596 @wireprotocommand('capabilities')
587 597 def capabilities(repo, proto):
588 598 return ' '.join(_capabilities(repo, proto))
589 599
590 600 @wireprotocommand('changegroup', 'roots')
591 601 def changegroup(repo, proto, roots):
592 602 nodes = decodelist(roots)
593 603 cg = changegroupmod.changegroup(repo, nodes, 'serve')
594 604 return streamres(proto.groupchunks(cg))
595 605
596 606 @wireprotocommand('changegroupsubset', 'bases heads')
597 607 def changegroupsubset(repo, proto, bases, heads):
598 608 bases = decodelist(bases)
599 609 heads = decodelist(heads)
600 610 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
601 611 return streamres(proto.groupchunks(cg))
602 612
603 613 @wireprotocommand('debugwireargs', 'one two *')
604 614 def debugwireargs(repo, proto, one, two, others):
605 615 # only accept optional args from the known set
606 616 opts = options('debugwireargs', ['three', 'four'], others)
607 617 return repo.debugwireargs(one, two, **opts)
608 618
609 619 @wireprotocommand('getbundle', '*')
610 620 def getbundle(repo, proto, others):
611 621 opts = options('getbundle', ['heads', 'common', 'bundlecaps'], others)
612 622 for k, v in opts.iteritems():
613 623 if k in ('heads', 'common'):
614 624 opts[k] = decodelist(v)
615 625 elif k == 'bundlecaps':
616 626 opts[k] = set(v.split(','))
617 627 cg = exchange.getbundle(repo, 'serve', **opts)
618 628 return streamres(proto.groupchunks(cg))
619 629
620 630 @wireprotocommand('heads')
621 631 def heads(repo, proto):
622 632 h = repo.heads()
623 633 return encodelist(h) + "\n"
624 634
625 635 @wireprotocommand('hello')
626 636 def hello(repo, proto):
627 637 '''the hello command returns a set of lines describing various
628 638 interesting things about the server, in an RFC822-like format.
629 639 Currently the only one defined is "capabilities", which
630 640 consists of a line in the form:
631 641
632 642 capabilities: space separated list of tokens
633 643 '''
634 644 return "capabilities: %s\n" % (capabilities(repo, proto))
635 645
636 646 @wireprotocommand('listkeys', 'namespace')
637 647 def listkeys(repo, proto, namespace):
638 648 d = repo.listkeys(encoding.tolocal(namespace)).items()
639 649 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
640 650 for k, v in d])
641 651 return t
642 652
643 653 @wireprotocommand('lookup', 'key')
644 654 def lookup(repo, proto, key):
645 655 try:
646 656 k = encoding.tolocal(key)
647 657 c = repo[k]
648 658 r = c.hex()
649 659 success = 1
650 660 except Exception, inst:
651 661 r = str(inst)
652 662 success = 0
653 663 return "%s %s\n" % (success, r)
654 664
655 665 @wireprotocommand('known', 'nodes *')
656 666 def known(repo, proto, nodes, others):
657 667 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
658 668
659 669 @wireprotocommand('pushkey', 'namespace key old new')
660 670 def pushkey(repo, proto, namespace, key, old, new):
661 671 # compatibility with pre-1.8 clients which were accidentally
662 672 # sending raw binary nodes rather than utf-8-encoded hex
663 673 if len(new) == 20 and new.encode('string-escape') != new:
664 674 # looks like it could be a binary node
665 675 try:
666 676 new.decode('utf-8')
667 677 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
668 678 except UnicodeDecodeError:
669 679 pass # binary, leave unmodified
670 680 else:
671 681 new = encoding.tolocal(new) # normal path
672 682
673 683 if util.safehasattr(proto, 'restore'):
674 684
675 685 proto.redirect()
676 686
677 687 try:
678 688 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
679 689 encoding.tolocal(old), new) or False
680 690 except util.Abort:
681 691 r = False
682 692
683 693 output = proto.restore()
684 694
685 695 return '%s\n%s' % (int(r), output)
686 696
687 697 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
688 698 encoding.tolocal(old), new)
689 699 return '%s\n' % int(r)
690 700
691 701 def _allowstream(ui):
692 702 return ui.configbool('server', 'uncompressed', True, untrusted=True)
693 703
694 704 def _walkstreamfiles(repo):
695 705 # this is it's own function so extensions can override it
696 706 return repo.store.walk()
697 707
698 708 @wireprotocommand('stream_out')
699 709 def stream(repo, proto):
700 710 '''If the server supports streaming clone, it advertises the "stream"
701 711 capability with a value representing the version and flags of the repo
702 712 it is serving. Client checks to see if it understands the format.
703 713
704 714 The format is simple: the server writes out a line with the amount
705 715 of files, then the total amount of bytes to be transferred (separated
706 716 by a space). Then, for each file, the server first writes the filename
707 717 and file size (separated by the null character), then the file contents.
708 718 '''
709 719
710 720 if not _allowstream(repo.ui):
711 721 return '1\n'
712 722
713 723 entries = []
714 724 total_bytes = 0
715 725 try:
716 726 # get consistent snapshot of repo, lock during scan
717 727 lock = repo.lock()
718 728 try:
719 729 repo.ui.debug('scanning\n')
720 730 for name, ename, size in _walkstreamfiles(repo):
721 731 if size:
722 732 entries.append((name, size))
723 733 total_bytes += size
724 734 finally:
725 735 lock.release()
726 736 except error.LockError:
727 737 return '2\n' # error: 2
728 738
729 739 def streamer(repo, entries, total):
730 740 '''stream out all metadata files in repository.'''
731 741 yield '0\n' # success
732 742 repo.ui.debug('%d files, %d bytes to transfer\n' %
733 743 (len(entries), total_bytes))
734 744 yield '%d %d\n' % (len(entries), total_bytes)
735 745
736 746 sopener = repo.sopener
737 747 oldaudit = sopener.mustaudit
738 748 debugflag = repo.ui.debugflag
739 749 sopener.mustaudit = False
740 750
741 751 try:
742 752 for name, size in entries:
743 753 if debugflag:
744 754 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
745 755 # partially encode name over the wire for backwards compat
746 756 yield '%s\0%d\n' % (store.encodedir(name), size)
747 757 if size <= 65536:
748 758 fp = sopener(name)
749 759 try:
750 760 data = fp.read(size)
751 761 finally:
752 762 fp.close()
753 763 yield data
754 764 else:
755 765 for chunk in util.filechunkiter(sopener(name), limit=size):
756 766 yield chunk
757 767 # replace with "finally:" when support for python 2.4 has been dropped
758 768 except Exception:
759 769 sopener.mustaudit = oldaudit
760 770 raise
761 771 sopener.mustaudit = oldaudit
762 772
763 773 return streamres(streamer(repo, entries, total_bytes))
764 774
765 775 @wireprotocommand('unbundle', 'heads')
766 776 def unbundle(repo, proto, heads):
767 777 their_heads = decodelist(heads)
768 778
769 779 try:
770 780 proto.redirect()
771 781
772 782 exchange.check_heads(repo, their_heads, 'preparing changes')
773 783
774 784 # write bundle data to temporary file because it can be big
775 785 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
776 786 fp = os.fdopen(fd, 'wb+')
777 787 r = 0
778 788 try:
779 789 proto.getfile(fp)
780 790 fp.seek(0)
781 791 gen = exchange.readbundle(repo.ui, fp, None)
782 792 r = exchange.unbundle(repo, gen, their_heads, 'serve',
783 793 proto._client())
794 if util.safehasattr(r, 'addpart'):
795 # The return looks streameable, we are in the bundle2 case and
796 # should return a stream.
797 return streamres(r.getchunks())
784 798 return pushres(r)
785 799
786 800 finally:
787 801 fp.close()
788 802 os.unlink(tempname)
789 803 except util.Abort, inst:
790 804 # The old code we moved used sys.stderr directly.
791 805 # We did not change it to minimise code change.
792 806 # This need to be moved to something proper.
793 807 # Feel free to do it.
794 808 sys.stderr.write("abort: %s\n" % inst)
795 809 return pushres(0)
796 810 except exchange.PushRaced, exc:
797 811 return pusherr(str(exc))
@@ -1,714 +1,785 b''
1 1
2 2 Create an extension to test bundle2 API
3 3
4 4 $ cat > bundle2.py << EOF
5 5 > """A small extension to test bundle2 implementation
6 6 >
7 7 > Current bundle2 implementation is far too limited to be used in any core
8 8 > code. We still need to be able to test it while it grow up.
9 9 > """
10 10 >
11 11 > import sys
12 12 > from mercurial import cmdutil
13 13 > from mercurial import util
14 14 > from mercurial import bundle2
15 15 > from mercurial import scmutil
16 16 > from mercurial import discovery
17 17 > from mercurial import changegroup
18 18 > cmdtable = {}
19 19 > command = cmdutil.command(cmdtable)
20 20 >
21 21 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
22 22 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
23 23 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
24 24 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
25 25 >
26 26 > @bundle2.parthandler('test:song')
27 27 > def songhandler(op, part):
28 28 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
29 29 > op.ui.write('The choir starts singing:\n')
30 30 > verses = 0
31 31 > for line in part.read().split('\n'):
32 32 > op.ui.write(' %s\n' % line)
33 33 > verses += 1
34 34 > op.records.add('song', {'verses': verses})
35 35 >
36 36 > @bundle2.parthandler('test:ping')
37 37 > def pinghandler(op, part):
38 38 > op.ui.write('received ping request (id %i)\n' % part.id)
39 39 > if op.reply is not None:
40 40 > rpart = bundle2.bundlepart('test:pong',
41 41 > [('in-reply-to', str(part.id))])
42 42 > op.reply.addpart(rpart)
43 43 >
44 44 > @command('bundle2',
45 45 > [('', 'param', [], 'stream level parameter'),
46 46 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
47 47 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
48 48 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
49 49 > '[OUTPUTFILE]')
50 50 > def cmdbundle2(ui, repo, path=None, **opts):
51 51 > """write a bundle2 container on standard ouput"""
52 52 > bundler = bundle2.bundle20(ui)
53 53 > for p in opts['param']:
54 54 > p = p.split('=', 1)
55 55 > try:
56 56 > bundler.addparam(*p)
57 57 > except ValueError, exc:
58 58 > raise util.Abort('%s' % exc)
59 59 >
60 60 > revs = opts['rev']
61 61 > if 'rev' in opts:
62 62 > revs = scmutil.revrange(repo, opts['rev'])
63 63 > if revs:
64 64 > # very crude version of a changegroup part creation
65 65 > bundled = repo.revs('%ld::%ld', revs, revs)
66 66 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
67 67 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
68 68 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
69 69 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
70 70 > part = bundle2.bundlepart('changegroup', data=cg.getchunks())
71 71 > bundler.addpart(part)
72 72 >
73 73 > if opts['parts']:
74 74 > part = bundle2.bundlepart('test:empty')
75 75 > bundler.addpart(part)
76 76 > # add a second one to make sure we handle multiple parts
77 77 > part = bundle2.bundlepart('test:empty')
78 78 > bundler.addpart(part)
79 79 > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG)
80 80 > bundler.addpart(part)
81 81 > part = bundle2.bundlepart('test:math',
82 82 > [('pi', '3.14'), ('e', '2.72')],
83 83 > [('cooking', 'raw')],
84 84 > '42')
85 85 > bundler.addpart(part)
86 86 > if opts['unknown']:
87 87 > part = bundle2.bundlepart('test:UNKNOWN',
88 88 > data='some random content')
89 89 > bundler.addpart(part)
90 90 > if opts['parts']:
91 91 > part = bundle2.bundlepart('test:ping')
92 92 > bundler.addpart(part)
93 93 >
94 94 > if path is None:
95 95 > file = sys.stdout
96 96 > else:
97 97 > file = open(path, 'w')
98 98 >
99 99 > for chunk in bundler.getchunks():
100 100 > file.write(chunk)
101 101 >
102 102 > @command('unbundle2', [], '')
103 103 > def cmdunbundle2(ui, repo, replypath=None):
104 104 > """process a bundle2 stream from stdin on the current repo"""
105 105 > try:
106 106 > tr = None
107 107 > lock = repo.lock()
108 108 > tr = repo.transaction('processbundle')
109 109 > try:
110 110 > unbundler = bundle2.unbundle20(ui, sys.stdin)
111 111 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
112 112 > tr.close()
113 113 > except KeyError, exc:
114 114 > raise util.Abort('missing support for %s' % exc)
115 115 > finally:
116 116 > if tr is not None:
117 117 > tr.release()
118 118 > lock.release()
119 119 > remains = sys.stdin.read()
120 120 > ui.write('%i unread bytes\n' % len(remains))
121 121 > if op.records['song']:
122 122 > totalverses = sum(r['verses'] for r in op.records['song'])
123 123 > ui.write('%i total verses sung\n' % totalverses)
124 124 > for rec in op.records['changegroup']:
125 125 > ui.write('addchangegroup return: %i\n' % rec['return'])
126 126 > if op.reply is not None and replypath is not None:
127 127 > file = open(replypath, 'w')
128 128 > for chunk in op.reply.getchunks():
129 129 > file.write(chunk)
130 130 >
131 131 > @command('statbundle2', [], '')
132 132 > def cmdstatbundle2(ui, repo):
133 133 > """print statistic on the bundle2 container read from stdin"""
134 134 > unbundler = bundle2.unbundle20(ui, sys.stdin)
135 135 > try:
136 136 > params = unbundler.params
137 137 > except KeyError, exc:
138 138 > raise util.Abort('unknown parameters: %s' % exc)
139 139 > ui.write('options count: %i\n' % len(params))
140 140 > for key in sorted(params):
141 141 > ui.write('- %s\n' % key)
142 142 > value = params[key]
143 143 > if value is not None:
144 144 > ui.write(' %s\n' % value)
145 145 > count = 0
146 146 > for p in unbundler:
147 147 > count += 1
148 148 > ui.write(' :%s:\n' % p.type)
149 149 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
150 150 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
151 151 > ui.write(' payload: %i bytes\n' % len(p.read()))
152 152 > ui.write('parts count: %i\n' % count)
153 153 > EOF
154 154 $ cat >> $HGRCPATH << EOF
155 155 > [extensions]
156 156 > bundle2=$TESTTMP/bundle2.py
157 157 > [server]
158 158 > bundle2=True
159 159 > [ui]
160 160 > ssh=python "$TESTDIR/dummyssh"
161 > [web]
162 > push_ssl = false
163 > allow_push = *
161 164 > EOF
162 165
163 166 The extension requires a repo (currently unused)
164 167
165 168 $ hg init main
166 169 $ cd main
167 170 $ touch a
168 171 $ hg add a
169 172 $ hg commit -m 'a'
170 173
171 174
172 175 Empty bundle
173 176 =================
174 177
175 178 - no option
176 179 - no parts
177 180
178 181 Test bundling
179 182
180 183 $ hg bundle2
181 184 HG20\x00\x00\x00\x00 (no-eol) (esc)
182 185
183 186 Test unbundling
184 187
185 188 $ hg bundle2 | hg statbundle2
186 189 options count: 0
187 190 parts count: 0
188 191
189 192 Test old style bundle are detected and refused
190 193
191 194 $ hg bundle --all ../bundle.hg
192 195 1 changesets found
193 196 $ hg statbundle2 < ../bundle.hg
194 197 abort: unknown bundle version 10
195 198 [255]
196 199
197 200 Test parameters
198 201 =================
199 202
200 203 - some options
201 204 - no parts
202 205
203 206 advisory parameters, no value
204 207 -------------------------------
205 208
206 209 Simplest possible parameters form
207 210
208 211 Test generation simple option
209 212
210 213 $ hg bundle2 --param 'caution'
211 214 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
212 215
213 216 Test unbundling
214 217
215 218 $ hg bundle2 --param 'caution' | hg statbundle2
216 219 options count: 1
217 220 - caution
218 221 parts count: 0
219 222
220 223 Test generation multiple option
221 224
222 225 $ hg bundle2 --param 'caution' --param 'meal'
223 226 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
224 227
225 228 Test unbundling
226 229
227 230 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
228 231 options count: 2
229 232 - caution
230 233 - meal
231 234 parts count: 0
232 235
233 236 advisory parameters, with value
234 237 -------------------------------
235 238
236 239 Test generation
237 240
238 241 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
239 242 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
240 243
241 244 Test unbundling
242 245
243 246 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
244 247 options count: 3
245 248 - caution
246 249 - elephants
247 250 - meal
248 251 vegan
249 252 parts count: 0
250 253
251 254 parameter with special char in value
252 255 ---------------------------------------------------
253 256
254 257 Test generation
255 258
256 259 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
257 260 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
258 261
259 262 Test unbundling
260 263
261 264 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
262 265 options count: 2
263 266 - e|! 7/
264 267 babar%#==tutu
265 268 - simple
266 269 parts count: 0
267 270
268 271 Test unknown mandatory option
269 272 ---------------------------------------------------
270 273
271 274 $ hg bundle2 --param 'Gravity' | hg statbundle2
272 275 abort: unknown parameters: 'Gravity'
273 276 [255]
274 277
275 278 Test debug output
276 279 ---------------------------------------------------
277 280
278 281 bundling debug
279 282
280 283 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
281 284 start emission of HG20 stream
282 285 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
283 286 start of parts
284 287 end of bundle
285 288
286 289 file content is ok
287 290
288 291 $ cat ../out.hg2
289 292 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
290 293
291 294 unbundling debug
292 295
293 296 $ hg statbundle2 --debug < ../out.hg2
294 297 start processing of HG20 stream
295 298 reading bundle2 stream parameters
296 299 ignoring unknown parameter 'e|! 7/'
297 300 ignoring unknown parameter 'simple'
298 301 options count: 2
299 302 - e|! 7/
300 303 babar%#==tutu
301 304 - simple
302 305 start extraction of bundle2 parts
303 306 part header size: 0
304 307 end of bundle2 stream
305 308 parts count: 0
306 309
307 310
308 311 Test buggy input
309 312 ---------------------------------------------------
310 313
311 314 empty parameter name
312 315
313 316 $ hg bundle2 --param '' --quiet
314 317 abort: empty parameter name
315 318 [255]
316 319
317 320 bad parameter name
318 321
319 322 $ hg bundle2 --param 42babar
320 323 abort: non letter first character: '42babar'
321 324 [255]
322 325
323 326
324 327 Test part
325 328 =================
326 329
327 330 $ hg bundle2 --parts ../parts.hg2 --debug
328 331 start emission of HG20 stream
329 332 bundle parameter:
330 333 start of parts
331 334 bundle part: "test:empty"
332 335 bundle part: "test:empty"
333 336 bundle part: "test:song"
334 337 bundle part: "test:math"
335 338 bundle part: "test:ping"
336 339 end of bundle
337 340
338 341 $ cat ../parts.hg2
339 342 HG20\x00\x00\x00\x11 (esc)
340 343 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
341 344 test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x10 test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
342 345 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
343 346 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x03\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
344 347
345 348
346 349 $ hg statbundle2 < ../parts.hg2
347 350 options count: 0
348 351 :test:empty:
349 352 mandatory: 0
350 353 advisory: 0
351 354 payload: 0 bytes
352 355 :test:empty:
353 356 mandatory: 0
354 357 advisory: 0
355 358 payload: 0 bytes
356 359 :test:song:
357 360 mandatory: 0
358 361 advisory: 0
359 362 payload: 178 bytes
360 363 :test:math:
361 364 mandatory: 2
362 365 advisory: 1
363 366 payload: 2 bytes
364 367 :test:ping:
365 368 mandatory: 0
366 369 advisory: 0
367 370 payload: 0 bytes
368 371 parts count: 5
369 372
370 373 $ hg statbundle2 --debug < ../parts.hg2
371 374 start processing of HG20 stream
372 375 reading bundle2 stream parameters
373 376 options count: 0
374 377 start extraction of bundle2 parts
375 378 part header size: 17
376 379 part type: "test:empty"
377 380 part id: "0"
378 381 part parameters: 0
379 382 :test:empty:
380 383 mandatory: 0
381 384 advisory: 0
382 385 payload chunk size: 0
383 386 payload: 0 bytes
384 387 part header size: 17
385 388 part type: "test:empty"
386 389 part id: "1"
387 390 part parameters: 0
388 391 :test:empty:
389 392 mandatory: 0
390 393 advisory: 0
391 394 payload chunk size: 0
392 395 payload: 0 bytes
393 396 part header size: 16
394 397 part type: "test:song"
395 398 part id: "2"
396 399 part parameters: 0
397 400 :test:song:
398 401 mandatory: 0
399 402 advisory: 0
400 403 payload chunk size: 178
401 404 payload chunk size: 0
402 405 payload: 178 bytes
403 406 part header size: 43
404 407 part type: "test:math"
405 408 part id: "3"
406 409 part parameters: 3
407 410 :test:math:
408 411 mandatory: 2
409 412 advisory: 1
410 413 payload chunk size: 2
411 414 payload chunk size: 0
412 415 payload: 2 bytes
413 416 part header size: 16
414 417 part type: "test:ping"
415 418 part id: "4"
416 419 part parameters: 0
417 420 :test:ping:
418 421 mandatory: 0
419 422 advisory: 0
420 423 payload chunk size: 0
421 424 payload: 0 bytes
422 425 part header size: 0
423 426 end of bundle2 stream
424 427 parts count: 5
425 428
426 429 Test actual unbundling of test part
427 430 =======================================
428 431
429 432 Process the bundle
430 433
431 434 $ hg unbundle2 --debug < ../parts.hg2
432 435 start processing of HG20 stream
433 436 reading bundle2 stream parameters
434 437 start extraction of bundle2 parts
435 438 part header size: 17
436 439 part type: "test:empty"
437 440 part id: "0"
438 441 part parameters: 0
439 442 ignoring unknown advisory part 'test:empty'
440 443 payload chunk size: 0
441 444 part header size: 17
442 445 part type: "test:empty"
443 446 part id: "1"
444 447 part parameters: 0
445 448 ignoring unknown advisory part 'test:empty'
446 449 payload chunk size: 0
447 450 part header size: 16
448 451 part type: "test:song"
449 452 part id: "2"
450 453 part parameters: 0
451 454 found a handler for part 'test:song'
452 455 The choir starts singing:
453 456 payload chunk size: 178
454 457 payload chunk size: 0
455 458 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
456 459 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
457 460 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
458 461 part header size: 43
459 462 part type: "test:math"
460 463 part id: "3"
461 464 part parameters: 3
462 465 ignoring unknown advisory part 'test:math'
463 466 payload chunk size: 2
464 467 payload chunk size: 0
465 468 part header size: 16
466 469 part type: "test:ping"
467 470 part id: "4"
468 471 part parameters: 0
469 472 found a handler for part 'test:ping'
470 473 received ping request (id 4)
471 474 payload chunk size: 0
472 475 part header size: 0
473 476 end of bundle2 stream
474 477 0 unread bytes
475 478 3 total verses sung
476 479
477 480 Unbundle with an unknown mandatory part
478 481 (should abort)
479 482
480 483 $ hg bundle2 --parts --unknown ../unknown.hg2
481 484
482 485 $ hg unbundle2 < ../unknown.hg2
483 486 The choir starts singing:
484 487 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
485 488 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
486 489 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
487 490 0 unread bytes
488 491 abort: missing support for 'test:unknown'
489 492 [255]
490 493
491 494 unbundle with a reply
492 495
493 496 $ hg unbundle2 ../reply.hg2 < ../parts.hg2
494 497 The choir starts singing:
495 498 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
496 499 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
497 500 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
498 501 received ping request (id 4)
499 502 0 unread bytes
500 503 3 total verses sung
501 504
502 505 The reply is a bundle
503 506
504 507 $ cat ../reply.hg2
505 508 HG20\x00\x00\x00\x1e test:pong\x00\x00\x00\x00\x01\x00\x0b\x01in-reply-to4\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
506 509
507 510 The reply is valid
508 511
509 512 $ hg statbundle2 < ../reply.hg2
510 513 options count: 0
511 514 :test:pong:
512 515 mandatory: 1
513 516 advisory: 0
514 517 payload: 0 bytes
515 518 parts count: 1
516 519
517 520 Support for changegroup
518 521 ===================================
519 522
520 523 $ hg unbundle $TESTDIR/bundles/rebase.hg
521 524 adding changesets
522 525 adding manifests
523 526 adding file changes
524 527 added 8 changesets with 7 changes to 7 files (+3 heads)
525 528 (run 'hg heads' to see heads, 'hg merge' to merge)
526 529
527 530 $ hg log -G
528 531 o changeset: 8:02de42196ebe
529 532 | tag: tip
530 533 | parent: 6:24b6387c8c8c
531 534 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
532 535 | date: Sat Apr 30 15:24:48 2011 +0200
533 536 | summary: H
534 537 |
535 538 | o changeset: 7:eea13746799a
536 539 |/| parent: 6:24b6387c8c8c
537 540 | | parent: 5:9520eea781bc
538 541 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
539 542 | | date: Sat Apr 30 15:24:48 2011 +0200
540 543 | | summary: G
541 544 | |
542 545 o | changeset: 6:24b6387c8c8c
543 546 | | parent: 1:cd010b8cd998
544 547 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
545 548 | | date: Sat Apr 30 15:24:48 2011 +0200
546 549 | | summary: F
547 550 | |
548 551 | o changeset: 5:9520eea781bc
549 552 |/ parent: 1:cd010b8cd998
550 553 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
551 554 | date: Sat Apr 30 15:24:48 2011 +0200
552 555 | summary: E
553 556 |
554 557 | o changeset: 4:32af7686d403
555 558 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
556 559 | | date: Sat Apr 30 15:24:48 2011 +0200
557 560 | | summary: D
558 561 | |
559 562 | o changeset: 3:5fddd98957c8
560 563 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
561 564 | | date: Sat Apr 30 15:24:48 2011 +0200
562 565 | | summary: C
563 566 | |
564 567 | o changeset: 2:42ccdea3bb16
565 568 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
566 569 | date: Sat Apr 30 15:24:48 2011 +0200
567 570 | summary: B
568 571 |
569 572 o changeset: 1:cd010b8cd998
570 573 parent: -1:000000000000
571 574 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
572 575 date: Sat Apr 30 15:24:48 2011 +0200
573 576 summary: A
574 577
575 578 @ changeset: 0:3903775176ed
576 579 user: test
577 580 date: Thu Jan 01 00:00:00 1970 +0000
578 581 summary: a
579 582
580 583
581 584 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
582 585 4 changesets found
583 586 list of changesets:
584 587 32af7686d403cf45b5d95f2d70cebea587ac806a
585 588 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
586 589 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
587 590 02de42196ebee42ef284b6780a87cdc96e8eaab6
588 591 start emission of HG20 stream
589 592 bundle parameter:
590 593 start of parts
591 594 bundle part: "changegroup"
592 595 bundling: 1/4 changesets (25.00%)
593 596 bundling: 2/4 changesets (50.00%)
594 597 bundling: 3/4 changesets (75.00%)
595 598 bundling: 4/4 changesets (100.00%)
596 599 bundling: 1/4 manifests (25.00%)
597 600 bundling: 2/4 manifests (50.00%)
598 601 bundling: 3/4 manifests (75.00%)
599 602 bundling: 4/4 manifests (100.00%)
600 603 bundling: D 1/3 files (33.33%)
601 604 bundling: E 2/3 files (66.67%)
602 605 bundling: H 3/3 files (100.00%)
603 606 end of bundle
604 607
605 608 $ cat ../rev.hg2
606 609 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
607 610 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
608 611 \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01D\x00\x00\x00\xa4\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xcd\x01\x0b\x8c\xd9\x98\xf3\x98\x1aZ\x81\x15\xf9O\x8d\xa4\xabP`\x89\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)4dece9c826f69490507b98c6383a3009b295837d (esc)
609 612 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
610 613 \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01E\x00\x00\x00\xa2\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)365b93d57fdf4814e2b5911d6bacff2b12014441 (esc)
611 614 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01G\x00\x00\x00\xa4\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
612 615 \x87\xcd\xc9n\x8e\xaa\xb6$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
613 616 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
614 617 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
615 618 \x00\x00\x00g\x00\x00\x00h\x00\x00\x00\x01H\x00\x00\x00\x00\x00\x00\x00\x8bn\x1fLG\xec\xb53\xff\xd0\xc8\xe5,\xdc\x88\xaf\xb6\xcd9\xe2\x0cf\xa5\xa0\x18\x17\xfd\xf5#\x9c'8\x02\xb5\xb7a\x8d\x05\x1c\x89\xe4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+D\x00c3f1ca2924c16a19b0656a84900e504e5b0aec2d (esc)
616 619 \x00\x00\x00\x8bM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0 \xb2\x95\x83}\x00}\x8c\x9d\x88\x84\x13%\xf5\xc6\xb0cq\xb3[N\x8a+\x1a\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00+\x00\x00\x00\xac\x00\x00\x00+E\x009c6fd0350a6c0d0c49d4a9c5017cf07043f54e58 (esc)
617 620 \x00\x00\x00\x8b6[\x93\xd5\x7f\xdfH\x14\xe2\xb5\x91\x1dk\xac\xff+\x12\x01DA(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xceM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0 \xb2\x95\x83}\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00V\x00\x00\x00V\x00\x00\x00+F\x0022bfcfd62a21a3287edbd4d656218d0f525ed76a (esc)
618 621 \x00\x00\x00\x97\x8b\xeeH\xed\xc71\x85A\xfc\x00\x13\xeeA\xb0\x89'j\x8c$\xbf(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
619 622 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00+\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+H\x008500189e74a9e0475e822093bc7db0d631aeb0b4 (esc)
620 623 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
621 624 \xec-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02D (esc)
622 625 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
623 626 l\r (no-eol) (esc)
624 627 \x0cI\xd4\xa9\xc5\x01|\xf0pC\xf5NX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02E (esc)
625 628 \x00\x00\x00\x00\x00\x00\x00\x05H\x00\x00\x00b\x85\x00\x18\x9et\xa9\xe0G^\x82 \x93\xbc}\xb0\xd61\xae\xb0\xb4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
626 629 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
627 630 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
628 631
629 632 $ hg unbundle2 ../rev-replay.hg2 < ../rev.hg2
630 633 adding changesets
631 634 adding manifests
632 635 adding file changes
633 636 added 0 changesets with 0 changes to 3 files
634 637 0 unread bytes
635 638 addchangegroup return: 1
636 639
637 640 $ cat ../rev-replay.hg2
638 641 HG20\x00\x00\x00/\x11reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to0return1\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
639 642
640 643 Real world exchange
641 644 =====================
642 645
643 646
644 647 clone --pull
645 648
646 649 $ cd ..
647 650 $ hg clone main other --pull --rev 9520eea781bc
648 651 adding changesets
649 652 adding manifests
650 653 adding file changes
651 654 added 2 changesets with 2 changes to 2 files
652 655 updating to branch default
653 656 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
654 657 $ hg -R other log -G
655 658 @ changeset: 1:9520eea781bc
656 659 | tag: tip
657 660 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
658 661 | date: Sat Apr 30 15:24:48 2011 +0200
659 662 | summary: E
660 663 |
661 664 o changeset: 0:cd010b8cd998
662 665 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
663 666 date: Sat Apr 30 15:24:48 2011 +0200
664 667 summary: A
665 668
666 669
667 670 pull
668 671
669 672 $ hg -R other pull -r 24b6387c8c8c
670 673 pulling from $TESTTMP/main (glob)
671 674 searching for changes
672 675 adding changesets
673 676 adding manifests
674 677 adding file changes
675 678 added 1 changesets with 1 changes to 1 files (+1 heads)
676 679 (run 'hg heads' to see heads, 'hg merge' to merge)
677 680
678 681 push
679 682
680 683 $ hg -R main push other --rev eea13746799a
681 684 pushing to other
682 685 searching for changes
683 686 adding changesets
684 687 adding manifests
685 688 adding file changes
686 689 added 1 changesets with 0 changes to 0 files (-1 heads)
687 690
688 691 pull over ssh
689 692
690 693 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
691 694 pulling from ssh://user@dummy/main
692 695 searching for changes
693 696 adding changesets
694 697 adding manifests
695 698 adding file changes
696 699 added 1 changesets with 1 changes to 1 files (+1 heads)
697 700 (run 'hg heads' to see heads, 'hg merge' to merge)
698 701
699 702 pull over http
700 703
701 704 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
702 705 $ cat main.pid >> $DAEMON_PIDS
703 706
704 707 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
705 708 pulling from http://localhost:$HGPORT/
706 709 searching for changes
707 710 adding changesets
708 711 adding manifests
709 712 adding file changes
710 713 added 1 changesets with 1 changes to 1 files (+1 heads)
711 714 (run 'hg heads .' to see heads, 'hg merge' to merge)
712 715 $ cat main-error.log
713 716
717 push over ssh
714 718
719 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
720 pushing to ssh://user@dummy/other
721 searching for changes
722 remote: adding changesets
723 remote: adding manifests
724 remote: adding file changes
725 remote: added 1 changesets with 1 changes to 1 files
726
727 push over http
728
729 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
730 $ cat other.pid >> $DAEMON_PIDS
731
732 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
733 pushing to http://localhost:$HGPORT2/
734 searching for changes
735 $ cat other-error.log
736
737 Check final content.
738
739 $ hg -R other log -G
740 o changeset: 7:32af7686d403
741 | tag: tip
742 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
743 | date: Sat Apr 30 15:24:48 2011 +0200
744 | summary: D
745 |
746 o changeset: 6:5fddd98957c8
747 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
748 | date: Sat Apr 30 15:24:48 2011 +0200
749 | summary: C
750 |
751 o changeset: 5:42ccdea3bb16
752 | parent: 0:cd010b8cd998
753 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
754 | date: Sat Apr 30 15:24:48 2011 +0200
755 | summary: B
756 |
757 | o changeset: 4:02de42196ebe
758 | | parent: 2:24b6387c8c8c
759 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
760 | | date: Sat Apr 30 15:24:48 2011 +0200
761 | | summary: H
762 | |
763 | | o changeset: 3:eea13746799a
764 | |/| parent: 2:24b6387c8c8c
765 | | | parent: 1:9520eea781bc
766 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
767 | | | date: Sat Apr 30 15:24:48 2011 +0200
768 | | | summary: G
769 | | |
770 | o | changeset: 2:24b6387c8c8c
771 |/ / parent: 0:cd010b8cd998
772 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
773 | | date: Sat Apr 30 15:24:48 2011 +0200
774 | | summary: F
775 | |
776 | @ changeset: 1:9520eea781bc
777 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
778 | date: Sat Apr 30 15:24:48 2011 +0200
779 | summary: E
780 |
781 o changeset: 0:cd010b8cd998
782 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
783 date: Sat Apr 30 15:24:48 2011 +0200
784 summary: A
785
General Comments 0
You need to be logged in to leave comments. Login now