##// END OF EJS Templates
bundle2: add generic debug output regarding processed part...
Pierre-Yves David -
r25333:5bfffdbd default
parent child Browse files
Show More
@@ -1,1332 +1,1352 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 status = 'unknown' # used by debug output
362 363 try:
363 364 try:
364 365 handler = parthandlermapping.get(part.type)
365 366 if handler is None:
367 status = 'unsupported-type'
366 368 raise error.UnsupportedPartError(parttype=part.type)
367 369 indebug(op.ui, 'found a handler for part %r' % part.type)
368 370 unknownparams = part.mandatorykeys - handler.params
369 371 if unknownparams:
370 372 unknownparams = list(unknownparams)
371 373 unknownparams.sort()
374 status = 'unsupported-params (%s)' % unknownparams
372 375 raise error.UnsupportedPartError(parttype=part.type,
373 376 params=unknownparams)
377 status = 'supported'
374 378 except error.UnsupportedPartError, exc:
375 379 if part.mandatory: # mandatory parts
376 380 raise
377 381 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
378 382 return # skip to part processing
383 finally:
384 if op.ui.debugflag:
385 msg = ['bundle2-input-part: "%s"' % part.type]
386 if not part.mandatory:
387 msg.append(' (advisory)')
388 nbmp = len(part.mandatorykeys)
389 nbap = len(part.params) - nbmp
390 if nbmp or nbap:
391 msg.append(' (params:')
392 if nbmp:
393 msg.append(' %i mandatory' % nbmp)
394 if nbap:
395 msg.append(' %i advisory' % nbmp)
396 msg.append(')')
397 msg.append(' %s\n' % status)
398 op.ui.debug(''.join(msg))
379 399
380 400 # handler is called outside the above try block so that we don't
381 401 # risk catching KeyErrors from anything other than the
382 402 # parthandlermapping lookup (any KeyError raised by handler()
383 403 # itself represents a defect of a different variety).
384 404 output = None
385 405 if op.captureoutput and op.reply is not None:
386 406 op.ui.pushbuffer(error=True, subproc=True)
387 407 output = ''
388 408 try:
389 409 handler(op, part)
390 410 finally:
391 411 if output is not None:
392 412 output = op.ui.popbuffer()
393 413 if output:
394 414 outpart = op.reply.newpart('output', data=output,
395 415 mandatory=False)
396 416 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
397 417 finally:
398 418 # consume the part content to not corrupt the stream.
399 419 part.seek(0, 2)
400 420
401 421
402 422 def decodecaps(blob):
403 423 """decode a bundle2 caps bytes blob into a dictionary
404 424
405 425 The blob is a list of capabilities (one per line)
406 426 Capabilities may have values using a line of the form::
407 427
408 428 capability=value1,value2,value3
409 429
410 430 The values are always a list."""
411 431 caps = {}
412 432 for line in blob.splitlines():
413 433 if not line:
414 434 continue
415 435 if '=' not in line:
416 436 key, vals = line, ()
417 437 else:
418 438 key, vals = line.split('=', 1)
419 439 vals = vals.split(',')
420 440 key = urllib.unquote(key)
421 441 vals = [urllib.unquote(v) for v in vals]
422 442 caps[key] = vals
423 443 return caps
424 444
425 445 def encodecaps(caps):
426 446 """encode a bundle2 caps dictionary into a bytes blob"""
427 447 chunks = []
428 448 for ca in sorted(caps):
429 449 vals = caps[ca]
430 450 ca = urllib.quote(ca)
431 451 vals = [urllib.quote(v) for v in vals]
432 452 if vals:
433 453 ca = "%s=%s" % (ca, ','.join(vals))
434 454 chunks.append(ca)
435 455 return '\n'.join(chunks)
436 456
437 457 class bundle20(object):
438 458 """represent an outgoing bundle2 container
439 459
440 460 Use the `addparam` method to add stream level parameter. and `newpart` to
441 461 populate it. Then call `getchunks` to retrieve all the binary chunks of
442 462 data that compose the bundle2 container."""
443 463
444 464 _magicstring = 'HG20'
445 465
446 466 def __init__(self, ui, capabilities=()):
447 467 self.ui = ui
448 468 self._params = []
449 469 self._parts = []
450 470 self.capabilities = dict(capabilities)
451 471
452 472 @property
453 473 def nbparts(self):
454 474 """total number of parts added to the bundler"""
455 475 return len(self._parts)
456 476
457 477 # methods used to defines the bundle2 content
458 478 def addparam(self, name, value=None):
459 479 """add a stream level parameter"""
460 480 if not name:
461 481 raise ValueError('empty parameter name')
462 482 if name[0] not in string.letters:
463 483 raise ValueError('non letter first character: %r' % name)
464 484 self._params.append((name, value))
465 485
466 486 def addpart(self, part):
467 487 """add a new part to the bundle2 container
468 488
469 489 Parts contains the actual applicative payload."""
470 490 assert part.id is None
471 491 part.id = len(self._parts) # very cheap counter
472 492 self._parts.append(part)
473 493
474 494 def newpart(self, typeid, *args, **kwargs):
475 495 """create a new part and add it to the containers
476 496
477 497 As the part is directly added to the containers. For now, this means
478 498 that any failure to properly initialize the part after calling
479 499 ``newpart`` should result in a failure of the whole bundling process.
480 500
481 501 You can still fall back to manually create and add if you need better
482 502 control."""
483 503 part = bundlepart(typeid, *args, **kwargs)
484 504 self.addpart(part)
485 505 return part
486 506
487 507 # methods used to generate the bundle2 stream
488 508 def getchunks(self):
489 509 if self.ui.debugflag:
490 510 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
491 511 if self._params:
492 512 msg.append(' (%i params)' % len(self._params))
493 513 msg.append(' %i parts total\n' % len(self._parts))
494 514 self.ui.debug(''.join(msg))
495 515 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
496 516 yield self._magicstring
497 517 param = self._paramchunk()
498 518 outdebug(self.ui, 'bundle parameter: %s' % param)
499 519 yield _pack(_fstreamparamsize, len(param))
500 520 if param:
501 521 yield param
502 522
503 523 outdebug(self.ui, 'start of parts')
504 524 for part in self._parts:
505 525 outdebug(self.ui, 'bundle part: "%s"' % part.type)
506 526 for chunk in part.getchunks(ui=self.ui):
507 527 yield chunk
508 528 outdebug(self.ui, 'end of bundle')
509 529 yield _pack(_fpartheadersize, 0)
510 530
511 531 def _paramchunk(self):
512 532 """return a encoded version of all stream parameters"""
513 533 blocks = []
514 534 for par, value in self._params:
515 535 par = urllib.quote(par)
516 536 if value is not None:
517 537 value = urllib.quote(value)
518 538 par = '%s=%s' % (par, value)
519 539 blocks.append(par)
520 540 return ' '.join(blocks)
521 541
522 542 def salvageoutput(self):
523 543 """return a list with a copy of all output parts in the bundle
524 544
525 545 This is meant to be used during error handling to make sure we preserve
526 546 server output"""
527 547 salvaged = []
528 548 for part in self._parts:
529 549 if part.type.startswith('output'):
530 550 salvaged.append(part.copy())
531 551 return salvaged
532 552
533 553
534 554 class unpackermixin(object):
535 555 """A mixin to extract bytes and struct data from a stream"""
536 556
537 557 def __init__(self, fp):
538 558 self._fp = fp
539 559 self._seekable = (util.safehasattr(fp, 'seek') and
540 560 util.safehasattr(fp, 'tell'))
541 561
542 562 def _unpack(self, format):
543 563 """unpack this struct format from the stream"""
544 564 data = self._readexact(struct.calcsize(format))
545 565 return _unpack(format, data)
546 566
547 567 def _readexact(self, size):
548 568 """read exactly <size> bytes from the stream"""
549 569 return changegroup.readexactly(self._fp, size)
550 570
551 571 def seek(self, offset, whence=0):
552 572 """move the underlying file pointer"""
553 573 if self._seekable:
554 574 return self._fp.seek(offset, whence)
555 575 else:
556 576 raise NotImplementedError(_('File pointer is not seekable'))
557 577
558 578 def tell(self):
559 579 """return the file offset, or None if file is not seekable"""
560 580 if self._seekable:
561 581 try:
562 582 return self._fp.tell()
563 583 except IOError, e:
564 584 if e.errno == errno.ESPIPE:
565 585 self._seekable = False
566 586 else:
567 587 raise
568 588 return None
569 589
570 590 def close(self):
571 591 """close underlying file"""
572 592 if util.safehasattr(self._fp, 'close'):
573 593 return self._fp.close()
574 594
575 595 def getunbundler(ui, fp, header=None):
576 596 """return a valid unbundler object for a given header"""
577 597 if header is None:
578 598 header = changegroup.readexactly(fp, 4)
579 599 magic, version = header[0:2], header[2:4]
580 600 if magic != 'HG':
581 601 raise util.Abort(_('not a Mercurial bundle'))
582 602 unbundlerclass = formatmap.get(version)
583 603 if unbundlerclass is None:
584 604 raise util.Abort(_('unknown bundle version %s') % version)
585 605 unbundler = unbundlerclass(ui, fp)
586 606 indebug(ui, 'start processing of %s stream' % header)
587 607 return unbundler
588 608
589 609 class unbundle20(unpackermixin):
590 610 """interpret a bundle2 stream
591 611
592 612 This class is fed with a binary stream and yields parts through its
593 613 `iterparts` methods."""
594 614
595 615 def __init__(self, ui, fp):
596 616 """If header is specified, we do not read it out of the stream."""
597 617 self.ui = ui
598 618 super(unbundle20, self).__init__(fp)
599 619
600 620 @util.propertycache
601 621 def params(self):
602 622 """dictionary of stream level parameters"""
603 623 indebug(self.ui, 'reading bundle2 stream parameters')
604 624 params = {}
605 625 paramssize = self._unpack(_fstreamparamsize)[0]
606 626 if paramssize < 0:
607 627 raise error.BundleValueError('negative bundle param size: %i'
608 628 % paramssize)
609 629 if paramssize:
610 630 for p in self._readexact(paramssize).split(' '):
611 631 p = p.split('=', 1)
612 632 p = [urllib.unquote(i) for i in p]
613 633 if len(p) < 2:
614 634 p.append(None)
615 635 self._processparam(*p)
616 636 params[p[0]] = p[1]
617 637 return params
618 638
619 639 def _processparam(self, name, value):
620 640 """process a parameter, applying its effect if needed
621 641
622 642 Parameter starting with a lower case letter are advisory and will be
623 643 ignored when unknown. Those starting with an upper case letter are
624 644 mandatory and will this function will raise a KeyError when unknown.
625 645
626 646 Note: no option are currently supported. Any input will be either
627 647 ignored or failing.
628 648 """
629 649 if not name:
630 650 raise ValueError('empty parameter name')
631 651 if name[0] not in string.letters:
632 652 raise ValueError('non letter first character: %r' % name)
633 653 # Some logic will be later added here to try to process the option for
634 654 # a dict of known parameter.
635 655 if name[0].islower():
636 656 indebug(self.ui, "ignoring unknown parameter %r" % name)
637 657 else:
638 658 raise error.UnsupportedPartError(params=(name,))
639 659
640 660
641 661 def iterparts(self):
642 662 """yield all parts contained in the stream"""
643 663 # make sure param have been loaded
644 664 self.params
645 665 indebug(self.ui, 'start extraction of bundle2 parts')
646 666 headerblock = self._readpartheader()
647 667 while headerblock is not None:
648 668 part = unbundlepart(self.ui, headerblock, self._fp)
649 669 yield part
650 670 part.seek(0, 2)
651 671 headerblock = self._readpartheader()
652 672 indebug(self.ui, 'end of bundle2 stream')
653 673
654 674 def _readpartheader(self):
655 675 """reads a part header size and return the bytes blob
656 676
657 677 returns None if empty"""
658 678 headersize = self._unpack(_fpartheadersize)[0]
659 679 if headersize < 0:
660 680 raise error.BundleValueError('negative part header size: %i'
661 681 % headersize)
662 682 indebug(self.ui, 'part header size: %i' % headersize)
663 683 if headersize:
664 684 return self._readexact(headersize)
665 685 return None
666 686
667 687 def compressed(self):
668 688 return False
669 689
670 690 formatmap = {'20': unbundle20}
671 691
672 692 class bundlepart(object):
673 693 """A bundle2 part contains application level payload
674 694
675 695 The part `type` is used to route the part to the application level
676 696 handler.
677 697
678 698 The part payload is contained in ``part.data``. It could be raw bytes or a
679 699 generator of byte chunks.
680 700
681 701 You can add parameters to the part using the ``addparam`` method.
682 702 Parameters can be either mandatory (default) or advisory. Remote side
683 703 should be able to safely ignore the advisory ones.
684 704
685 705 Both data and parameters cannot be modified after the generation has begun.
686 706 """
687 707
688 708 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
689 709 data='', mandatory=True):
690 710 validateparttype(parttype)
691 711 self.id = None
692 712 self.type = parttype
693 713 self._data = data
694 714 self._mandatoryparams = list(mandatoryparams)
695 715 self._advisoryparams = list(advisoryparams)
696 716 # checking for duplicated entries
697 717 self._seenparams = set()
698 718 for pname, __ in self._mandatoryparams + self._advisoryparams:
699 719 if pname in self._seenparams:
700 720 raise RuntimeError('duplicated params: %s' % pname)
701 721 self._seenparams.add(pname)
702 722 # status of the part's generation:
703 723 # - None: not started,
704 724 # - False: currently generated,
705 725 # - True: generation done.
706 726 self._generated = None
707 727 self.mandatory = mandatory
708 728
709 729 def copy(self):
710 730 """return a copy of the part
711 731
712 732 The new part have the very same content but no partid assigned yet.
713 733 Parts with generated data cannot be copied."""
714 734 assert not util.safehasattr(self.data, 'next')
715 735 return self.__class__(self.type, self._mandatoryparams,
716 736 self._advisoryparams, self._data, self.mandatory)
717 737
718 738 # methods used to defines the part content
719 739 def __setdata(self, data):
720 740 if self._generated is not None:
721 741 raise error.ReadOnlyPartError('part is being generated')
722 742 self._data = data
723 743 def __getdata(self):
724 744 return self._data
725 745 data = property(__getdata, __setdata)
726 746
727 747 @property
728 748 def mandatoryparams(self):
729 749 # make it an immutable tuple to force people through ``addparam``
730 750 return tuple(self._mandatoryparams)
731 751
732 752 @property
733 753 def advisoryparams(self):
734 754 # make it an immutable tuple to force people through ``addparam``
735 755 return tuple(self._advisoryparams)
736 756
737 757 def addparam(self, name, value='', mandatory=True):
738 758 if self._generated is not None:
739 759 raise error.ReadOnlyPartError('part is being generated')
740 760 if name in self._seenparams:
741 761 raise ValueError('duplicated params: %s' % name)
742 762 self._seenparams.add(name)
743 763 params = self._advisoryparams
744 764 if mandatory:
745 765 params = self._mandatoryparams
746 766 params.append((name, value))
747 767
748 768 # methods used to generates the bundle2 stream
749 769 def getchunks(self, ui):
750 770 if self._generated is not None:
751 771 raise RuntimeError('part can only be consumed once')
752 772 self._generated = False
753 773
754 774 if ui.debugflag:
755 775 msg = ['bundle2-output-part: "%s"' % self.type]
756 776 if not self.mandatory:
757 777 msg.append(' (advisory)')
758 778 nbmp = len(self.mandatoryparams)
759 779 nbap = len(self.advisoryparams)
760 780 if nbmp or nbap:
761 781 msg.append(' (params:')
762 782 if nbmp:
763 783 msg.append(' %i mandatory' % nbmp)
764 784 if nbap:
765 785 msg.append(' %i advisory' % nbmp)
766 786 msg.append(')')
767 787 if not self.data:
768 788 msg.append(' empty payload')
769 789 elif util.safehasattr(self.data, 'next'):
770 790 msg.append(' streamed payload')
771 791 else:
772 792 msg.append(' %i bytes payload' % len(self.data))
773 793 msg.append('\n')
774 794 ui.debug(''.join(msg))
775 795
776 796 #### header
777 797 if self.mandatory:
778 798 parttype = self.type.upper()
779 799 else:
780 800 parttype = self.type.lower()
781 801 outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
782 802 ## parttype
783 803 header = [_pack(_fparttypesize, len(parttype)),
784 804 parttype, _pack(_fpartid, self.id),
785 805 ]
786 806 ## parameters
787 807 # count
788 808 manpar = self.mandatoryparams
789 809 advpar = self.advisoryparams
790 810 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
791 811 # size
792 812 parsizes = []
793 813 for key, value in manpar:
794 814 parsizes.append(len(key))
795 815 parsizes.append(len(value))
796 816 for key, value in advpar:
797 817 parsizes.append(len(key))
798 818 parsizes.append(len(value))
799 819 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
800 820 header.append(paramsizes)
801 821 # key, value
802 822 for key, value in manpar:
803 823 header.append(key)
804 824 header.append(value)
805 825 for key, value in advpar:
806 826 header.append(key)
807 827 header.append(value)
808 828 ## finalize header
809 829 headerchunk = ''.join(header)
810 830 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
811 831 yield _pack(_fpartheadersize, len(headerchunk))
812 832 yield headerchunk
813 833 ## payload
814 834 try:
815 835 for chunk in self._payloadchunks():
816 836 outdebug(ui, 'payload chunk size: %i' % len(chunk))
817 837 yield _pack(_fpayloadsize, len(chunk))
818 838 yield chunk
819 839 except BaseException, exc:
820 840 # backup exception data for later
821 841 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
822 842 % exc)
823 843 exc_info = sys.exc_info()
824 844 msg = 'unexpected error: %s' % exc
825 845 interpart = bundlepart('error:abort', [('message', msg)],
826 846 mandatory=False)
827 847 interpart.id = 0
828 848 yield _pack(_fpayloadsize, -1)
829 849 for chunk in interpart.getchunks(ui=ui):
830 850 yield chunk
831 851 outdebug(ui, 'closing payload chunk')
832 852 # abort current part payload
833 853 yield _pack(_fpayloadsize, 0)
834 854 raise exc_info[0], exc_info[1], exc_info[2]
835 855 # end of payload
836 856 outdebug(ui, 'closing payload chunk')
837 857 yield _pack(_fpayloadsize, 0)
838 858 self._generated = True
839 859
840 860 def _payloadchunks(self):
841 861 """yield chunks of a the part payload
842 862
843 863 Exists to handle the different methods to provide data to a part."""
844 864 # we only support fixed size data now.
845 865 # This will be improved in the future.
846 866 if util.safehasattr(self.data, 'next'):
847 867 buff = util.chunkbuffer(self.data)
848 868 chunk = buff.read(preferedchunksize)
849 869 while chunk:
850 870 yield chunk
851 871 chunk = buff.read(preferedchunksize)
852 872 elif len(self.data):
853 873 yield self.data
854 874
855 875
856 876 flaginterrupt = -1
857 877
858 878 class interrupthandler(unpackermixin):
859 879 """read one part and process it with restricted capability
860 880
861 881 This allows to transmit exception raised on the producer size during part
862 882 iteration while the consumer is reading a part.
863 883
864 884 Part processed in this manner only have access to a ui object,"""
865 885
866 886 def __init__(self, ui, fp):
867 887 super(interrupthandler, self).__init__(fp)
868 888 self.ui = ui
869 889
870 890 def _readpartheader(self):
871 891 """reads a part header size and return the bytes blob
872 892
873 893 returns None if empty"""
874 894 headersize = self._unpack(_fpartheadersize)[0]
875 895 if headersize < 0:
876 896 raise error.BundleValueError('negative part header size: %i'
877 897 % headersize)
878 898 indebug(self.ui, 'part header size: %i\n' % headersize)
879 899 if headersize:
880 900 return self._readexact(headersize)
881 901 return None
882 902
883 903 def __call__(self):
884 904 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
885 905 headerblock = self._readpartheader()
886 906 if headerblock is None:
887 907 indebug(self.ui, 'no part found during interruption.')
888 908 return
889 909 part = unbundlepart(self.ui, headerblock, self._fp)
890 910 op = interruptoperation(self.ui)
891 911 _processpart(op, part)
892 912
893 913 class interruptoperation(object):
894 914 """A limited operation to be use by part handler during interruption
895 915
896 916 It only have access to an ui object.
897 917 """
898 918
899 919 def __init__(self, ui):
900 920 self.ui = ui
901 921 self.reply = None
902 922 self.captureoutput = False
903 923
904 924 @property
905 925 def repo(self):
906 926 raise RuntimeError('no repo access from stream interruption')
907 927
908 928 def gettransaction(self):
909 929 raise TransactionUnavailable('no repo access from stream interruption')
910 930
911 931 class unbundlepart(unpackermixin):
912 932 """a bundle part read from a bundle"""
913 933
914 934 def __init__(self, ui, header, fp):
915 935 super(unbundlepart, self).__init__(fp)
916 936 self.ui = ui
917 937 # unbundle state attr
918 938 self._headerdata = header
919 939 self._headeroffset = 0
920 940 self._initialized = False
921 941 self.consumed = False
922 942 # part data
923 943 self.id = None
924 944 self.type = None
925 945 self.mandatoryparams = None
926 946 self.advisoryparams = None
927 947 self.params = None
928 948 self.mandatorykeys = ()
929 949 self._payloadstream = None
930 950 self._readheader()
931 951 self._mandatory = None
932 952 self._chunkindex = [] #(payload, file) position tuples for chunk starts
933 953 self._pos = 0
934 954
935 955 def _fromheader(self, size):
936 956 """return the next <size> byte from the header"""
937 957 offset = self._headeroffset
938 958 data = self._headerdata[offset:(offset + size)]
939 959 self._headeroffset = offset + size
940 960 return data
941 961
942 962 def _unpackheader(self, format):
943 963 """read given format from header
944 964
945 965 This automatically compute the size of the format to read."""
946 966 data = self._fromheader(struct.calcsize(format))
947 967 return _unpack(format, data)
948 968
949 969 def _initparams(self, mandatoryparams, advisoryparams):
950 970 """internal function to setup all logic related parameters"""
951 971 # make it read only to prevent people touching it by mistake.
952 972 self.mandatoryparams = tuple(mandatoryparams)
953 973 self.advisoryparams = tuple(advisoryparams)
954 974 # user friendly UI
955 975 self.params = dict(self.mandatoryparams)
956 976 self.params.update(dict(self.advisoryparams))
957 977 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
958 978
959 979 def _payloadchunks(self, chunknum=0):
960 980 '''seek to specified chunk and start yielding data'''
961 981 if len(self._chunkindex) == 0:
962 982 assert chunknum == 0, 'Must start with chunk 0'
963 983 self._chunkindex.append((0, super(unbundlepart, self).tell()))
964 984 else:
965 985 assert chunknum < len(self._chunkindex), \
966 986 'Unknown chunk %d' % chunknum
967 987 super(unbundlepart, self).seek(self._chunkindex[chunknum][1])
968 988
969 989 pos = self._chunkindex[chunknum][0]
970 990 payloadsize = self._unpack(_fpayloadsize)[0]
971 991 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
972 992 while payloadsize:
973 993 if payloadsize == flaginterrupt:
974 994 # interruption detection, the handler will now read a
975 995 # single part and process it.
976 996 interrupthandler(self.ui, self._fp)()
977 997 elif payloadsize < 0:
978 998 msg = 'negative payload chunk size: %i' % payloadsize
979 999 raise error.BundleValueError(msg)
980 1000 else:
981 1001 result = self._readexact(payloadsize)
982 1002 chunknum += 1
983 1003 pos += payloadsize
984 1004 if chunknum == len(self._chunkindex):
985 1005 self._chunkindex.append((pos,
986 1006 super(unbundlepart, self).tell()))
987 1007 yield result
988 1008 payloadsize = self._unpack(_fpayloadsize)[0]
989 1009 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
990 1010
991 1011 def _findchunk(self, pos):
992 1012 '''for a given payload position, return a chunk number and offset'''
993 1013 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
994 1014 if ppos == pos:
995 1015 return chunk, 0
996 1016 elif ppos > pos:
997 1017 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
998 1018 raise ValueError('Unknown chunk')
999 1019
1000 1020 def _readheader(self):
1001 1021 """read the header and setup the object"""
1002 1022 typesize = self._unpackheader(_fparttypesize)[0]
1003 1023 self.type = self._fromheader(typesize)
1004 1024 indebug(self.ui, 'part type: "%s"' % self.type)
1005 1025 self.id = self._unpackheader(_fpartid)[0]
1006 1026 indebug(self.ui, 'part id: "%s"' % self.id)
1007 1027 # extract mandatory bit from type
1008 1028 self.mandatory = (self.type != self.type.lower())
1009 1029 self.type = self.type.lower()
1010 1030 ## reading parameters
1011 1031 # param count
1012 1032 mancount, advcount = self._unpackheader(_fpartparamcount)
1013 1033 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1014 1034 # param size
1015 1035 fparamsizes = _makefpartparamsizes(mancount + advcount)
1016 1036 paramsizes = self._unpackheader(fparamsizes)
1017 1037 # make it a list of couple again
1018 1038 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
1019 1039 # split mandatory from advisory
1020 1040 mansizes = paramsizes[:mancount]
1021 1041 advsizes = paramsizes[mancount:]
1022 1042 # retrieve param value
1023 1043 manparams = []
1024 1044 for key, value in mansizes:
1025 1045 manparams.append((self._fromheader(key), self._fromheader(value)))
1026 1046 advparams = []
1027 1047 for key, value in advsizes:
1028 1048 advparams.append((self._fromheader(key), self._fromheader(value)))
1029 1049 self._initparams(manparams, advparams)
1030 1050 ## part payload
1031 1051 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1032 1052 # we read the data, tell it
1033 1053 self._initialized = True
1034 1054
1035 1055 def read(self, size=None):
1036 1056 """read payload data"""
1037 1057 if not self._initialized:
1038 1058 self._readheader()
1039 1059 if size is None:
1040 1060 data = self._payloadstream.read()
1041 1061 else:
1042 1062 data = self._payloadstream.read(size)
1043 1063 if size is None or len(data) < size:
1044 1064 self.consumed = True
1045 1065 self._pos += len(data)
1046 1066 return data
1047 1067
1048 1068 def tell(self):
1049 1069 return self._pos
1050 1070
1051 1071 def seek(self, offset, whence=0):
1052 1072 if whence == 0:
1053 1073 newpos = offset
1054 1074 elif whence == 1:
1055 1075 newpos = self._pos + offset
1056 1076 elif whence == 2:
1057 1077 if not self.consumed:
1058 1078 self.read()
1059 1079 newpos = self._chunkindex[-1][0] - offset
1060 1080 else:
1061 1081 raise ValueError('Unknown whence value: %r' % (whence,))
1062 1082
1063 1083 if newpos > self._chunkindex[-1][0] and not self.consumed:
1064 1084 self.read()
1065 1085 if not 0 <= newpos <= self._chunkindex[-1][0]:
1066 1086 raise ValueError('Offset out of range')
1067 1087
1068 1088 if self._pos != newpos:
1069 1089 chunk, internaloffset = self._findchunk(newpos)
1070 1090 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1071 1091 adjust = self.read(internaloffset)
1072 1092 if len(adjust) != internaloffset:
1073 1093 raise util.Abort(_('Seek failed\n'))
1074 1094 self._pos = newpos
1075 1095
1076 1096 # These are only the static capabilities.
1077 1097 # Check the 'getrepocaps' function for the rest.
1078 1098 capabilities = {'HG20': (),
1079 1099 'listkeys': (),
1080 1100 'pushkey': (),
1081 1101 'digests': tuple(sorted(util.DIGESTS.keys())),
1082 1102 'remote-changegroup': ('http', 'https'),
1083 1103 }
1084 1104
1085 1105 def getrepocaps(repo, allowpushback=False):
1086 1106 """return the bundle2 capabilities for a given repo
1087 1107
1088 1108 Exists to allow extensions (like evolution) to mutate the capabilities.
1089 1109 """
1090 1110 caps = capabilities.copy()
1091 1111 caps['changegroup'] = tuple(sorted(changegroup.packermap.keys()))
1092 1112 if obsolete.isenabled(repo, obsolete.exchangeopt):
1093 1113 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1094 1114 caps['obsmarkers'] = supportedformat
1095 1115 if allowpushback:
1096 1116 caps['pushback'] = ()
1097 1117 return caps
1098 1118
1099 1119 def bundle2caps(remote):
1100 1120 """return the bundle capabilities of a peer as dict"""
1101 1121 raw = remote.capable('bundle2')
1102 1122 if not raw and raw != '':
1103 1123 return {}
1104 1124 capsblob = urllib.unquote(remote.capable('bundle2'))
1105 1125 return decodecaps(capsblob)
1106 1126
1107 1127 def obsmarkersversion(caps):
1108 1128 """extract the list of supported obsmarkers versions from a bundle2caps dict
1109 1129 """
1110 1130 obscaps = caps.get('obsmarkers', ())
1111 1131 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1112 1132
1113 1133 @parthandler('changegroup', ('version',))
1114 1134 def handlechangegroup(op, inpart):
1115 1135 """apply a changegroup part on the repo
1116 1136
1117 1137 This is a very early implementation that will massive rework before being
1118 1138 inflicted to any end-user.
1119 1139 """
1120 1140 # Make sure we trigger a transaction creation
1121 1141 #
1122 1142 # The addchangegroup function will get a transaction object by itself, but
1123 1143 # we need to make sure we trigger the creation of a transaction object used
1124 1144 # for the whole processing scope.
1125 1145 op.gettransaction()
1126 1146 unpackerversion = inpart.params.get('version', '01')
1127 1147 # We should raise an appropriate exception here
1128 1148 unpacker = changegroup.packermap[unpackerversion][1]
1129 1149 cg = unpacker(inpart, 'UN')
1130 1150 # the source and url passed here are overwritten by the one contained in
1131 1151 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1132 1152 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
1133 1153 op.records.add('changegroup', {'return': ret})
1134 1154 if op.reply is not None:
1135 1155 # This is definitely not the final form of this
1136 1156 # return. But one need to start somewhere.
1137 1157 part = op.reply.newpart('reply:changegroup', mandatory=False)
1138 1158 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1139 1159 part.addparam('return', '%i' % ret, mandatory=False)
1140 1160 assert not inpart.read()
1141 1161
1142 1162 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1143 1163 ['digest:%s' % k for k in util.DIGESTS.keys()])
1144 1164 @parthandler('remote-changegroup', _remotechangegroupparams)
1145 1165 def handleremotechangegroup(op, inpart):
1146 1166 """apply a bundle10 on the repo, given an url and validation information
1147 1167
1148 1168 All the information about the remote bundle to import are given as
1149 1169 parameters. The parameters include:
1150 1170 - url: the url to the bundle10.
1151 1171 - size: the bundle10 file size. It is used to validate what was
1152 1172 retrieved by the client matches the server knowledge about the bundle.
1153 1173 - digests: a space separated list of the digest types provided as
1154 1174 parameters.
1155 1175 - digest:<digest-type>: the hexadecimal representation of the digest with
1156 1176 that name. Like the size, it is used to validate what was retrieved by
1157 1177 the client matches what the server knows about the bundle.
1158 1178
1159 1179 When multiple digest types are given, all of them are checked.
1160 1180 """
1161 1181 try:
1162 1182 raw_url = inpart.params['url']
1163 1183 except KeyError:
1164 1184 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1165 1185 parsed_url = util.url(raw_url)
1166 1186 if parsed_url.scheme not in capabilities['remote-changegroup']:
1167 1187 raise util.Abort(_('remote-changegroup does not support %s urls') %
1168 1188 parsed_url.scheme)
1169 1189
1170 1190 try:
1171 1191 size = int(inpart.params['size'])
1172 1192 except ValueError:
1173 1193 raise util.Abort(_('remote-changegroup: invalid value for param "%s"')
1174 1194 % 'size')
1175 1195 except KeyError:
1176 1196 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1177 1197
1178 1198 digests = {}
1179 1199 for typ in inpart.params.get('digests', '').split():
1180 1200 param = 'digest:%s' % typ
1181 1201 try:
1182 1202 value = inpart.params[param]
1183 1203 except KeyError:
1184 1204 raise util.Abort(_('remote-changegroup: missing "%s" param') %
1185 1205 param)
1186 1206 digests[typ] = value
1187 1207
1188 1208 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1189 1209
1190 1210 # Make sure we trigger a transaction creation
1191 1211 #
1192 1212 # The addchangegroup function will get a transaction object by itself, but
1193 1213 # we need to make sure we trigger the creation of a transaction object used
1194 1214 # for the whole processing scope.
1195 1215 op.gettransaction()
1196 1216 import exchange
1197 1217 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1198 1218 if not isinstance(cg, changegroup.cg1unpacker):
1199 1219 raise util.Abort(_('%s: not a bundle version 1.0') %
1200 1220 util.hidepassword(raw_url))
1201 1221 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
1202 1222 op.records.add('changegroup', {'return': ret})
1203 1223 if op.reply is not None:
1204 1224 # This is definitely not the final form of this
1205 1225 # return. But one need to start somewhere.
1206 1226 part = op.reply.newpart('reply:changegroup')
1207 1227 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1208 1228 part.addparam('return', '%i' % ret, mandatory=False)
1209 1229 try:
1210 1230 real_part.validate()
1211 1231 except util.Abort, e:
1212 1232 raise util.Abort(_('bundle at %s is corrupted:\n%s') %
1213 1233 (util.hidepassword(raw_url), str(e)))
1214 1234 assert not inpart.read()
1215 1235
1216 1236 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1217 1237 def handlereplychangegroup(op, inpart):
1218 1238 ret = int(inpart.params['return'])
1219 1239 replyto = int(inpart.params['in-reply-to'])
1220 1240 op.records.add('changegroup', {'return': ret}, replyto)
1221 1241
1222 1242 @parthandler('check:heads')
1223 1243 def handlecheckheads(op, inpart):
1224 1244 """check that head of the repo did not change
1225 1245
1226 1246 This is used to detect a push race when using unbundle.
1227 1247 This replaces the "heads" argument of unbundle."""
1228 1248 h = inpart.read(20)
1229 1249 heads = []
1230 1250 while len(h) == 20:
1231 1251 heads.append(h)
1232 1252 h = inpart.read(20)
1233 1253 assert not h
1234 1254 if heads != op.repo.heads():
1235 1255 raise error.PushRaced('repository changed while pushing - '
1236 1256 'please try again')
1237 1257
1238 1258 @parthandler('output')
1239 1259 def handleoutput(op, inpart):
1240 1260 """forward output captured on the server to the client"""
1241 1261 for line in inpart.read().splitlines():
1242 1262 op.ui.status(('remote: %s\n' % line))
1243 1263
1244 1264 @parthandler('replycaps')
1245 1265 def handlereplycaps(op, inpart):
1246 1266 """Notify that a reply bundle should be created
1247 1267
1248 1268 The payload contains the capabilities information for the reply"""
1249 1269 caps = decodecaps(inpart.read())
1250 1270 if op.reply is None:
1251 1271 op.reply = bundle20(op.ui, caps)
1252 1272
1253 1273 @parthandler('error:abort', ('message', 'hint'))
1254 1274 def handleerrorabort(op, inpart):
1255 1275 """Used to transmit abort error over the wire"""
1256 1276 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
1257 1277
1258 1278 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1259 1279 def handleerrorunsupportedcontent(op, inpart):
1260 1280 """Used to transmit unknown content error over the wire"""
1261 1281 kwargs = {}
1262 1282 parttype = inpart.params.get('parttype')
1263 1283 if parttype is not None:
1264 1284 kwargs['parttype'] = parttype
1265 1285 params = inpart.params.get('params')
1266 1286 if params is not None:
1267 1287 kwargs['params'] = params.split('\0')
1268 1288
1269 1289 raise error.UnsupportedPartError(**kwargs)
1270 1290
1271 1291 @parthandler('error:pushraced', ('message',))
1272 1292 def handleerrorpushraced(op, inpart):
1273 1293 """Used to transmit push race error over the wire"""
1274 1294 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1275 1295
1276 1296 @parthandler('listkeys', ('namespace',))
1277 1297 def handlelistkeys(op, inpart):
1278 1298 """retrieve pushkey namespace content stored in a bundle2"""
1279 1299 namespace = inpart.params['namespace']
1280 1300 r = pushkey.decodekeys(inpart.read())
1281 1301 op.records.add('listkeys', (namespace, r))
1282 1302
1283 1303 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1284 1304 def handlepushkey(op, inpart):
1285 1305 """process a pushkey request"""
1286 1306 dec = pushkey.decode
1287 1307 namespace = dec(inpart.params['namespace'])
1288 1308 key = dec(inpart.params['key'])
1289 1309 old = dec(inpart.params['old'])
1290 1310 new = dec(inpart.params['new'])
1291 1311 ret = op.repo.pushkey(namespace, key, old, new)
1292 1312 record = {'namespace': namespace,
1293 1313 'key': key,
1294 1314 'old': old,
1295 1315 'new': new}
1296 1316 op.records.add('pushkey', record)
1297 1317 if op.reply is not None:
1298 1318 rpart = op.reply.newpart('reply:pushkey')
1299 1319 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1300 1320 rpart.addparam('return', '%i' % ret, mandatory=False)
1301 1321
1302 1322 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
1303 1323 def handlepushkeyreply(op, inpart):
1304 1324 """retrieve the result of a pushkey request"""
1305 1325 ret = int(inpart.params['return'])
1306 1326 partid = int(inpart.params['in-reply-to'])
1307 1327 op.records.add('pushkey', {'return': ret}, partid)
1308 1328
1309 1329 @parthandler('obsmarkers')
1310 1330 def handleobsmarker(op, inpart):
1311 1331 """add a stream of obsmarkers to the repo"""
1312 1332 tr = op.gettransaction()
1313 1333 markerdata = inpart.read()
1314 1334 if op.ui.config('experimental', 'obsmarkers-exchange-debug', False):
1315 1335 op.ui.write(('obsmarker-exchange: %i bytes received\n')
1316 1336 % len(markerdata))
1317 1337 new = op.repo.obsstore.mergemarkers(tr, markerdata)
1318 1338 if new:
1319 1339 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
1320 1340 op.records.add('obsmarkers', {'new': new})
1321 1341 if op.reply is not None:
1322 1342 rpart = op.reply.newpart('reply:obsmarkers')
1323 1343 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1324 1344 rpart.addparam('new', '%i' % new, mandatory=False)
1325 1345
1326 1346
1327 1347 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
1328 1348 def handlepushkeyreply(op, inpart):
1329 1349 """retrieve the result of a pushkey request"""
1330 1350 ret = int(inpart.params['new'])
1331 1351 partid = int(inpart.params['in-reply-to'])
1332 1352 op.records.add('obsmarkers', {'new': ret}, partid)
@@ -1,843 +1,850 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 503 payload: 178 bytes
504 504 bundle2-input: part header size: 22
505 505 bundle2-input: part type: "test:debugreply"
506 506 bundle2-input: part id: "3"
507 507 bundle2-input: part parameters: 0
508 508 :test:debugreply:
509 509 mandatory: 0
510 510 advisory: 0
511 511 bundle2-input: payload chunk size: 0
512 512 payload: 0 bytes
513 513 bundle2-input: part header size: 43
514 514 bundle2-input: part type: "test:math"
515 515 bundle2-input: part id: "4"
516 516 bundle2-input: part parameters: 3
517 517 :test:math:
518 518 mandatory: 2
519 519 advisory: 1
520 520 bundle2-input: payload chunk size: 2
521 521 bundle2-input: payload chunk size: 0
522 522 payload: 2 bytes
523 523 bundle2-input: part header size: 29
524 524 bundle2-input: part type: "test:song"
525 525 bundle2-input: part id: "5"
526 526 bundle2-input: part parameters: 1
527 527 :test:song:
528 528 mandatory: 1
529 529 advisory: 0
530 530 bundle2-input: payload chunk size: 0
531 531 payload: 0 bytes
532 532 bundle2-input: part header size: 16
533 533 bundle2-input: part type: "test:ping"
534 534 bundle2-input: part id: "6"
535 535 bundle2-input: part parameters: 0
536 536 :test:ping:
537 537 mandatory: 0
538 538 advisory: 0
539 539 bundle2-input: payload chunk size: 0
540 540 payload: 0 bytes
541 541 bundle2-input: part header size: 0
542 542 bundle2-input: end of bundle2 stream
543 543 parts count: 7
544 544
545 545 Test actual unbundling of test part
546 546 =======================================
547 547
548 548 Process the bundle
549 549
550 550 $ hg unbundle2 --debug --config progress.debug=true < ../parts.hg2
551 551 bundle2-input: start processing of HG20 stream
552 552 bundle2-input: reading bundle2 stream parameters
553 553 bundle2-input-bundle: with-transaction
554 554 bundle2-input: start extraction of bundle2 parts
555 555 bundle2-input: part header size: 17
556 556 bundle2-input: part type: "test:empty"
557 557 bundle2-input: part id: "0"
558 558 bundle2-input: part parameters: 0
559 559 bundle2-input: ignoring unsupported advisory part test:empty
560 bundle2-input-part: "test:empty" (advisory) unsupported-type
560 561 bundle2-input: payload chunk size: 0
561 562 bundle2-input: part header size: 17
562 563 bundle2-input: part type: "test:empty"
563 564 bundle2-input: part id: "1"
564 565 bundle2-input: part parameters: 0
565 566 bundle2-input: ignoring unsupported advisory part test:empty
567 bundle2-input-part: "test:empty" (advisory) unsupported-type
566 568 bundle2-input: payload chunk size: 0
567 569 bundle2-input: part header size: 16
568 570 bundle2-input: part type: "test:song"
569 571 bundle2-input: part id: "2"
570 572 bundle2-input: part parameters: 0
571 573 bundle2-input: found a handler for part 'test:song'
574 bundle2-input-part: "test:song" (advisory) supported
572 575 The choir starts singing:
573 576 bundle2-input: payload chunk size: 178
574 577 bundle2-input: payload chunk size: 0
575 578 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
576 579 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
577 580 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
578 581 bundle2-input: part header size: 22
579 582 bundle2-input: part type: "test:debugreply"
580 583 bundle2-input: part id: "3"
581 584 bundle2-input: part parameters: 0
582 585 bundle2-input: found a handler for part 'test:debugreply'
586 bundle2-input-part: "test:debugreply" (advisory) supported
583 587 debugreply: no reply
584 588 bundle2-input: payload chunk size: 0
585 589 bundle2-input: part header size: 43
586 590 bundle2-input: part type: "test:math"
587 591 bundle2-input: part id: "4"
588 592 bundle2-input: part parameters: 3
589 593 bundle2-input: ignoring unsupported advisory part test:math
594 bundle2-input-part: "test:math" (advisory) (params: 2 mandatory 2 advisory) unsupported-type
590 595 bundle2-input: payload chunk size: 2
591 596 bundle2-input: payload chunk size: 0
592 597 bundle2-input: part header size: 29
593 598 bundle2-input: part type: "test:song"
594 599 bundle2-input: part id: "5"
595 600 bundle2-input: part parameters: 1
596 601 bundle2-input: found a handler for part 'test:song'
597 602 bundle2-input: ignoring unsupported advisory part test:song - randomparam
603 bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (['randomparam'])
598 604 bundle2-input: payload chunk size: 0
599 605 bundle2-input: part header size: 16
600 606 bundle2-input: part type: "test:ping"
601 607 bundle2-input: part id: "6"
602 608 bundle2-input: part parameters: 0
603 609 bundle2-input: found a handler for part 'test:ping'
610 bundle2-input-part: "test:ping" (advisory) supported
604 611 received ping request (id 6)
605 612 bundle2-input: payload chunk size: 0
606 613 bundle2-input: part header size: 0
607 614 bundle2-input: end of bundle2 stream
608 615 bundle2-input-bundle: 6 parts total
609 616 0 unread bytes
610 617 3 total verses sung
611 618
612 619 Unbundle with an unknown mandatory part
613 620 (should abort)
614 621
615 622 $ hg bundle2 --parts --unknown ../unknown.hg2
616 623
617 624 $ hg unbundle2 < ../unknown.hg2
618 625 The choir starts singing:
619 626 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
620 627 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
621 628 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
622 629 debugreply: no reply
623 630 0 unread bytes
624 631 abort: missing support for test:unknown
625 632 [255]
626 633
627 634 Unbundle with an unknown mandatory part parameters
628 635 (should abort)
629 636
630 637 $ hg bundle2 --unknownparams ../unknown.hg2
631 638
632 639 $ hg unbundle2 < ../unknown.hg2
633 640 0 unread bytes
634 641 abort: missing support for test:song - randomparams
635 642 [255]
636 643
637 644 unbundle with a reply
638 645
639 646 $ hg bundle2 --parts --reply ../parts-reply.hg2
640 647 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
641 648 0 unread bytes
642 649 3 total verses sung
643 650
644 651 The reply is a bundle
645 652
646 653 $ cat ../reply.hg2
647 654 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)
648 655 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
649 656 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
650 657 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
651 658 \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)
652 659 debugreply: 'city=!'
653 660 debugreply: 'celeste,ville'
654 661 debugreply: 'elephants'
655 662 debugreply: 'babar'
656 663 debugreply: 'celeste'
657 664 debugreply: 'ping-pong'
658 665 \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)
659 666 replying to ping request (id 7)
660 667 \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
661 668
662 669 The reply is valid
663 670
664 671 $ hg statbundle2 < ../reply.hg2
665 672 options count: 0
666 673 :output:
667 674 mandatory: 0
668 675 advisory: 1
669 676 payload: 217 bytes
670 677 :output:
671 678 mandatory: 0
672 679 advisory: 1
673 680 payload: 201 bytes
674 681 :test:pong:
675 682 mandatory: 1
676 683 advisory: 0
677 684 payload: 0 bytes
678 685 :output:
679 686 mandatory: 0
680 687 advisory: 1
681 688 payload: 61 bytes
682 689 parts count: 4
683 690
684 691 Unbundle the reply to get the output:
685 692
686 693 $ hg unbundle2 < ../reply.hg2
687 694 remote: The choir starts singing:
688 695 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
689 696 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
690 697 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
691 698 remote: debugreply: capabilities:
692 699 remote: debugreply: 'city=!'
693 700 remote: debugreply: 'celeste,ville'
694 701 remote: debugreply: 'elephants'
695 702 remote: debugreply: 'babar'
696 703 remote: debugreply: 'celeste'
697 704 remote: debugreply: 'ping-pong'
698 705 remote: received ping request (id 7)
699 706 remote: replying to ping request (id 7)
700 707 0 unread bytes
701 708
702 709 Test push race detection
703 710
704 711 $ hg bundle2 --pushrace ../part-race.hg2
705 712
706 713 $ hg unbundle2 < ../part-race.hg2
707 714 0 unread bytes
708 715 abort: push race: repository changed while pushing - please try again
709 716 [255]
710 717
711 718 Support for changegroup
712 719 ===================================
713 720
714 721 $ hg unbundle $TESTDIR/bundles/rebase.hg
715 722 adding changesets
716 723 adding manifests
717 724 adding file changes
718 725 added 8 changesets with 7 changes to 7 files (+3 heads)
719 726 (run 'hg heads' to see heads, 'hg merge' to merge)
720 727
721 728 $ hg log -G
722 729 o 8:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
723 730 |
724 731 | o 7:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com> G
725 732 |/|
726 733 o | 6:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
727 734 | |
728 735 | o 5:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
729 736 |/
730 737 | o 4:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com> D
731 738 | |
732 739 | o 3:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C
733 740 | |
734 741 | o 2:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B
735 742 |/
736 743 o 1:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com> A
737 744
738 745 @ 0:3903775176ed draft test a
739 746
740 747
741 748 $ hg bundle2 --debug --config progress.debug=true --rev '8+7+5+4' ../rev.hg2
742 749 4 changesets found
743 750 list of changesets:
744 751 32af7686d403cf45b5d95f2d70cebea587ac806a
745 752 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
746 753 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
747 754 02de42196ebee42ef284b6780a87cdc96e8eaab6
748 755 bundle2-output-bundle: "HG20", 1 parts total
749 756 bundle2-output: start emission of HG20 stream
750 757 bundle2-output: bundle parameter:
751 758 bundle2-output: start of parts
752 759 bundle2-output: bundle part: "changegroup"
753 760 bundle2-output-part: "changegroup" (advisory) streamed payload
754 761 bundle2-output: part 0: "changegroup"
755 762 bundle2-output: header chunk size: 18
756 763 bundling: 1/4 changesets (25.00%)
757 764 bundling: 2/4 changesets (50.00%)
758 765 bundling: 3/4 changesets (75.00%)
759 766 bundling: 4/4 changesets (100.00%)
760 767 bundling: 1/4 manifests (25.00%)
761 768 bundling: 2/4 manifests (50.00%)
762 769 bundling: 3/4 manifests (75.00%)
763 770 bundling: 4/4 manifests (100.00%)
764 771 bundling: D 1/3 files (33.33%)
765 772 bundling: E 2/3 files (66.67%)
766 773 bundling: H 3/3 files (100.00%)
767 774 bundle2-output: payload chunk size: 1555
768 775 bundle2-output: closing payload chunk
769 776 bundle2-output: end of bundle
770 777
771 778 $ cat ../rev.hg2
772 779 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)
773 780 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
774 781 \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)
775 782 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
776 783 \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)
777 784 \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)
778 785 \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)
779 786 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
780 787 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
781 788 \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)
782 789 \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)
783 790 \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)
784 791 \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)
785 792 \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)
786 793 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
787 794 \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)
788 795 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
789 796 l\r (no-eol) (esc)
790 797 \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)
791 798 \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)
792 799 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
793 800 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
794 801
795 802 $ hg debugbundle ../rev.hg2
796 803 Stream params: {}
797 804 changegroup -- '{}'
798 805 32af7686d403cf45b5d95f2d70cebea587ac806a
799 806 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
800 807 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
801 808 02de42196ebee42ef284b6780a87cdc96e8eaab6
802 809 $ hg unbundle ../rev.hg2
803 810 adding changesets
804 811 adding manifests
805 812 adding file changes
806 813 added 0 changesets with 0 changes to 3 files
807 814
808 815 with reply
809 816
810 817 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
811 818 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
812 819 0 unread bytes
813 820 addchangegroup return: 1
814 821
815 822 $ cat ../rev-reply.hg2
816 823 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)
817 824 adding manifests
818 825 adding file changes
819 826 added 0 changesets with 0 changes to 3 files
820 827 \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
821 828
822 829 Check handling of exception during generation.
823 830 ----------------------------------------------
824 831
825 832 $ hg bundle2 --genraise > ../genfailed.hg2
826 833 abort: Someone set up us the bomb!
827 834 [255]
828 835
829 836 Should still be a valid bundle
830 837
831 838 $ cat ../genfailed.hg2
832 839 HG20\x00\x00\x00\x00\x00\x00\x00\r (no-eol) (esc)
833 840 \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)
834 841
835 842 And its handling on the other size raise a clean exception
836 843
837 844 $ cat ../genfailed.hg2 | hg unbundle2
838 845 0 unread bytes
839 846 abort: unexpected error: Someone set up us the bomb!
840 847 [255]
841 848
842 849
843 850 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now