##// END OF EJS Templates
bundle2: protect capabilities name and values with url quoting...
Pierre-Yves David -
r21137:341a0836 default
parent child Browse files
Show More
@@ -1,718 +1,720 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: (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 :payload:
117 117
118 118 payload is a series of `<chunksize><chunkdata>`.
119 119
120 120 `chunksize` is a 32 bits integer, `chunkdata` are plain bytes (as much as
121 121 `chunksize` says)` The payload part is concluded by a zero size chunk.
122 122
123 123 The current implementation always produces either zero or one chunk.
124 124 This is an implementation limitation that will ultimately be lifted.
125 125
126 126 Bundle processing
127 127 ============================
128 128
129 129 Each part is processed in order using a "part handler". Handler are registered
130 130 for a certain part type.
131 131
132 132 The matching of a part to its handler is case insensitive. The case of the
133 133 part type is used to know if a part is mandatory or advisory. If the Part type
134 134 contains any uppercase char it is considered mandatory. When no handler is
135 135 known for a Mandatory part, the process is aborted and an exception is raised.
136 136 If the part is advisory and no handler is known, the part is ignored. When the
137 137 process is aborted, the full bundle is still read from the stream to keep the
138 138 channel usable. But none of the part read from an abort are processed. In the
139 139 future, dropping the stream may become an option for channel we do not care to
140 140 preserve.
141 141 """
142 142
143 143 import util
144 144 import struct
145 145 import urllib
146 146 import string
147 147
148 148 import changegroup
149 149 from i18n import _
150 150
151 151 _pack = struct.pack
152 152 _unpack = struct.unpack
153 153
154 154 _magicstring = 'HG20'
155 155
156 156 _fstreamparamsize = '>H'
157 157 _fpartheadersize = '>H'
158 158 _fparttypesize = '>B'
159 159 _fpartid = '>I'
160 160 _fpayloadsize = '>I'
161 161 _fpartparamcount = '>BB'
162 162
163 163 preferedchunksize = 4096
164 164
165 165 def _makefpartparamsizes(nbparams):
166 166 """return a struct format to read part parameter sizes
167 167
168 168 The number parameters is variable so we need to build that format
169 169 dynamically.
170 170 """
171 171 return '>'+('BB'*nbparams)
172 172
173 173 parthandlermapping = {}
174 174
175 175 def parthandler(parttype):
176 176 """decorator that register a function as a bundle2 part handler
177 177
178 178 eg::
179 179
180 180 @parthandler('myparttype')
181 181 def myparttypehandler(...):
182 182 '''process a part of type "my part".'''
183 183 ...
184 184 """
185 185 def _decorator(func):
186 186 lparttype = parttype.lower() # enforce lower case matching.
187 187 assert lparttype not in parthandlermapping
188 188 parthandlermapping[lparttype] = func
189 189 return func
190 190 return _decorator
191 191
192 192 class unbundlerecords(object):
193 193 """keep record of what happens during and unbundle
194 194
195 195 New records are added using `records.add('cat', obj)`. Where 'cat' is a
196 196 category of record and obj is an arbitrary object.
197 197
198 198 `records['cat']` will return all entries of this category 'cat'.
199 199
200 200 Iterating on the object itself will yield `('category', obj)` tuples
201 201 for all entries.
202 202
203 203 All iterations happens in chronological order.
204 204 """
205 205
206 206 def __init__(self):
207 207 self._categories = {}
208 208 self._sequences = []
209 209 self._replies = {}
210 210
211 211 def add(self, category, entry, inreplyto=None):
212 212 """add a new record of a given category.
213 213
214 214 The entry can then be retrieved in the list returned by
215 215 self['category']."""
216 216 self._categories.setdefault(category, []).append(entry)
217 217 self._sequences.append((category, entry))
218 218 if inreplyto is not None:
219 219 self.getreplies(inreplyto).add(category, entry)
220 220
221 221 def getreplies(self, partid):
222 222 """get the subrecords that replies to a specific part"""
223 223 return self._replies.setdefault(partid, unbundlerecords())
224 224
225 225 def __getitem__(self, cat):
226 226 return tuple(self._categories.get(cat, ()))
227 227
228 228 def __iter__(self):
229 229 return iter(self._sequences)
230 230
231 231 def __len__(self):
232 232 return len(self._sequences)
233 233
234 234 def __nonzero__(self):
235 235 return bool(self._sequences)
236 236
237 237 class bundleoperation(object):
238 238 """an object that represents a single bundling process
239 239
240 240 Its purpose is to carry unbundle-related objects and states.
241 241
242 242 A new object should be created at the beginning of each bundle processing.
243 243 The object is to be returned by the processing function.
244 244
245 245 The object has very little content now it will ultimately contain:
246 246 * an access to the repo the bundle is applied to,
247 247 * a ui object,
248 248 * a way to retrieve a transaction to add changes to the repo,
249 249 * a way to record the result of processing each part,
250 250 * a way to construct a bundle response when applicable.
251 251 """
252 252
253 253 def __init__(self, repo, transactiongetter):
254 254 self.repo = repo
255 255 self.ui = repo.ui
256 256 self.records = unbundlerecords()
257 257 self.gettransaction = transactiongetter
258 258 self.reply = None
259 259
260 260 class TransactionUnavailable(RuntimeError):
261 261 pass
262 262
263 263 def _notransaction():
264 264 """default method to get a transaction while processing a bundle
265 265
266 266 Raise an exception to highlight the fact that no transaction was expected
267 267 to be created"""
268 268 raise TransactionUnavailable()
269 269
270 270 def processbundle(repo, unbundler, transactiongetter=_notransaction):
271 271 """This function process a bundle, apply effect to/from a repo
272 272
273 273 It iterates over each part then searches for and uses the proper handling
274 274 code to process the part. Parts are processed in order.
275 275
276 276 This is very early version of this function that will be strongly reworked
277 277 before final usage.
278 278
279 279 Unknown Mandatory part will abort the process.
280 280 """
281 281 op = bundleoperation(repo, transactiongetter)
282 282 # todo:
283 283 # - replace this is a init function soon.
284 284 # - exception catching
285 285 unbundler.params
286 286 iterparts = unbundler.iterparts()
287 287 part = None
288 288 try:
289 289 for part in iterparts:
290 290 parttype = part.type
291 291 # part key are matched lower case
292 292 key = parttype.lower()
293 293 try:
294 294 handler = parthandlermapping[key]
295 295 op.ui.debug('found a handler for part %r\n' % parttype)
296 296 except KeyError:
297 297 if key != parttype: # mandatory parts
298 298 # todo:
299 299 # - use a more precise exception
300 300 raise
301 301 op.ui.debug('ignoring unknown advisory part %r\n' % key)
302 302 # consuming the part
303 303 part.read()
304 304 continue
305 305
306 306 # handler is called outside the above try block so that we don't
307 307 # risk catching KeyErrors from anything other than the
308 308 # parthandlermapping lookup (any KeyError raised by handler()
309 309 # itself represents a defect of a different variety).
310 310 output = None
311 311 if op.reply is not None:
312 312 op.ui.pushbuffer(error=True)
313 313 output = ''
314 314 try:
315 315 handler(op, part)
316 316 finally:
317 317 if output is not None:
318 318 output = op.ui.popbuffer()
319 319 if output:
320 320 outpart = bundlepart('output',
321 321 advisoryparams=[('in-reply-to',
322 322 str(part.id))],
323 323 data=output)
324 324 op.reply.addpart(outpart)
325 325 part.read()
326 326 except Exception:
327 327 if part is not None:
328 328 # consume the bundle content
329 329 part.read()
330 330 for part in iterparts:
331 331 # consume the bundle content
332 332 part.read()
333 333 raise
334 334 return op
335 335
336 336 class bundle20(object):
337 337 """represent an outgoing bundle2 container
338 338
339 339 Use the `addparam` method to add stream level parameter. and `addpart` to
340 340 populate it. Then call `getchunks` to retrieve all the binary chunks of
341 341 data that compose the bundle2 container."""
342 342
343 343 def __init__(self, ui, capabilities=()):
344 344 self.ui = ui
345 345 self._params = []
346 346 self._parts = []
347 347 self.capabilities = dict(capabilities)
348 348
349 349 def addparam(self, name, value=None):
350 350 """add a stream level parameter"""
351 351 if not name:
352 352 raise ValueError('empty parameter name')
353 353 if name[0] not in string.letters:
354 354 raise ValueError('non letter first character: %r' % name)
355 355 self._params.append((name, value))
356 356
357 357 def addpart(self, part):
358 358 """add a new part to the bundle2 container
359 359
360 360 Parts contains the actual applicative payload."""
361 361 assert part.id is None
362 362 part.id = len(self._parts) # very cheap counter
363 363 self._parts.append(part)
364 364
365 365 def getchunks(self):
366 366 self.ui.debug('start emission of %s stream\n' % _magicstring)
367 367 yield _magicstring
368 368 param = self._paramchunk()
369 369 self.ui.debug('bundle parameter: %s\n' % param)
370 370 yield _pack(_fstreamparamsize, len(param))
371 371 if param:
372 372 yield param
373 373
374 374 self.ui.debug('start of parts\n')
375 375 for part in self._parts:
376 376 self.ui.debug('bundle part: "%s"\n' % part.type)
377 377 for chunk in part.getchunks():
378 378 yield chunk
379 379 self.ui.debug('end of bundle\n')
380 380 yield '\0\0'
381 381
382 382 def _paramchunk(self):
383 383 """return a encoded version of all stream parameters"""
384 384 blocks = []
385 385 for par, value in self._params:
386 386 par = urllib.quote(par)
387 387 if value is not None:
388 388 value = urllib.quote(value)
389 389 par = '%s=%s' % (par, value)
390 390 blocks.append(par)
391 391 return ' '.join(blocks)
392 392
393 393 class unpackermixin(object):
394 394 """A mixin to extract bytes and struct data from a stream"""
395 395
396 396 def __init__(self, fp):
397 397 self._fp = fp
398 398
399 399 def _unpack(self, format):
400 400 """unpack this struct format from the stream"""
401 401 data = self._readexact(struct.calcsize(format))
402 402 return _unpack(format, data)
403 403
404 404 def _readexact(self, size):
405 405 """read exactly <size> bytes from the stream"""
406 406 return changegroup.readexactly(self._fp, size)
407 407
408 408
409 409 class unbundle20(unpackermixin):
410 410 """interpret a bundle2 stream
411 411
412 412 This class is fed with a binary stream and yields parts through its
413 413 `iterparts` methods."""
414 414
415 415 def __init__(self, ui, fp, header=None):
416 416 """If header is specified, we do not read it out of the stream."""
417 417 self.ui = ui
418 418 super(unbundle20, self).__init__(fp)
419 419 if header is None:
420 420 header = self._readexact(4)
421 421 magic, version = header[0:2], header[2:4]
422 422 if magic != 'HG':
423 423 raise util.Abort(_('not a Mercurial bundle'))
424 424 if version != '20':
425 425 raise util.Abort(_('unknown bundle version %s') % version)
426 426 self.ui.debug('start processing of %s stream\n' % header)
427 427
428 428 @util.propertycache
429 429 def params(self):
430 430 """dictionary of stream level parameters"""
431 431 self.ui.debug('reading bundle2 stream parameters\n')
432 432 params = {}
433 433 paramssize = self._unpack(_fstreamparamsize)[0]
434 434 if paramssize:
435 435 for p in self._readexact(paramssize).split(' '):
436 436 p = p.split('=', 1)
437 437 p = [urllib.unquote(i) for i in p]
438 438 if len(p) < 2:
439 439 p.append(None)
440 440 self._processparam(*p)
441 441 params[p[0]] = p[1]
442 442 return params
443 443
444 444 def _processparam(self, name, value):
445 445 """process a parameter, applying its effect if needed
446 446
447 447 Parameter starting with a lower case letter are advisory and will be
448 448 ignored when unknown. Those starting with an upper case letter are
449 449 mandatory and will this function will raise a KeyError when unknown.
450 450
451 451 Note: no option are currently supported. Any input will be either
452 452 ignored or failing.
453 453 """
454 454 if not name:
455 455 raise ValueError('empty parameter name')
456 456 if name[0] not in string.letters:
457 457 raise ValueError('non letter first character: %r' % name)
458 458 # Some logic will be later added here to try to process the option for
459 459 # a dict of known parameter.
460 460 if name[0].islower():
461 461 self.ui.debug("ignoring unknown parameter %r\n" % name)
462 462 else:
463 463 raise KeyError(name)
464 464
465 465
466 466 def iterparts(self):
467 467 """yield all parts contained in the stream"""
468 468 # make sure param have been loaded
469 469 self.params
470 470 self.ui.debug('start extraction of bundle2 parts\n')
471 471 headerblock = self._readpartheader()
472 472 while headerblock is not None:
473 473 part = unbundlepart(self.ui, headerblock, self._fp)
474 474 yield part
475 475 headerblock = self._readpartheader()
476 476 self.ui.debug('end of bundle2 stream\n')
477 477
478 478 def _readpartheader(self):
479 479 """reads a part header size and return the bytes blob
480 480
481 481 returns None if empty"""
482 482 headersize = self._unpack(_fpartheadersize)[0]
483 483 self.ui.debug('part header size: %i\n' % headersize)
484 484 if headersize:
485 485 return self._readexact(headersize)
486 486 return None
487 487
488 488
489 489 class bundlepart(object):
490 490 """A bundle2 part contains application level payload
491 491
492 492 The part `type` is used to route the part to the application level
493 493 handler.
494 494 """
495 495
496 496 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
497 497 data=''):
498 498 self.id = None
499 499 self.type = parttype
500 500 self.data = data
501 501 self.mandatoryparams = mandatoryparams
502 502 self.advisoryparams = advisoryparams
503 503
504 504 def getchunks(self):
505 505 #### header
506 506 ## parttype
507 507 header = [_pack(_fparttypesize, len(self.type)),
508 508 self.type, _pack(_fpartid, self.id),
509 509 ]
510 510 ## parameters
511 511 # count
512 512 manpar = self.mandatoryparams
513 513 advpar = self.advisoryparams
514 514 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
515 515 # size
516 516 parsizes = []
517 517 for key, value in manpar:
518 518 parsizes.append(len(key))
519 519 parsizes.append(len(value))
520 520 for key, value in advpar:
521 521 parsizes.append(len(key))
522 522 parsizes.append(len(value))
523 523 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
524 524 header.append(paramsizes)
525 525 # key, value
526 526 for key, value in manpar:
527 527 header.append(key)
528 528 header.append(value)
529 529 for key, value in advpar:
530 530 header.append(key)
531 531 header.append(value)
532 532 ## finalize header
533 533 headerchunk = ''.join(header)
534 534 yield _pack(_fpartheadersize, len(headerchunk))
535 535 yield headerchunk
536 536 ## payload
537 537 for chunk in self._payloadchunks():
538 538 yield _pack(_fpayloadsize, len(chunk))
539 539 yield chunk
540 540 # end of payload
541 541 yield _pack(_fpayloadsize, 0)
542 542
543 543 def _payloadchunks(self):
544 544 """yield chunks of a the part payload
545 545
546 546 Exists to handle the different methods to provide data to a part."""
547 547 # we only support fixed size data now.
548 548 # This will be improved in the future.
549 549 if util.safehasattr(self.data, 'next'):
550 550 buff = util.chunkbuffer(self.data)
551 551 chunk = buff.read(preferedchunksize)
552 552 while chunk:
553 553 yield chunk
554 554 chunk = buff.read(preferedchunksize)
555 555 elif len(self.data):
556 556 yield self.data
557 557
558 558 class unbundlepart(unpackermixin):
559 559 """a bundle part read from a bundle"""
560 560
561 561 def __init__(self, ui, header, fp):
562 562 super(unbundlepart, self).__init__(fp)
563 563 self.ui = ui
564 564 # unbundle state attr
565 565 self._headerdata = header
566 566 self._headeroffset = 0
567 567 self._initialized = False
568 568 self.consumed = False
569 569 # part data
570 570 self.id = None
571 571 self.type = None
572 572 self.mandatoryparams = None
573 573 self.advisoryparams = None
574 574 self._payloadstream = None
575 575 self._readheader()
576 576
577 577 def _fromheader(self, size):
578 578 """return the next <size> byte from the header"""
579 579 offset = self._headeroffset
580 580 data = self._headerdata[offset:(offset + size)]
581 581 self._headeroffset = offset + size
582 582 return data
583 583
584 584 def _unpackheader(self, format):
585 585 """read given format from header
586 586
587 587 This automatically compute the size of the format to read."""
588 588 data = self._fromheader(struct.calcsize(format))
589 589 return _unpack(format, data)
590 590
591 591 def _readheader(self):
592 592 """read the header and setup the object"""
593 593 typesize = self._unpackheader(_fparttypesize)[0]
594 594 self.type = self._fromheader(typesize)
595 595 self.ui.debug('part type: "%s"\n' % self.type)
596 596 self.id = self._unpackheader(_fpartid)[0]
597 597 self.ui.debug('part id: "%s"\n' % self.id)
598 598 ## reading parameters
599 599 # param count
600 600 mancount, advcount = self._unpackheader(_fpartparamcount)
601 601 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
602 602 # param size
603 603 fparamsizes = _makefpartparamsizes(mancount + advcount)
604 604 paramsizes = self._unpackheader(fparamsizes)
605 605 # make it a list of couple again
606 606 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
607 607 # split mandatory from advisory
608 608 mansizes = paramsizes[:mancount]
609 609 advsizes = paramsizes[mancount:]
610 610 # retrive param value
611 611 manparams = []
612 612 for key, value in mansizes:
613 613 manparams.append((self._fromheader(key), self._fromheader(value)))
614 614 advparams = []
615 615 for key, value in advsizes:
616 616 advparams.append((self._fromheader(key), self._fromheader(value)))
617 617 self.mandatoryparams = manparams
618 618 self.advisoryparams = advparams
619 619 ## part payload
620 620 def payloadchunks():
621 621 payloadsize = self._unpack(_fpayloadsize)[0]
622 622 self.ui.debug('payload chunk size: %i\n' % payloadsize)
623 623 while payloadsize:
624 624 yield self._readexact(payloadsize)
625 625 payloadsize = self._unpack(_fpayloadsize)[0]
626 626 self.ui.debug('payload chunk size: %i\n' % payloadsize)
627 627 self._payloadstream = util.chunkbuffer(payloadchunks())
628 628 # we read the data, tell it
629 629 self._initialized = True
630 630
631 631 def read(self, size=None):
632 632 """read payload data"""
633 633 if not self._initialized:
634 634 self._readheader()
635 635 if size is None:
636 636 data = self._payloadstream.read()
637 637 else:
638 638 data = self._payloadstream.read(size)
639 639 if size is None or len(data) < size:
640 640 self.consumed = True
641 641 return data
642 642
643 643
644 644 @parthandler('changegroup')
645 645 def handlechangegroup(op, inpart):
646 646 """apply a changegroup part on the repo
647 647
648 648 This is a very early implementation that will massive rework before being
649 649 inflicted to any end-user.
650 650 """
651 651 # Make sure we trigger a transaction creation
652 652 #
653 653 # The addchangegroup function will get a transaction object by itself, but
654 654 # we need to make sure we trigger the creation of a transaction object used
655 655 # for the whole processing scope.
656 656 op.gettransaction()
657 657 cg = changegroup.unbundle10(inpart, 'UN')
658 658 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
659 659 op.records.add('changegroup', {'return': ret})
660 660 if op.reply is not None:
661 661 # This is definitly not the final form of this
662 662 # return. But one need to start somewhere.
663 663 part = bundlepart('reply:changegroup', (),
664 664 [('in-reply-to', str(inpart.id)),
665 665 ('return', '%i' % ret)])
666 666 op.reply.addpart(part)
667 667 assert not inpart.read()
668 668
669 669 @parthandler('reply:changegroup')
670 670 def handlechangegroup(op, inpart):
671 671 p = dict(inpart.advisoryparams)
672 672 ret = int(p['return'])
673 673 op.records.add('changegroup', {'return': ret}, int(p['in-reply-to']))
674 674
675 675 @parthandler('check:heads')
676 676 def handlechangegroup(op, inpart):
677 677 """check that head of the repo did not change
678 678
679 679 This is used to detect a push race when using unbundle.
680 680 This replaces the "heads" argument of unbundle."""
681 681 h = inpart.read(20)
682 682 heads = []
683 683 while len(h) == 20:
684 684 heads.append(h)
685 685 h = inpart.read(20)
686 686 assert not h
687 687 if heads != op.repo.heads():
688 688 raise exchange.PushRaced()
689 689
690 690 @parthandler('output')
691 691 def handleoutput(op, inpart):
692 692 """forward output captured on the server to the client"""
693 693 for line in inpart.read().splitlines():
694 694 op.ui.write(('remote: %s\n' % line))
695 695
696 696 @parthandler('replycaps')
697 697 def handlereplycaps(op, inpart):
698 698 """Notify that a reply bundle should be created
699 699
700 700 The part payload is a list of capabilities (one per line)
701 701 Capabilities may have values using a line of form::
702 702
703 703 capability=value1,value2,value3
704 704
705 705 The value are alway a list."""
706 706 caps = {}
707 707 for line in inpart.read().splitlines():
708 708 if not line:
709 709 continue
710 710 if '=' not in line:
711 711 key, vals = line, ()
712 712 else:
713 713 key, vals = line.split('=', 1)
714 714 vals = vals.split(',')
715 key = urllib.unquote(key)
716 vals = [urllib.unquote(v) for v in vals]
715 717 caps[key] = vals
716 718 if op.reply is None:
717 719 op.reply = bundle20(op.ui, caps)
718 720
@@ -1,881 +1,881 b''
1 1
2 2 Create an extension to test bundle2 API
3 3
4 4 $ cat > bundle2.py << EOF
5 5 > """A small extension to test bundle2 implementation
6 6 >
7 7 > Current bundle2 implementation is far too limited to be used in any core
8 8 > code. We still need to be able to test it while it grow up.
9 9 > """
10 10 >
11 11 > import sys
12 12 > from mercurial import cmdutil
13 13 > from mercurial import util
14 14 > from mercurial import bundle2
15 15 > from mercurial import scmutil
16 16 > from mercurial import discovery
17 17 > from mercurial import changegroup
18 18 > cmdtable = {}
19 19 > command = cmdutil.command(cmdtable)
20 20 >
21 21 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
22 22 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
23 23 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
24 24 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
25 25 >
26 26 > @bundle2.parthandler('test:song')
27 27 > def songhandler(op, part):
28 28 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
29 29 > op.ui.write('The choir starts singing:\n')
30 30 > verses = 0
31 31 > for line in part.read().split('\n'):
32 32 > op.ui.write(' %s\n' % line)
33 33 > verses += 1
34 34 > op.records.add('song', {'verses': verses})
35 35 >
36 36 > @bundle2.parthandler('test:ping')
37 37 > def pinghandler(op, part):
38 38 > op.ui.write('received ping request (id %i)\n' % part.id)
39 39 > if op.reply is not None and 'ping-pong' in op.reply.capabilities:
40 40 > op.ui.write_err('replying to ping request (id %i)\n' % part.id)
41 41 > rpart = bundle2.bundlepart('test:pong',
42 42 > [('in-reply-to', str(part.id))])
43 43 > op.reply.addpart(rpart)
44 44 >
45 45 > @bundle2.parthandler('test:debugreply')
46 46 > def debugreply(op, part):
47 47 > """print data about the capacity of the bundle reply"""
48 48 > if op.reply is None:
49 49 > op.ui.write('debugreply: no reply\n')
50 50 > else:
51 51 > op.ui.write('debugreply: capabilities:\n')
52 52 > for cap in sorted(op.reply.capabilities):
53 53 > op.ui.write('debugreply: %r\n' % cap)
54 54 > for val in op.reply.capabilities[cap]:
55 55 > op.ui.write('debugreply: %r\n' % val)
56 56 >
57 57 > @command('bundle2',
58 58 > [('', 'param', [], 'stream level parameter'),
59 59 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
60 60 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
61 61 > ('', 'reply', False, 'produce a reply bundle'),
62 62 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
63 63 > '[OUTPUTFILE]')
64 64 > def cmdbundle2(ui, repo, path=None, **opts):
65 65 > """write a bundle2 container on standard ouput"""
66 66 > bundler = bundle2.bundle20(ui)
67 67 > for p in opts['param']:
68 68 > p = p.split('=', 1)
69 69 > try:
70 70 > bundler.addparam(*p)
71 71 > except ValueError, exc:
72 72 > raise util.Abort('%s' % exc)
73 73 >
74 74 > if opts['reply']:
75 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity=celesteville'
75 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
76 76 > bundler.addpart(bundle2.bundlepart('replycaps', data=capsstring))
77 77 >
78 78 > revs = opts['rev']
79 79 > if 'rev' in opts:
80 80 > revs = scmutil.revrange(repo, opts['rev'])
81 81 > if revs:
82 82 > # very crude version of a changegroup part creation
83 83 > bundled = repo.revs('%ld::%ld', revs, revs)
84 84 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
85 85 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
86 86 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
87 87 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
88 88 > part = bundle2.bundlepart('changegroup', data=cg.getchunks())
89 89 > bundler.addpart(part)
90 90 >
91 91 > if opts['parts']:
92 92 > part = bundle2.bundlepart('test:empty')
93 93 > bundler.addpart(part)
94 94 > # add a second one to make sure we handle multiple parts
95 95 > part = bundle2.bundlepart('test:empty')
96 96 > bundler.addpart(part)
97 97 > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG)
98 98 > bundler.addpart(part)
99 99 > part = bundle2.bundlepart('test:debugreply')
100 100 > bundler.addpart(part)
101 101 > part = bundle2.bundlepart('test:math',
102 102 > [('pi', '3.14'), ('e', '2.72')],
103 103 > [('cooking', 'raw')],
104 104 > '42')
105 105 > bundler.addpart(part)
106 106 > if opts['unknown']:
107 107 > part = bundle2.bundlepart('test:UNKNOWN',
108 108 > data='some random content')
109 109 > bundler.addpart(part)
110 110 > if opts['parts']:
111 111 > part = bundle2.bundlepart('test:ping')
112 112 > bundler.addpart(part)
113 113 >
114 114 > if path is None:
115 115 > file = sys.stdout
116 116 > else:
117 117 > file = open(path, 'w')
118 118 >
119 119 > for chunk in bundler.getchunks():
120 120 > file.write(chunk)
121 121 >
122 122 > @command('unbundle2', [], '')
123 123 > def cmdunbundle2(ui, repo, replypath=None):
124 124 > """process a bundle2 stream from stdin on the current repo"""
125 125 > try:
126 126 > tr = None
127 127 > lock = repo.lock()
128 128 > tr = repo.transaction('processbundle')
129 129 > try:
130 130 > unbundler = bundle2.unbundle20(ui, sys.stdin)
131 131 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
132 132 > tr.close()
133 133 > except KeyError, exc:
134 134 > raise util.Abort('missing support for %s' % exc)
135 135 > finally:
136 136 > if tr is not None:
137 137 > tr.release()
138 138 > lock.release()
139 139 > remains = sys.stdin.read()
140 140 > ui.write('%i unread bytes\n' % len(remains))
141 141 > if op.records['song']:
142 142 > totalverses = sum(r['verses'] for r in op.records['song'])
143 143 > ui.write('%i total verses sung\n' % totalverses)
144 144 > for rec in op.records['changegroup']:
145 145 > ui.write('addchangegroup return: %i\n' % rec['return'])
146 146 > if op.reply is not None and replypath is not None:
147 147 > file = open(replypath, 'w')
148 148 > for chunk in op.reply.getchunks():
149 149 > file.write(chunk)
150 150 >
151 151 > @command('statbundle2', [], '')
152 152 > def cmdstatbundle2(ui, repo):
153 153 > """print statistic on the bundle2 container read from stdin"""
154 154 > unbundler = bundle2.unbundle20(ui, sys.stdin)
155 155 > try:
156 156 > params = unbundler.params
157 157 > except KeyError, exc:
158 158 > raise util.Abort('unknown parameters: %s' % exc)
159 159 > ui.write('options count: %i\n' % len(params))
160 160 > for key in sorted(params):
161 161 > ui.write('- %s\n' % key)
162 162 > value = params[key]
163 163 > if value is not None:
164 164 > ui.write(' %s\n' % value)
165 165 > count = 0
166 166 > for p in unbundler.iterparts():
167 167 > count += 1
168 168 > ui.write(' :%s:\n' % p.type)
169 169 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
170 170 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
171 171 > ui.write(' payload: %i bytes\n' % len(p.read()))
172 172 > ui.write('parts count: %i\n' % count)
173 173 > EOF
174 174 $ cat >> $HGRCPATH << EOF
175 175 > [extensions]
176 176 > bundle2=$TESTTMP/bundle2.py
177 177 > [server]
178 178 > bundle2=True
179 179 > [ui]
180 180 > ssh=python "$TESTDIR/dummyssh"
181 181 > [web]
182 182 > push_ssl = false
183 183 > allow_push = *
184 184 > EOF
185 185
186 186 The extension requires a repo (currently unused)
187 187
188 188 $ hg init main
189 189 $ cd main
190 190 $ touch a
191 191 $ hg add a
192 192 $ hg commit -m 'a'
193 193
194 194
195 195 Empty bundle
196 196 =================
197 197
198 198 - no option
199 199 - no parts
200 200
201 201 Test bundling
202 202
203 203 $ hg bundle2
204 204 HG20\x00\x00\x00\x00 (no-eol) (esc)
205 205
206 206 Test unbundling
207 207
208 208 $ hg bundle2 | hg statbundle2
209 209 options count: 0
210 210 parts count: 0
211 211
212 212 Test old style bundle are detected and refused
213 213
214 214 $ hg bundle --all ../bundle.hg
215 215 1 changesets found
216 216 $ hg statbundle2 < ../bundle.hg
217 217 abort: unknown bundle version 10
218 218 [255]
219 219
220 220 Test parameters
221 221 =================
222 222
223 223 - some options
224 224 - no parts
225 225
226 226 advisory parameters, no value
227 227 -------------------------------
228 228
229 229 Simplest possible parameters form
230 230
231 231 Test generation simple option
232 232
233 233 $ hg bundle2 --param 'caution'
234 234 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
235 235
236 236 Test unbundling
237 237
238 238 $ hg bundle2 --param 'caution' | hg statbundle2
239 239 options count: 1
240 240 - caution
241 241 parts count: 0
242 242
243 243 Test generation multiple option
244 244
245 245 $ hg bundle2 --param 'caution' --param 'meal'
246 246 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
247 247
248 248 Test unbundling
249 249
250 250 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
251 251 options count: 2
252 252 - caution
253 253 - meal
254 254 parts count: 0
255 255
256 256 advisory parameters, with value
257 257 -------------------------------
258 258
259 259 Test generation
260 260
261 261 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
262 262 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
263 263
264 264 Test unbundling
265 265
266 266 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
267 267 options count: 3
268 268 - caution
269 269 - elephants
270 270 - meal
271 271 vegan
272 272 parts count: 0
273 273
274 274 parameter with special char in value
275 275 ---------------------------------------------------
276 276
277 277 Test generation
278 278
279 279 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
280 280 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
281 281
282 282 Test unbundling
283 283
284 284 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
285 285 options count: 2
286 286 - e|! 7/
287 287 babar%#==tutu
288 288 - simple
289 289 parts count: 0
290 290
291 291 Test unknown mandatory option
292 292 ---------------------------------------------------
293 293
294 294 $ hg bundle2 --param 'Gravity' | hg statbundle2
295 295 abort: unknown parameters: 'Gravity'
296 296 [255]
297 297
298 298 Test debug output
299 299 ---------------------------------------------------
300 300
301 301 bundling debug
302 302
303 303 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
304 304 start emission of HG20 stream
305 305 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
306 306 start of parts
307 307 end of bundle
308 308
309 309 file content is ok
310 310
311 311 $ cat ../out.hg2
312 312 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
313 313
314 314 unbundling debug
315 315
316 316 $ hg statbundle2 --debug < ../out.hg2
317 317 start processing of HG20 stream
318 318 reading bundle2 stream parameters
319 319 ignoring unknown parameter 'e|! 7/'
320 320 ignoring unknown parameter 'simple'
321 321 options count: 2
322 322 - e|! 7/
323 323 babar%#==tutu
324 324 - simple
325 325 start extraction of bundle2 parts
326 326 part header size: 0
327 327 end of bundle2 stream
328 328 parts count: 0
329 329
330 330
331 331 Test buggy input
332 332 ---------------------------------------------------
333 333
334 334 empty parameter name
335 335
336 336 $ hg bundle2 --param '' --quiet
337 337 abort: empty parameter name
338 338 [255]
339 339
340 340 bad parameter name
341 341
342 342 $ hg bundle2 --param 42babar
343 343 abort: non letter first character: '42babar'
344 344 [255]
345 345
346 346
347 347 Test part
348 348 =================
349 349
350 350 $ hg bundle2 --parts ../parts.hg2 --debug
351 351 start emission of HG20 stream
352 352 bundle parameter:
353 353 start of parts
354 354 bundle part: "test:empty"
355 355 bundle part: "test:empty"
356 356 bundle part: "test:song"
357 357 bundle part: "test:debugreply"
358 358 bundle part: "test:math"
359 359 bundle part: "test:ping"
360 360 end of bundle
361 361
362 362 $ cat ../parts.hg2
363 363 HG20\x00\x00\x00\x11 (esc)
364 364 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
365 365 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)
366 366 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
367 367 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\x10 test:ping\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
368 368
369 369
370 370 $ hg statbundle2 < ../parts.hg2
371 371 options count: 0
372 372 :test:empty:
373 373 mandatory: 0
374 374 advisory: 0
375 375 payload: 0 bytes
376 376 :test:empty:
377 377 mandatory: 0
378 378 advisory: 0
379 379 payload: 0 bytes
380 380 :test:song:
381 381 mandatory: 0
382 382 advisory: 0
383 383 payload: 178 bytes
384 384 :test:debugreply:
385 385 mandatory: 0
386 386 advisory: 0
387 387 payload: 0 bytes
388 388 :test:math:
389 389 mandatory: 2
390 390 advisory: 1
391 391 payload: 2 bytes
392 392 :test:ping:
393 393 mandatory: 0
394 394 advisory: 0
395 395 payload: 0 bytes
396 396 parts count: 6
397 397
398 398 $ hg statbundle2 --debug < ../parts.hg2
399 399 start processing of HG20 stream
400 400 reading bundle2 stream parameters
401 401 options count: 0
402 402 start extraction of bundle2 parts
403 403 part header size: 17
404 404 part type: "test:empty"
405 405 part id: "0"
406 406 part parameters: 0
407 407 :test:empty:
408 408 mandatory: 0
409 409 advisory: 0
410 410 payload chunk size: 0
411 411 payload: 0 bytes
412 412 part header size: 17
413 413 part type: "test:empty"
414 414 part id: "1"
415 415 part parameters: 0
416 416 :test:empty:
417 417 mandatory: 0
418 418 advisory: 0
419 419 payload chunk size: 0
420 420 payload: 0 bytes
421 421 part header size: 16
422 422 part type: "test:song"
423 423 part id: "2"
424 424 part parameters: 0
425 425 :test:song:
426 426 mandatory: 0
427 427 advisory: 0
428 428 payload chunk size: 178
429 429 payload chunk size: 0
430 430 payload: 178 bytes
431 431 part header size: 22
432 432 part type: "test:debugreply"
433 433 part id: "3"
434 434 part parameters: 0
435 435 :test:debugreply:
436 436 mandatory: 0
437 437 advisory: 0
438 438 payload chunk size: 0
439 439 payload: 0 bytes
440 440 part header size: 43
441 441 part type: "test:math"
442 442 part id: "4"
443 443 part parameters: 3
444 444 :test:math:
445 445 mandatory: 2
446 446 advisory: 1
447 447 payload chunk size: 2
448 448 payload chunk size: 0
449 449 payload: 2 bytes
450 450 part header size: 16
451 451 part type: "test:ping"
452 452 part id: "5"
453 453 part parameters: 0
454 454 :test:ping:
455 455 mandatory: 0
456 456 advisory: 0
457 457 payload chunk size: 0
458 458 payload: 0 bytes
459 459 part header size: 0
460 460 end of bundle2 stream
461 461 parts count: 6
462 462
463 463 Test actual unbundling of test part
464 464 =======================================
465 465
466 466 Process the bundle
467 467
468 468 $ hg unbundle2 --debug < ../parts.hg2
469 469 start processing of HG20 stream
470 470 reading bundle2 stream parameters
471 471 start extraction of bundle2 parts
472 472 part header size: 17
473 473 part type: "test:empty"
474 474 part id: "0"
475 475 part parameters: 0
476 476 ignoring unknown advisory part 'test:empty'
477 477 payload chunk size: 0
478 478 part header size: 17
479 479 part type: "test:empty"
480 480 part id: "1"
481 481 part parameters: 0
482 482 ignoring unknown advisory part 'test:empty'
483 483 payload chunk size: 0
484 484 part header size: 16
485 485 part type: "test:song"
486 486 part id: "2"
487 487 part parameters: 0
488 488 found a handler for part 'test:song'
489 489 The choir starts singing:
490 490 payload chunk size: 178
491 491 payload chunk size: 0
492 492 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
493 493 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
494 494 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
495 495 part header size: 22
496 496 part type: "test:debugreply"
497 497 part id: "3"
498 498 part parameters: 0
499 499 found a handler for part 'test:debugreply'
500 500 debugreply: no reply
501 501 payload chunk size: 0
502 502 part header size: 43
503 503 part type: "test:math"
504 504 part id: "4"
505 505 part parameters: 3
506 506 ignoring unknown advisory part 'test:math'
507 507 payload chunk size: 2
508 508 payload chunk size: 0
509 509 part header size: 16
510 510 part type: "test:ping"
511 511 part id: "5"
512 512 part parameters: 0
513 513 found a handler for part 'test:ping'
514 514 received ping request (id 5)
515 515 payload chunk size: 0
516 516 part header size: 0
517 517 end of bundle2 stream
518 518 0 unread bytes
519 519 3 total verses sung
520 520
521 521 Unbundle with an unknown mandatory part
522 522 (should abort)
523 523
524 524 $ hg bundle2 --parts --unknown ../unknown.hg2
525 525
526 526 $ hg unbundle2 < ../unknown.hg2
527 527 The choir starts singing:
528 528 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
529 529 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
530 530 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
531 531 debugreply: no reply
532 532 0 unread bytes
533 533 abort: missing support for 'test:unknown'
534 534 [255]
535 535
536 536 unbundle with a reply
537 537
538 538 $ hg bundle2 --parts --reply ../parts-reply.hg2
539 539 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
540 540 0 unread bytes
541 541 3 total verses sung
542 542
543 543 The reply is a bundle
544 544
545 545 $ cat ../reply.hg2
546 546 HG20\x00\x00\x00\x1b\x06output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
547 547 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
548 548 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
549 549 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
550 \x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc6debugreply: capabilities: (esc)
551 debugreply: 'city'
552 debugreply: 'celesteville'
550 \x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
551 debugreply: 'city=!'
552 debugreply: 'celeste,ville'
553 553 debugreply: 'elephants'
554 554 debugreply: 'babar'
555 555 debugreply: 'celeste'
556 556 debugreply: 'ping-pong'
557 557 \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to6\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to6\x00\x00\x00=received ping request (id 6) (esc)
558 558 replying to ping request (id 6)
559 559 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
560 560
561 561 The reply is valid
562 562
563 563 $ hg statbundle2 < ../reply.hg2
564 564 options count: 0
565 565 :output:
566 566 mandatory: 0
567 567 advisory: 1
568 568 payload: 217 bytes
569 569 :output:
570 570 mandatory: 0
571 571 advisory: 1
572 payload: 198 bytes
572 payload: 201 bytes
573 573 :test:pong:
574 574 mandatory: 1
575 575 advisory: 0
576 576 payload: 0 bytes
577 577 :output:
578 578 mandatory: 0
579 579 advisory: 1
580 580 payload: 61 bytes
581 581 parts count: 4
582 582
583 583 Unbundle the reply to get the output:
584 584
585 585 $ hg unbundle2 < ../reply.hg2
586 586 remote: The choir starts singing:
587 587 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
588 588 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
589 589 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
590 590 remote: debugreply: capabilities:
591 remote: debugreply: 'city'
592 remote: debugreply: 'celesteville'
591 remote: debugreply: 'city=!'
592 remote: debugreply: 'celeste,ville'
593 593 remote: debugreply: 'elephants'
594 594 remote: debugreply: 'babar'
595 595 remote: debugreply: 'celeste'
596 596 remote: debugreply: 'ping-pong'
597 597 remote: received ping request (id 6)
598 598 remote: replying to ping request (id 6)
599 599 0 unread bytes
600 600
601 601 Support for changegroup
602 602 ===================================
603 603
604 604 $ hg unbundle $TESTDIR/bundles/rebase.hg
605 605 adding changesets
606 606 adding manifests
607 607 adding file changes
608 608 added 8 changesets with 7 changes to 7 files (+3 heads)
609 609 (run 'hg heads' to see heads, 'hg merge' to merge)
610 610
611 611 $ hg log -G
612 612 o changeset: 8:02de42196ebe
613 613 | tag: tip
614 614 | parent: 6:24b6387c8c8c
615 615 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
616 616 | date: Sat Apr 30 15:24:48 2011 +0200
617 617 | summary: H
618 618 |
619 619 | o changeset: 7:eea13746799a
620 620 |/| parent: 6:24b6387c8c8c
621 621 | | parent: 5:9520eea781bc
622 622 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
623 623 | | date: Sat Apr 30 15:24:48 2011 +0200
624 624 | | summary: G
625 625 | |
626 626 o | changeset: 6:24b6387c8c8c
627 627 | | parent: 1:cd010b8cd998
628 628 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
629 629 | | date: Sat Apr 30 15:24:48 2011 +0200
630 630 | | summary: F
631 631 | |
632 632 | o changeset: 5:9520eea781bc
633 633 |/ parent: 1:cd010b8cd998
634 634 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
635 635 | date: Sat Apr 30 15:24:48 2011 +0200
636 636 | summary: E
637 637 |
638 638 | o changeset: 4:32af7686d403
639 639 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
640 640 | | date: Sat Apr 30 15:24:48 2011 +0200
641 641 | | summary: D
642 642 | |
643 643 | o changeset: 3:5fddd98957c8
644 644 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
645 645 | | date: Sat Apr 30 15:24:48 2011 +0200
646 646 | | summary: C
647 647 | |
648 648 | o changeset: 2:42ccdea3bb16
649 649 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
650 650 | date: Sat Apr 30 15:24:48 2011 +0200
651 651 | summary: B
652 652 |
653 653 o changeset: 1:cd010b8cd998
654 654 parent: -1:000000000000
655 655 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
656 656 date: Sat Apr 30 15:24:48 2011 +0200
657 657 summary: A
658 658
659 659 @ changeset: 0:3903775176ed
660 660 user: test
661 661 date: Thu Jan 01 00:00:00 1970 +0000
662 662 summary: a
663 663
664 664
665 665 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
666 666 4 changesets found
667 667 list of changesets:
668 668 32af7686d403cf45b5d95f2d70cebea587ac806a
669 669 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
670 670 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
671 671 02de42196ebee42ef284b6780a87cdc96e8eaab6
672 672 start emission of HG20 stream
673 673 bundle parameter:
674 674 start of parts
675 675 bundle part: "changegroup"
676 676 bundling: 1/4 changesets (25.00%)
677 677 bundling: 2/4 changesets (50.00%)
678 678 bundling: 3/4 changesets (75.00%)
679 679 bundling: 4/4 changesets (100.00%)
680 680 bundling: 1/4 manifests (25.00%)
681 681 bundling: 2/4 manifests (50.00%)
682 682 bundling: 3/4 manifests (75.00%)
683 683 bundling: 4/4 manifests (100.00%)
684 684 bundling: D 1/3 files (33.33%)
685 685 bundling: E 2/3 files (66.67%)
686 686 bundling: H 3/3 files (100.00%)
687 687 end of bundle
688 688
689 689 $ cat ../rev.hg2
690 690 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
691 691 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
692 692 \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)
693 693 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
694 694 \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)
695 695 \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)
696 696 \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)
697 697 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
698 698 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
699 699 \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)
700 700 \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)
701 701 \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)
702 702 \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)
703 703 \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)
704 704 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
705 705 \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)
706 706 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
707 707 l\r (no-eol) (esc)
708 708 \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)
709 709 \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)
710 710 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
711 711 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
712 712
713 713 $ hg unbundle2 < ../rev.hg2
714 714 adding changesets
715 715 adding manifests
716 716 adding file changes
717 717 added 0 changesets with 0 changes to 3 files
718 718 0 unread bytes
719 719 addchangegroup return: 1
720 720
721 721 with reply
722 722
723 723 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
724 724 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
725 725 0 unread bytes
726 726 addchangegroup return: 1
727 727
728 728 $ cat ../rev-reply.hg2
729 729 HG20\x00\x00\x00/\x11reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
730 730 adding manifests
731 731 adding file changes
732 732 added 0 changesets with 0 changes to 3 files
733 733 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
734 734
735 735 Real world exchange
736 736 =====================
737 737
738 738
739 739 clone --pull
740 740
741 741 $ cd ..
742 742 $ hg clone main other --pull --rev 9520eea781bc
743 743 adding changesets
744 744 adding manifests
745 745 adding file changes
746 746 added 2 changesets with 2 changes to 2 files
747 747 updating to branch default
748 748 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
749 749 $ hg -R other log -G
750 750 @ changeset: 1:9520eea781bc
751 751 | tag: tip
752 752 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
753 753 | date: Sat Apr 30 15:24:48 2011 +0200
754 754 | summary: E
755 755 |
756 756 o changeset: 0:cd010b8cd998
757 757 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
758 758 date: Sat Apr 30 15:24:48 2011 +0200
759 759 summary: A
760 760
761 761
762 762 pull
763 763
764 764 $ hg -R other pull -r 24b6387c8c8c
765 765 pulling from $TESTTMP/main (glob)
766 766 searching for changes
767 767 adding changesets
768 768 adding manifests
769 769 adding file changes
770 770 added 1 changesets with 1 changes to 1 files (+1 heads)
771 771 (run 'hg heads' to see heads, 'hg merge' to merge)
772 772
773 773 push
774 774
775 775 $ hg -R main push other --rev eea13746799a
776 776 pushing to other
777 777 searching for changes
778 778 remote: adding changesets
779 779 remote: adding manifests
780 780 remote: adding file changes
781 781 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
782 782
783 783 pull over ssh
784 784
785 785 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
786 786 pulling from ssh://user@dummy/main
787 787 searching for changes
788 788 adding changesets
789 789 adding manifests
790 790 adding file changes
791 791 added 1 changesets with 1 changes to 1 files (+1 heads)
792 792 (run 'hg heads' to see heads, 'hg merge' to merge)
793 793
794 794 pull over http
795 795
796 796 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
797 797 $ cat main.pid >> $DAEMON_PIDS
798 798
799 799 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
800 800 pulling from http://localhost:$HGPORT/
801 801 searching for changes
802 802 adding changesets
803 803 adding manifests
804 804 adding file changes
805 805 added 1 changesets with 1 changes to 1 files (+1 heads)
806 806 (run 'hg heads .' to see heads, 'hg merge' to merge)
807 807 $ cat main-error.log
808 808
809 809 push over ssh
810 810
811 811 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
812 812 pushing to ssh://user@dummy/other
813 813 searching for changes
814 814 remote: adding changesets
815 815 remote: adding manifests
816 816 remote: adding file changes
817 817 remote: added 1 changesets with 1 changes to 1 files
818 818
819 819 push over http
820 820
821 821 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
822 822 $ cat other.pid >> $DAEMON_PIDS
823 823
824 824 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
825 825 pushing to http://localhost:$HGPORT2/
826 826 searching for changes
827 827 remote: adding changesets
828 828 remote: adding manifests
829 829 remote: adding file changes
830 830 remote: added 1 changesets with 1 changes to 1 files
831 831 $ cat other-error.log
832 832
833 833 Check final content.
834 834
835 835 $ hg -R other log -G
836 836 o changeset: 7:32af7686d403
837 837 | tag: tip
838 838 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
839 839 | date: Sat Apr 30 15:24:48 2011 +0200
840 840 | summary: D
841 841 |
842 842 o changeset: 6:5fddd98957c8
843 843 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
844 844 | date: Sat Apr 30 15:24:48 2011 +0200
845 845 | summary: C
846 846 |
847 847 o changeset: 5:42ccdea3bb16
848 848 | parent: 0:cd010b8cd998
849 849 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
850 850 | date: Sat Apr 30 15:24:48 2011 +0200
851 851 | summary: B
852 852 |
853 853 | o changeset: 4:02de42196ebe
854 854 | | parent: 2:24b6387c8c8c
855 855 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
856 856 | | date: Sat Apr 30 15:24:48 2011 +0200
857 857 | | summary: H
858 858 | |
859 859 | | o changeset: 3:eea13746799a
860 860 | |/| parent: 2:24b6387c8c8c
861 861 | | | parent: 1:9520eea781bc
862 862 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
863 863 | | | date: Sat Apr 30 15:24:48 2011 +0200
864 864 | | | summary: G
865 865 | | |
866 866 | o | changeset: 2:24b6387c8c8c
867 867 |/ / parent: 0:cd010b8cd998
868 868 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
869 869 | | date: Sat Apr 30 15:24:48 2011 +0200
870 870 | | summary: F
871 871 | |
872 872 | @ changeset: 1:9520eea781bc
873 873 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
874 874 | date: Sat Apr 30 15:24:48 2011 +0200
875 875 | summary: E
876 876 |
877 877 o changeset: 0:cd010b8cd998
878 878 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
879 879 date: Sat Apr 30 15:24:48 2011 +0200
880 880 summary: A
881 881
General Comments 0
You need to be logged in to leave comments. Login now