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