##// END OF EJS Templates
bundle2: record processing results in the bundleoperation object...
Pierre-Yves David -
r20949:571f2903 default
parent child Browse files
Show More
@@ -1,486 +1,526 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. parameter 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 safefly 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 remains simple and we want to discourage any
56 56 crazy usage.
57 57 - Textual data allow easy human inspection of a the 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 :typename: alphanumerical part name
89 89
90 90 :parameters:
91 91
92 92 Part's parameter may have arbitraty content, the binary structure is::
93 93
94 94 <mandatory-count><advisory-count><param-sizes><param-data>
95 95
96 96 :mandatory-count: 1 byte, number of mandatory parameters
97 97
98 98 :advisory-count: 1 byte, number of advisory parameters
99 99
100 100 :param-sizes:
101 101
102 102 N couple of bytes, where N is the total number of parameters. Each
103 103 couple contains (<size-of-key>, <size-of-value) for one parameter.
104 104
105 105 :param-data:
106 106
107 107 A blob of bytes from which each parameter key and value can be
108 108 retrieved using the list of size couples stored in the previous
109 109 field.
110 110
111 111 Mandatory parameters comes first, then the advisory ones.
112 112
113 113 :payload:
114 114
115 115 payload is a series of `<chunksize><chunkdata>`.
116 116
117 117 `chunksize` is a 32 bits integer, `chunkdata` are plain bytes (as much as
118 118 `chunksize` says)` The payload part is concluded by a zero size chunk.
119 119
120 120 The current implementation always produces either zero or one chunk.
121 121 This is an implementation limitation that will ultimatly be lifted.
122 122
123 123 Bundle processing
124 124 ============================
125 125
126 126 Each part is processed in order using a "part handler". Handler are registered
127 127 for a certain part type.
128 128
129 129 The matching of a part to its handler is case insensitive. The case of the
130 130 part type is used to know if a part is mandatory or advisory. If the Part type
131 131 contains any uppercase char it is considered mandatory. When no handler is
132 132 known for a Mandatory part, the process is aborted and an exception is raised.
133 133 If the part is advisory and no handler is known, the part is ignored. When the
134 134 process is aborted, the full bundle is still read from the stream to keep the
135 135 channel usable. But none of the part read from an abort are processed. In the
136 136 future, dropping the stream may become an option for channel we do not care to
137 137 preserve.
138 138 """
139 139
140 140 import util
141 141 import struct
142 142 import urllib
143 143 import string
144 144
145 145 import changegroup
146 146 from i18n import _
147 147
148 148 _pack = struct.pack
149 149 _unpack = struct.unpack
150 150
151 151 _magicstring = 'HG20'
152 152
153 153 _fstreamparamsize = '>H'
154 154 _fpartheadersize = '>H'
155 155 _fparttypesize = '>B'
156 156 _fpayloadsize = '>I'
157 157 _fpartparamcount = '>BB'
158 158
159 159 def _makefpartparamsizes(nbparams):
160 160 """return a struct format to read part parameter sizes
161 161
162 162 The number parameters is variable so we need to build that format
163 163 dynamically.
164 164 """
165 165 return '>'+('BB'*nbparams)
166 166
167 167 parthandlermapping = {}
168 168
169 169 def parthandler(parttype):
170 170 """decorator that register a function as a bundle2 part handler
171 171
172 172 eg::
173 173
174 174 @parthandler('myparttype')
175 175 def myparttypehandler(...):
176 176 '''process a part of type "my part".'''
177 177 ...
178 178 """
179 179 def _decorator(func):
180 180 lparttype = parttype.lower() # enforce lower case matching.
181 181 assert lparttype not in parthandlermapping
182 182 parthandlermapping[lparttype] = func
183 183 return func
184 184 return _decorator
185 185
186 class unbundlerecords(object):
187 """keep record of what happens during and unbundle
188
189 New records are added using `records.add('cat', obj)`. Where 'cat' is a
190 category of record and obj is an arbitraty object.
191
192 `records['cat']` will return all entries of this category 'cat'.
193
194 Iterating on the object itself will yield `('category', obj)` tuples
195 for all entries.
196
197 All iterations happens in chronological order.
198 """
199
200 def __init__(self):
201 self._categories = {}
202 self._sequences = []
203
204 def add(self, category, entry):
205 """add a new record of a given category.
206
207 The entry can then be retrieved in the list returned by
208 self['category']."""
209 self._categories.setdefault(category, []).append(entry)
210 self._sequences.append((category, entry))
211
212 def __getitem__(self, cat):
213 return tuple(self._categories.get(cat, ()))
214
215 def __iter__(self):
216 return iter(self._sequences)
217
218 def __len__(self):
219 return len(self._sequences)
220
221 def __nonzero__(self):
222 return bool(self._sequences)
223
186 224 class bundleoperation(object):
187 225 """an object that represents a single bundling process
188 226
189 227 Its purpose is to carry unbundle-related objects and states.
190 228
191 229 A new object should be created at the beginning of each bundle processing.
192 230 The object is to be returned by the processing function.
193 231
194 232 The object has very little content now it will ultimately contain:
195 233 * an access to the repo the bundle is applied to,
196 234 * a ui object,
197 235 * a way to retrieve a transaction to add changes to the repo,
198 236 * a way to record the result of processing each part,
199 237 * a way to construct a bundle response when applicable.
200 238 """
201 239
202 240 def __init__(self, repo):
203 241 self.repo = repo
204 242 self.ui = repo.ui
243 self.records = unbundlerecords()
205 244
206 245 def processbundle(repo, unbundler):
207 246 """This function process a bundle, apply effect to/from a repo
208 247
209 248 It iterates over each part then searches for and uses the proper handling
210 249 code to process the part. Parts are processed in order.
211 250
212 251 This is very early version of this function that will be strongly reworked
213 252 before final usage.
214 253
215 254 Unknown Mandatory part will abort the process.
216 255 """
217 256 op = bundleoperation(repo)
218 257 # todo:
219 258 # - replace this is a init function soon.
220 259 # - exception catching
221 260 unbundler.params
222 261 iterparts = iter(unbundler)
223 262 try:
224 263 for part in iterparts:
225 264 parttype = part.type
226 265 # part key are matched lower case
227 266 key = parttype.lower()
228 267 try:
229 268 handler = parthandlermapping[key]
230 269 op.ui.debug('found a handler for part %r\n' % parttype)
231 270 except KeyError:
232 271 if key != parttype: # mandatory parts
233 272 # todo:
234 273 # - use a more precise exception
235 274 raise
236 275 op.ui.debug('ignoring unknown advisory part %r\n' % key)
237 276 # todo:
238 277 # - consume the part once we use streaming
239 278 continue
240 279 handler(op, part)
241 280 except Exception:
242 281 for part in iterparts:
243 282 pass # consume the bundle content
244 283 raise
284 return op
245 285
246 286 class bundle20(object):
247 287 """represent an outgoing bundle2 container
248 288
249 289 Use the `addparam` method to add stream level parameter. and `addpart` to
250 290 populate it. Then call `getchunks` to retrieve all the binary chunks of
251 291 datathat compose the bundle2 container."""
252 292
253 293 def __init__(self, ui):
254 294 self.ui = ui
255 295 self._params = []
256 296 self._parts = []
257 297
258 298 def addparam(self, name, value=None):
259 299 """add a stream level parameter"""
260 300 if not name:
261 301 raise ValueError('empty parameter name')
262 302 if name[0] not in string.letters:
263 303 raise ValueError('non letter first character: %r' % name)
264 304 self._params.append((name, value))
265 305
266 306 def addpart(self, part):
267 307 """add a new part to the bundle2 container
268 308
269 309 Parts contains the actuall applicative payload."""
270 310 self._parts.append(part)
271 311
272 312 def getchunks(self):
273 313 self.ui.debug('start emission of %s stream\n' % _magicstring)
274 314 yield _magicstring
275 315 param = self._paramchunk()
276 316 self.ui.debug('bundle parameter: %s\n' % param)
277 317 yield _pack(_fstreamparamsize, len(param))
278 318 if param:
279 319 yield param
280 320
281 321 self.ui.debug('start of parts\n')
282 322 for part in self._parts:
283 323 self.ui.debug('bundle part: "%s"\n' % part.type)
284 324 for chunk in part.getchunks():
285 325 yield chunk
286 326 self.ui.debug('end of bundle\n')
287 327 yield '\0\0'
288 328
289 329 def _paramchunk(self):
290 330 """return a encoded version of all stream parameters"""
291 331 blocks = []
292 332 for par, value in self._params:
293 333 par = urllib.quote(par)
294 334 if value is not None:
295 335 value = urllib.quote(value)
296 336 par = '%s=%s' % (par, value)
297 337 blocks.append(par)
298 338 return ' '.join(blocks)
299 339
300 340 class unbundle20(object):
301 341 """interpret a bundle2 stream
302 342
303 343 (this will eventually yield parts)"""
304 344
305 345 def __init__(self, ui, fp):
306 346 self.ui = ui
307 347 self._fp = fp
308 348 header = self._readexact(4)
309 349 magic, version = header[0:2], header[2:4]
310 350 if magic != 'HG':
311 351 raise util.Abort(_('not a Mercurial bundle'))
312 352 if version != '20':
313 353 raise util.Abort(_('unknown bundle version %s') % version)
314 354 self.ui.debug('start processing of %s stream\n' % header)
315 355
316 356 def _unpack(self, format):
317 357 """unpack this struct format from the stream"""
318 358 data = self._readexact(struct.calcsize(format))
319 359 return _unpack(format, data)
320 360
321 361 def _readexact(self, size):
322 362 """read exactly <size> bytes from the stream"""
323 363 return changegroup.readexactly(self._fp, size)
324 364
325 365 @util.propertycache
326 366 def params(self):
327 367 """dictionnary of stream level parameters"""
328 368 self.ui.debug('reading bundle2 stream parameters\n')
329 369 params = {}
330 370 paramssize = self._unpack(_fstreamparamsize)[0]
331 371 if paramssize:
332 372 for p in self._readexact(paramssize).split(' '):
333 373 p = p.split('=', 1)
334 374 p = [urllib.unquote(i) for i in p]
335 375 if len(p) < 2:
336 376 p.append(None)
337 377 self._processparam(*p)
338 378 params[p[0]] = p[1]
339 379 return params
340 380
341 381 def _processparam(self, name, value):
342 382 """process a parameter, applying its effect if needed
343 383
344 384 Parameter starting with a lower case letter are advisory and will be
345 385 ignored when unknown. Those starting with an upper case letter are
346 386 mandatory and will this function will raise a KeyError when unknown.
347 387
348 388 Note: no option are currently supported. Any input will be either
349 389 ignored or failing.
350 390 """
351 391 if not name:
352 392 raise ValueError('empty parameter name')
353 393 if name[0] not in string.letters:
354 394 raise ValueError('non letter first character: %r' % name)
355 395 # Some logic will be later added here to try to process the option for
356 396 # a dict of known parameter.
357 397 if name[0].islower():
358 398 self.ui.debug("ignoring unknown parameter %r\n" % name)
359 399 else:
360 400 raise KeyError(name)
361 401
362 402
363 403 def __iter__(self):
364 404 """yield all parts contained in the stream"""
365 405 # make sure param have been loaded
366 406 self.params
367 407 self.ui.debug('start extraction of bundle2 parts\n')
368 408 part = self._readpart()
369 409 while part is not None:
370 410 yield part
371 411 part = self._readpart()
372 412 self.ui.debug('end of bundle2 stream\n')
373 413
374 414 def _readpart(self):
375 415 """return None when an end of stream markers is reach"""
376 416
377 417 headersize = self._unpack(_fpartheadersize)[0]
378 418 self.ui.debug('part header size: %i\n' % headersize)
379 419 if not headersize:
380 420 return None
381 421 headerblock = self._readexact(headersize)
382 422 # some utility to help reading from the header block
383 423 self._offset = 0 # layer violation to have something easy to understand
384 424 def fromheader(size):
385 425 """return the next <size> byte from the header"""
386 426 offset = self._offset
387 427 data = headerblock[offset:(offset + size)]
388 428 self._offset = offset + size
389 429 return data
390 430 def unpackheader(format):
391 431 """read given format from header
392 432
393 433 This automatically compute the size of the format to read."""
394 434 data = fromheader(struct.calcsize(format))
395 435 return _unpack(format, data)
396 436
397 437 typesize = unpackheader(_fparttypesize)[0]
398 438 parttype = fromheader(typesize)
399 439 self.ui.debug('part type: "%s"\n' % parttype)
400 440 ## reading parameters
401 441 # param count
402 442 mancount, advcount = unpackheader(_fpartparamcount)
403 443 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
404 444 # param size
405 445 paramsizes = unpackheader(_makefpartparamsizes(mancount + advcount))
406 446 # make it a list of couple again
407 447 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
408 448 # split mandatory from advisory
409 449 mansizes = paramsizes[:mancount]
410 450 advsizes = paramsizes[mancount:]
411 451 # retrive param value
412 452 manparams = []
413 453 for key, value in mansizes:
414 454 manparams.append((fromheader(key), fromheader(value)))
415 455 advparams = []
416 456 for key, value in advsizes:
417 457 advparams.append((fromheader(key), fromheader(value)))
418 458 del self._offset # clean up layer, nobody saw anything.
419 459 ## part payload
420 460 payload = []
421 461 payloadsize = self._unpack(_fpayloadsize)[0]
422 462 self.ui.debug('payload chunk size: %i\n' % payloadsize)
423 463 while payloadsize:
424 464 payload.append(self._readexact(payloadsize))
425 465 payloadsize = self._unpack(_fpayloadsize)[0]
426 466 self.ui.debug('payload chunk size: %i\n' % payloadsize)
427 467 payload = ''.join(payload)
428 468 current = part(parttype, manparams, advparams, data=payload)
429 469 return current
430 470
431 471
432 472 class part(object):
433 473 """A bundle2 part contains application level payload
434 474
435 475 The part `type` is used to route the part to the application level
436 476 handler.
437 477 """
438 478
439 479 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
440 480 data=''):
441 481 self.type = parttype
442 482 self.data = data
443 483 self.mandatoryparams = mandatoryparams
444 484 self.advisoryparams = advisoryparams
445 485
446 486 def getchunks(self):
447 487 #### header
448 488 ## parttype
449 489 header = [_pack(_fparttypesize, len(self.type)),
450 490 self.type,
451 491 ]
452 492 ## parameters
453 493 # count
454 494 manpar = self.mandatoryparams
455 495 advpar = self.advisoryparams
456 496 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
457 497 # size
458 498 parsizes = []
459 499 for key, value in manpar:
460 500 parsizes.append(len(key))
461 501 parsizes.append(len(value))
462 502 for key, value in advpar:
463 503 parsizes.append(len(key))
464 504 parsizes.append(len(value))
465 505 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
466 506 header.append(paramsizes)
467 507 # key, value
468 508 for key, value in manpar:
469 509 header.append(key)
470 510 header.append(value)
471 511 for key, value in advpar:
472 512 header.append(key)
473 513 header.append(value)
474 514 ## finalize header
475 515 headerchunk = ''.join(header)
476 516 yield _pack(_fpartheadersize, len(headerchunk))
477 517 yield headerchunk
478 518 ## payload
479 519 # we only support fixed size data now.
480 520 # This will be improved in the future.
481 521 if len(self.data):
482 522 yield _pack(_fpayloadsize, len(self.data))
483 523 yield self.data
484 524 # end of payload
485 525 yield _pack(_fpayloadsize, 0)
486 526
@@ -1,407 +1,414 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 > cmdtable = {}
16 16 > command = cmdutil.command(cmdtable)
17 17 >
18 18 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
19 19 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
20 20 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
21 21 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
22 22 >
23 23 > @bundle2.parthandler('test:song')
24 24 > def songhandler(op, part):
25 25 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
26 26 > op.ui.write('The choir starts singing:\n')
27 > verses = 0
27 28 > for line in part.data.split('\n'):
28 29 > op.ui.write(' %s\n' % line)
30 > verses += 1
31 > op.records.add('song', {'verses': verses})
29 32 >
30 33 > @command('bundle2',
31 34 > [('', 'param', [], 'stream level parameter'),
32 35 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
33 36 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),],
34 37 > '[OUTPUTFILE]')
35 38 > def cmdbundle2(ui, repo, path=None, **opts):
36 39 > """write a bundle2 container on standard ouput"""
37 40 > bundler = bundle2.bundle20(ui)
38 41 > for p in opts['param']:
39 42 > p = p.split('=', 1)
40 43 > try:
41 44 > bundler.addparam(*p)
42 45 > except ValueError, exc:
43 46 > raise util.Abort('%s' % exc)
44 47 >
45 48 > if opts['parts']:
46 49 > part = bundle2.part('test:empty')
47 50 > bundler.addpart(part)
48 51 > # add a second one to make sure we handle multiple parts
49 52 > part = bundle2.part('test:empty')
50 53 > bundler.addpart(part)
51 54 > part = bundle2.part('test:song', data=ELEPHANTSSONG)
52 55 > bundler.addpart(part)
53 56 > part = bundle2.part('test:math',
54 57 > [('pi', '3.14'), ('e', '2.72')],
55 58 > [('cooking', 'raw')],
56 59 > '42')
57 60 > bundler.addpart(part)
58 61 > if opts['unknown']:
59 62 > part = bundle2.part('test:UNKNOWN',
60 63 > data='some random content')
61 64 > bundler.addpart(part)
62 65 >
63 66 > if path is None:
64 67 > file = sys.stdout
65 68 > else:
66 69 > file = open(path, 'w')
67 70 >
68 71 > for chunk in bundler.getchunks():
69 72 > file.write(chunk)
70 73 >
71 74 > @command('unbundle2', [], '')
72 75 > def cmdunbundle2(ui, repo):
73 76 > """process a bundle2 stream from stdin on the current repo"""
74 77 > try:
75 78 > lock = repo.lock()
76 79 > try:
77 80 > unbundler = bundle2.unbundle20(ui, sys.stdin)
78 > bundle2.processbundle(repo, unbundler)
81 > op = bundle2.processbundle(repo, unbundler)
79 82 > except KeyError, exc:
80 83 > raise util.Abort('missing support for %s' % exc)
81 84 > finally:
82 85 > lock.release()
83 86 > remains = sys.stdin.read()
84 87 > ui.write('%i unread bytes\n' % len(remains))
88 > if op.records['song']:
89 > totalverses = sum(r['verses'] for r in op.records['song'])
90 > ui.write('%i total verses sung\n' % totalverses)
85 91 >
86 92 > @command('statbundle2', [], '')
87 93 > def cmdstatbundle2(ui, repo):
88 94 > """print statistic on the bundle2 container read from stdin"""
89 95 > unbundler = bundle2.unbundle20(ui, sys.stdin)
90 96 > try:
91 97 > params = unbundler.params
92 98 > except KeyError, exc:
93 99 > raise util.Abort('unknown parameters: %s' % exc)
94 100 > ui.write('options count: %i\n' % len(params))
95 101 > for key in sorted(params):
96 102 > ui.write('- %s\n' % key)
97 103 > value = params[key]
98 104 > if value is not None:
99 105 > ui.write(' %s\n' % value)
100 106 > parts = list(unbundler)
101 107 > ui.write('parts count: %i\n' % len(parts))
102 108 > for p in parts:
103 109 > ui.write(' :%s:\n' % p.type)
104 110 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
105 111 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
106 112 > ui.write(' payload: %i bytes\n' % len(p.data))
107 113 > EOF
108 114 $ cat >> $HGRCPATH << EOF
109 115 > [extensions]
110 116 > bundle2=$TESTTMP/bundle2.py
111 117 > EOF
112 118
113 119 The extension requires a repo (currently unused)
114 120
115 121 $ hg init main
116 122 $ cd main
117 123 $ touch a
118 124 $ hg add a
119 125 $ hg commit -m 'a'
120 126
121 127
122 128 Empty bundle
123 129 =================
124 130
125 131 - no option
126 132 - no parts
127 133
128 134 Test bundling
129 135
130 136 $ hg bundle2
131 137 HG20\x00\x00\x00\x00 (no-eol) (esc)
132 138
133 139 Test unbundling
134 140
135 141 $ hg bundle2 | hg statbundle2
136 142 options count: 0
137 143 parts count: 0
138 144
139 145 Test old style bundle are detected and refused
140 146
141 147 $ hg bundle --all ../bundle.hg
142 148 1 changesets found
143 149 $ hg statbundle2 < ../bundle.hg
144 150 abort: unknown bundle version 10
145 151 [255]
146 152
147 153 Test parameters
148 154 =================
149 155
150 156 - some options
151 157 - no parts
152 158
153 159 advisory parameters, no value
154 160 -------------------------------
155 161
156 162 Simplest possible parameters form
157 163
158 164 Test generation simple option
159 165
160 166 $ hg bundle2 --param 'caution'
161 167 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
162 168
163 169 Test unbundling
164 170
165 171 $ hg bundle2 --param 'caution' | hg statbundle2
166 172 options count: 1
167 173 - caution
168 174 parts count: 0
169 175
170 176 Test generation multiple option
171 177
172 178 $ hg bundle2 --param 'caution' --param 'meal'
173 179 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
174 180
175 181 Test unbundling
176 182
177 183 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
178 184 options count: 2
179 185 - caution
180 186 - meal
181 187 parts count: 0
182 188
183 189 advisory parameters, with value
184 190 -------------------------------
185 191
186 192 Test generation
187 193
188 194 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
189 195 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
190 196
191 197 Test unbundling
192 198
193 199 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
194 200 options count: 3
195 201 - caution
196 202 - elephants
197 203 - meal
198 204 vegan
199 205 parts count: 0
200 206
201 207 parameter with special char in value
202 208 ---------------------------------------------------
203 209
204 210 Test generation
205 211
206 212 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
207 213 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
208 214
209 215 Test unbundling
210 216
211 217 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
212 218 options count: 2
213 219 - e|! 7/
214 220 babar%#==tutu
215 221 - simple
216 222 parts count: 0
217 223
218 224 Test unknown mandatory option
219 225 ---------------------------------------------------
220 226
221 227 $ hg bundle2 --param 'Gravity' | hg statbundle2
222 228 abort: unknown parameters: 'Gravity'
223 229 [255]
224 230
225 231 Test debug output
226 232 ---------------------------------------------------
227 233
228 234 bundling debug
229 235
230 236 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
231 237 start emission of HG20 stream
232 238 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
233 239 start of parts
234 240 end of bundle
235 241
236 242 file content is ok
237 243
238 244 $ cat ../out.hg2
239 245 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
240 246
241 247 unbundling debug
242 248
243 249 $ hg statbundle2 --debug < ../out.hg2
244 250 start processing of HG20 stream
245 251 reading bundle2 stream parameters
246 252 ignoring unknown parameter 'e|! 7/'
247 253 ignoring unknown parameter 'simple'
248 254 options count: 2
249 255 - e|! 7/
250 256 babar%#==tutu
251 257 - simple
252 258 start extraction of bundle2 parts
253 259 part header size: 0
254 260 end of bundle2 stream
255 261 parts count: 0
256 262
257 263
258 264 Test buggy input
259 265 ---------------------------------------------------
260 266
261 267 empty parameter name
262 268
263 269 $ hg bundle2 --param '' --quiet
264 270 abort: empty parameter name
265 271 [255]
266 272
267 273 bad parameter name
268 274
269 275 $ hg bundle2 --param 42babar
270 276 abort: non letter first character: '42babar'
271 277 [255]
272 278
273 279
274 280 Test part
275 281 =================
276 282
277 283 $ hg bundle2 --parts ../parts.hg2 --debug
278 284 start emission of HG20 stream
279 285 bundle parameter:
280 286 start of parts
281 287 bundle part: "test:empty"
282 288 bundle part: "test:empty"
283 289 bundle part: "test:song"
284 290 bundle part: "test:math"
285 291 end of bundle
286 292
287 293 $ cat ../parts.hg2
288 294 HG20\x00\x00\x00\r (esc)
289 295 test:empty\x00\x00\x00\x00\x00\x00\x00\r (esc)
290 296 test:empty\x00\x00\x00\x00\x00\x00\x00\x0c test:song\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
291 297 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
292 298 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00' test:math\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
293 299
294 300
295 301 $ hg statbundle2 < ../parts.hg2
296 302 options count: 0
297 303 parts count: 4
298 304 :test:empty:
299 305 mandatory: 0
300 306 advisory: 0
301 307 payload: 0 bytes
302 308 :test:empty:
303 309 mandatory: 0
304 310 advisory: 0
305 311 payload: 0 bytes
306 312 :test:song:
307 313 mandatory: 0
308 314 advisory: 0
309 315 payload: 178 bytes
310 316 :test:math:
311 317 mandatory: 2
312 318 advisory: 1
313 319 payload: 2 bytes
314 320
315 321 $ hg statbundle2 --debug < ../parts.hg2
316 322 start processing of HG20 stream
317 323 reading bundle2 stream parameters
318 324 options count: 0
319 325 start extraction of bundle2 parts
320 326 part header size: 13
321 327 part type: "test:empty"
322 328 part parameters: 0
323 329 payload chunk size: 0
324 330 part header size: 13
325 331 part type: "test:empty"
326 332 part parameters: 0
327 333 payload chunk size: 0
328 334 part header size: 12
329 335 part type: "test:song"
330 336 part parameters: 0
331 337 payload chunk size: 178
332 338 payload chunk size: 0
333 339 part header size: 39
334 340 part type: "test:math"
335 341 part parameters: 3
336 342 payload chunk size: 2
337 343 payload chunk size: 0
338 344 part header size: 0
339 345 end of bundle2 stream
340 346 parts count: 4
341 347 :test:empty:
342 348 mandatory: 0
343 349 advisory: 0
344 350 payload: 0 bytes
345 351 :test:empty:
346 352 mandatory: 0
347 353 advisory: 0
348 354 payload: 0 bytes
349 355 :test:song:
350 356 mandatory: 0
351 357 advisory: 0
352 358 payload: 178 bytes
353 359 :test:math:
354 360 mandatory: 2
355 361 advisory: 1
356 362 payload: 2 bytes
357 363
358 364 Test actual unbundling
359 365 ========================
360 366
361 367 Process the bundle
362 368
363 369 $ hg unbundle2 --debug < ../parts.hg2
364 370 start processing of HG20 stream
365 371 reading bundle2 stream parameters
366 372 start extraction of bundle2 parts
367 373 part header size: 13
368 374 part type: "test:empty"
369 375 part parameters: 0
370 376 payload chunk size: 0
371 377 ignoring unknown advisory part 'test:empty'
372 378 part header size: 13
373 379 part type: "test:empty"
374 380 part parameters: 0
375 381 payload chunk size: 0
376 382 ignoring unknown advisory part 'test:empty'
377 383 part header size: 12
378 384 part type: "test:song"
379 385 part parameters: 0
380 386 payload chunk size: 178
381 387 payload chunk size: 0
382 388 found an handler for part 'test:song'
383 389 The choir start singing:
384 390 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
385 391 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
386 392 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
387 393 part header size: 39
388 394 part type: "test:math"
389 395 part parameters: 3
390 396 payload chunk size: 2
391 397 payload chunk size: 0
392 398 ignoring unknown advisory part 'test:math'
393 399 part header size: 0
394 400 end of bundle2 stream
395 401 0 unread bytes
402 3 total verses sung
396 403
397 404
398 405 $ hg bundle2 --parts --unknown ../unknown.hg2
399 406
400 407 $ hg unbundle2 < ../unknown.hg2
401 408 The choir start singing:
402 409 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
403 410 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
404 411 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
405 412 0 unread bytes
406 413 abort: missing support for 'test:unknown'
407 414 [255]
General Comments 0
You need to be logged in to leave comments. Login now