##// END OF EJS Templates
bundle2: add generic debug output regarding processed part payload...
Pierre-Yves David -
r25334:76cba1ec default
parent child Browse files
Show More
@@ -1,1352 +1,1355 b''
1 1 # bundle2.py - generic container format to transmit arbitrary data.
2 2 #
3 3 # Copyright 2013 Facebook, Inc.
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 """Handling of the new bundle2 format
8 8
9 9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 11 that will be handed to and processed by the application layer.
12 12
13 13
14 14 General format architecture
15 15 ===========================
16 16
17 17 The format is architectured as follow
18 18
19 19 - magic string
20 20 - stream level parameters
21 21 - payload parts (any number)
22 22 - end of stream marker.
23 23
24 24 the Binary format
25 25 ============================
26 26
27 27 All numbers are unsigned and big-endian.
28 28
29 29 stream level parameters
30 30 ------------------------
31 31
32 32 Binary format is as follow
33 33
34 34 :params size: int32
35 35
36 36 The total number of Bytes used by the parameters
37 37
38 38 :params value: arbitrary number of Bytes
39 39
40 40 A blob of `params size` containing the serialized version of all stream level
41 41 parameters.
42 42
43 43 The blob contains a space separated list of parameters. Parameters with value
44 44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
45 45
46 46 Empty name are obviously forbidden.
47 47
48 48 Name MUST start with a letter. If this first letter is lower case, the
49 49 parameter is advisory and can be safely ignored. However when the first
50 50 letter is capital, the parameter is mandatory and the bundling process MUST
51 51 stop if he is not able to proceed it.
52 52
53 53 Stream parameters use a simple textual format for two main reasons:
54 54
55 55 - Stream level parameters should remain simple and we want to discourage any
56 56 crazy usage.
57 57 - Textual data allow easy human inspection of a bundle2 header in case of
58 58 troubles.
59 59
60 60 Any Applicative level options MUST go into a bundle2 part instead.
61 61
62 62 Payload part
63 63 ------------------------
64 64
65 65 Binary format is as follow
66 66
67 67 :header size: int32
68 68
69 69 The total number of Bytes used by the part headers. When the header is empty
70 70 (size = 0) this is interpreted as the end of stream marker.
71 71
72 72 :header:
73 73
74 74 The header defines how to interpret the part. It contains two piece of
75 75 data: the part type, and the part parameters.
76 76
77 77 The part type is used to route an application level handler, that can
78 78 interpret payload.
79 79
80 80 Part parameters are passed to the application level handler. They are
81 81 meant to convey information that will help the application level object to
82 82 interpret the part payload.
83 83
84 84 The binary format of the header is has follow
85 85
86 86 :typesize: (one byte)
87 87
88 88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
89 89
90 90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
91 91 to this part.
92 92
93 93 :parameters:
94 94
95 95 Part's parameter may have arbitrary content, the binary structure is::
96 96
97 97 <mandatory-count><advisory-count><param-sizes><param-data>
98 98
99 99 :mandatory-count: 1 byte, number of mandatory parameters
100 100
101 101 :advisory-count: 1 byte, number of advisory parameters
102 102
103 103 :param-sizes:
104 104
105 105 N couple of bytes, where N is the total number of parameters. Each
106 106 couple contains (<size-of-key>, <size-of-value) for one parameter.
107 107
108 108 :param-data:
109 109
110 110 A blob of bytes from which each parameter key and value can be
111 111 retrieved using the list of size couples stored in the previous
112 112 field.
113 113
114 114 Mandatory parameters comes first, then the advisory ones.
115 115
116 116 Each parameter's key MUST be unique within the part.
117 117
118 118 :payload:
119 119
120 120 payload is a series of `<chunksize><chunkdata>`.
121 121
122 122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
123 123 `chunksize` says)` The payload part is concluded by a zero size chunk.
124 124
125 125 The current implementation always produces either zero or one chunk.
126 126 This is an implementation limitation that will ultimately be lifted.
127 127
128 128 `chunksize` can be negative to trigger special case processing. No such
129 129 processing is in place yet.
130 130
131 131 Bundle processing
132 132 ============================
133 133
134 134 Each part is processed in order using a "part handler". Handler are registered
135 135 for a certain part type.
136 136
137 137 The matching of a part to its handler is case insensitive. The case of the
138 138 part type is used to know if a part is mandatory or advisory. If the Part type
139 139 contains any uppercase char it is considered mandatory. When no handler is
140 140 known for a Mandatory part, the process is aborted and an exception is raised.
141 141 If the part is advisory and no handler is known, the part is ignored. When the
142 142 process is aborted, the full bundle is still read from the stream to keep the
143 143 channel usable. But none of the part read from an abort are processed. In the
144 144 future, dropping the stream may become an option for channel we do not care to
145 145 preserve.
146 146 """
147 147
148 148 import errno
149 149 import sys
150 150 import util
151 151 import struct
152 152 import urllib
153 153 import string
154 154 import obsolete
155 155 import pushkey
156 156 import url
157 157 import re
158 158
159 159 import changegroup, error
160 160 from i18n import _
161 161
162 162 _pack = struct.pack
163 163 _unpack = struct.unpack
164 164
165 165 _fstreamparamsize = '>i'
166 166 _fpartheadersize = '>i'
167 167 _fparttypesize = '>B'
168 168 _fpartid = '>I'
169 169 _fpayloadsize = '>i'
170 170 _fpartparamcount = '>BB'
171 171
172 172 preferedchunksize = 4096
173 173
174 174 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
175 175
176 176 def outdebug(ui, message):
177 177 """debug regarding output stream (bundling)"""
178 178 ui.debug('bundle2-output: %s\n' % message)
179 179
180 180 def indebug(ui, message):
181 181 """debug on input stream (unbundling)"""
182 182 ui.debug('bundle2-input: %s\n' % message)
183 183
184 184 def validateparttype(parttype):
185 185 """raise ValueError if a parttype contains invalid character"""
186 186 if _parttypeforbidden.search(parttype):
187 187 raise ValueError(parttype)
188 188
189 189 def _makefpartparamsizes(nbparams):
190 190 """return a struct format to read part parameter sizes
191 191
192 192 The number parameters is variable so we need to build that format
193 193 dynamically.
194 194 """
195 195 return '>'+('BB'*nbparams)
196 196
197 197 parthandlermapping = {}
198 198
199 199 def parthandler(parttype, params=()):
200 200 """decorator that register a function as a bundle2 part handler
201 201
202 202 eg::
203 203
204 204 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
205 205 def myparttypehandler(...):
206 206 '''process a part of type "my part".'''
207 207 ...
208 208 """
209 209 validateparttype(parttype)
210 210 def _decorator(func):
211 211 lparttype = parttype.lower() # enforce lower case matching.
212 212 assert lparttype not in parthandlermapping
213 213 parthandlermapping[lparttype] = func
214 214 func.params = frozenset(params)
215 215 return func
216 216 return _decorator
217 217
218 218 class unbundlerecords(object):
219 219 """keep record of what happens during and unbundle
220 220
221 221 New records are added using `records.add('cat', obj)`. Where 'cat' is a
222 222 category of record and obj is an arbitrary object.
223 223
224 224 `records['cat']` will return all entries of this category 'cat'.
225 225
226 226 Iterating on the object itself will yield `('category', obj)` tuples
227 227 for all entries.
228 228
229 229 All iterations happens in chronological order.
230 230 """
231 231
232 232 def __init__(self):
233 233 self._categories = {}
234 234 self._sequences = []
235 235 self._replies = {}
236 236
237 237 def add(self, category, entry, inreplyto=None):
238 238 """add a new record of a given category.
239 239
240 240 The entry can then be retrieved in the list returned by
241 241 self['category']."""
242 242 self._categories.setdefault(category, []).append(entry)
243 243 self._sequences.append((category, entry))
244 244 if inreplyto is not None:
245 245 self.getreplies(inreplyto).add(category, entry)
246 246
247 247 def getreplies(self, partid):
248 248 """get the records that are replies to a specific part"""
249 249 return self._replies.setdefault(partid, unbundlerecords())
250 250
251 251 def __getitem__(self, cat):
252 252 return tuple(self._categories.get(cat, ()))
253 253
254 254 def __iter__(self):
255 255 return iter(self._sequences)
256 256
257 257 def __len__(self):
258 258 return len(self._sequences)
259 259
260 260 def __nonzero__(self):
261 261 return bool(self._sequences)
262 262
263 263 class bundleoperation(object):
264 264 """an object that represents a single bundling process
265 265
266 266 Its purpose is to carry unbundle-related objects and states.
267 267
268 268 A new object should be created at the beginning of each bundle processing.
269 269 The object is to be returned by the processing function.
270 270
271 271 The object has very little content now it will ultimately contain:
272 272 * an access to the repo the bundle is applied to,
273 273 * a ui object,
274 274 * a way to retrieve a transaction to add changes to the repo,
275 275 * a way to record the result of processing each part,
276 276 * a way to construct a bundle response when applicable.
277 277 """
278 278
279 279 def __init__(self, repo, transactiongetter, captureoutput=True):
280 280 self.repo = repo
281 281 self.ui = repo.ui
282 282 self.records = unbundlerecords()
283 283 self.gettransaction = transactiongetter
284 284 self.reply = None
285 285 self.captureoutput = captureoutput
286 286
287 287 class TransactionUnavailable(RuntimeError):
288 288 pass
289 289
290 290 def _notransaction():
291 291 """default method to get a transaction while processing a bundle
292 292
293 293 Raise an exception to highlight the fact that no transaction was expected
294 294 to be created"""
295 295 raise TransactionUnavailable()
296 296
297 297 def processbundle(repo, unbundler, transactiongetter=None, op=None):
298 298 """This function process a bundle, apply effect to/from a repo
299 299
300 300 It iterates over each part then searches for and uses the proper handling
301 301 code to process the part. Parts are processed in order.
302 302
303 303 This is very early version of this function that will be strongly reworked
304 304 before final usage.
305 305
306 306 Unknown Mandatory part will abort the process.
307 307
308 308 It is temporarily possible to provide a prebuilt bundleoperation to the
309 309 function. This is used to ensure output is properly propagated in case of
310 310 an error during the unbundling. This output capturing part will likely be
311 311 reworked and this ability will probably go away in the process.
312 312 """
313 313 if op is None:
314 314 if transactiongetter is None:
315 315 transactiongetter = _notransaction
316 316 op = bundleoperation(repo, transactiongetter)
317 317 # todo:
318 318 # - replace this is a init function soon.
319 319 # - exception catching
320 320 unbundler.params
321 321 if repo.ui.debugflag:
322 322 msg = ['bundle2-input-bundle:']
323 323 if unbundler.params:
324 324 msg.append(' %i params')
325 325 if op.gettransaction is None:
326 326 msg.append(' no-transaction')
327 327 else:
328 328 msg.append(' with-transaction')
329 329 msg.append('\n')
330 330 repo.ui.debug(''.join(msg))
331 331 iterparts = enumerate(unbundler.iterparts())
332 332 part = None
333 333 nbpart = 0
334 334 try:
335 335 for nbpart, part in iterparts:
336 336 _processpart(op, part)
337 337 except BaseException, exc:
338 338 for nbpart, part in iterparts:
339 339 # consume the bundle content
340 340 part.seek(0, 2)
341 341 # Small hack to let caller code distinguish exceptions from bundle2
342 342 # processing from processing the old format. This is mostly
343 343 # needed to handle different return codes to unbundle according to the
344 344 # type of bundle. We should probably clean up or drop this return code
345 345 # craziness in a future version.
346 346 exc.duringunbundle2 = True
347 347 salvaged = []
348 348 if op.reply is not None:
349 349 salvaged = op.reply.salvageoutput()
350 350 exc._bundle2salvagedoutput = salvaged
351 351 raise
352 352 finally:
353 353 repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
354 354
355 355 return op
356 356
357 357 def _processpart(op, part):
358 358 """process a single part from a bundle
359 359
360 360 The part is guaranteed to have been fully consumed when the function exits
361 361 (even if an exception is raised)."""
362 362 status = 'unknown' # used by debug output
363 363 try:
364 364 try:
365 365 handler = parthandlermapping.get(part.type)
366 366 if handler is None:
367 367 status = 'unsupported-type'
368 368 raise error.UnsupportedPartError(parttype=part.type)
369 369 indebug(op.ui, 'found a handler for part %r' % part.type)
370 370 unknownparams = part.mandatorykeys - handler.params
371 371 if unknownparams:
372 372 unknownparams = list(unknownparams)
373 373 unknownparams.sort()
374 374 status = 'unsupported-params (%s)' % unknownparams
375 375 raise error.UnsupportedPartError(parttype=part.type,
376 376 params=unknownparams)
377 377 status = 'supported'
378 378 except error.UnsupportedPartError, exc:
379 379 if part.mandatory: # mandatory parts
380 380 raise
381 381 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
382 382 return # skip to part processing
383 383 finally:
384 384 if op.ui.debugflag:
385 385 msg = ['bundle2-input-part: "%s"' % part.type]
386 386 if not part.mandatory:
387 387 msg.append(' (advisory)')
388 388 nbmp = len(part.mandatorykeys)
389 389 nbap = len(part.params) - nbmp
390 390 if nbmp or nbap:
391 391 msg.append(' (params:')
392 392 if nbmp:
393 393 msg.append(' %i mandatory' % nbmp)
394 394 if nbap:
395 395 msg.append(' %i advisory' % nbmp)
396 396 msg.append(')')
397 397 msg.append(' %s\n' % status)
398 398 op.ui.debug(''.join(msg))
399 399
400 400 # handler is called outside the above try block so that we don't
401 401 # risk catching KeyErrors from anything other than the
402 402 # parthandlermapping lookup (any KeyError raised by handler()
403 403 # itself represents a defect of a different variety).
404 404 output = None
405 405 if op.captureoutput and op.reply is not None:
406 406 op.ui.pushbuffer(error=True, subproc=True)
407 407 output = ''
408 408 try:
409 409 handler(op, part)
410 410 finally:
411 411 if output is not None:
412 412 output = op.ui.popbuffer()
413 413 if output:
414 414 outpart = op.reply.newpart('output', data=output,
415 415 mandatory=False)
416 416 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
417 417 finally:
418 418 # consume the part content to not corrupt the stream.
419 419 part.seek(0, 2)
420 420
421 421
422 422 def decodecaps(blob):
423 423 """decode a bundle2 caps bytes blob into a dictionary
424 424
425 425 The blob is a list of capabilities (one per line)
426 426 Capabilities may have values using a line of the form::
427 427
428 428 capability=value1,value2,value3
429 429
430 430 The values are always a list."""
431 431 caps = {}
432 432 for line in blob.splitlines():
433 433 if not line:
434 434 continue
435 435 if '=' not in line:
436 436 key, vals = line, ()
437 437 else:
438 438 key, vals = line.split('=', 1)
439 439 vals = vals.split(',')
440 440 key = urllib.unquote(key)
441 441 vals = [urllib.unquote(v) for v in vals]
442 442 caps[key] = vals
443 443 return caps
444 444
445 445 def encodecaps(caps):
446 446 """encode a bundle2 caps dictionary into a bytes blob"""
447 447 chunks = []
448 448 for ca in sorted(caps):
449 449 vals = caps[ca]
450 450 ca = urllib.quote(ca)
451 451 vals = [urllib.quote(v) for v in vals]
452 452 if vals:
453 453 ca = "%s=%s" % (ca, ','.join(vals))
454 454 chunks.append(ca)
455 455 return '\n'.join(chunks)
456 456
457 457 class bundle20(object):
458 458 """represent an outgoing bundle2 container
459 459
460 460 Use the `addparam` method to add stream level parameter. and `newpart` to
461 461 populate it. Then call `getchunks` to retrieve all the binary chunks of
462 462 data that compose the bundle2 container."""
463 463
464 464 _magicstring = 'HG20'
465 465
466 466 def __init__(self, ui, capabilities=()):
467 467 self.ui = ui
468 468 self._params = []
469 469 self._parts = []
470 470 self.capabilities = dict(capabilities)
471 471
472 472 @property
473 473 def nbparts(self):
474 474 """total number of parts added to the bundler"""
475 475 return len(self._parts)
476 476
477 477 # methods used to defines the bundle2 content
478 478 def addparam(self, name, value=None):
479 479 """add a stream level parameter"""
480 480 if not name:
481 481 raise ValueError('empty parameter name')
482 482 if name[0] not in string.letters:
483 483 raise ValueError('non letter first character: %r' % name)
484 484 self._params.append((name, value))
485 485
486 486 def addpart(self, part):
487 487 """add a new part to the bundle2 container
488 488
489 489 Parts contains the actual applicative payload."""
490 490 assert part.id is None
491 491 part.id = len(self._parts) # very cheap counter
492 492 self._parts.append(part)
493 493
494 494 def newpart(self, typeid, *args, **kwargs):
495 495 """create a new part and add it to the containers
496 496
497 497 As the part is directly added to the containers. For now, this means
498 498 that any failure to properly initialize the part after calling
499 499 ``newpart`` should result in a failure of the whole bundling process.
500 500
501 501 You can still fall back to manually create and add if you need better
502 502 control."""
503 503 part = bundlepart(typeid, *args, **kwargs)
504 504 self.addpart(part)
505 505 return part
506 506
507 507 # methods used to generate the bundle2 stream
508 508 def getchunks(self):
509 509 if self.ui.debugflag:
510 510 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
511 511 if self._params:
512 512 msg.append(' (%i params)' % len(self._params))
513 513 msg.append(' %i parts total\n' % len(self._parts))
514 514 self.ui.debug(''.join(msg))
515 515 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
516 516 yield self._magicstring
517 517 param = self._paramchunk()
518 518 outdebug(self.ui, 'bundle parameter: %s' % param)
519 519 yield _pack(_fstreamparamsize, len(param))
520 520 if param:
521 521 yield param
522 522
523 523 outdebug(self.ui, 'start of parts')
524 524 for part in self._parts:
525 525 outdebug(self.ui, 'bundle part: "%s"' % part.type)
526 526 for chunk in part.getchunks(ui=self.ui):
527 527 yield chunk
528 528 outdebug(self.ui, 'end of bundle')
529 529 yield _pack(_fpartheadersize, 0)
530 530
531 531 def _paramchunk(self):
532 532 """return a encoded version of all stream parameters"""
533 533 blocks = []
534 534 for par, value in self._params:
535 535 par = urllib.quote(par)
536 536 if value is not None:
537 537 value = urllib.quote(value)
538 538 par = '%s=%s' % (par, value)
539 539 blocks.append(par)
540 540 return ' '.join(blocks)
541 541
542 542 def salvageoutput(self):
543 543 """return a list with a copy of all output parts in the bundle
544 544
545 545 This is meant to be used during error handling to make sure we preserve
546 546 server output"""
547 547 salvaged = []
548 548 for part in self._parts:
549 549 if part.type.startswith('output'):
550 550 salvaged.append(part.copy())
551 551 return salvaged
552 552
553 553
554 554 class unpackermixin(object):
555 555 """A mixin to extract bytes and struct data from a stream"""
556 556
557 557 def __init__(self, fp):
558 558 self._fp = fp
559 559 self._seekable = (util.safehasattr(fp, 'seek') and
560 560 util.safehasattr(fp, 'tell'))
561 561
562 562 def _unpack(self, format):
563 563 """unpack this struct format from the stream"""
564 564 data = self._readexact(struct.calcsize(format))
565 565 return _unpack(format, data)
566 566
567 567 def _readexact(self, size):
568 568 """read exactly <size> bytes from the stream"""
569 569 return changegroup.readexactly(self._fp, size)
570 570
571 571 def seek(self, offset, whence=0):
572 572 """move the underlying file pointer"""
573 573 if self._seekable:
574 574 return self._fp.seek(offset, whence)
575 575 else:
576 576 raise NotImplementedError(_('File pointer is not seekable'))
577 577
578 578 def tell(self):
579 579 """return the file offset, or None if file is not seekable"""
580 580 if self._seekable:
581 581 try:
582 582 return self._fp.tell()
583 583 except IOError, e:
584 584 if e.errno == errno.ESPIPE:
585 585 self._seekable = False
586 586 else:
587 587 raise
588 588 return None
589 589
590 590 def close(self):
591 591 """close underlying file"""
592 592 if util.safehasattr(self._fp, 'close'):
593 593 return self._fp.close()
594 594
595 595 def getunbundler(ui, fp, header=None):
596 596 """return a valid unbundler object for a given header"""
597 597 if header is None:
598 598 header = changegroup.readexactly(fp, 4)
599 599 magic, version = header[0:2], header[2:4]
600 600 if magic != 'HG':
601 601 raise util.Abort(_('not a Mercurial bundle'))
602 602 unbundlerclass = formatmap.get(version)
603 603 if unbundlerclass is None:
604 604 raise util.Abort(_('unknown bundle version %s') % version)
605 605 unbundler = unbundlerclass(ui, fp)
606 606 indebug(ui, 'start processing of %s stream' % header)
607 607 return unbundler
608 608
609 609 class unbundle20(unpackermixin):
610 610 """interpret a bundle2 stream
611 611
612 612 This class is fed with a binary stream and yields parts through its
613 613 `iterparts` methods."""
614 614
615 615 def __init__(self, ui, fp):
616 616 """If header is specified, we do not read it out of the stream."""
617 617 self.ui = ui
618 618 super(unbundle20, self).__init__(fp)
619 619
620 620 @util.propertycache
621 621 def params(self):
622 622 """dictionary of stream level parameters"""
623 623 indebug(self.ui, 'reading bundle2 stream parameters')
624 624 params = {}
625 625 paramssize = self._unpack(_fstreamparamsize)[0]
626 626 if paramssize < 0:
627 627 raise error.BundleValueError('negative bundle param size: %i'
628 628 % paramssize)
629 629 if paramssize:
630 630 for p in self._readexact(paramssize).split(' '):
631 631 p = p.split('=', 1)
632 632 p = [urllib.unquote(i) for i in p]
633 633 if len(p) < 2:
634 634 p.append(None)
635 635 self._processparam(*p)
636 636 params[p[0]] = p[1]
637 637 return params
638 638
639 639 def _processparam(self, name, value):
640 640 """process a parameter, applying its effect if needed
641 641
642 642 Parameter starting with a lower case letter are advisory and will be
643 643 ignored when unknown. Those starting with an upper case letter are
644 644 mandatory and will this function will raise a KeyError when unknown.
645 645
646 646 Note: no option are currently supported. Any input will be either
647 647 ignored or failing.
648 648 """
649 649 if not name:
650 650 raise ValueError('empty parameter name')
651 651 if name[0] not in string.letters:
652 652 raise ValueError('non letter first character: %r' % name)
653 653 # Some logic will be later added here to try to process the option for
654 654 # a dict of known parameter.
655 655 if name[0].islower():
656 656 indebug(self.ui, "ignoring unknown parameter %r" % name)
657 657 else:
658 658 raise error.UnsupportedPartError(params=(name,))
659 659
660 660
661 661 def iterparts(self):
662 662 """yield all parts contained in the stream"""
663 663 # make sure param have been loaded
664 664 self.params
665 665 indebug(self.ui, 'start extraction of bundle2 parts')
666 666 headerblock = self._readpartheader()
667 667 while headerblock is not None:
668 668 part = unbundlepart(self.ui, headerblock, self._fp)
669 669 yield part
670 670 part.seek(0, 2)
671 671 headerblock = self._readpartheader()
672 672 indebug(self.ui, 'end of bundle2 stream')
673 673
674 674 def _readpartheader(self):
675 675 """reads a part header size and return the bytes blob
676 676
677 677 returns None if empty"""
678 678 headersize = self._unpack(_fpartheadersize)[0]
679 679 if headersize < 0:
680 680 raise error.BundleValueError('negative part header size: %i'
681 681 % headersize)
682 682 indebug(self.ui, 'part header size: %i' % headersize)
683 683 if headersize:
684 684 return self._readexact(headersize)
685 685 return None
686 686
687 687 def compressed(self):
688 688 return False
689 689
690 690 formatmap = {'20': unbundle20}
691 691
692 692 class bundlepart(object):
693 693 """A bundle2 part contains application level payload
694 694
695 695 The part `type` is used to route the part to the application level
696 696 handler.
697 697
698 698 The part payload is contained in ``part.data``. It could be raw bytes or a
699 699 generator of byte chunks.
700 700
701 701 You can add parameters to the part using the ``addparam`` method.
702 702 Parameters can be either mandatory (default) or advisory. Remote side
703 703 should be able to safely ignore the advisory ones.
704 704
705 705 Both data and parameters cannot be modified after the generation has begun.
706 706 """
707 707
708 708 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
709 709 data='', mandatory=True):
710 710 validateparttype(parttype)
711 711 self.id = None
712 712 self.type = parttype
713 713 self._data = data
714 714 self._mandatoryparams = list(mandatoryparams)
715 715 self._advisoryparams = list(advisoryparams)
716 716 # checking for duplicated entries
717 717 self._seenparams = set()
718 718 for pname, __ in self._mandatoryparams + self._advisoryparams:
719 719 if pname in self._seenparams:
720 720 raise RuntimeError('duplicated params: %s' % pname)
721 721 self._seenparams.add(pname)
722 722 # status of the part's generation:
723 723 # - None: not started,
724 724 # - False: currently generated,
725 725 # - True: generation done.
726 726 self._generated = None
727 727 self.mandatory = mandatory
728 728
729 729 def copy(self):
730 730 """return a copy of the part
731 731
732 732 The new part have the very same content but no partid assigned yet.
733 733 Parts with generated data cannot be copied."""
734 734 assert not util.safehasattr(self.data, 'next')
735 735 return self.__class__(self.type, self._mandatoryparams,
736 736 self._advisoryparams, self._data, self.mandatory)
737 737
738 738 # methods used to defines the part content
739 739 def __setdata(self, data):
740 740 if self._generated is not None:
741 741 raise error.ReadOnlyPartError('part is being generated')
742 742 self._data = data
743 743 def __getdata(self):
744 744 return self._data
745 745 data = property(__getdata, __setdata)
746 746
747 747 @property
748 748 def mandatoryparams(self):
749 749 # make it an immutable tuple to force people through ``addparam``
750 750 return tuple(self._mandatoryparams)
751 751
752 752 @property
753 753 def advisoryparams(self):
754 754 # make it an immutable tuple to force people through ``addparam``
755 755 return tuple(self._advisoryparams)
756 756
757 757 def addparam(self, name, value='', mandatory=True):
758 758 if self._generated is not None:
759 759 raise error.ReadOnlyPartError('part is being generated')
760 760 if name in self._seenparams:
761 761 raise ValueError('duplicated params: %s' % name)
762 762 self._seenparams.add(name)
763 763 params = self._advisoryparams
764 764 if mandatory:
765 765 params = self._mandatoryparams
766 766 params.append((name, value))
767 767
768 768 # methods used to generates the bundle2 stream
769 769 def getchunks(self, ui):
770 770 if self._generated is not None:
771 771 raise RuntimeError('part can only be consumed once')
772 772 self._generated = False
773 773
774 774 if ui.debugflag:
775 775 msg = ['bundle2-output-part: "%s"' % self.type]
776 776 if not self.mandatory:
777 777 msg.append(' (advisory)')
778 778 nbmp = len(self.mandatoryparams)
779 779 nbap = len(self.advisoryparams)
780 780 if nbmp or nbap:
781 781 msg.append(' (params:')
782 782 if nbmp:
783 783 msg.append(' %i mandatory' % nbmp)
784 784 if nbap:
785 785 msg.append(' %i advisory' % nbmp)
786 786 msg.append(')')
787 787 if not self.data:
788 788 msg.append(' empty payload')
789 789 elif util.safehasattr(self.data, 'next'):
790 790 msg.append(' streamed payload')
791 791 else:
792 792 msg.append(' %i bytes payload' % len(self.data))
793 793 msg.append('\n')
794 794 ui.debug(''.join(msg))
795 795
796 796 #### header
797 797 if self.mandatory:
798 798 parttype = self.type.upper()
799 799 else:
800 800 parttype = self.type.lower()
801 801 outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
802 802 ## parttype
803 803 header = [_pack(_fparttypesize, len(parttype)),
804 804 parttype, _pack(_fpartid, self.id),
805 805 ]
806 806 ## parameters
807 807 # count
808 808 manpar = self.mandatoryparams
809 809 advpar = self.advisoryparams
810 810 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
811 811 # size
812 812 parsizes = []
813 813 for key, value in manpar:
814 814 parsizes.append(len(key))
815 815 parsizes.append(len(value))
816 816 for key, value in advpar:
817 817 parsizes.append(len(key))
818 818 parsizes.append(len(value))
819 819 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
820 820 header.append(paramsizes)
821 821 # key, value
822 822 for key, value in manpar:
823 823 header.append(key)
824 824 header.append(value)
825 825 for key, value in advpar:
826 826 header.append(key)
827 827 header.append(value)
828 828 ## finalize header
829 829 headerchunk = ''.join(header)
830 830 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
831 831 yield _pack(_fpartheadersize, len(headerchunk))
832 832 yield headerchunk
833 833 ## payload
834 834 try:
835 835 for chunk in self._payloadchunks():
836 836 outdebug(ui, 'payload chunk size: %i' % len(chunk))
837 837 yield _pack(_fpayloadsize, len(chunk))
838 838 yield chunk
839 839 except BaseException, exc:
840 840 # backup exception data for later
841 841 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
842 842 % exc)
843 843 exc_info = sys.exc_info()
844 844 msg = 'unexpected error: %s' % exc
845 845 interpart = bundlepart('error:abort', [('message', msg)],
846 846 mandatory=False)
847 847 interpart.id = 0
848 848 yield _pack(_fpayloadsize, -1)
849 849 for chunk in interpart.getchunks(ui=ui):
850 850 yield chunk
851 851 outdebug(ui, 'closing payload chunk')
852 852 # abort current part payload
853 853 yield _pack(_fpayloadsize, 0)
854 854 raise exc_info[0], exc_info[1], exc_info[2]
855 855 # end of payload
856 856 outdebug(ui, 'closing payload chunk')
857 857 yield _pack(_fpayloadsize, 0)
858 858 self._generated = True
859 859
860 860 def _payloadchunks(self):
861 861 """yield chunks of a the part payload
862 862
863 863 Exists to handle the different methods to provide data to a part."""
864 864 # we only support fixed size data now.
865 865 # This will be improved in the future.
866 866 if util.safehasattr(self.data, 'next'):
867 867 buff = util.chunkbuffer(self.data)
868 868 chunk = buff.read(preferedchunksize)
869 869 while chunk:
870 870 yield chunk
871 871 chunk = buff.read(preferedchunksize)
872 872 elif len(self.data):
873 873 yield self.data
874 874
875 875
876 876 flaginterrupt = -1
877 877
878 878 class interrupthandler(unpackermixin):
879 879 """read one part and process it with restricted capability
880 880
881 881 This allows to transmit exception raised on the producer size during part
882 882 iteration while the consumer is reading a part.
883 883
884 884 Part processed in this manner only have access to a ui object,"""
885 885
886 886 def __init__(self, ui, fp):
887 887 super(interrupthandler, self).__init__(fp)
888 888 self.ui = ui
889 889
890 890 def _readpartheader(self):
891 891 """reads a part header size and return the bytes blob
892 892
893 893 returns None if empty"""
894 894 headersize = self._unpack(_fpartheadersize)[0]
895 895 if headersize < 0:
896 896 raise error.BundleValueError('negative part header size: %i'
897 897 % headersize)
898 898 indebug(self.ui, 'part header size: %i\n' % headersize)
899 899 if headersize:
900 900 return self._readexact(headersize)
901 901 return None
902 902
903 903 def __call__(self):
904 904 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
905 905 headerblock = self._readpartheader()
906 906 if headerblock is None:
907 907 indebug(self.ui, 'no part found during interruption.')
908 908 return
909 909 part = unbundlepart(self.ui, headerblock, self._fp)
910 910 op = interruptoperation(self.ui)
911 911 _processpart(op, part)
912 912
913 913 class interruptoperation(object):
914 914 """A limited operation to be use by part handler during interruption
915 915
916 916 It only have access to an ui object.
917 917 """
918 918
919 919 def __init__(self, ui):
920 920 self.ui = ui
921 921 self.reply = None
922 922 self.captureoutput = False
923 923
924 924 @property
925 925 def repo(self):
926 926 raise RuntimeError('no repo access from stream interruption')
927 927
928 928 def gettransaction(self):
929 929 raise TransactionUnavailable('no repo access from stream interruption')
930 930
931 931 class unbundlepart(unpackermixin):
932 932 """a bundle part read from a bundle"""
933 933
934 934 def __init__(self, ui, header, fp):
935 935 super(unbundlepart, self).__init__(fp)
936 936 self.ui = ui
937 937 # unbundle state attr
938 938 self._headerdata = header
939 939 self._headeroffset = 0
940 940 self._initialized = False
941 941 self.consumed = False
942 942 # part data
943 943 self.id = None
944 944 self.type = None
945 945 self.mandatoryparams = None
946 946 self.advisoryparams = None
947 947 self.params = None
948 948 self.mandatorykeys = ()
949 949 self._payloadstream = None
950 950 self._readheader()
951 951 self._mandatory = None
952 952 self._chunkindex = [] #(payload, file) position tuples for chunk starts
953 953 self._pos = 0
954 954
955 955 def _fromheader(self, size):
956 956 """return the next <size> byte from the header"""
957 957 offset = self._headeroffset
958 958 data = self._headerdata[offset:(offset + size)]
959 959 self._headeroffset = offset + size
960 960 return data
961 961
962 962 def _unpackheader(self, format):
963 963 """read given format from header
964 964
965 965 This automatically compute the size of the format to read."""
966 966 data = self._fromheader(struct.calcsize(format))
967 967 return _unpack(format, data)
968 968
969 969 def _initparams(self, mandatoryparams, advisoryparams):
970 970 """internal function to setup all logic related parameters"""
971 971 # make it read only to prevent people touching it by mistake.
972 972 self.mandatoryparams = tuple(mandatoryparams)
973 973 self.advisoryparams = tuple(advisoryparams)
974 974 # user friendly UI
975 975 self.params = dict(self.mandatoryparams)
976 976 self.params.update(dict(self.advisoryparams))
977 977 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
978 978
979 979 def _payloadchunks(self, chunknum=0):
980 980 '''seek to specified chunk and start yielding data'''
981 981 if len(self._chunkindex) == 0:
982 982 assert chunknum == 0, 'Must start with chunk 0'
983 983 self._chunkindex.append((0, super(unbundlepart, self).tell()))
984 984 else:
985 985 assert chunknum < len(self._chunkindex), \
986 986 'Unknown chunk %d' % chunknum
987 987 super(unbundlepart, self).seek(self._chunkindex[chunknum][1])
988 988
989 989 pos = self._chunkindex[chunknum][0]
990 990 payloadsize = self._unpack(_fpayloadsize)[0]
991 991 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
992 992 while payloadsize:
993 993 if payloadsize == flaginterrupt:
994 994 # interruption detection, the handler will now read a
995 995 # single part and process it.
996 996 interrupthandler(self.ui, self._fp)()
997 997 elif payloadsize < 0:
998 998 msg = 'negative payload chunk size: %i' % payloadsize
999 999 raise error.BundleValueError(msg)
1000 1000 else:
1001 1001 result = self._readexact(payloadsize)
1002 1002 chunknum += 1
1003 1003 pos += payloadsize
1004 1004 if chunknum == len(self._chunkindex):
1005 1005 self._chunkindex.append((pos,
1006 1006 super(unbundlepart, self).tell()))
1007 1007 yield result
1008 1008 payloadsize = self._unpack(_fpayloadsize)[0]
1009 1009 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1010 1010
1011 1011 def _findchunk(self, pos):
1012 1012 '''for a given payload position, return a chunk number and offset'''
1013 1013 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1014 1014 if ppos == pos:
1015 1015 return chunk, 0
1016 1016 elif ppos > pos:
1017 1017 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1018 1018 raise ValueError('Unknown chunk')
1019 1019
1020 1020 def _readheader(self):
1021 1021 """read the header and setup the object"""
1022 1022 typesize = self._unpackheader(_fparttypesize)[0]
1023 1023 self.type = self._fromheader(typesize)
1024 1024 indebug(self.ui, 'part type: "%s"' % self.type)
1025 1025 self.id = self._unpackheader(_fpartid)[0]
1026 1026 indebug(self.ui, 'part id: "%s"' % self.id)
1027 1027 # extract mandatory bit from type
1028 1028 self.mandatory = (self.type != self.type.lower())
1029 1029 self.type = self.type.lower()
1030 1030 ## reading parameters
1031 1031 # param count
1032 1032 mancount, advcount = self._unpackheader(_fpartparamcount)
1033 1033 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1034 1034 # param size
1035 1035 fparamsizes = _makefpartparamsizes(mancount + advcount)
1036 1036 paramsizes = self._unpackheader(fparamsizes)
1037 1037 # make it a list of couple again
1038 1038 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
1039 1039 # split mandatory from advisory
1040 1040 mansizes = paramsizes[:mancount]
1041 1041 advsizes = paramsizes[mancount:]
1042 1042 # retrieve param value
1043 1043 manparams = []
1044 1044 for key, value in mansizes:
1045 1045 manparams.append((self._fromheader(key), self._fromheader(value)))
1046 1046 advparams = []
1047 1047 for key, value in advsizes:
1048 1048 advparams.append((self._fromheader(key), self._fromheader(value)))
1049 1049 self._initparams(manparams, advparams)
1050 1050 ## part payload
1051 1051 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1052 1052 # we read the data, tell it
1053 1053 self._initialized = True
1054 1054
1055 1055 def read(self, size=None):
1056 1056 """read payload data"""
1057 1057 if not self._initialized:
1058 1058 self._readheader()
1059 1059 if size is None:
1060 1060 data = self._payloadstream.read()
1061 1061 else:
1062 1062 data = self._payloadstream.read(size)
1063 self._pos += len(data)
1063 1064 if size is None or len(data) < size:
1065 if not self.consumed and self._pos:
1066 self.ui.debug('bundle2-input-part: total payload size %i\n'
1067 % self._pos)
1064 1068 self.consumed = True
1065 self._pos += len(data)
1066 1069 return data
1067 1070
1068 1071 def tell(self):
1069 1072 return self._pos
1070 1073
1071 1074 def seek(self, offset, whence=0):
1072 1075 if whence == 0:
1073 1076 newpos = offset
1074 1077 elif whence == 1:
1075 1078 newpos = self._pos + offset
1076 1079 elif whence == 2:
1077 1080 if not self.consumed:
1078 1081 self.read()
1079 1082 newpos = self._chunkindex[-1][0] - offset
1080 1083 else:
1081 1084 raise ValueError('Unknown whence value: %r' % (whence,))
1082 1085
1083 1086 if newpos > self._chunkindex[-1][0] and not self.consumed:
1084 1087 self.read()
1085 1088 if not 0 <= newpos <= self._chunkindex[-1][0]:
1086 1089 raise ValueError('Offset out of range')
1087 1090
1088 1091 if self._pos != newpos:
1089 1092 chunk, internaloffset = self._findchunk(newpos)
1090 1093 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1091 1094 adjust = self.read(internaloffset)
1092 1095 if len(adjust) != internaloffset:
1093 1096 raise util.Abort(_('Seek failed\n'))
1094 1097 self._pos = newpos
1095 1098
1096 1099 # These are only the static capabilities.
1097 1100 # Check the 'getrepocaps' function for the rest.
1098 1101 capabilities = {'HG20': (),
1099 1102 'listkeys': (),
1100 1103 'pushkey': (),
1101 1104 'digests': tuple(sorted(util.DIGESTS.keys())),
1102 1105 'remote-changegroup': ('http', 'https'),
1103 1106 }
1104 1107
1105 1108 def getrepocaps(repo, allowpushback=False):
1106 1109 """return the bundle2 capabilities for a given repo
1107 1110
1108 1111 Exists to allow extensions (like evolution) to mutate the capabilities.
1109 1112 """
1110 1113 caps = capabilities.copy()
1111 1114 caps['changegroup'] = tuple(sorted(changegroup.packermap.keys()))
1112 1115 if obsolete.isenabled(repo, obsolete.exchangeopt):
1113 1116 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1114 1117 caps['obsmarkers'] = supportedformat
1115 1118 if allowpushback:
1116 1119 caps['pushback'] = ()
1117 1120 return caps
1118 1121
1119 1122 def bundle2caps(remote):
1120 1123 """return the bundle capabilities of a peer as dict"""
1121 1124 raw = remote.capable('bundle2')
1122 1125 if not raw and raw != '':
1123 1126 return {}
1124 1127 capsblob = urllib.unquote(remote.capable('bundle2'))
1125 1128 return decodecaps(capsblob)
1126 1129
1127 1130 def obsmarkersversion(caps):
1128 1131 """extract the list of supported obsmarkers versions from a bundle2caps dict
1129 1132 """
1130 1133 obscaps = caps.get('obsmarkers', ())
1131 1134 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1132 1135
1133 1136 @parthandler('changegroup', ('version',))
1134 1137 def handlechangegroup(op, inpart):
1135 1138 """apply a changegroup part on the repo
1136 1139
1137 1140 This is a very early implementation that will massive rework before being
1138 1141 inflicted to any end-user.
1139 1142 """
1140 1143 # Make sure we trigger a transaction creation
1141 1144 #
1142 1145 # The addchangegroup function will get a transaction object by itself, but
1143 1146 # we need to make sure we trigger the creation of a transaction object used
1144 1147 # for the whole processing scope.
1145 1148 op.gettransaction()
1146 1149 unpackerversion = inpart.params.get('version', '01')
1147 1150 # We should raise an appropriate exception here
1148 1151 unpacker = changegroup.packermap[unpackerversion][1]
1149 1152 cg = unpacker(inpart, 'UN')
1150 1153 # the source and url passed here are overwritten by the one contained in
1151 1154 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1152 1155 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
1153 1156 op.records.add('changegroup', {'return': ret})
1154 1157 if op.reply is not None:
1155 1158 # This is definitely not the final form of this
1156 1159 # return. But one need to start somewhere.
1157 1160 part = op.reply.newpart('reply:changegroup', mandatory=False)
1158 1161 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1159 1162 part.addparam('return', '%i' % ret, mandatory=False)
1160 1163 assert not inpart.read()
1161 1164
1162 1165 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1163 1166 ['digest:%s' % k for k in util.DIGESTS.keys()])
1164 1167 @parthandler('remote-changegroup', _remotechangegroupparams)
1165 1168 def handleremotechangegroup(op, inpart):
1166 1169 """apply a bundle10 on the repo, given an url and validation information
1167 1170
1168 1171 All the information about the remote bundle to import are given as
1169 1172 parameters. The parameters include:
1170 1173 - url: the url to the bundle10.
1171 1174 - size: the bundle10 file size. It is used to validate what was
1172 1175 retrieved by the client matches the server knowledge about the bundle.
1173 1176 - digests: a space separated list of the digest types provided as
1174 1177 parameters.
1175 1178 - digest:<digest-type>: the hexadecimal representation of the digest with
1176 1179 that name. Like the size, it is used to validate what was retrieved by
1177 1180 the client matches what the server knows about the bundle.
1178 1181
1179 1182 When multiple digest types are given, all of them are checked.
1180 1183 """
1181 1184 try:
1182 1185 raw_url = inpart.params['url']
1183 1186 except KeyError:
1184 1187 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1185 1188 parsed_url = util.url(raw_url)
1186 1189 if parsed_url.scheme not in capabilities['remote-changegroup']:
1187 1190 raise util.Abort(_('remote-changegroup does not support %s urls') %
1188 1191 parsed_url.scheme)
1189 1192
1190 1193 try:
1191 1194 size = int(inpart.params['size'])
1192 1195 except ValueError:
1193 1196 raise util.Abort(_('remote-changegroup: invalid value for param "%s"')
1194 1197 % 'size')
1195 1198 except KeyError:
1196 1199 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1197 1200
1198 1201 digests = {}
1199 1202 for typ in inpart.params.get('digests', '').split():
1200 1203 param = 'digest:%s' % typ
1201 1204 try:
1202 1205 value = inpart.params[param]
1203 1206 except KeyError:
1204 1207 raise util.Abort(_('remote-changegroup: missing "%s" param') %
1205 1208 param)
1206 1209 digests[typ] = value
1207 1210
1208 1211 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1209 1212
1210 1213 # Make sure we trigger a transaction creation
1211 1214 #
1212 1215 # The addchangegroup function will get a transaction object by itself, but
1213 1216 # we need to make sure we trigger the creation of a transaction object used
1214 1217 # for the whole processing scope.
1215 1218 op.gettransaction()
1216 1219 import exchange
1217 1220 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1218 1221 if not isinstance(cg, changegroup.cg1unpacker):
1219 1222 raise util.Abort(_('%s: not a bundle version 1.0') %
1220 1223 util.hidepassword(raw_url))
1221 1224 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
1222 1225 op.records.add('changegroup', {'return': ret})
1223 1226 if op.reply is not None:
1224 1227 # This is definitely not the final form of this
1225 1228 # return. But one need to start somewhere.
1226 1229 part = op.reply.newpart('reply:changegroup')
1227 1230 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1228 1231 part.addparam('return', '%i' % ret, mandatory=False)
1229 1232 try:
1230 1233 real_part.validate()
1231 1234 except util.Abort, e:
1232 1235 raise util.Abort(_('bundle at %s is corrupted:\n%s') %
1233 1236 (util.hidepassword(raw_url), str(e)))
1234 1237 assert not inpart.read()
1235 1238
1236 1239 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1237 1240 def handlereplychangegroup(op, inpart):
1238 1241 ret = int(inpart.params['return'])
1239 1242 replyto = int(inpart.params['in-reply-to'])
1240 1243 op.records.add('changegroup', {'return': ret}, replyto)
1241 1244
1242 1245 @parthandler('check:heads')
1243 1246 def handlecheckheads(op, inpart):
1244 1247 """check that head of the repo did not change
1245 1248
1246 1249 This is used to detect a push race when using unbundle.
1247 1250 This replaces the "heads" argument of unbundle."""
1248 1251 h = inpart.read(20)
1249 1252 heads = []
1250 1253 while len(h) == 20:
1251 1254 heads.append(h)
1252 1255 h = inpart.read(20)
1253 1256 assert not h
1254 1257 if heads != op.repo.heads():
1255 1258 raise error.PushRaced('repository changed while pushing - '
1256 1259 'please try again')
1257 1260
1258 1261 @parthandler('output')
1259 1262 def handleoutput(op, inpart):
1260 1263 """forward output captured on the server to the client"""
1261 1264 for line in inpart.read().splitlines():
1262 1265 op.ui.status(('remote: %s\n' % line))
1263 1266
1264 1267 @parthandler('replycaps')
1265 1268 def handlereplycaps(op, inpart):
1266 1269 """Notify that a reply bundle should be created
1267 1270
1268 1271 The payload contains the capabilities information for the reply"""
1269 1272 caps = decodecaps(inpart.read())
1270 1273 if op.reply is None:
1271 1274 op.reply = bundle20(op.ui, caps)
1272 1275
1273 1276 @parthandler('error:abort', ('message', 'hint'))
1274 1277 def handleerrorabort(op, inpart):
1275 1278 """Used to transmit abort error over the wire"""
1276 1279 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
1277 1280
1278 1281 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1279 1282 def handleerrorunsupportedcontent(op, inpart):
1280 1283 """Used to transmit unknown content error over the wire"""
1281 1284 kwargs = {}
1282 1285 parttype = inpart.params.get('parttype')
1283 1286 if parttype is not None:
1284 1287 kwargs['parttype'] = parttype
1285 1288 params = inpart.params.get('params')
1286 1289 if params is not None:
1287 1290 kwargs['params'] = params.split('\0')
1288 1291
1289 1292 raise error.UnsupportedPartError(**kwargs)
1290 1293
1291 1294 @parthandler('error:pushraced', ('message',))
1292 1295 def handleerrorpushraced(op, inpart):
1293 1296 """Used to transmit push race error over the wire"""
1294 1297 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1295 1298
1296 1299 @parthandler('listkeys', ('namespace',))
1297 1300 def handlelistkeys(op, inpart):
1298 1301 """retrieve pushkey namespace content stored in a bundle2"""
1299 1302 namespace = inpart.params['namespace']
1300 1303 r = pushkey.decodekeys(inpart.read())
1301 1304 op.records.add('listkeys', (namespace, r))
1302 1305
1303 1306 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1304 1307 def handlepushkey(op, inpart):
1305 1308 """process a pushkey request"""
1306 1309 dec = pushkey.decode
1307 1310 namespace = dec(inpart.params['namespace'])
1308 1311 key = dec(inpart.params['key'])
1309 1312 old = dec(inpart.params['old'])
1310 1313 new = dec(inpart.params['new'])
1311 1314 ret = op.repo.pushkey(namespace, key, old, new)
1312 1315 record = {'namespace': namespace,
1313 1316 'key': key,
1314 1317 'old': old,
1315 1318 'new': new}
1316 1319 op.records.add('pushkey', record)
1317 1320 if op.reply is not None:
1318 1321 rpart = op.reply.newpart('reply:pushkey')
1319 1322 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1320 1323 rpart.addparam('return', '%i' % ret, mandatory=False)
1321 1324
1322 1325 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
1323 1326 def handlepushkeyreply(op, inpart):
1324 1327 """retrieve the result of a pushkey request"""
1325 1328 ret = int(inpart.params['return'])
1326 1329 partid = int(inpart.params['in-reply-to'])
1327 1330 op.records.add('pushkey', {'return': ret}, partid)
1328 1331
1329 1332 @parthandler('obsmarkers')
1330 1333 def handleobsmarker(op, inpart):
1331 1334 """add a stream of obsmarkers to the repo"""
1332 1335 tr = op.gettransaction()
1333 1336 markerdata = inpart.read()
1334 1337 if op.ui.config('experimental', 'obsmarkers-exchange-debug', False):
1335 1338 op.ui.write(('obsmarker-exchange: %i bytes received\n')
1336 1339 % len(markerdata))
1337 1340 new = op.repo.obsstore.mergemarkers(tr, markerdata)
1338 1341 if new:
1339 1342 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
1340 1343 op.records.add('obsmarkers', {'new': new})
1341 1344 if op.reply is not None:
1342 1345 rpart = op.reply.newpart('reply:obsmarkers')
1343 1346 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1344 1347 rpart.addparam('new', '%i' % new, mandatory=False)
1345 1348
1346 1349
1347 1350 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
1348 1351 def handlepushkeyreply(op, inpart):
1349 1352 """retrieve the result of a pushkey request"""
1350 1353 ret = int(inpart.params['new'])
1351 1354 partid = int(inpart.params['in-reply-to'])
1352 1355 op.records.add('obsmarkers', {'new': ret}, partid)
@@ -1,850 +1,854 b''
1 1 This test is dedicated to test the bundle2 container format
2 2
3 3 It test multiple existing parts to test different feature of the container. You
4 4 probably do not need to touch this test unless you change the binary encoding
5 5 of the bundle2 format itself.
6 6
7 7 Create an extension to test bundle2 API
8 8
9 9 $ cat > bundle2.py << EOF
10 10 > """A small extension to test bundle2 implementation
11 11 >
12 12 > Current bundle2 implementation is far too limited to be used in any core
13 13 > code. We still need to be able to test it while it grow up.
14 14 > """
15 15 >
16 16 > import sys, os
17 17 > from mercurial import cmdutil
18 18 > from mercurial import util
19 19 > from mercurial import bundle2
20 20 > from mercurial import scmutil
21 21 > from mercurial import discovery
22 22 > from mercurial import changegroup
23 23 > from mercurial import error
24 24 > from mercurial import obsolete
25 25 >
26 26 >
27 27 > try:
28 28 > import msvcrt
29 29 > msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
30 30 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
31 31 > msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
32 32 > except ImportError:
33 33 > pass
34 34 >
35 35 > cmdtable = {}
36 36 > command = cmdutil.command(cmdtable)
37 37 >
38 38 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
39 39 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
40 40 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
41 41 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
42 42 >
43 43 > @bundle2.parthandler('test:song')
44 44 > def songhandler(op, part):
45 45 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
46 46 > op.ui.write('The choir starts singing:\n')
47 47 > verses = 0
48 48 > for line in part.read().split('\n'):
49 49 > op.ui.write(' %s\n' % line)
50 50 > verses += 1
51 51 > op.records.add('song', {'verses': verses})
52 52 >
53 53 > @bundle2.parthandler('test:ping')
54 54 > def pinghandler(op, part):
55 55 > op.ui.write('received ping request (id %i)\n' % part.id)
56 56 > if op.reply is not None and 'ping-pong' in op.reply.capabilities:
57 57 > op.ui.write_err('replying to ping request (id %i)\n' % part.id)
58 58 > op.reply.newpart('test:pong', [('in-reply-to', str(part.id))],
59 59 > mandatory=False)
60 60 >
61 61 > @bundle2.parthandler('test:debugreply')
62 62 > def debugreply(op, part):
63 63 > """print data about the capacity of the bundle reply"""
64 64 > if op.reply is None:
65 65 > op.ui.write('debugreply: no reply\n')
66 66 > else:
67 67 > op.ui.write('debugreply: capabilities:\n')
68 68 > for cap in sorted(op.reply.capabilities):
69 69 > op.ui.write('debugreply: %r\n' % cap)
70 70 > for val in op.reply.capabilities[cap]:
71 71 > op.ui.write('debugreply: %r\n' % val)
72 72 >
73 73 > @command('bundle2',
74 74 > [('', 'param', [], 'stream level parameter'),
75 75 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
76 76 > ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
77 77 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
78 78 > ('', 'reply', False, 'produce a reply bundle'),
79 79 > ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
80 80 > ('', 'genraise', False, 'includes a part that raise an exception during generation'),
81 81 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
82 82 > '[OUTPUTFILE]')
83 83 > def cmdbundle2(ui, repo, path=None, **opts):
84 84 > """write a bundle2 container on standard output"""
85 85 > bundler = bundle2.bundle20(ui)
86 86 > for p in opts['param']:
87 87 > p = p.split('=', 1)
88 88 > try:
89 89 > bundler.addparam(*p)
90 90 > except ValueError, exc:
91 91 > raise util.Abort('%s' % exc)
92 92 >
93 93 > if opts['reply']:
94 94 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
95 95 > bundler.newpart('replycaps', data=capsstring)
96 96 >
97 97 > if opts['pushrace']:
98 98 > # also serve to test the assignement of data outside of init
99 99 > part = bundler.newpart('check:heads')
100 100 > part.data = '01234567890123456789'
101 101 >
102 102 > revs = opts['rev']
103 103 > if 'rev' in opts:
104 104 > revs = scmutil.revrange(repo, opts['rev'])
105 105 > if revs:
106 106 > # very crude version of a changegroup part creation
107 107 > bundled = repo.revs('%ld::%ld', revs, revs)
108 108 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
109 109 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
110 110 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
111 111 > cg = changegroup.getlocalchangegroup(repo, 'test:bundle2', outgoing, None)
112 112 > bundler.newpart('changegroup', data=cg.getchunks(),
113 113 > mandatory=False)
114 114 >
115 115 > if opts['parts']:
116 116 > bundler.newpart('test:empty', mandatory=False)
117 117 > # add a second one to make sure we handle multiple parts
118 118 > bundler.newpart('test:empty', mandatory=False)
119 119 > bundler.newpart('test:song', data=ELEPHANTSSONG, mandatory=False)
120 120 > bundler.newpart('test:debugreply', mandatory=False)
121 121 > mathpart = bundler.newpart('test:math')
122 122 > mathpart.addparam('pi', '3.14')
123 123 > mathpart.addparam('e', '2.72')
124 124 > mathpart.addparam('cooking', 'raw', mandatory=False)
125 125 > mathpart.data = '42'
126 126 > mathpart.mandatory = False
127 127 > # advisory known part with unknown mandatory param
128 128 > bundler.newpart('test:song', [('randomparam','')], mandatory=False)
129 129 > if opts['unknown']:
130 130 > bundler.newpart('test:unknown', data='some random content')
131 131 > if opts['unknownparams']:
132 132 > bundler.newpart('test:song', [('randomparams', '')])
133 133 > if opts['parts']:
134 134 > bundler.newpart('test:ping', mandatory=False)
135 135 > if opts['genraise']:
136 136 > def genraise():
137 137 > yield 'first line\n'
138 138 > raise RuntimeError('Someone set up us the bomb!')
139 139 > bundler.newpart('output', data=genraise(), mandatory=False)
140 140 >
141 141 > if path is None:
142 142 > file = sys.stdout
143 143 > else:
144 144 > file = open(path, 'wb')
145 145 >
146 146 > try:
147 147 > for chunk in bundler.getchunks():
148 148 > file.write(chunk)
149 149 > except RuntimeError, exc:
150 150 > raise util.Abort(exc)
151 151 >
152 152 > @command('unbundle2', [], '')
153 153 > def cmdunbundle2(ui, repo, replypath=None):
154 154 > """process a bundle2 stream from stdin on the current repo"""
155 155 > try:
156 156 > tr = None
157 157 > lock = repo.lock()
158 158 > tr = repo.transaction('processbundle')
159 159 > try:
160 160 > unbundler = bundle2.getunbundler(ui, sys.stdin)
161 161 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
162 162 > tr.close()
163 163 > except error.BundleValueError, exc:
164 164 > raise util.Abort('missing support for %s' % exc)
165 165 > except error.PushRaced, exc:
166 166 > raise util.Abort('push race: %s' % exc)
167 167 > finally:
168 168 > if tr is not None:
169 169 > tr.release()
170 170 > lock.release()
171 171 > remains = sys.stdin.read()
172 172 > ui.write('%i unread bytes\n' % len(remains))
173 173 > if op.records['song']:
174 174 > totalverses = sum(r['verses'] for r in op.records['song'])
175 175 > ui.write('%i total verses sung\n' % totalverses)
176 176 > for rec in op.records['changegroup']:
177 177 > ui.write('addchangegroup return: %i\n' % rec['return'])
178 178 > if op.reply is not None and replypath is not None:
179 179 > file = open(replypath, 'wb')
180 180 > for chunk in op.reply.getchunks():
181 181 > file.write(chunk)
182 182 >
183 183 > @command('statbundle2', [], '')
184 184 > def cmdstatbundle2(ui, repo):
185 185 > """print statistic on the bundle2 container read from stdin"""
186 186 > unbundler = bundle2.getunbundler(ui, sys.stdin)
187 187 > try:
188 188 > params = unbundler.params
189 189 > except error.BundleValueError, exc:
190 190 > raise util.Abort('unknown parameters: %s' % exc)
191 191 > ui.write('options count: %i\n' % len(params))
192 192 > for key in sorted(params):
193 193 > ui.write('- %s\n' % key)
194 194 > value = params[key]
195 195 > if value is not None:
196 196 > ui.write(' %s\n' % value)
197 197 > count = 0
198 198 > for p in unbundler.iterparts():
199 199 > count += 1
200 200 > ui.write(' :%s:\n' % p.type)
201 201 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
202 202 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
203 203 > ui.write(' payload: %i bytes\n' % len(p.read()))
204 204 > ui.write('parts count: %i\n' % count)
205 205 > EOF
206 206 $ cat >> $HGRCPATH << EOF
207 207 > [extensions]
208 208 > bundle2=$TESTTMP/bundle2.py
209 209 > [experimental]
210 210 > bundle2-exp=True
211 211 > evolution=createmarkers
212 212 > [ui]
213 213 > ssh=python "$TESTDIR/dummyssh"
214 214 > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
215 215 > [web]
216 216 > push_ssl = false
217 217 > allow_push = *
218 218 > [phases]
219 219 > publish=False
220 220 > EOF
221 221
222 222 The extension requires a repo (currently unused)
223 223
224 224 $ hg init main
225 225 $ cd main
226 226 $ touch a
227 227 $ hg add a
228 228 $ hg commit -m 'a'
229 229
230 230
231 231 Empty bundle
232 232 =================
233 233
234 234 - no option
235 235 - no parts
236 236
237 237 Test bundling
238 238
239 239 $ hg bundle2
240 240 HG20\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
241 241
242 242 Test unbundling
243 243
244 244 $ hg bundle2 | hg statbundle2
245 245 options count: 0
246 246 parts count: 0
247 247
248 248 Test old style bundle are detected and refused
249 249
250 250 $ hg bundle --all ../bundle.hg
251 251 1 changesets found
252 252 $ hg statbundle2 < ../bundle.hg
253 253 abort: unknown bundle version 10
254 254 [255]
255 255
256 256 Test parameters
257 257 =================
258 258
259 259 - some options
260 260 - no parts
261 261
262 262 advisory parameters, no value
263 263 -------------------------------
264 264
265 265 Simplest possible parameters form
266 266
267 267 Test generation simple option
268 268
269 269 $ hg bundle2 --param 'caution'
270 270 HG20\x00\x00\x00\x07caution\x00\x00\x00\x00 (no-eol) (esc)
271 271
272 272 Test unbundling
273 273
274 274 $ hg bundle2 --param 'caution' | hg statbundle2
275 275 options count: 1
276 276 - caution
277 277 parts count: 0
278 278
279 279 Test generation multiple option
280 280
281 281 $ hg bundle2 --param 'caution' --param 'meal'
282 282 HG20\x00\x00\x00\x0ccaution meal\x00\x00\x00\x00 (no-eol) (esc)
283 283
284 284 Test unbundling
285 285
286 286 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
287 287 options count: 2
288 288 - caution
289 289 - meal
290 290 parts count: 0
291 291
292 292 advisory parameters, with value
293 293 -------------------------------
294 294
295 295 Test generation
296 296
297 297 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
298 298 HG20\x00\x00\x00\x1ccaution meal=vegan elephants\x00\x00\x00\x00 (no-eol) (esc)
299 299
300 300 Test unbundling
301 301
302 302 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
303 303 options count: 3
304 304 - caution
305 305 - elephants
306 306 - meal
307 307 vegan
308 308 parts count: 0
309 309
310 310 parameter with special char in value
311 311 ---------------------------------------------------
312 312
313 313 Test generation
314 314
315 315 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
316 316 HG20\x00\x00\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00\x00\x00 (no-eol) (esc)
317 317
318 318 Test unbundling
319 319
320 320 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
321 321 options count: 2
322 322 - e|! 7/
323 323 babar%#==tutu
324 324 - simple
325 325 parts count: 0
326 326
327 327 Test unknown mandatory option
328 328 ---------------------------------------------------
329 329
330 330 $ hg bundle2 --param 'Gravity' | hg statbundle2
331 331 abort: unknown parameters: Stream Parameter - Gravity
332 332 [255]
333 333
334 334 Test debug output
335 335 ---------------------------------------------------
336 336
337 337 bundling debug
338 338
339 339 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2 --config progress.debug=true
340 340 bundle2-output-bundle: "HG20", (2 params) 0 parts total
341 341 bundle2-output: start emission of HG20 stream
342 342 bundle2-output: bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
343 343 bundle2-output: start of parts
344 344 bundle2-output: end of bundle
345 345
346 346 file content is ok
347 347
348 348 $ cat ../out.hg2
349 349 HG20\x00\x00\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00\x00\x00 (no-eol) (esc)
350 350
351 351 unbundling debug
352 352
353 353 $ hg statbundle2 --debug --config progress.debug=true < ../out.hg2
354 354 bundle2-input: start processing of HG20 stream
355 355 bundle2-input: reading bundle2 stream parameters
356 356 bundle2-input: ignoring unknown parameter 'e|! 7/'
357 357 bundle2-input: ignoring unknown parameter 'simple'
358 358 options count: 2
359 359 - e|! 7/
360 360 babar%#==tutu
361 361 - simple
362 362 bundle2-input: start extraction of bundle2 parts
363 363 bundle2-input: part header size: 0
364 364 bundle2-input: end of bundle2 stream
365 365 parts count: 0
366 366
367 367
368 368 Test buggy input
369 369 ---------------------------------------------------
370 370
371 371 empty parameter name
372 372
373 373 $ hg bundle2 --param '' --quiet
374 374 abort: empty parameter name
375 375 [255]
376 376
377 377 bad parameter name
378 378
379 379 $ hg bundle2 --param 42babar
380 380 abort: non letter first character: '42babar'
381 381 [255]
382 382
383 383
384 384 Test part
385 385 =================
386 386
387 387 $ hg bundle2 --parts ../parts.hg2 --debug --config progress.debug=true
388 388 bundle2-output-bundle: "HG20", 7 parts total
389 389 bundle2-output: start emission of HG20 stream
390 390 bundle2-output: bundle parameter:
391 391 bundle2-output: start of parts
392 392 bundle2-output: bundle part: "test:empty"
393 393 bundle2-output-part: "test:empty" (advisory) empty payload
394 394 bundle2-output: part 0: "test:empty"
395 395 bundle2-output: header chunk size: 17
396 396 bundle2-output: closing payload chunk
397 397 bundle2-output: bundle part: "test:empty"
398 398 bundle2-output-part: "test:empty" (advisory) empty payload
399 399 bundle2-output: part 1: "test:empty"
400 400 bundle2-output: header chunk size: 17
401 401 bundle2-output: closing payload chunk
402 402 bundle2-output: bundle part: "test:song"
403 403 bundle2-output-part: "test:song" (advisory) 178 bytes payload
404 404 bundle2-output: part 2: "test:song"
405 405 bundle2-output: header chunk size: 16
406 406 bundle2-output: payload chunk size: 178
407 407 bundle2-output: closing payload chunk
408 408 bundle2-output: bundle part: "test:debugreply"
409 409 bundle2-output-part: "test:debugreply" (advisory) empty payload
410 410 bundle2-output: part 3: "test:debugreply"
411 411 bundle2-output: header chunk size: 22
412 412 bundle2-output: closing payload chunk
413 413 bundle2-output: bundle part: "test:math"
414 414 bundle2-output-part: "test:math" (advisory) (params: 2 mandatory 2 advisory) 2 bytes payload
415 415 bundle2-output: part 4: "test:math"
416 416 bundle2-output: header chunk size: 43
417 417 bundle2-output: payload chunk size: 2
418 418 bundle2-output: closing payload chunk
419 419 bundle2-output: bundle part: "test:song"
420 420 bundle2-output-part: "test:song" (advisory) (params: 1 mandatory) empty payload
421 421 bundle2-output: part 5: "test:song"
422 422 bundle2-output: header chunk size: 29
423 423 bundle2-output: closing payload chunk
424 424 bundle2-output: bundle part: "test:ping"
425 425 bundle2-output-part: "test:ping" (advisory) empty payload
426 426 bundle2-output: part 6: "test:ping"
427 427 bundle2-output: header chunk size: 16
428 428 bundle2-output: closing payload chunk
429 429 bundle2-output: end of bundle
430 430
431 431 $ cat ../parts.hg2
432 432 HG20\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
433 433 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
434 434 test:empty\x00\x00\x00\x01\x00\x00\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)
435 435 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
436 436 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x04\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x00\x00\x1d test:song\x00\x00\x00\x05\x01\x00\x0b\x00randomparam\x00\x00\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
437 437
438 438
439 439 $ hg statbundle2 < ../parts.hg2
440 440 options count: 0
441 441 :test:empty:
442 442 mandatory: 0
443 443 advisory: 0
444 444 payload: 0 bytes
445 445 :test:empty:
446 446 mandatory: 0
447 447 advisory: 0
448 448 payload: 0 bytes
449 449 :test:song:
450 450 mandatory: 0
451 451 advisory: 0
452 452 payload: 178 bytes
453 453 :test:debugreply:
454 454 mandatory: 0
455 455 advisory: 0
456 456 payload: 0 bytes
457 457 :test:math:
458 458 mandatory: 2
459 459 advisory: 1
460 460 payload: 2 bytes
461 461 :test:song:
462 462 mandatory: 1
463 463 advisory: 0
464 464 payload: 0 bytes
465 465 :test:ping:
466 466 mandatory: 0
467 467 advisory: 0
468 468 payload: 0 bytes
469 469 parts count: 7
470 470
471 471 $ hg statbundle2 --debug --config progress.debug=true < ../parts.hg2
472 472 bundle2-input: start processing of HG20 stream
473 473 bundle2-input: reading bundle2 stream parameters
474 474 options count: 0
475 475 bundle2-input: start extraction of bundle2 parts
476 476 bundle2-input: part header size: 17
477 477 bundle2-input: part type: "test:empty"
478 478 bundle2-input: part id: "0"
479 479 bundle2-input: part parameters: 0
480 480 :test:empty:
481 481 mandatory: 0
482 482 advisory: 0
483 483 bundle2-input: payload chunk size: 0
484 484 payload: 0 bytes
485 485 bundle2-input: part header size: 17
486 486 bundle2-input: part type: "test:empty"
487 487 bundle2-input: part id: "1"
488 488 bundle2-input: part parameters: 0
489 489 :test:empty:
490 490 mandatory: 0
491 491 advisory: 0
492 492 bundle2-input: payload chunk size: 0
493 493 payload: 0 bytes
494 494 bundle2-input: part header size: 16
495 495 bundle2-input: part type: "test:song"
496 496 bundle2-input: part id: "2"
497 497 bundle2-input: part parameters: 0
498 498 :test:song:
499 499 mandatory: 0
500 500 advisory: 0
501 501 bundle2-input: payload chunk size: 178
502 502 bundle2-input: payload chunk size: 0
503 bundle2-input-part: total payload size 178
503 504 payload: 178 bytes
504 505 bundle2-input: part header size: 22
505 506 bundle2-input: part type: "test:debugreply"
506 507 bundle2-input: part id: "3"
507 508 bundle2-input: part parameters: 0
508 509 :test:debugreply:
509 510 mandatory: 0
510 511 advisory: 0
511 512 bundle2-input: payload chunk size: 0
512 513 payload: 0 bytes
513 514 bundle2-input: part header size: 43
514 515 bundle2-input: part type: "test:math"
515 516 bundle2-input: part id: "4"
516 517 bundle2-input: part parameters: 3
517 518 :test:math:
518 519 mandatory: 2
519 520 advisory: 1
520 521 bundle2-input: payload chunk size: 2
521 522 bundle2-input: payload chunk size: 0
523 bundle2-input-part: total payload size 2
522 524 payload: 2 bytes
523 525 bundle2-input: part header size: 29
524 526 bundle2-input: part type: "test:song"
525 527 bundle2-input: part id: "5"
526 528 bundle2-input: part parameters: 1
527 529 :test:song:
528 530 mandatory: 1
529 531 advisory: 0
530 532 bundle2-input: payload chunk size: 0
531 533 payload: 0 bytes
532 534 bundle2-input: part header size: 16
533 535 bundle2-input: part type: "test:ping"
534 536 bundle2-input: part id: "6"
535 537 bundle2-input: part parameters: 0
536 538 :test:ping:
537 539 mandatory: 0
538 540 advisory: 0
539 541 bundle2-input: payload chunk size: 0
540 542 payload: 0 bytes
541 543 bundle2-input: part header size: 0
542 544 bundle2-input: end of bundle2 stream
543 545 parts count: 7
544 546
545 547 Test actual unbundling of test part
546 548 =======================================
547 549
548 550 Process the bundle
549 551
550 552 $ hg unbundle2 --debug --config progress.debug=true < ../parts.hg2
551 553 bundle2-input: start processing of HG20 stream
552 554 bundle2-input: reading bundle2 stream parameters
553 555 bundle2-input-bundle: with-transaction
554 556 bundle2-input: start extraction of bundle2 parts
555 557 bundle2-input: part header size: 17
556 558 bundle2-input: part type: "test:empty"
557 559 bundle2-input: part id: "0"
558 560 bundle2-input: part parameters: 0
559 561 bundle2-input: ignoring unsupported advisory part test:empty
560 562 bundle2-input-part: "test:empty" (advisory) unsupported-type
561 563 bundle2-input: payload chunk size: 0
562 564 bundle2-input: part header size: 17
563 565 bundle2-input: part type: "test:empty"
564 566 bundle2-input: part id: "1"
565 567 bundle2-input: part parameters: 0
566 568 bundle2-input: ignoring unsupported advisory part test:empty
567 569 bundle2-input-part: "test:empty" (advisory) unsupported-type
568 570 bundle2-input: payload chunk size: 0
569 571 bundle2-input: part header size: 16
570 572 bundle2-input: part type: "test:song"
571 573 bundle2-input: part id: "2"
572 574 bundle2-input: part parameters: 0
573 575 bundle2-input: found a handler for part 'test:song'
574 576 bundle2-input-part: "test:song" (advisory) supported
575 577 The choir starts singing:
576 578 bundle2-input: payload chunk size: 178
577 579 bundle2-input: payload chunk size: 0
580 bundle2-input-part: total payload size 178
578 581 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
579 582 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
580 583 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
581 584 bundle2-input: part header size: 22
582 585 bundle2-input: part type: "test:debugreply"
583 586 bundle2-input: part id: "3"
584 587 bundle2-input: part parameters: 0
585 588 bundle2-input: found a handler for part 'test:debugreply'
586 589 bundle2-input-part: "test:debugreply" (advisory) supported
587 590 debugreply: no reply
588 591 bundle2-input: payload chunk size: 0
589 592 bundle2-input: part header size: 43
590 593 bundle2-input: part type: "test:math"
591 594 bundle2-input: part id: "4"
592 595 bundle2-input: part parameters: 3
593 596 bundle2-input: ignoring unsupported advisory part test:math
594 597 bundle2-input-part: "test:math" (advisory) (params: 2 mandatory 2 advisory) unsupported-type
595 598 bundle2-input: payload chunk size: 2
596 599 bundle2-input: payload chunk size: 0
600 bundle2-input-part: total payload size 2
597 601 bundle2-input: part header size: 29
598 602 bundle2-input: part type: "test:song"
599 603 bundle2-input: part id: "5"
600 604 bundle2-input: part parameters: 1
601 605 bundle2-input: found a handler for part 'test:song'
602 606 bundle2-input: ignoring unsupported advisory part test:song - randomparam
603 607 bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (['randomparam'])
604 608 bundle2-input: payload chunk size: 0
605 609 bundle2-input: part header size: 16
606 610 bundle2-input: part type: "test:ping"
607 611 bundle2-input: part id: "6"
608 612 bundle2-input: part parameters: 0
609 613 bundle2-input: found a handler for part 'test:ping'
610 614 bundle2-input-part: "test:ping" (advisory) supported
611 615 received ping request (id 6)
612 616 bundle2-input: payload chunk size: 0
613 617 bundle2-input: part header size: 0
614 618 bundle2-input: end of bundle2 stream
615 619 bundle2-input-bundle: 6 parts total
616 620 0 unread bytes
617 621 3 total verses sung
618 622
619 623 Unbundle with an unknown mandatory part
620 624 (should abort)
621 625
622 626 $ hg bundle2 --parts --unknown ../unknown.hg2
623 627
624 628 $ hg unbundle2 < ../unknown.hg2
625 629 The choir starts singing:
626 630 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
627 631 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
628 632 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
629 633 debugreply: no reply
630 634 0 unread bytes
631 635 abort: missing support for test:unknown
632 636 [255]
633 637
634 638 Unbundle with an unknown mandatory part parameters
635 639 (should abort)
636 640
637 641 $ hg bundle2 --unknownparams ../unknown.hg2
638 642
639 643 $ hg unbundle2 < ../unknown.hg2
640 644 0 unread bytes
641 645 abort: missing support for test:song - randomparams
642 646 [255]
643 647
644 648 unbundle with a reply
645 649
646 650 $ hg bundle2 --parts --reply ../parts-reply.hg2
647 651 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
648 652 0 unread bytes
649 653 3 total verses sung
650 654
651 655 The reply is a bundle
652 656
653 657 $ cat ../reply.hg2
654 658 HG20\x00\x00\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
655 659 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
656 660 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
657 661 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
658 662 \x00\x00\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
659 663 debugreply: 'city=!'
660 664 debugreply: 'celeste,ville'
661 665 debugreply: 'elephants'
662 666 debugreply: 'babar'
663 667 debugreply: 'celeste'
664 668 debugreply: 'ping-pong'
665 669 \x00\x00\x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to7\x00\x00\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to7\x00\x00\x00=received ping request (id 7) (esc)
666 670 replying to ping request (id 7)
667 671 \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
668 672
669 673 The reply is valid
670 674
671 675 $ hg statbundle2 < ../reply.hg2
672 676 options count: 0
673 677 :output:
674 678 mandatory: 0
675 679 advisory: 1
676 680 payload: 217 bytes
677 681 :output:
678 682 mandatory: 0
679 683 advisory: 1
680 684 payload: 201 bytes
681 685 :test:pong:
682 686 mandatory: 1
683 687 advisory: 0
684 688 payload: 0 bytes
685 689 :output:
686 690 mandatory: 0
687 691 advisory: 1
688 692 payload: 61 bytes
689 693 parts count: 4
690 694
691 695 Unbundle the reply to get the output:
692 696
693 697 $ hg unbundle2 < ../reply.hg2
694 698 remote: The choir starts singing:
695 699 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
696 700 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
697 701 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
698 702 remote: debugreply: capabilities:
699 703 remote: debugreply: 'city=!'
700 704 remote: debugreply: 'celeste,ville'
701 705 remote: debugreply: 'elephants'
702 706 remote: debugreply: 'babar'
703 707 remote: debugreply: 'celeste'
704 708 remote: debugreply: 'ping-pong'
705 709 remote: received ping request (id 7)
706 710 remote: replying to ping request (id 7)
707 711 0 unread bytes
708 712
709 713 Test push race detection
710 714
711 715 $ hg bundle2 --pushrace ../part-race.hg2
712 716
713 717 $ hg unbundle2 < ../part-race.hg2
714 718 0 unread bytes
715 719 abort: push race: repository changed while pushing - please try again
716 720 [255]
717 721
718 722 Support for changegroup
719 723 ===================================
720 724
721 725 $ hg unbundle $TESTDIR/bundles/rebase.hg
722 726 adding changesets
723 727 adding manifests
724 728 adding file changes
725 729 added 8 changesets with 7 changes to 7 files (+3 heads)
726 730 (run 'hg heads' to see heads, 'hg merge' to merge)
727 731
728 732 $ hg log -G
729 733 o 8:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
730 734 |
731 735 | o 7:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com> G
732 736 |/|
733 737 o | 6:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
734 738 | |
735 739 | o 5:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
736 740 |/
737 741 | o 4:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com> D
738 742 | |
739 743 | o 3:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C
740 744 | |
741 745 | o 2:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B
742 746 |/
743 747 o 1:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com> A
744 748
745 749 @ 0:3903775176ed draft test a
746 750
747 751
748 752 $ hg bundle2 --debug --config progress.debug=true --rev '8+7+5+4' ../rev.hg2
749 753 4 changesets found
750 754 list of changesets:
751 755 32af7686d403cf45b5d95f2d70cebea587ac806a
752 756 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
753 757 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
754 758 02de42196ebee42ef284b6780a87cdc96e8eaab6
755 759 bundle2-output-bundle: "HG20", 1 parts total
756 760 bundle2-output: start emission of HG20 stream
757 761 bundle2-output: bundle parameter:
758 762 bundle2-output: start of parts
759 763 bundle2-output: bundle part: "changegroup"
760 764 bundle2-output-part: "changegroup" (advisory) streamed payload
761 765 bundle2-output: part 0: "changegroup"
762 766 bundle2-output: header chunk size: 18
763 767 bundling: 1/4 changesets (25.00%)
764 768 bundling: 2/4 changesets (50.00%)
765 769 bundling: 3/4 changesets (75.00%)
766 770 bundling: 4/4 changesets (100.00%)
767 771 bundling: 1/4 manifests (25.00%)
768 772 bundling: 2/4 manifests (50.00%)
769 773 bundling: 3/4 manifests (75.00%)
770 774 bundling: 4/4 manifests (100.00%)
771 775 bundling: D 1/3 files (33.33%)
772 776 bundling: E 2/3 files (66.67%)
773 777 bundling: H 3/3 files (100.00%)
774 778 bundle2-output: payload chunk size: 1555
775 779 bundle2-output: closing payload chunk
776 780 bundle2-output: end of bundle
777 781
778 782 $ cat ../rev.hg2
779 783 HG20\x00\x00\x00\x00\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)
780 784 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
781 785 \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)
782 786 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
783 787 \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)
784 788 \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)
785 789 \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)
786 790 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
787 791 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
788 792 \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)
789 793 \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)
790 794 \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)
791 795 \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)
792 796 \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)
793 797 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
794 798 \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)
795 799 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
796 800 l\r (no-eol) (esc)
797 801 \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)
798 802 \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)
799 803 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
800 804 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
801 805
802 806 $ hg debugbundle ../rev.hg2
803 807 Stream params: {}
804 808 changegroup -- '{}'
805 809 32af7686d403cf45b5d95f2d70cebea587ac806a
806 810 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
807 811 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
808 812 02de42196ebee42ef284b6780a87cdc96e8eaab6
809 813 $ hg unbundle ../rev.hg2
810 814 adding changesets
811 815 adding manifests
812 816 adding file changes
813 817 added 0 changesets with 0 changes to 3 files
814 818
815 819 with reply
816 820
817 821 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
818 822 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
819 823 0 unread bytes
820 824 addchangegroup return: 1
821 825
822 826 $ cat ../rev-reply.hg2
823 827 HG20\x00\x00\x00\x00\x00\x00\x00/\x11reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
824 828 adding manifests
825 829 adding file changes
826 830 added 0 changesets with 0 changes to 3 files
827 831 \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
828 832
829 833 Check handling of exception during generation.
830 834 ----------------------------------------------
831 835
832 836 $ hg bundle2 --genraise > ../genfailed.hg2
833 837 abort: Someone set up us the bomb!
834 838 [255]
835 839
836 840 Should still be a valid bundle
837 841
838 842 $ cat ../genfailed.hg2
839 843 HG20\x00\x00\x00\x00\x00\x00\x00\r (no-eol) (esc)
840 844 \x06output\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00H\x0berror:abort\x00\x00\x00\x00\x01\x00\x07-messageunexpected error: Someone set up us the bomb!\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
841 845
842 846 And its handling on the other size raise a clean exception
843 847
844 848 $ cat ../genfailed.hg2 | hg unbundle2
845 849 0 unread bytes
846 850 abort: unexpected error: Someone set up us the bomb!
847 851 [255]
848 852
849 853
850 854 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now