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