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