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