##// END OF EJS Templates
bundle2: raise BundleValueError error for stream level unsupported params...
Pierre-Yves David -
r21628:7c5a8561 default
parent child Browse files
Show More
@@ -1,855 +1,855
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: (16 bits integer)
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: (16 bits inter)
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
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 a 32 bits integer, `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 Bundle processing
129 129 ============================
130 130
131 131 Each part is processed in order using a "part handler". Handler are registered
132 132 for a certain part type.
133 133
134 134 The matching of a part to its handler is case insensitive. The case of the
135 135 part type is used to know if a part is mandatory or advisory. If the Part type
136 136 contains any uppercase char it is considered mandatory. When no handler is
137 137 known for a Mandatory part, the process is aborted and an exception is raised.
138 138 If the part is advisory and no handler is known, the part is ignored. When the
139 139 process is aborted, the full bundle is still read from the stream to keep the
140 140 channel usable. But none of the part read from an abort are processed. In the
141 141 future, dropping the stream may become an option for channel we do not care to
142 142 preserve.
143 143 """
144 144
145 145 import util
146 146 import struct
147 147 import urllib
148 148 import string
149 149
150 150 import changegroup, error
151 151 from i18n import _
152 152
153 153 _pack = struct.pack
154 154 _unpack = struct.unpack
155 155
156 156 _magicstring = 'HG2X'
157 157
158 158 _fstreamparamsize = '>H'
159 159 _fpartheadersize = '>H'
160 160 _fparttypesize = '>B'
161 161 _fpartid = '>I'
162 162 _fpayloadsize = '>I'
163 163 _fpartparamcount = '>BB'
164 164
165 165 preferedchunksize = 4096
166 166
167 167 def _makefpartparamsizes(nbparams):
168 168 """return a struct format to read part parameter sizes
169 169
170 170 The number parameters is variable so we need to build that format
171 171 dynamically.
172 172 """
173 173 return '>'+('BB'*nbparams)
174 174
175 175 parthandlermapping = {}
176 176
177 177 def parthandler(parttype, params=()):
178 178 """decorator that register a function as a bundle2 part handler
179 179
180 180 eg::
181 181
182 182 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
183 183 def myparttypehandler(...):
184 184 '''process a part of type "my part".'''
185 185 ...
186 186 """
187 187 def _decorator(func):
188 188 lparttype = parttype.lower() # enforce lower case matching.
189 189 assert lparttype not in parthandlermapping
190 190 parthandlermapping[lparttype] = func
191 191 func.params = frozenset(params)
192 192 return func
193 193 return _decorator
194 194
195 195 class unbundlerecords(object):
196 196 """keep record of what happens during and unbundle
197 197
198 198 New records are added using `records.add('cat', obj)`. Where 'cat' is a
199 199 category of record and obj is an arbitrary object.
200 200
201 201 `records['cat']` will return all entries of this category 'cat'.
202 202
203 203 Iterating on the object itself will yield `('category', obj)` tuples
204 204 for all entries.
205 205
206 206 All iterations happens in chronological order.
207 207 """
208 208
209 209 def __init__(self):
210 210 self._categories = {}
211 211 self._sequences = []
212 212 self._replies = {}
213 213
214 214 def add(self, category, entry, inreplyto=None):
215 215 """add a new record of a given category.
216 216
217 217 The entry can then be retrieved in the list returned by
218 218 self['category']."""
219 219 self._categories.setdefault(category, []).append(entry)
220 220 self._sequences.append((category, entry))
221 221 if inreplyto is not None:
222 222 self.getreplies(inreplyto).add(category, entry)
223 223
224 224 def getreplies(self, partid):
225 225 """get the subrecords that replies to a specific part"""
226 226 return self._replies.setdefault(partid, unbundlerecords())
227 227
228 228 def __getitem__(self, cat):
229 229 return tuple(self._categories.get(cat, ()))
230 230
231 231 def __iter__(self):
232 232 return iter(self._sequences)
233 233
234 234 def __len__(self):
235 235 return len(self._sequences)
236 236
237 237 def __nonzero__(self):
238 238 return bool(self._sequences)
239 239
240 240 class bundleoperation(object):
241 241 """an object that represents a single bundling process
242 242
243 243 Its purpose is to carry unbundle-related objects and states.
244 244
245 245 A new object should be created at the beginning of each bundle processing.
246 246 The object is to be returned by the processing function.
247 247
248 248 The object has very little content now it will ultimately contain:
249 249 * an access to the repo the bundle is applied to,
250 250 * a ui object,
251 251 * a way to retrieve a transaction to add changes to the repo,
252 252 * a way to record the result of processing each part,
253 253 * a way to construct a bundle response when applicable.
254 254 """
255 255
256 256 def __init__(self, repo, transactiongetter):
257 257 self.repo = repo
258 258 self.ui = repo.ui
259 259 self.records = unbundlerecords()
260 260 self.gettransaction = transactiongetter
261 261 self.reply = None
262 262
263 263 class TransactionUnavailable(RuntimeError):
264 264 pass
265 265
266 266 def _notransaction():
267 267 """default method to get a transaction while processing a bundle
268 268
269 269 Raise an exception to highlight the fact that no transaction was expected
270 270 to be created"""
271 271 raise TransactionUnavailable()
272 272
273 273 def processbundle(repo, unbundler, transactiongetter=_notransaction):
274 274 """This function process a bundle, apply effect to/from a repo
275 275
276 276 It iterates over each part then searches for and uses the proper handling
277 277 code to process the part. Parts are processed in order.
278 278
279 279 This is very early version of this function that will be strongly reworked
280 280 before final usage.
281 281
282 282 Unknown Mandatory part will abort the process.
283 283 """
284 284 op = bundleoperation(repo, transactiongetter)
285 285 # todo:
286 286 # - replace this is a init function soon.
287 287 # - exception catching
288 288 unbundler.params
289 289 iterparts = unbundler.iterparts()
290 290 part = None
291 291 try:
292 292 for part in iterparts:
293 293 parttype = part.type
294 294 # part key are matched lower case
295 295 key = parttype.lower()
296 296 try:
297 297 handler = parthandlermapping.get(key)
298 298 if handler is None:
299 299 raise error.BundleValueError(parttype=key)
300 300 op.ui.debug('found a handler for part %r\n' % parttype)
301 301 unknownparams = part.mandatorykeys - handler.params
302 302 if unknownparams:
303 303 unknownparams = list(unknownparams)
304 304 unknownparams.sort()
305 305 raise error.BundleValueError(parttype=key,
306 306 params=unknownparams)
307 307 except error.BundleValueError, exc:
308 308 if key != parttype: # mandatory parts
309 309 raise
310 310 op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
311 311 # consuming the part
312 312 part.read()
313 313 continue
314 314
315 315
316 316 # handler is called outside the above try block so that we don't
317 317 # risk catching KeyErrors from anything other than the
318 318 # parthandlermapping lookup (any KeyError raised by handler()
319 319 # itself represents a defect of a different variety).
320 320 output = None
321 321 if op.reply is not None:
322 322 op.ui.pushbuffer(error=True)
323 323 output = ''
324 324 try:
325 325 handler(op, part)
326 326 finally:
327 327 if output is not None:
328 328 output = op.ui.popbuffer()
329 329 if output:
330 330 outpart = op.reply.newpart('b2x:output', data=output)
331 331 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
332 332 part.read()
333 333 except Exception, exc:
334 334 if part is not None:
335 335 # consume the bundle content
336 336 part.read()
337 337 for part in iterparts:
338 338 # consume the bundle content
339 339 part.read()
340 340 # Small hack to let caller code distinguish exceptions from bundle2
341 341 # processing fron the ones from bundle1 processing. This is mostly
342 342 # needed to handle different return codes to unbundle according to the
343 343 # type of bundle. We should probably clean up or drop this return code
344 344 # craziness in a future version.
345 345 exc.duringunbundle2 = True
346 346 raise
347 347 return op
348 348
349 349 def decodecaps(blob):
350 350 """decode a bundle2 caps bytes blob into a dictionnary
351 351
352 352 The blob is a list of capabilities (one per line)
353 353 Capabilities may have values using a line of the form::
354 354
355 355 capability=value1,value2,value3
356 356
357 357 The values are always a list."""
358 358 caps = {}
359 359 for line in blob.splitlines():
360 360 if not line:
361 361 continue
362 362 if '=' not in line:
363 363 key, vals = line, ()
364 364 else:
365 365 key, vals = line.split('=', 1)
366 366 vals = vals.split(',')
367 367 key = urllib.unquote(key)
368 368 vals = [urllib.unquote(v) for v in vals]
369 369 caps[key] = vals
370 370 return caps
371 371
372 372 def encodecaps(caps):
373 373 """encode a bundle2 caps dictionary into a bytes blob"""
374 374 chunks = []
375 375 for ca in sorted(caps):
376 376 vals = caps[ca]
377 377 ca = urllib.quote(ca)
378 378 vals = [urllib.quote(v) for v in vals]
379 379 if vals:
380 380 ca = "%s=%s" % (ca, ','.join(vals))
381 381 chunks.append(ca)
382 382 return '\n'.join(chunks)
383 383
384 384 class bundle20(object):
385 385 """represent an outgoing bundle2 container
386 386
387 387 Use the `addparam` method to add stream level parameter. and `newpart` to
388 388 populate it. Then call `getchunks` to retrieve all the binary chunks of
389 389 data that compose the bundle2 container."""
390 390
391 391 def __init__(self, ui, capabilities=()):
392 392 self.ui = ui
393 393 self._params = []
394 394 self._parts = []
395 395 self.capabilities = dict(capabilities)
396 396
397 397 # methods used to defines the bundle2 content
398 398 def addparam(self, name, value=None):
399 399 """add a stream level parameter"""
400 400 if not name:
401 401 raise ValueError('empty parameter name')
402 402 if name[0] not in string.letters:
403 403 raise ValueError('non letter first character: %r' % name)
404 404 self._params.append((name, value))
405 405
406 406 def addpart(self, part):
407 407 """add a new part to the bundle2 container
408 408
409 409 Parts contains the actual applicative payload."""
410 410 assert part.id is None
411 411 part.id = len(self._parts) # very cheap counter
412 412 self._parts.append(part)
413 413
414 414 def newpart(self, typeid, *args, **kwargs):
415 415 """create a new part and add it to the containers
416 416
417 417 As the part is directly added to the containers. For now, this means
418 418 that any failure to properly initialize the part after calling
419 419 ``newpart`` should result in a failure of the whole bundling process.
420 420
421 421 You can still fall back to manually create and add if you need better
422 422 control."""
423 423 part = bundlepart(typeid, *args, **kwargs)
424 424 self.addpart(part)
425 425 return part
426 426
427 427 # methods used to generate the bundle2 stream
428 428 def getchunks(self):
429 429 self.ui.debug('start emission of %s stream\n' % _magicstring)
430 430 yield _magicstring
431 431 param = self._paramchunk()
432 432 self.ui.debug('bundle parameter: %s\n' % param)
433 433 yield _pack(_fstreamparamsize, len(param))
434 434 if param:
435 435 yield param
436 436
437 437 self.ui.debug('start of parts\n')
438 438 for part in self._parts:
439 439 self.ui.debug('bundle part: "%s"\n' % part.type)
440 440 for chunk in part.getchunks():
441 441 yield chunk
442 442 self.ui.debug('end of bundle\n')
443 443 yield '\0\0'
444 444
445 445 def _paramchunk(self):
446 446 """return a encoded version of all stream parameters"""
447 447 blocks = []
448 448 for par, value in self._params:
449 449 par = urllib.quote(par)
450 450 if value is not None:
451 451 value = urllib.quote(value)
452 452 par = '%s=%s' % (par, value)
453 453 blocks.append(par)
454 454 return ' '.join(blocks)
455 455
456 456 class unpackermixin(object):
457 457 """A mixin to extract bytes and struct data from a stream"""
458 458
459 459 def __init__(self, fp):
460 460 self._fp = fp
461 461
462 462 def _unpack(self, format):
463 463 """unpack this struct format from the stream"""
464 464 data = self._readexact(struct.calcsize(format))
465 465 return _unpack(format, data)
466 466
467 467 def _readexact(self, size):
468 468 """read exactly <size> bytes from the stream"""
469 469 return changegroup.readexactly(self._fp, size)
470 470
471 471
472 472 class unbundle20(unpackermixin):
473 473 """interpret a bundle2 stream
474 474
475 475 This class is fed with a binary stream and yields parts through its
476 476 `iterparts` methods."""
477 477
478 478 def __init__(self, ui, fp, header=None):
479 479 """If header is specified, we do not read it out of the stream."""
480 480 self.ui = ui
481 481 super(unbundle20, self).__init__(fp)
482 482 if header is None:
483 483 header = self._readexact(4)
484 484 magic, version = header[0:2], header[2:4]
485 485 if magic != 'HG':
486 486 raise util.Abort(_('not a Mercurial bundle'))
487 487 if version != '2X':
488 488 raise util.Abort(_('unknown bundle version %s') % version)
489 489 self.ui.debug('start processing of %s stream\n' % header)
490 490
491 491 @util.propertycache
492 492 def params(self):
493 493 """dictionary of stream level parameters"""
494 494 self.ui.debug('reading bundle2 stream parameters\n')
495 495 params = {}
496 496 paramssize = self._unpack(_fstreamparamsize)[0]
497 497 if paramssize:
498 498 for p in self._readexact(paramssize).split(' '):
499 499 p = p.split('=', 1)
500 500 p = [urllib.unquote(i) for i in p]
501 501 if len(p) < 2:
502 502 p.append(None)
503 503 self._processparam(*p)
504 504 params[p[0]] = p[1]
505 505 return params
506 506
507 507 def _processparam(self, name, value):
508 508 """process a parameter, applying its effect if needed
509 509
510 510 Parameter starting with a lower case letter are advisory and will be
511 511 ignored when unknown. Those starting with an upper case letter are
512 512 mandatory and will this function will raise a KeyError when unknown.
513 513
514 514 Note: no option are currently supported. Any input will be either
515 515 ignored or failing.
516 516 """
517 517 if not name:
518 518 raise ValueError('empty parameter name')
519 519 if name[0] not in string.letters:
520 520 raise ValueError('non letter first character: %r' % name)
521 521 # Some logic will be later added here to try to process the option for
522 522 # a dict of known parameter.
523 523 if name[0].islower():
524 524 self.ui.debug("ignoring unknown parameter %r\n" % name)
525 525 else:
526 raise KeyError(name)
526 raise error.BundleValueError(params=(name,))
527 527
528 528
529 529 def iterparts(self):
530 530 """yield all parts contained in the stream"""
531 531 # make sure param have been loaded
532 532 self.params
533 533 self.ui.debug('start extraction of bundle2 parts\n')
534 534 headerblock = self._readpartheader()
535 535 while headerblock is not None:
536 536 part = unbundlepart(self.ui, headerblock, self._fp)
537 537 yield part
538 538 headerblock = self._readpartheader()
539 539 self.ui.debug('end of bundle2 stream\n')
540 540
541 541 def _readpartheader(self):
542 542 """reads a part header size and return the bytes blob
543 543
544 544 returns None if empty"""
545 545 headersize = self._unpack(_fpartheadersize)[0]
546 546 self.ui.debug('part header size: %i\n' % headersize)
547 547 if headersize:
548 548 return self._readexact(headersize)
549 549 return None
550 550
551 551
552 552 class bundlepart(object):
553 553 """A bundle2 part contains application level payload
554 554
555 555 The part `type` is used to route the part to the application level
556 556 handler.
557 557
558 558 The part payload is contained in ``part.data``. It could be raw bytes or a
559 559 generator of byte chunks.
560 560
561 561 You can add parameters to the part using the ``addparam`` method.
562 562 Parameters can be either mandatory (default) or advisory. Remote side
563 563 should be able to safely ignore the advisory ones.
564 564
565 565 Both data and parameters cannot be modified after the generation has begun.
566 566 """
567 567
568 568 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
569 569 data=''):
570 570 self.id = None
571 571 self.type = parttype
572 572 self._data = data
573 573 self._mandatoryparams = list(mandatoryparams)
574 574 self._advisoryparams = list(advisoryparams)
575 575 # checking for duplicated entries
576 576 self._seenparams = set()
577 577 for pname, __ in self._mandatoryparams + self._advisoryparams:
578 578 if pname in self._seenparams:
579 579 raise RuntimeError('duplicated params: %s' % pname)
580 580 self._seenparams.add(pname)
581 581 # status of the part's generation:
582 582 # - None: not started,
583 583 # - False: currently generated,
584 584 # - True: generation done.
585 585 self._generated = None
586 586
587 587 # methods used to defines the part content
588 588 def __setdata(self, data):
589 589 if self._generated is not None:
590 590 raise error.ReadOnlyPartError('part is being generated')
591 591 self._data = data
592 592 def __getdata(self):
593 593 return self._data
594 594 data = property(__getdata, __setdata)
595 595
596 596 @property
597 597 def mandatoryparams(self):
598 598 # make it an immutable tuple to force people through ``addparam``
599 599 return tuple(self._mandatoryparams)
600 600
601 601 @property
602 602 def advisoryparams(self):
603 603 # make it an immutable tuple to force people through ``addparam``
604 604 return tuple(self._advisoryparams)
605 605
606 606 def addparam(self, name, value='', mandatory=True):
607 607 if self._generated is not None:
608 608 raise error.ReadOnlyPartError('part is being generated')
609 609 if name in self._seenparams:
610 610 raise ValueError('duplicated params: %s' % name)
611 611 self._seenparams.add(name)
612 612 params = self._advisoryparams
613 613 if mandatory:
614 614 params = self._mandatoryparams
615 615 params.append((name, value))
616 616
617 617 # methods used to generates the bundle2 stream
618 618 def getchunks(self):
619 619 if self._generated is not None:
620 620 raise RuntimeError('part can only be consumed once')
621 621 self._generated = False
622 622 #### header
623 623 ## parttype
624 624 header = [_pack(_fparttypesize, len(self.type)),
625 625 self.type, _pack(_fpartid, self.id),
626 626 ]
627 627 ## parameters
628 628 # count
629 629 manpar = self.mandatoryparams
630 630 advpar = self.advisoryparams
631 631 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
632 632 # size
633 633 parsizes = []
634 634 for key, value in manpar:
635 635 parsizes.append(len(key))
636 636 parsizes.append(len(value))
637 637 for key, value in advpar:
638 638 parsizes.append(len(key))
639 639 parsizes.append(len(value))
640 640 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
641 641 header.append(paramsizes)
642 642 # key, value
643 643 for key, value in manpar:
644 644 header.append(key)
645 645 header.append(value)
646 646 for key, value in advpar:
647 647 header.append(key)
648 648 header.append(value)
649 649 ## finalize header
650 650 headerchunk = ''.join(header)
651 651 yield _pack(_fpartheadersize, len(headerchunk))
652 652 yield headerchunk
653 653 ## payload
654 654 for chunk in self._payloadchunks():
655 655 yield _pack(_fpayloadsize, len(chunk))
656 656 yield chunk
657 657 # end of payload
658 658 yield _pack(_fpayloadsize, 0)
659 659 self._generated = True
660 660
661 661 def _payloadchunks(self):
662 662 """yield chunks of a the part payload
663 663
664 664 Exists to handle the different methods to provide data to a part."""
665 665 # we only support fixed size data now.
666 666 # This will be improved in the future.
667 667 if util.safehasattr(self.data, 'next'):
668 668 buff = util.chunkbuffer(self.data)
669 669 chunk = buff.read(preferedchunksize)
670 670 while chunk:
671 671 yield chunk
672 672 chunk = buff.read(preferedchunksize)
673 673 elif len(self.data):
674 674 yield self.data
675 675
676 676 class unbundlepart(unpackermixin):
677 677 """a bundle part read from a bundle"""
678 678
679 679 def __init__(self, ui, header, fp):
680 680 super(unbundlepart, self).__init__(fp)
681 681 self.ui = ui
682 682 # unbundle state attr
683 683 self._headerdata = header
684 684 self._headeroffset = 0
685 685 self._initialized = False
686 686 self.consumed = False
687 687 # part data
688 688 self.id = None
689 689 self.type = None
690 690 self.mandatoryparams = None
691 691 self.advisoryparams = None
692 692 self.params = None
693 693 self.mandatorykeys = ()
694 694 self._payloadstream = None
695 695 self._readheader()
696 696
697 697 def _fromheader(self, size):
698 698 """return the next <size> byte from the header"""
699 699 offset = self._headeroffset
700 700 data = self._headerdata[offset:(offset + size)]
701 701 self._headeroffset = offset + size
702 702 return data
703 703
704 704 def _unpackheader(self, format):
705 705 """read given format from header
706 706
707 707 This automatically compute the size of the format to read."""
708 708 data = self._fromheader(struct.calcsize(format))
709 709 return _unpack(format, data)
710 710
711 711 def _initparams(self, mandatoryparams, advisoryparams):
712 712 """internal function to setup all logic related parameters"""
713 713 # make it read only to prevent people touching it by mistake.
714 714 self.mandatoryparams = tuple(mandatoryparams)
715 715 self.advisoryparams = tuple(advisoryparams)
716 716 # user friendly UI
717 717 self.params = dict(self.mandatoryparams)
718 718 self.params.update(dict(self.advisoryparams))
719 719 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
720 720
721 721 def _readheader(self):
722 722 """read the header and setup the object"""
723 723 typesize = self._unpackheader(_fparttypesize)[0]
724 724 self.type = self._fromheader(typesize)
725 725 self.ui.debug('part type: "%s"\n' % self.type)
726 726 self.id = self._unpackheader(_fpartid)[0]
727 727 self.ui.debug('part id: "%s"\n' % self.id)
728 728 ## reading parameters
729 729 # param count
730 730 mancount, advcount = self._unpackheader(_fpartparamcount)
731 731 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
732 732 # param size
733 733 fparamsizes = _makefpartparamsizes(mancount + advcount)
734 734 paramsizes = self._unpackheader(fparamsizes)
735 735 # make it a list of couple again
736 736 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
737 737 # split mandatory from advisory
738 738 mansizes = paramsizes[:mancount]
739 739 advsizes = paramsizes[mancount:]
740 740 # retrive param value
741 741 manparams = []
742 742 for key, value in mansizes:
743 743 manparams.append((self._fromheader(key), self._fromheader(value)))
744 744 advparams = []
745 745 for key, value in advsizes:
746 746 advparams.append((self._fromheader(key), self._fromheader(value)))
747 747 self._initparams(manparams, advparams)
748 748 ## part payload
749 749 def payloadchunks():
750 750 payloadsize = self._unpack(_fpayloadsize)[0]
751 751 self.ui.debug('payload chunk size: %i\n' % payloadsize)
752 752 while payloadsize:
753 753 yield self._readexact(payloadsize)
754 754 payloadsize = self._unpack(_fpayloadsize)[0]
755 755 self.ui.debug('payload chunk size: %i\n' % payloadsize)
756 756 self._payloadstream = util.chunkbuffer(payloadchunks())
757 757 # we read the data, tell it
758 758 self._initialized = True
759 759
760 760 def read(self, size=None):
761 761 """read payload data"""
762 762 if not self._initialized:
763 763 self._readheader()
764 764 if size is None:
765 765 data = self._payloadstream.read()
766 766 else:
767 767 data = self._payloadstream.read(size)
768 768 if size is None or len(data) < size:
769 769 self.consumed = True
770 770 return data
771 771
772 772
773 773 @parthandler('b2x:changegroup')
774 774 def handlechangegroup(op, inpart):
775 775 """apply a changegroup part on the repo
776 776
777 777 This is a very early implementation that will massive rework before being
778 778 inflicted to any end-user.
779 779 """
780 780 # Make sure we trigger a transaction creation
781 781 #
782 782 # The addchangegroup function will get a transaction object by itself, but
783 783 # we need to make sure we trigger the creation of a transaction object used
784 784 # for the whole processing scope.
785 785 op.gettransaction()
786 786 cg = changegroup.unbundle10(inpart, 'UN')
787 787 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
788 788 op.records.add('changegroup', {'return': ret})
789 789 if op.reply is not None:
790 790 # This is definitly not the final form of this
791 791 # return. But one need to start somewhere.
792 792 part = op.reply.newpart('b2x:reply:changegroup')
793 793 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
794 794 part.addparam('return', '%i' % ret, mandatory=False)
795 795 assert not inpart.read()
796 796
797 797 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to'))
798 798 def handlechangegroup(op, inpart):
799 799 ret = int(inpart.params['return'])
800 800 replyto = int(inpart.params['in-reply-to'])
801 801 op.records.add('changegroup', {'return': ret}, replyto)
802 802
803 803 @parthandler('b2x:check:heads')
804 804 def handlechangegroup(op, inpart):
805 805 """check that head of the repo did not change
806 806
807 807 This is used to detect a push race when using unbundle.
808 808 This replaces the "heads" argument of unbundle."""
809 809 h = inpart.read(20)
810 810 heads = []
811 811 while len(h) == 20:
812 812 heads.append(h)
813 813 h = inpart.read(20)
814 814 assert not h
815 815 if heads != op.repo.heads():
816 816 raise error.PushRaced('repository changed while pushing - '
817 817 'please try again')
818 818
819 819 @parthandler('b2x:output')
820 820 def handleoutput(op, inpart):
821 821 """forward output captured on the server to the client"""
822 822 for line in inpart.read().splitlines():
823 823 op.ui.write(('remote: %s\n' % line))
824 824
825 825 @parthandler('b2x:replycaps')
826 826 def handlereplycaps(op, inpart):
827 827 """Notify that a reply bundle should be created
828 828
829 829 The payload contains the capabilities information for the reply"""
830 830 caps = decodecaps(inpart.read())
831 831 if op.reply is None:
832 832 op.reply = bundle20(op.ui, caps)
833 833
834 834 @parthandler('b2x:error:abort', ('message', 'hint'))
835 835 def handlereplycaps(op, inpart):
836 836 """Used to transmit abort error over the wire"""
837 837 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
838 838
839 839 @parthandler('b2x:error:unsupportedcontent', ('parttype', 'params'))
840 840 def handlereplycaps(op, inpart):
841 841 """Used to transmit unknown content error over the wire"""
842 842 kwargs = {}
843 843 parttype = inpart.params.get('parttype')
844 844 if parttype is not None:
845 845 kwargs['parttype'] = parttype
846 846 params = inpart.params.get('params')
847 847 if params is not None:
848 848 kwargs['params'] = params.split('\0')
849 849
850 850 raise error.BundleValueError(**kwargs)
851 851
852 852 @parthandler('b2x:error:pushraced', ('message',))
853 853 def handlereplycaps(op, inpart):
854 854 """Used to transmit push race error over the wire"""
855 855 raise error.ResponseError(_('push failed:'), inpart.params['message'])
@@ -1,1121 +1,1121
1 1
2 2 Create an extension to test bundle2 API
3 3
4 4 $ cat > bundle2.py << EOF
5 5 > """A small extension to test bundle2 implementation
6 6 >
7 7 > Current bundle2 implementation is far too limited to be used in any core
8 8 > code. We still need to be able to test it while it grow up.
9 9 > """
10 10 >
11 11 > try:
12 12 > import msvcrt
13 13 > msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
14 14 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
15 15 > msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
16 16 > except ImportError:
17 17 > pass
18 18 >
19 19 > import sys
20 20 > from mercurial import cmdutil
21 21 > from mercurial import util
22 22 > from mercurial import bundle2
23 23 > from mercurial import scmutil
24 24 > from mercurial import discovery
25 25 > from mercurial import changegroup
26 26 > from mercurial import error
27 27 > cmdtable = {}
28 28 > command = cmdutil.command(cmdtable)
29 29 >
30 30 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
31 31 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
32 32 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
33 33 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
34 34 >
35 35 > @bundle2.parthandler('test:song')
36 36 > def songhandler(op, part):
37 37 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
38 38 > op.ui.write('The choir starts singing:\n')
39 39 > verses = 0
40 40 > for line in part.read().split('\n'):
41 41 > op.ui.write(' %s\n' % line)
42 42 > verses += 1
43 43 > op.records.add('song', {'verses': verses})
44 44 >
45 45 > @bundle2.parthandler('test:ping')
46 46 > def pinghandler(op, part):
47 47 > op.ui.write('received ping request (id %i)\n' % part.id)
48 48 > if op.reply is not None and 'ping-pong' in op.reply.capabilities:
49 49 > op.ui.write_err('replying to ping request (id %i)\n' % part.id)
50 50 > op.reply.newpart('test:pong', [('in-reply-to', str(part.id))])
51 51 >
52 52 > @bundle2.parthandler('test:debugreply')
53 53 > def debugreply(op, part):
54 54 > """print data about the capacity of the bundle reply"""
55 55 > if op.reply is None:
56 56 > op.ui.write('debugreply: no reply\n')
57 57 > else:
58 58 > op.ui.write('debugreply: capabilities:\n')
59 59 > for cap in sorted(op.reply.capabilities):
60 60 > op.ui.write('debugreply: %r\n' % cap)
61 61 > for val in op.reply.capabilities[cap]:
62 62 > op.ui.write('debugreply: %r\n' % val)
63 63 >
64 64 > @command('bundle2',
65 65 > [('', 'param', [], 'stream level parameter'),
66 66 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
67 67 > ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
68 68 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
69 69 > ('', 'reply', False, 'produce a reply bundle'),
70 70 > ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
71 71 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
72 72 > '[OUTPUTFILE]')
73 73 > def cmdbundle2(ui, repo, path=None, **opts):
74 74 > """write a bundle2 container on standard ouput"""
75 75 > bundler = bundle2.bundle20(ui)
76 76 > for p in opts['param']:
77 77 > p = p.split('=', 1)
78 78 > try:
79 79 > bundler.addparam(*p)
80 80 > except ValueError, exc:
81 81 > raise util.Abort('%s' % exc)
82 82 >
83 83 > if opts['reply']:
84 84 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
85 85 > bundler.newpart('b2x:replycaps', data=capsstring)
86 86 >
87 87 > if opts['pushrace']:
88 88 > # also serve to test the assignement of data outside of init
89 89 > part = bundler.newpart('b2x:check:heads')
90 90 > part.data = '01234567890123456789'
91 91 >
92 92 > revs = opts['rev']
93 93 > if 'rev' in opts:
94 94 > revs = scmutil.revrange(repo, opts['rev'])
95 95 > if revs:
96 96 > # very crude version of a changegroup part creation
97 97 > bundled = repo.revs('%ld::%ld', revs, revs)
98 98 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
99 99 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
100 100 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
101 101 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
102 102 > bundler.newpart('b2x:changegroup', data=cg.getchunks())
103 103 >
104 104 > if opts['parts']:
105 105 > bundler.newpart('test:empty')
106 106 > # add a second one to make sure we handle multiple parts
107 107 > bundler.newpart('test:empty')
108 108 > bundler.newpart('test:song', data=ELEPHANTSSONG)
109 109 > bundler.newpart('test:debugreply')
110 110 > mathpart = bundler.newpart('test:math')
111 111 > mathpart.addparam('pi', '3.14')
112 112 > mathpart.addparam('e', '2.72')
113 113 > mathpart.addparam('cooking', 'raw', mandatory=False)
114 114 > mathpart.data = '42'
115 115 > # advisory known part with unknown mandatory param
116 116 > bundler.newpart('test:song', [('randomparam','')])
117 117 > if opts['unknown']:
118 118 > bundler.newpart('test:UNKNOWN', data='some random content')
119 119 > if opts['unknownparams']:
120 120 > bundler.newpart('test:SONG', [('randomparams', '')])
121 121 > if opts['parts']:
122 122 > bundler.newpart('test:ping')
123 123 >
124 124 > if path is None:
125 125 > file = sys.stdout
126 126 > else:
127 127 > file = open(path, 'w')
128 128 >
129 129 > for chunk in bundler.getchunks():
130 130 > file.write(chunk)
131 131 >
132 132 > @command('unbundle2', [], '')
133 133 > def cmdunbundle2(ui, repo, replypath=None):
134 134 > """process a bundle2 stream from stdin on the current repo"""
135 135 > try:
136 136 > tr = None
137 137 > lock = repo.lock()
138 138 > tr = repo.transaction('processbundle')
139 139 > try:
140 140 > unbundler = bundle2.unbundle20(ui, sys.stdin)
141 141 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
142 142 > tr.close()
143 143 > except error.BundleValueError, exc:
144 144 > raise util.Abort('missing support for %s' % exc)
145 145 > except error.PushRaced, exc:
146 146 > raise util.Abort('push race: %s' % exc)
147 147 > finally:
148 148 > if tr is not None:
149 149 > tr.release()
150 150 > lock.release()
151 151 > remains = sys.stdin.read()
152 152 > ui.write('%i unread bytes\n' % len(remains))
153 153 > if op.records['song']:
154 154 > totalverses = sum(r['verses'] for r in op.records['song'])
155 155 > ui.write('%i total verses sung\n' % totalverses)
156 156 > for rec in op.records['changegroup']:
157 157 > ui.write('addchangegroup return: %i\n' % rec['return'])
158 158 > if op.reply is not None and replypath is not None:
159 159 > file = open(replypath, 'w')
160 160 > for chunk in op.reply.getchunks():
161 161 > file.write(chunk)
162 162 >
163 163 > @command('statbundle2', [], '')
164 164 > def cmdstatbundle2(ui, repo):
165 165 > """print statistic on the bundle2 container read from stdin"""
166 166 > unbundler = bundle2.unbundle20(ui, sys.stdin)
167 167 > try:
168 168 > params = unbundler.params
169 > except KeyError, exc:
169 > except error.BundleValueError, exc:
170 170 > raise util.Abort('unknown parameters: %s' % exc)
171 171 > ui.write('options count: %i\n' % len(params))
172 172 > for key in sorted(params):
173 173 > ui.write('- %s\n' % key)
174 174 > value = params[key]
175 175 > if value is not None:
176 176 > ui.write(' %s\n' % value)
177 177 > count = 0
178 178 > for p in unbundler.iterparts():
179 179 > count += 1
180 180 > ui.write(' :%s:\n' % p.type)
181 181 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
182 182 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
183 183 > ui.write(' payload: %i bytes\n' % len(p.read()))
184 184 > ui.write('parts count: %i\n' % count)
185 185 > EOF
186 186 $ cat >> $HGRCPATH << EOF
187 187 > [extensions]
188 188 > bundle2=$TESTTMP/bundle2.py
189 189 > [experimental]
190 190 > bundle2-exp=True
191 191 > [ui]
192 192 > ssh=python "$TESTDIR/dummyssh"
193 193 > [web]
194 194 > push_ssl = false
195 195 > allow_push = *
196 196 > EOF
197 197
198 198 The extension requires a repo (currently unused)
199 199
200 200 $ hg init main
201 201 $ cd main
202 202 $ touch a
203 203 $ hg add a
204 204 $ hg commit -m 'a'
205 205
206 206
207 207 Empty bundle
208 208 =================
209 209
210 210 - no option
211 211 - no parts
212 212
213 213 Test bundling
214 214
215 215 $ hg bundle2
216 216 HG2X\x00\x00\x00\x00 (no-eol) (esc)
217 217
218 218 Test unbundling
219 219
220 220 $ hg bundle2 | hg statbundle2
221 221 options count: 0
222 222 parts count: 0
223 223
224 224 Test old style bundle are detected and refused
225 225
226 226 $ hg bundle --all ../bundle.hg
227 227 1 changesets found
228 228 $ hg statbundle2 < ../bundle.hg
229 229 abort: unknown bundle version 10
230 230 [255]
231 231
232 232 Test parameters
233 233 =================
234 234
235 235 - some options
236 236 - no parts
237 237
238 238 advisory parameters, no value
239 239 -------------------------------
240 240
241 241 Simplest possible parameters form
242 242
243 243 Test generation simple option
244 244
245 245 $ hg bundle2 --param 'caution'
246 246 HG2X\x00\x07caution\x00\x00 (no-eol) (esc)
247 247
248 248 Test unbundling
249 249
250 250 $ hg bundle2 --param 'caution' | hg statbundle2
251 251 options count: 1
252 252 - caution
253 253 parts count: 0
254 254
255 255 Test generation multiple option
256 256
257 257 $ hg bundle2 --param 'caution' --param 'meal'
258 258 HG2X\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
259 259
260 260 Test unbundling
261 261
262 262 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
263 263 options count: 2
264 264 - caution
265 265 - meal
266 266 parts count: 0
267 267
268 268 advisory parameters, with value
269 269 -------------------------------
270 270
271 271 Test generation
272 272
273 273 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
274 274 HG2X\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
275 275
276 276 Test unbundling
277 277
278 278 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
279 279 options count: 3
280 280 - caution
281 281 - elephants
282 282 - meal
283 283 vegan
284 284 parts count: 0
285 285
286 286 parameter with special char in value
287 287 ---------------------------------------------------
288 288
289 289 Test generation
290 290
291 291 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
292 292 HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
293 293
294 294 Test unbundling
295 295
296 296 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
297 297 options count: 2
298 298 - e|! 7/
299 299 babar%#==tutu
300 300 - simple
301 301 parts count: 0
302 302
303 303 Test unknown mandatory option
304 304 ---------------------------------------------------
305 305
306 306 $ hg bundle2 --param 'Gravity' | hg statbundle2
307 abort: unknown parameters: 'Gravity'
307 abort: unknown parameters: Stream Parameter - Gravity
308 308 [255]
309 309
310 310 Test debug output
311 311 ---------------------------------------------------
312 312
313 313 bundling debug
314 314
315 315 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
316 316 start emission of HG2X stream
317 317 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
318 318 start of parts
319 319 end of bundle
320 320
321 321 file content is ok
322 322
323 323 $ cat ../out.hg2
324 324 HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
325 325
326 326 unbundling debug
327 327
328 328 $ hg statbundle2 --debug < ../out.hg2
329 329 start processing of HG2X stream
330 330 reading bundle2 stream parameters
331 331 ignoring unknown parameter 'e|! 7/'
332 332 ignoring unknown parameter 'simple'
333 333 options count: 2
334 334 - e|! 7/
335 335 babar%#==tutu
336 336 - simple
337 337 start extraction of bundle2 parts
338 338 part header size: 0
339 339 end of bundle2 stream
340 340 parts count: 0
341 341
342 342
343 343 Test buggy input
344 344 ---------------------------------------------------
345 345
346 346 empty parameter name
347 347
348 348 $ hg bundle2 --param '' --quiet
349 349 abort: empty parameter name
350 350 [255]
351 351
352 352 bad parameter name
353 353
354 354 $ hg bundle2 --param 42babar
355 355 abort: non letter first character: '42babar'
356 356 [255]
357 357
358 358
359 359 Test part
360 360 =================
361 361
362 362 $ hg bundle2 --parts ../parts.hg2 --debug
363 363 start emission of HG2X stream
364 364 bundle parameter:
365 365 start of parts
366 366 bundle part: "test:empty"
367 367 bundle part: "test:empty"
368 368 bundle part: "test:song"
369 369 bundle part: "test:debugreply"
370 370 bundle part: "test:math"
371 371 bundle part: "test:song"
372 372 bundle part: "test:ping"
373 373 end of bundle
374 374
375 375 $ cat ../parts.hg2
376 376 HG2X\x00\x00\x00\x11 (esc)
377 377 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
378 378 test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x10 test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
379 379 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
380 380 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\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\x1d test:song\x00\x00\x00\x05\x01\x00\x0b\x00randomparam\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
381 381
382 382
383 383 $ hg statbundle2 < ../parts.hg2
384 384 options count: 0
385 385 :test:empty:
386 386 mandatory: 0
387 387 advisory: 0
388 388 payload: 0 bytes
389 389 :test:empty:
390 390 mandatory: 0
391 391 advisory: 0
392 392 payload: 0 bytes
393 393 :test:song:
394 394 mandatory: 0
395 395 advisory: 0
396 396 payload: 178 bytes
397 397 :test:debugreply:
398 398 mandatory: 0
399 399 advisory: 0
400 400 payload: 0 bytes
401 401 :test:math:
402 402 mandatory: 2
403 403 advisory: 1
404 404 payload: 2 bytes
405 405 :test:song:
406 406 mandatory: 1
407 407 advisory: 0
408 408 payload: 0 bytes
409 409 :test:ping:
410 410 mandatory: 0
411 411 advisory: 0
412 412 payload: 0 bytes
413 413 parts count: 7
414 414
415 415 $ hg statbundle2 --debug < ../parts.hg2
416 416 start processing of HG2X stream
417 417 reading bundle2 stream parameters
418 418 options count: 0
419 419 start extraction of bundle2 parts
420 420 part header size: 17
421 421 part type: "test:empty"
422 422 part id: "0"
423 423 part parameters: 0
424 424 :test:empty:
425 425 mandatory: 0
426 426 advisory: 0
427 427 payload chunk size: 0
428 428 payload: 0 bytes
429 429 part header size: 17
430 430 part type: "test:empty"
431 431 part id: "1"
432 432 part parameters: 0
433 433 :test:empty:
434 434 mandatory: 0
435 435 advisory: 0
436 436 payload chunk size: 0
437 437 payload: 0 bytes
438 438 part header size: 16
439 439 part type: "test:song"
440 440 part id: "2"
441 441 part parameters: 0
442 442 :test:song:
443 443 mandatory: 0
444 444 advisory: 0
445 445 payload chunk size: 178
446 446 payload chunk size: 0
447 447 payload: 178 bytes
448 448 part header size: 22
449 449 part type: "test:debugreply"
450 450 part id: "3"
451 451 part parameters: 0
452 452 :test:debugreply:
453 453 mandatory: 0
454 454 advisory: 0
455 455 payload chunk size: 0
456 456 payload: 0 bytes
457 457 part header size: 43
458 458 part type: "test:math"
459 459 part id: "4"
460 460 part parameters: 3
461 461 :test:math:
462 462 mandatory: 2
463 463 advisory: 1
464 464 payload chunk size: 2
465 465 payload chunk size: 0
466 466 payload: 2 bytes
467 467 part header size: 29
468 468 part type: "test:song"
469 469 part id: "5"
470 470 part parameters: 1
471 471 :test:song:
472 472 mandatory: 1
473 473 advisory: 0
474 474 payload chunk size: 0
475 475 payload: 0 bytes
476 476 part header size: 16
477 477 part type: "test:ping"
478 478 part id: "6"
479 479 part parameters: 0
480 480 :test:ping:
481 481 mandatory: 0
482 482 advisory: 0
483 483 payload chunk size: 0
484 484 payload: 0 bytes
485 485 part header size: 0
486 486 end of bundle2 stream
487 487 parts count: 7
488 488
489 489 Test actual unbundling of test part
490 490 =======================================
491 491
492 492 Process the bundle
493 493
494 494 $ hg unbundle2 --debug < ../parts.hg2
495 495 start processing of HG2X stream
496 496 reading bundle2 stream parameters
497 497 start extraction of bundle2 parts
498 498 part header size: 17
499 499 part type: "test:empty"
500 500 part id: "0"
501 501 part parameters: 0
502 502 ignoring unsupported advisory part test:empty
503 503 payload chunk size: 0
504 504 part header size: 17
505 505 part type: "test:empty"
506 506 part id: "1"
507 507 part parameters: 0
508 508 ignoring unsupported advisory part test:empty
509 509 payload chunk size: 0
510 510 part header size: 16
511 511 part type: "test:song"
512 512 part id: "2"
513 513 part parameters: 0
514 514 found a handler for part 'test:song'
515 515 The choir starts singing:
516 516 payload chunk size: 178
517 517 payload chunk size: 0
518 518 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
519 519 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
520 520 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
521 521 part header size: 22
522 522 part type: "test:debugreply"
523 523 part id: "3"
524 524 part parameters: 0
525 525 found a handler for part 'test:debugreply'
526 526 debugreply: no reply
527 527 payload chunk size: 0
528 528 part header size: 43
529 529 part type: "test:math"
530 530 part id: "4"
531 531 part parameters: 3
532 532 ignoring unsupported advisory part test:math
533 533 payload chunk size: 2
534 534 payload chunk size: 0
535 535 part header size: 29
536 536 part type: "test:song"
537 537 part id: "5"
538 538 part parameters: 1
539 539 found a handler for part 'test:song'
540 540 ignoring unsupported advisory part test:song - randomparam
541 541 payload chunk size: 0
542 542 part header size: 16
543 543 part type: "test:ping"
544 544 part id: "6"
545 545 part parameters: 0
546 546 found a handler for part 'test:ping'
547 547 received ping request (id 6)
548 548 payload chunk size: 0
549 549 part header size: 0
550 550 end of bundle2 stream
551 551 0 unread bytes
552 552 3 total verses sung
553 553
554 554 Unbundle with an unknown mandatory part
555 555 (should abort)
556 556
557 557 $ hg bundle2 --parts --unknown ../unknown.hg2
558 558
559 559 $ hg unbundle2 < ../unknown.hg2
560 560 The choir starts singing:
561 561 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
562 562 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
563 563 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
564 564 debugreply: no reply
565 565 0 unread bytes
566 566 abort: missing support for test:unknown
567 567 [255]
568 568
569 569 Unbundle with an unknown mandatory part parameters
570 570 (should abort)
571 571
572 572 $ hg bundle2 --unknownparams ../unknown.hg2
573 573
574 574 $ hg unbundle2 < ../unknown.hg2
575 575 0 unread bytes
576 576 abort: missing support for test:song - randomparams
577 577 [255]
578 578
579 579 unbundle with a reply
580 580
581 581 $ hg bundle2 --parts --reply ../parts-reply.hg2
582 582 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
583 583 0 unread bytes
584 584 3 total verses sung
585 585
586 586 The reply is a bundle
587 587
588 588 $ cat ../reply.hg2
589 589 HG2X\x00\x00\x00\x1f (esc)
590 590 b2x:output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
591 591 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
592 592 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
593 593 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
594 594 \x00\x00\x00\x00\x00\x1f (esc)
595 595 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
596 596 debugreply: 'city=!'
597 597 debugreply: 'celeste,ville'
598 598 debugreply: 'elephants'
599 599 debugreply: 'babar'
600 600 debugreply: 'celeste'
601 601 debugreply: 'ping-pong'
602 602 \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to7\x00\x00\x00\x00\x00\x1f (esc)
603 603 b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to7\x00\x00\x00=received ping request (id 7) (esc)
604 604 replying to ping request (id 7)
605 605 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
606 606
607 607 The reply is valid
608 608
609 609 $ hg statbundle2 < ../reply.hg2
610 610 options count: 0
611 611 :b2x:output:
612 612 mandatory: 0
613 613 advisory: 1
614 614 payload: 217 bytes
615 615 :b2x:output:
616 616 mandatory: 0
617 617 advisory: 1
618 618 payload: 201 bytes
619 619 :test:pong:
620 620 mandatory: 1
621 621 advisory: 0
622 622 payload: 0 bytes
623 623 :b2x:output:
624 624 mandatory: 0
625 625 advisory: 1
626 626 payload: 61 bytes
627 627 parts count: 4
628 628
629 629 Unbundle the reply to get the output:
630 630
631 631 $ hg unbundle2 < ../reply.hg2
632 632 remote: The choir starts singing:
633 633 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
634 634 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
635 635 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
636 636 remote: debugreply: capabilities:
637 637 remote: debugreply: 'city=!'
638 638 remote: debugreply: 'celeste,ville'
639 639 remote: debugreply: 'elephants'
640 640 remote: debugreply: 'babar'
641 641 remote: debugreply: 'celeste'
642 642 remote: debugreply: 'ping-pong'
643 643 remote: received ping request (id 7)
644 644 remote: replying to ping request (id 7)
645 645 0 unread bytes
646 646
647 647 Test push race detection
648 648
649 649 $ hg bundle2 --pushrace ../part-race.hg2
650 650
651 651 $ hg unbundle2 < ../part-race.hg2
652 652 0 unread bytes
653 653 abort: push race: repository changed while pushing - please try again
654 654 [255]
655 655
656 656 Support for changegroup
657 657 ===================================
658 658
659 659 $ hg unbundle $TESTDIR/bundles/rebase.hg
660 660 adding changesets
661 661 adding manifests
662 662 adding file changes
663 663 added 8 changesets with 7 changes to 7 files (+3 heads)
664 664 (run 'hg heads' to see heads, 'hg merge' to merge)
665 665
666 666 $ hg log -G
667 667 o changeset: 8:02de42196ebe
668 668 | tag: tip
669 669 | parent: 6:24b6387c8c8c
670 670 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
671 671 | date: Sat Apr 30 15:24:48 2011 +0200
672 672 | summary: H
673 673 |
674 674 | o changeset: 7:eea13746799a
675 675 |/| parent: 6:24b6387c8c8c
676 676 | | parent: 5:9520eea781bc
677 677 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
678 678 | | date: Sat Apr 30 15:24:48 2011 +0200
679 679 | | summary: G
680 680 | |
681 681 o | changeset: 6:24b6387c8c8c
682 682 | | parent: 1:cd010b8cd998
683 683 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
684 684 | | date: Sat Apr 30 15:24:48 2011 +0200
685 685 | | summary: F
686 686 | |
687 687 | o changeset: 5:9520eea781bc
688 688 |/ parent: 1:cd010b8cd998
689 689 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
690 690 | date: Sat Apr 30 15:24:48 2011 +0200
691 691 | summary: E
692 692 |
693 693 | o changeset: 4:32af7686d403
694 694 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
695 695 | | date: Sat Apr 30 15:24:48 2011 +0200
696 696 | | summary: D
697 697 | |
698 698 | o changeset: 3:5fddd98957c8
699 699 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
700 700 | | date: Sat Apr 30 15:24:48 2011 +0200
701 701 | | summary: C
702 702 | |
703 703 | o changeset: 2:42ccdea3bb16
704 704 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
705 705 | date: Sat Apr 30 15:24:48 2011 +0200
706 706 | summary: B
707 707 |
708 708 o changeset: 1:cd010b8cd998
709 709 parent: -1:000000000000
710 710 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
711 711 date: Sat Apr 30 15:24:48 2011 +0200
712 712 summary: A
713 713
714 714 @ changeset: 0:3903775176ed
715 715 user: test
716 716 date: Thu Jan 01 00:00:00 1970 +0000
717 717 summary: a
718 718
719 719
720 720 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
721 721 4 changesets found
722 722 list of changesets:
723 723 32af7686d403cf45b5d95f2d70cebea587ac806a
724 724 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
725 725 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
726 726 02de42196ebee42ef284b6780a87cdc96e8eaab6
727 727 start emission of HG2X stream
728 728 bundle parameter:
729 729 start of parts
730 730 bundle part: "b2x:changegroup"
731 731 bundling: 1/4 changesets (25.00%)
732 732 bundling: 2/4 changesets (50.00%)
733 733 bundling: 3/4 changesets (75.00%)
734 734 bundling: 4/4 changesets (100.00%)
735 735 bundling: 1/4 manifests (25.00%)
736 736 bundling: 2/4 manifests (50.00%)
737 737 bundling: 3/4 manifests (75.00%)
738 738 bundling: 4/4 manifests (100.00%)
739 739 bundling: D 1/3 files (33.33%)
740 740 bundling: E 2/3 files (66.67%)
741 741 bundling: H 3/3 files (100.00%)
742 742 end of bundle
743 743
744 744 $ cat ../rev.hg2
745 745 HG2X\x00\x00\x00\x16\x0fb2x:changegroup\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)
746 746 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
747 747 \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)
748 748 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
749 749 \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)
750 750 \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)
751 751 \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)
752 752 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
753 753 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
754 754 \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)
755 755 \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)
756 756 \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)
757 757 \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)
758 758 \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)
759 759 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
760 760 \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)
761 761 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
762 762 l\r (no-eol) (esc)
763 763 \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)
764 764 \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)
765 765 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
766 766 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
767 767
768 768 $ hg unbundle2 < ../rev.hg2
769 769 adding changesets
770 770 adding manifests
771 771 adding file changes
772 772 added 0 changesets with 0 changes to 3 files
773 773 0 unread bytes
774 774 addchangegroup return: 1
775 775
776 776 with reply
777 777
778 778 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
779 779 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
780 780 0 unread bytes
781 781 addchangegroup return: 1
782 782
783 783 $ cat ../rev-reply.hg2
784 784 HG2X\x00\x00\x003\x15b2x:reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1f (esc)
785 785 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
786 786 adding manifests
787 787 adding file changes
788 788 added 0 changesets with 0 changes to 3 files
789 789 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
790 790
791 791 Real world exchange
792 792 =====================
793 793
794 794
795 795 clone --pull
796 796
797 797 $ cd ..
798 798 $ hg clone main other --pull --rev 9520eea781bc
799 799 adding changesets
800 800 adding manifests
801 801 adding file changes
802 802 added 2 changesets with 2 changes to 2 files
803 803 updating to branch default
804 804 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
805 805 $ hg -R other log -G
806 806 @ changeset: 1:9520eea781bc
807 807 | tag: tip
808 808 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
809 809 | date: Sat Apr 30 15:24:48 2011 +0200
810 810 | summary: E
811 811 |
812 812 o changeset: 0:cd010b8cd998
813 813 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
814 814 date: Sat Apr 30 15:24:48 2011 +0200
815 815 summary: A
816 816
817 817
818 818 pull
819 819
820 820 $ hg -R other pull -r 24b6387c8c8c
821 821 pulling from $TESTTMP/main (glob)
822 822 searching for changes
823 823 adding changesets
824 824 adding manifests
825 825 adding file changes
826 826 added 1 changesets with 1 changes to 1 files (+1 heads)
827 827 (run 'hg heads' to see heads, 'hg merge' to merge)
828 828
829 829 pull empty
830 830
831 831 $ hg -R other pull -r 24b6387c8c8c
832 832 pulling from $TESTTMP/main (glob)
833 833 no changes found
834 834
835 835 push
836 836
837 837 $ hg -R main push other --rev eea13746799a
838 838 pushing to other
839 839 searching for changes
840 840 remote: adding changesets
841 841 remote: adding manifests
842 842 remote: adding file changes
843 843 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
844 844
845 845 pull over ssh
846 846
847 847 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
848 848 pulling from ssh://user@dummy/main
849 849 searching for changes
850 850 adding changesets
851 851 adding manifests
852 852 adding file changes
853 853 added 1 changesets with 1 changes to 1 files (+1 heads)
854 854 (run 'hg heads' to see heads, 'hg merge' to merge)
855 855
856 856 pull over http
857 857
858 858 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
859 859 $ cat main.pid >> $DAEMON_PIDS
860 860
861 861 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
862 862 pulling from http://localhost:$HGPORT/
863 863 searching for changes
864 864 adding changesets
865 865 adding manifests
866 866 adding file changes
867 867 added 1 changesets with 1 changes to 1 files (+1 heads)
868 868 (run 'hg heads .' to see heads, 'hg merge' to merge)
869 869 $ cat main-error.log
870 870
871 871 push over ssh
872 872
873 873 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
874 874 pushing to ssh://user@dummy/other
875 875 searching for changes
876 876 remote: adding changesets
877 877 remote: adding manifests
878 878 remote: adding file changes
879 879 remote: added 1 changesets with 1 changes to 1 files
880 880
881 881 push over http
882 882
883 883 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
884 884 $ cat other.pid >> $DAEMON_PIDS
885 885
886 886 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
887 887 pushing to http://localhost:$HGPORT2/
888 888 searching for changes
889 889 remote: adding changesets
890 890 remote: adding manifests
891 891 remote: adding file changes
892 892 remote: added 1 changesets with 1 changes to 1 files
893 893 $ cat other-error.log
894 894
895 895 Check final content.
896 896
897 897 $ hg -R other log -G
898 898 o changeset: 7:32af7686d403
899 899 | tag: tip
900 900 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
901 901 | date: Sat Apr 30 15:24:48 2011 +0200
902 902 | summary: D
903 903 |
904 904 o changeset: 6:5fddd98957c8
905 905 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
906 906 | date: Sat Apr 30 15:24:48 2011 +0200
907 907 | summary: C
908 908 |
909 909 o changeset: 5:42ccdea3bb16
910 910 | parent: 0:cd010b8cd998
911 911 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
912 912 | date: Sat Apr 30 15:24:48 2011 +0200
913 913 | summary: B
914 914 |
915 915 | o changeset: 4:02de42196ebe
916 916 | | parent: 2:24b6387c8c8c
917 917 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
918 918 | | date: Sat Apr 30 15:24:48 2011 +0200
919 919 | | summary: H
920 920 | |
921 921 | | o changeset: 3:eea13746799a
922 922 | |/| parent: 2:24b6387c8c8c
923 923 | | | parent: 1:9520eea781bc
924 924 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
925 925 | | | date: Sat Apr 30 15:24:48 2011 +0200
926 926 | | | summary: G
927 927 | | |
928 928 | o | changeset: 2:24b6387c8c8c
929 929 |/ / parent: 0:cd010b8cd998
930 930 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
931 931 | | date: Sat Apr 30 15:24:48 2011 +0200
932 932 | | summary: F
933 933 | |
934 934 | @ changeset: 1:9520eea781bc
935 935 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
936 936 | date: Sat Apr 30 15:24:48 2011 +0200
937 937 | summary: E
938 938 |
939 939 o changeset: 0:cd010b8cd998
940 940 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
941 941 date: Sat Apr 30 15:24:48 2011 +0200
942 942 summary: A
943 943
944 944
945 945 Error Handling
946 946 ==============
947 947
948 948 Check that errors are properly returned to the client during push.
949 949
950 950 Setting up
951 951
952 952 $ cat > failpush.py << EOF
953 953 > """A small extension that makes push fails when using bundle2
954 954 >
955 955 > used to test error handling in bundle2
956 956 > """
957 957 >
958 958 > from mercurial import util
959 959 > from mercurial import bundle2
960 960 > from mercurial import exchange
961 961 > from mercurial import extensions
962 962 >
963 963 > def _pushbundle2failpart(orig, pushop, bundler):
964 964 > extradata = orig(pushop, bundler)
965 965 > reason = pushop.ui.config('failpush', 'reason', None)
966 966 > part = None
967 967 > if reason == 'abort':
968 968 > bundler.newpart('test:abort')
969 969 > if reason == 'unknown':
970 970 > bundler.newpart('TEST:UNKNOWN')
971 971 > if reason == 'race':
972 972 > # 20 Bytes of crap
973 973 > bundler.newpart('b2x:check:heads', data='01234567890123456789')
974 974 > return extradata
975 975 >
976 976 > @bundle2.parthandler("test:abort")
977 977 > def handleabort(op, part):
978 978 > raise util.Abort('Abandon ship!', hint="don't panic")
979 979 >
980 980 > def uisetup(ui):
981 981 > extensions.wrapfunction(exchange, '_pushbundle2extraparts', _pushbundle2failpart)
982 982 >
983 983 > EOF
984 984
985 985 $ cd main
986 986 $ hg up tip
987 987 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
988 988 $ echo 'I' > I
989 989 $ hg add I
990 990 $ hg ci -m 'I'
991 991 $ hg id
992 992 e7ec4e813ba6 tip
993 993 $ cd ..
994 994
995 995 $ cat << EOF >> $HGRCPATH
996 996 > [extensions]
997 997 > failpush=$TESTTMP/failpush.py
998 998 > EOF
999 999
1000 1000 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
1001 1001 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
1002 1002 $ cat other.pid >> $DAEMON_PIDS
1003 1003
1004 1004 Doing the actual push: Abort error
1005 1005
1006 1006 $ cat << EOF >> $HGRCPATH
1007 1007 > [failpush]
1008 1008 > reason = abort
1009 1009 > EOF
1010 1010
1011 1011 $ hg -R main push other -r e7ec4e813ba6
1012 1012 pushing to other
1013 1013 searching for changes
1014 1014 abort: Abandon ship!
1015 1015 (don't panic)
1016 1016 [255]
1017 1017
1018 1018 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1019 1019 pushing to ssh://user@dummy/other
1020 1020 searching for changes
1021 1021 abort: Abandon ship!
1022 1022 (don't panic)
1023 1023 [255]
1024 1024
1025 1025 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1026 1026 pushing to http://localhost:$HGPORT2/
1027 1027 searching for changes
1028 1028 abort: Abandon ship!
1029 1029 (don't panic)
1030 1030 [255]
1031 1031
1032 1032
1033 1033 Doing the actual push: unknown mandatory parts
1034 1034
1035 1035 $ cat << EOF >> $HGRCPATH
1036 1036 > [failpush]
1037 1037 > reason = unknown
1038 1038 > EOF
1039 1039
1040 1040 $ hg -R main push other -r e7ec4e813ba6
1041 1041 pushing to other
1042 1042 searching for changes
1043 1043 abort: missing support for test:unknown
1044 1044 [255]
1045 1045
1046 1046 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1047 1047 pushing to ssh://user@dummy/other
1048 1048 searching for changes
1049 1049 abort: missing support for test:unknown
1050 1050 [255]
1051 1051
1052 1052 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1053 1053 pushing to http://localhost:$HGPORT2/
1054 1054 searching for changes
1055 1055 abort: missing support for test:unknown
1056 1056 [255]
1057 1057
1058 1058 Doing the actual push: race
1059 1059
1060 1060 $ cat << EOF >> $HGRCPATH
1061 1061 > [failpush]
1062 1062 > reason = race
1063 1063 > EOF
1064 1064
1065 1065 $ hg -R main push other -r e7ec4e813ba6
1066 1066 pushing to other
1067 1067 searching for changes
1068 1068 abort: push failed:
1069 1069 'repository changed while pushing - please try again'
1070 1070 [255]
1071 1071
1072 1072 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1073 1073 pushing to ssh://user@dummy/other
1074 1074 searching for changes
1075 1075 abort: push failed:
1076 1076 'repository changed while pushing - please try again'
1077 1077 [255]
1078 1078
1079 1079 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1080 1080 pushing to http://localhost:$HGPORT2/
1081 1081 searching for changes
1082 1082 abort: push failed:
1083 1083 'repository changed while pushing - please try again'
1084 1084 [255]
1085 1085
1086 1086 Doing the actual push: hook abort
1087 1087
1088 1088 $ cat << EOF >> $HGRCPATH
1089 1089 > [failpush]
1090 1090 > reason =
1091 1091 > [hooks]
1092 1092 > b2x-pretransactionclose.failpush = false
1093 1093 > EOF
1094 1094
1095 1095 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
1096 1096 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
1097 1097 $ cat other.pid >> $DAEMON_PIDS
1098 1098
1099 1099 $ hg -R main push other -r e7ec4e813ba6
1100 1100 pushing to other
1101 1101 searching for changes
1102 1102 transaction abort!
1103 1103 rollback completed
1104 1104 abort: b2x-pretransactionclose.failpush hook exited with status 1
1105 1105 [255]
1106 1106
1107 1107 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1108 1108 pushing to ssh://user@dummy/other
1109 1109 searching for changes
1110 1110 abort: b2x-pretransactionclose.failpush hook exited with status 1
1111 1111 remote: transaction abort!
1112 1112 remote: rollback completed
1113 1113 [255]
1114 1114
1115 1115 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1116 1116 pushing to http://localhost:$HGPORT2/
1117 1117 searching for changes
1118 1118 abort: b2x-pretransactionclose.failpush hook exited with status 1
1119 1119 [255]
1120 1120
1121 1121
General Comments 0
You need to be logged in to leave comments. Login now