##// END OF EJS Templates
bundle2: add generic debug output regarding generated parts...
Pierre-Yves David -
r25323:21a25fb8 default
parent child Browse files
Show More
@@ -1,1293 +1,1316
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 iterparts = unbundler.iterparts()
321 iterparts = unbundler.iterparts()
322 part = None
322 part = None
323 try:
323 try:
324 for part in iterparts:
324 for part in iterparts:
325 _processpart(op, part)
325 _processpart(op, part)
326 except BaseException, exc:
326 except BaseException, exc:
327 for part in iterparts:
327 for part in iterparts:
328 # consume the bundle content
328 # consume the bundle content
329 part.seek(0, 2)
329 part.seek(0, 2)
330 # Small hack to let caller code distinguish exceptions from bundle2
330 # Small hack to let caller code distinguish exceptions from bundle2
331 # processing from processing the old format. This is mostly
331 # processing from processing the old format. This is mostly
332 # needed to handle different return codes to unbundle according to the
332 # 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
333 # type of bundle. We should probably clean up or drop this return code
334 # craziness in a future version.
334 # craziness in a future version.
335 exc.duringunbundle2 = True
335 exc.duringunbundle2 = True
336 salvaged = []
336 salvaged = []
337 if op.reply is not None:
337 if op.reply is not None:
338 salvaged = op.reply.salvageoutput()
338 salvaged = op.reply.salvageoutput()
339 exc._bundle2salvagedoutput = salvaged
339 exc._bundle2salvagedoutput = salvaged
340 raise
340 raise
341 return op
341 return op
342
342
343 def _processpart(op, part):
343 def _processpart(op, part):
344 """process a single part from a bundle
344 """process a single part from a bundle
345
345
346 The part is guaranteed to have been fully consumed when the function exits
346 The part is guaranteed to have been fully consumed when the function exits
347 (even if an exception is raised)."""
347 (even if an exception is raised)."""
348 try:
348 try:
349 try:
349 try:
350 handler = parthandlermapping.get(part.type)
350 handler = parthandlermapping.get(part.type)
351 if handler is None:
351 if handler is None:
352 raise error.UnsupportedPartError(parttype=part.type)
352 raise error.UnsupportedPartError(parttype=part.type)
353 indebug(op.ui, 'found a handler for part %r' % part.type)
353 indebug(op.ui, 'found a handler for part %r' % part.type)
354 unknownparams = part.mandatorykeys - handler.params
354 unknownparams = part.mandatorykeys - handler.params
355 if unknownparams:
355 if unknownparams:
356 unknownparams = list(unknownparams)
356 unknownparams = list(unknownparams)
357 unknownparams.sort()
357 unknownparams.sort()
358 raise error.UnsupportedPartError(parttype=part.type,
358 raise error.UnsupportedPartError(parttype=part.type,
359 params=unknownparams)
359 params=unknownparams)
360 except error.UnsupportedPartError, exc:
360 except error.UnsupportedPartError, exc:
361 if part.mandatory: # mandatory parts
361 if part.mandatory: # mandatory parts
362 raise
362 raise
363 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
363 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
364 return # skip to part processing
364 return # skip to part processing
365
365
366 # handler is called outside the above try block so that we don't
366 # handler is called outside the above try block so that we don't
367 # risk catching KeyErrors from anything other than the
367 # risk catching KeyErrors from anything other than the
368 # parthandlermapping lookup (any KeyError raised by handler()
368 # parthandlermapping lookup (any KeyError raised by handler()
369 # itself represents a defect of a different variety).
369 # itself represents a defect of a different variety).
370 output = None
370 output = None
371 if op.captureoutput and op.reply is not None:
371 if op.captureoutput and op.reply is not None:
372 op.ui.pushbuffer(error=True, subproc=True)
372 op.ui.pushbuffer(error=True, subproc=True)
373 output = ''
373 output = ''
374 try:
374 try:
375 handler(op, part)
375 handler(op, part)
376 finally:
376 finally:
377 if output is not None:
377 if output is not None:
378 output = op.ui.popbuffer()
378 output = op.ui.popbuffer()
379 if output:
379 if output:
380 outpart = op.reply.newpart('output', data=output,
380 outpart = op.reply.newpart('output', data=output,
381 mandatory=False)
381 mandatory=False)
382 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
382 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
383 finally:
383 finally:
384 # consume the part content to not corrupt the stream.
384 # consume the part content to not corrupt the stream.
385 part.seek(0, 2)
385 part.seek(0, 2)
386
386
387
387
388 def decodecaps(blob):
388 def decodecaps(blob):
389 """decode a bundle2 caps bytes blob into a dictionary
389 """decode a bundle2 caps bytes blob into a dictionary
390
390
391 The blob is a list of capabilities (one per line)
391 The blob is a list of capabilities (one per line)
392 Capabilities may have values using a line of the form::
392 Capabilities may have values using a line of the form::
393
393
394 capability=value1,value2,value3
394 capability=value1,value2,value3
395
395
396 The values are always a list."""
396 The values are always a list."""
397 caps = {}
397 caps = {}
398 for line in blob.splitlines():
398 for line in blob.splitlines():
399 if not line:
399 if not line:
400 continue
400 continue
401 if '=' not in line:
401 if '=' not in line:
402 key, vals = line, ()
402 key, vals = line, ()
403 else:
403 else:
404 key, vals = line.split('=', 1)
404 key, vals = line.split('=', 1)
405 vals = vals.split(',')
405 vals = vals.split(',')
406 key = urllib.unquote(key)
406 key = urllib.unquote(key)
407 vals = [urllib.unquote(v) for v in vals]
407 vals = [urllib.unquote(v) for v in vals]
408 caps[key] = vals
408 caps[key] = vals
409 return caps
409 return caps
410
410
411 def encodecaps(caps):
411 def encodecaps(caps):
412 """encode a bundle2 caps dictionary into a bytes blob"""
412 """encode a bundle2 caps dictionary into a bytes blob"""
413 chunks = []
413 chunks = []
414 for ca in sorted(caps):
414 for ca in sorted(caps):
415 vals = caps[ca]
415 vals = caps[ca]
416 ca = urllib.quote(ca)
416 ca = urllib.quote(ca)
417 vals = [urllib.quote(v) for v in vals]
417 vals = [urllib.quote(v) for v in vals]
418 if vals:
418 if vals:
419 ca = "%s=%s" % (ca, ','.join(vals))
419 ca = "%s=%s" % (ca, ','.join(vals))
420 chunks.append(ca)
420 chunks.append(ca)
421 return '\n'.join(chunks)
421 return '\n'.join(chunks)
422
422
423 class bundle20(object):
423 class bundle20(object):
424 """represent an outgoing bundle2 container
424 """represent an outgoing bundle2 container
425
425
426 Use the `addparam` method to add stream level parameter. and `newpart` to
426 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
427 populate it. Then call `getchunks` to retrieve all the binary chunks of
428 data that compose the bundle2 container."""
428 data that compose the bundle2 container."""
429
429
430 _magicstring = 'HG20'
430 _magicstring = 'HG20'
431
431
432 def __init__(self, ui, capabilities=()):
432 def __init__(self, ui, capabilities=()):
433 self.ui = ui
433 self.ui = ui
434 self._params = []
434 self._params = []
435 self._parts = []
435 self._parts = []
436 self.capabilities = dict(capabilities)
436 self.capabilities = dict(capabilities)
437
437
438 @property
438 @property
439 def nbparts(self):
439 def nbparts(self):
440 """total number of parts added to the bundler"""
440 """total number of parts added to the bundler"""
441 return len(self._parts)
441 return len(self._parts)
442
442
443 # methods used to defines the bundle2 content
443 # methods used to defines the bundle2 content
444 def addparam(self, name, value=None):
444 def addparam(self, name, value=None):
445 """add a stream level parameter"""
445 """add a stream level parameter"""
446 if not name:
446 if not name:
447 raise ValueError('empty parameter name')
447 raise ValueError('empty parameter name')
448 if name[0] not in string.letters:
448 if name[0] not in string.letters:
449 raise ValueError('non letter first character: %r' % name)
449 raise ValueError('non letter first character: %r' % name)
450 self._params.append((name, value))
450 self._params.append((name, value))
451
451
452 def addpart(self, part):
452 def addpart(self, part):
453 """add a new part to the bundle2 container
453 """add a new part to the bundle2 container
454
454
455 Parts contains the actual applicative payload."""
455 Parts contains the actual applicative payload."""
456 assert part.id is None
456 assert part.id is None
457 part.id = len(self._parts) # very cheap counter
457 part.id = len(self._parts) # very cheap counter
458 self._parts.append(part)
458 self._parts.append(part)
459
459
460 def newpart(self, typeid, *args, **kwargs):
460 def newpart(self, typeid, *args, **kwargs):
461 """create a new part and add it to the containers
461 """create a new part and add it to the containers
462
462
463 As the part is directly added to the containers. For now, this means
463 As the part is directly added to the containers. For now, this means
464 that any failure to properly initialize the part after calling
464 that any failure to properly initialize the part after calling
465 ``newpart`` should result in a failure of the whole bundling process.
465 ``newpart`` should result in a failure of the whole bundling process.
466
466
467 You can still fall back to manually create and add if you need better
467 You can still fall back to manually create and add if you need better
468 control."""
468 control."""
469 part = bundlepart(typeid, *args, **kwargs)
469 part = bundlepart(typeid, *args, **kwargs)
470 self.addpart(part)
470 self.addpart(part)
471 return part
471 return part
472
472
473 # methods used to generate the bundle2 stream
473 # methods used to generate the bundle2 stream
474 def getchunks(self):
474 def getchunks(self):
475 if self.ui.debugflag:
475 if self.ui.debugflag:
476 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
476 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
477 if self._params:
477 if self._params:
478 msg.append(' (%i params)' % len(self._params))
478 msg.append(' (%i params)' % len(self._params))
479 msg.append(' %i parts total\n' % len(self._parts))
479 msg.append(' %i parts total\n' % len(self._parts))
480 self.ui.debug(''.join(msg))
480 self.ui.debug(''.join(msg))
481 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
481 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
482 yield self._magicstring
482 yield self._magicstring
483 param = self._paramchunk()
483 param = self._paramchunk()
484 outdebug(self.ui, 'bundle parameter: %s' % param)
484 outdebug(self.ui, 'bundle parameter: %s' % param)
485 yield _pack(_fstreamparamsize, len(param))
485 yield _pack(_fstreamparamsize, len(param))
486 if param:
486 if param:
487 yield param
487 yield param
488
488
489 outdebug(self.ui, 'start of parts')
489 outdebug(self.ui, 'start of parts')
490 for part in self._parts:
490 for part in self._parts:
491 outdebug(self.ui, 'bundle part: "%s"' % part.type)
491 outdebug(self.ui, 'bundle part: "%s"' % part.type)
492 for chunk in part.getchunks(ui=self.ui):
492 for chunk in part.getchunks(ui=self.ui):
493 yield chunk
493 yield chunk
494 outdebug(self.ui, 'end of bundle')
494 outdebug(self.ui, 'end of bundle')
495 yield _pack(_fpartheadersize, 0)
495 yield _pack(_fpartheadersize, 0)
496
496
497 def _paramchunk(self):
497 def _paramchunk(self):
498 """return a encoded version of all stream parameters"""
498 """return a encoded version of all stream parameters"""
499 blocks = []
499 blocks = []
500 for par, value in self._params:
500 for par, value in self._params:
501 par = urllib.quote(par)
501 par = urllib.quote(par)
502 if value is not None:
502 if value is not None:
503 value = urllib.quote(value)
503 value = urllib.quote(value)
504 par = '%s=%s' % (par, value)
504 par = '%s=%s' % (par, value)
505 blocks.append(par)
505 blocks.append(par)
506 return ' '.join(blocks)
506 return ' '.join(blocks)
507
507
508 def salvageoutput(self):
508 def salvageoutput(self):
509 """return a list with a copy of all output parts in the bundle
509 """return a list with a copy of all output parts in the bundle
510
510
511 This is meant to be used during error handling to make sure we preserve
511 This is meant to be used during error handling to make sure we preserve
512 server output"""
512 server output"""
513 salvaged = []
513 salvaged = []
514 for part in self._parts:
514 for part in self._parts:
515 if part.type.startswith('output'):
515 if part.type.startswith('output'):
516 salvaged.append(part.copy())
516 salvaged.append(part.copy())
517 return salvaged
517 return salvaged
518
518
519
519
520 class unpackermixin(object):
520 class unpackermixin(object):
521 """A mixin to extract bytes and struct data from a stream"""
521 """A mixin to extract bytes and struct data from a stream"""
522
522
523 def __init__(self, fp):
523 def __init__(self, fp):
524 self._fp = fp
524 self._fp = fp
525 self._seekable = (util.safehasattr(fp, 'seek') and
525 self._seekable = (util.safehasattr(fp, 'seek') and
526 util.safehasattr(fp, 'tell'))
526 util.safehasattr(fp, 'tell'))
527
527
528 def _unpack(self, format):
528 def _unpack(self, format):
529 """unpack this struct format from the stream"""
529 """unpack this struct format from the stream"""
530 data = self._readexact(struct.calcsize(format))
530 data = self._readexact(struct.calcsize(format))
531 return _unpack(format, data)
531 return _unpack(format, data)
532
532
533 def _readexact(self, size):
533 def _readexact(self, size):
534 """read exactly <size> bytes from the stream"""
534 """read exactly <size> bytes from the stream"""
535 return changegroup.readexactly(self._fp, size)
535 return changegroup.readexactly(self._fp, size)
536
536
537 def seek(self, offset, whence=0):
537 def seek(self, offset, whence=0):
538 """move the underlying file pointer"""
538 """move the underlying file pointer"""
539 if self._seekable:
539 if self._seekable:
540 return self._fp.seek(offset, whence)
540 return self._fp.seek(offset, whence)
541 else:
541 else:
542 raise NotImplementedError(_('File pointer is not seekable'))
542 raise NotImplementedError(_('File pointer is not seekable'))
543
543
544 def tell(self):
544 def tell(self):
545 """return the file offset, or None if file is not seekable"""
545 """return the file offset, or None if file is not seekable"""
546 if self._seekable:
546 if self._seekable:
547 try:
547 try:
548 return self._fp.tell()
548 return self._fp.tell()
549 except IOError, e:
549 except IOError, e:
550 if e.errno == errno.ESPIPE:
550 if e.errno == errno.ESPIPE:
551 self._seekable = False
551 self._seekable = False
552 else:
552 else:
553 raise
553 raise
554 return None
554 return None
555
555
556 def close(self):
556 def close(self):
557 """close underlying file"""
557 """close underlying file"""
558 if util.safehasattr(self._fp, 'close'):
558 if util.safehasattr(self._fp, 'close'):
559 return self._fp.close()
559 return self._fp.close()
560
560
561 def getunbundler(ui, fp, header=None):
561 def getunbundler(ui, fp, header=None):
562 """return a valid unbundler object for a given header"""
562 """return a valid unbundler object for a given header"""
563 if header is None:
563 if header is None:
564 header = changegroup.readexactly(fp, 4)
564 header = changegroup.readexactly(fp, 4)
565 magic, version = header[0:2], header[2:4]
565 magic, version = header[0:2], header[2:4]
566 if magic != 'HG':
566 if magic != 'HG':
567 raise util.Abort(_('not a Mercurial bundle'))
567 raise util.Abort(_('not a Mercurial bundle'))
568 unbundlerclass = formatmap.get(version)
568 unbundlerclass = formatmap.get(version)
569 if unbundlerclass is None:
569 if unbundlerclass is None:
570 raise util.Abort(_('unknown bundle version %s') % version)
570 raise util.Abort(_('unknown bundle version %s') % version)
571 unbundler = unbundlerclass(ui, fp)
571 unbundler = unbundlerclass(ui, fp)
572 indebug(ui, 'start processing of %s stream' % header)
572 indebug(ui, 'start processing of %s stream' % header)
573 return unbundler
573 return unbundler
574
574
575 class unbundle20(unpackermixin):
575 class unbundle20(unpackermixin):
576 """interpret a bundle2 stream
576 """interpret a bundle2 stream
577
577
578 This class is fed with a binary stream and yields parts through its
578 This class is fed with a binary stream and yields parts through its
579 `iterparts` methods."""
579 `iterparts` methods."""
580
580
581 def __init__(self, ui, fp):
581 def __init__(self, ui, fp):
582 """If header is specified, we do not read it out of the stream."""
582 """If header is specified, we do not read it out of the stream."""
583 self.ui = ui
583 self.ui = ui
584 super(unbundle20, self).__init__(fp)
584 super(unbundle20, self).__init__(fp)
585
585
586 @util.propertycache
586 @util.propertycache
587 def params(self):
587 def params(self):
588 """dictionary of stream level parameters"""
588 """dictionary of stream level parameters"""
589 indebug(self.ui, 'reading bundle2 stream parameters')
589 indebug(self.ui, 'reading bundle2 stream parameters')
590 params = {}
590 params = {}
591 paramssize = self._unpack(_fstreamparamsize)[0]
591 paramssize = self._unpack(_fstreamparamsize)[0]
592 if paramssize < 0:
592 if paramssize < 0:
593 raise error.BundleValueError('negative bundle param size: %i'
593 raise error.BundleValueError('negative bundle param size: %i'
594 % paramssize)
594 % paramssize)
595 if paramssize:
595 if paramssize:
596 for p in self._readexact(paramssize).split(' '):
596 for p in self._readexact(paramssize).split(' '):
597 p = p.split('=', 1)
597 p = p.split('=', 1)
598 p = [urllib.unquote(i) for i in p]
598 p = [urllib.unquote(i) for i in p]
599 if len(p) < 2:
599 if len(p) < 2:
600 p.append(None)
600 p.append(None)
601 self._processparam(*p)
601 self._processparam(*p)
602 params[p[0]] = p[1]
602 params[p[0]] = p[1]
603 return params
603 return params
604
604
605 def _processparam(self, name, value):
605 def _processparam(self, name, value):
606 """process a parameter, applying its effect if needed
606 """process a parameter, applying its effect if needed
607
607
608 Parameter starting with a lower case letter are advisory and will be
608 Parameter starting with a lower case letter are advisory and will be
609 ignored when unknown. Those starting with an upper case letter are
609 ignored when unknown. Those starting with an upper case letter are
610 mandatory and will this function will raise a KeyError when unknown.
610 mandatory and will this function will raise a KeyError when unknown.
611
611
612 Note: no option are currently supported. Any input will be either
612 Note: no option are currently supported. Any input will be either
613 ignored or failing.
613 ignored or failing.
614 """
614 """
615 if not name:
615 if not name:
616 raise ValueError('empty parameter name')
616 raise ValueError('empty parameter name')
617 if name[0] not in string.letters:
617 if name[0] not in string.letters:
618 raise ValueError('non letter first character: %r' % name)
618 raise ValueError('non letter first character: %r' % name)
619 # Some logic will be later added here to try to process the option for
619 # Some logic will be later added here to try to process the option for
620 # a dict of known parameter.
620 # a dict of known parameter.
621 if name[0].islower():
621 if name[0].islower():
622 indebug(self.ui, "ignoring unknown parameter %r" % name)
622 indebug(self.ui, "ignoring unknown parameter %r" % name)
623 else:
623 else:
624 raise error.UnsupportedPartError(params=(name,))
624 raise error.UnsupportedPartError(params=(name,))
625
625
626
626
627 def iterparts(self):
627 def iterparts(self):
628 """yield all parts contained in the stream"""
628 """yield all parts contained in the stream"""
629 # make sure param have been loaded
629 # make sure param have been loaded
630 self.params
630 self.params
631 indebug(self.ui, 'start extraction of bundle2 parts')
631 indebug(self.ui, 'start extraction of bundle2 parts')
632 headerblock = self._readpartheader()
632 headerblock = self._readpartheader()
633 while headerblock is not None:
633 while headerblock is not None:
634 part = unbundlepart(self.ui, headerblock, self._fp)
634 part = unbundlepart(self.ui, headerblock, self._fp)
635 yield part
635 yield part
636 part.seek(0, 2)
636 part.seek(0, 2)
637 headerblock = self._readpartheader()
637 headerblock = self._readpartheader()
638 indebug(self.ui, 'end of bundle2 stream')
638 indebug(self.ui, 'end of bundle2 stream')
639
639
640 def _readpartheader(self):
640 def _readpartheader(self):
641 """reads a part header size and return the bytes blob
641 """reads a part header size and return the bytes blob
642
642
643 returns None if empty"""
643 returns None if empty"""
644 headersize = self._unpack(_fpartheadersize)[0]
644 headersize = self._unpack(_fpartheadersize)[0]
645 if headersize < 0:
645 if headersize < 0:
646 raise error.BundleValueError('negative part header size: %i'
646 raise error.BundleValueError('negative part header size: %i'
647 % headersize)
647 % headersize)
648 indebug(self.ui, 'part header size: %i' % headersize)
648 indebug(self.ui, 'part header size: %i' % headersize)
649 if headersize:
649 if headersize:
650 return self._readexact(headersize)
650 return self._readexact(headersize)
651 return None
651 return None
652
652
653 def compressed(self):
653 def compressed(self):
654 return False
654 return False
655
655
656 formatmap = {'20': unbundle20}
656 formatmap = {'20': unbundle20}
657
657
658 class bundlepart(object):
658 class bundlepart(object):
659 """A bundle2 part contains application level payload
659 """A bundle2 part contains application level payload
660
660
661 The part `type` is used to route the part to the application level
661 The part `type` is used to route the part to the application level
662 handler.
662 handler.
663
663
664 The part payload is contained in ``part.data``. It could be raw bytes or a
664 The part payload is contained in ``part.data``. It could be raw bytes or a
665 generator of byte chunks.
665 generator of byte chunks.
666
666
667 You can add parameters to the part using the ``addparam`` method.
667 You can add parameters to the part using the ``addparam`` method.
668 Parameters can be either mandatory (default) or advisory. Remote side
668 Parameters can be either mandatory (default) or advisory. Remote side
669 should be able to safely ignore the advisory ones.
669 should be able to safely ignore the advisory ones.
670
670
671 Both data and parameters cannot be modified after the generation has begun.
671 Both data and parameters cannot be modified after the generation has begun.
672 """
672 """
673
673
674 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
674 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
675 data='', mandatory=True):
675 data='', mandatory=True):
676 validateparttype(parttype)
676 validateparttype(parttype)
677 self.id = None
677 self.id = None
678 self.type = parttype
678 self.type = parttype
679 self._data = data
679 self._data = data
680 self._mandatoryparams = list(mandatoryparams)
680 self._mandatoryparams = list(mandatoryparams)
681 self._advisoryparams = list(advisoryparams)
681 self._advisoryparams = list(advisoryparams)
682 # checking for duplicated entries
682 # checking for duplicated entries
683 self._seenparams = set()
683 self._seenparams = set()
684 for pname, __ in self._mandatoryparams + self._advisoryparams:
684 for pname, __ in self._mandatoryparams + self._advisoryparams:
685 if pname in self._seenparams:
685 if pname in self._seenparams:
686 raise RuntimeError('duplicated params: %s' % pname)
686 raise RuntimeError('duplicated params: %s' % pname)
687 self._seenparams.add(pname)
687 self._seenparams.add(pname)
688 # status of the part's generation:
688 # status of the part's generation:
689 # - None: not started,
689 # - None: not started,
690 # - False: currently generated,
690 # - False: currently generated,
691 # - True: generation done.
691 # - True: generation done.
692 self._generated = None
692 self._generated = None
693 self.mandatory = mandatory
693 self.mandatory = mandatory
694
694
695 def copy(self):
695 def copy(self):
696 """return a copy of the part
696 """return a copy of the part
697
697
698 The new part have the very same content but no partid assigned yet.
698 The new part have the very same content but no partid assigned yet.
699 Parts with generated data cannot be copied."""
699 Parts with generated data cannot be copied."""
700 assert not util.safehasattr(self.data, 'next')
700 assert not util.safehasattr(self.data, 'next')
701 return self.__class__(self.type, self._mandatoryparams,
701 return self.__class__(self.type, self._mandatoryparams,
702 self._advisoryparams, self._data, self.mandatory)
702 self._advisoryparams, self._data, self.mandatory)
703
703
704 # methods used to defines the part content
704 # methods used to defines the part content
705 def __setdata(self, data):
705 def __setdata(self, data):
706 if self._generated is not None:
706 if self._generated is not None:
707 raise error.ReadOnlyPartError('part is being generated')
707 raise error.ReadOnlyPartError('part is being generated')
708 self._data = data
708 self._data = data
709 def __getdata(self):
709 def __getdata(self):
710 return self._data
710 return self._data
711 data = property(__getdata, __setdata)
711 data = property(__getdata, __setdata)
712
712
713 @property
713 @property
714 def mandatoryparams(self):
714 def mandatoryparams(self):
715 # make it an immutable tuple to force people through ``addparam``
715 # make it an immutable tuple to force people through ``addparam``
716 return tuple(self._mandatoryparams)
716 return tuple(self._mandatoryparams)
717
717
718 @property
718 @property
719 def advisoryparams(self):
719 def advisoryparams(self):
720 # make it an immutable tuple to force people through ``addparam``
720 # make it an immutable tuple to force people through ``addparam``
721 return tuple(self._advisoryparams)
721 return tuple(self._advisoryparams)
722
722
723 def addparam(self, name, value='', mandatory=True):
723 def addparam(self, name, value='', mandatory=True):
724 if self._generated is not None:
724 if self._generated is not None:
725 raise error.ReadOnlyPartError('part is being generated')
725 raise error.ReadOnlyPartError('part is being generated')
726 if name in self._seenparams:
726 if name in self._seenparams:
727 raise ValueError('duplicated params: %s' % name)
727 raise ValueError('duplicated params: %s' % name)
728 self._seenparams.add(name)
728 self._seenparams.add(name)
729 params = self._advisoryparams
729 params = self._advisoryparams
730 if mandatory:
730 if mandatory:
731 params = self._mandatoryparams
731 params = self._mandatoryparams
732 params.append((name, value))
732 params.append((name, value))
733
733
734 # methods used to generates the bundle2 stream
734 # methods used to generates the bundle2 stream
735 def getchunks(self, ui):
735 def getchunks(self, ui):
736 if self._generated is not None:
736 if self._generated is not None:
737 raise RuntimeError('part can only be consumed once')
737 raise RuntimeError('part can only be consumed once')
738 self._generated = False
738 self._generated = False
739
740 if ui.debugflag:
741 msg = ['bundle2-output-part: "%s"' % self.type]
742 if not self.mandatory:
743 msg.append(' (advisory)')
744 nbmp = len(self.mandatoryparams)
745 nbap = len(self.advisoryparams)
746 if nbmp or nbap:
747 msg.append(' (params:')
748 if nbmp:
749 msg.append(' %i mandatory' % nbmp)
750 if nbap:
751 msg.append(' %i advisory' % nbmp)
752 msg.append(')')
753 if not self.data:
754 msg.append(' empty payload')
755 elif util.safehasattr(self.data, 'next'):
756 msg.append(' streamed payload')
757 else:
758 msg.append(' %i bytes payload' % len(self.data))
759 msg.append('\n')
760 ui.debug(''.join(msg))
761
739 #### header
762 #### header
740 if self.mandatory:
763 if self.mandatory:
741 parttype = self.type.upper()
764 parttype = self.type.upper()
742 else:
765 else:
743 parttype = self.type.lower()
766 parttype = self.type.lower()
744 outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
767 outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
745 ## parttype
768 ## parttype
746 header = [_pack(_fparttypesize, len(parttype)),
769 header = [_pack(_fparttypesize, len(parttype)),
747 parttype, _pack(_fpartid, self.id),
770 parttype, _pack(_fpartid, self.id),
748 ]
771 ]
749 ## parameters
772 ## parameters
750 # count
773 # count
751 manpar = self.mandatoryparams
774 manpar = self.mandatoryparams
752 advpar = self.advisoryparams
775 advpar = self.advisoryparams
753 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
776 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
754 # size
777 # size
755 parsizes = []
778 parsizes = []
756 for key, value in manpar:
779 for key, value in manpar:
757 parsizes.append(len(key))
780 parsizes.append(len(key))
758 parsizes.append(len(value))
781 parsizes.append(len(value))
759 for key, value in advpar:
782 for key, value in advpar:
760 parsizes.append(len(key))
783 parsizes.append(len(key))
761 parsizes.append(len(value))
784 parsizes.append(len(value))
762 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
785 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
763 header.append(paramsizes)
786 header.append(paramsizes)
764 # key, value
787 # key, value
765 for key, value in manpar:
788 for key, value in manpar:
766 header.append(key)
789 header.append(key)
767 header.append(value)
790 header.append(value)
768 for key, value in advpar:
791 for key, value in advpar:
769 header.append(key)
792 header.append(key)
770 header.append(value)
793 header.append(value)
771 ## finalize header
794 ## finalize header
772 headerchunk = ''.join(header)
795 headerchunk = ''.join(header)
773 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
796 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
774 yield _pack(_fpartheadersize, len(headerchunk))
797 yield _pack(_fpartheadersize, len(headerchunk))
775 yield headerchunk
798 yield headerchunk
776 ## payload
799 ## payload
777 try:
800 try:
778 for chunk in self._payloadchunks():
801 for chunk in self._payloadchunks():
779 outdebug(ui, 'payload chunk size: %i' % len(chunk))
802 outdebug(ui, 'payload chunk size: %i' % len(chunk))
780 yield _pack(_fpayloadsize, len(chunk))
803 yield _pack(_fpayloadsize, len(chunk))
781 yield chunk
804 yield chunk
782 except BaseException, exc:
805 except BaseException, exc:
783 # backup exception data for later
806 # backup exception data for later
784 exc_info = sys.exc_info()
807 exc_info = sys.exc_info()
785 msg = 'unexpected error: %s' % exc
808 msg = 'unexpected error: %s' % exc
786 interpart = bundlepart('error:abort', [('message', msg)],
809 interpart = bundlepart('error:abort', [('message', msg)],
787 mandatory=False)
810 mandatory=False)
788 interpart.id = 0
811 interpart.id = 0
789 yield _pack(_fpayloadsize, -1)
812 yield _pack(_fpayloadsize, -1)
790 for chunk in interpart.getchunks(ui=ui):
813 for chunk in interpart.getchunks(ui=ui):
791 yield chunk
814 yield chunk
792 outdebug(ui, 'closing payload chunk')
815 outdebug(ui, 'closing payload chunk')
793 # abort current part payload
816 # abort current part payload
794 yield _pack(_fpayloadsize, 0)
817 yield _pack(_fpayloadsize, 0)
795 raise exc_info[0], exc_info[1], exc_info[2]
818 raise exc_info[0], exc_info[1], exc_info[2]
796 # end of payload
819 # end of payload
797 outdebug(ui, 'closing payload chunk')
820 outdebug(ui, 'closing payload chunk')
798 yield _pack(_fpayloadsize, 0)
821 yield _pack(_fpayloadsize, 0)
799 self._generated = True
822 self._generated = True
800
823
801 def _payloadchunks(self):
824 def _payloadchunks(self):
802 """yield chunks of a the part payload
825 """yield chunks of a the part payload
803
826
804 Exists to handle the different methods to provide data to a part."""
827 Exists to handle the different methods to provide data to a part."""
805 # we only support fixed size data now.
828 # we only support fixed size data now.
806 # This will be improved in the future.
829 # This will be improved in the future.
807 if util.safehasattr(self.data, 'next'):
830 if util.safehasattr(self.data, 'next'):
808 buff = util.chunkbuffer(self.data)
831 buff = util.chunkbuffer(self.data)
809 chunk = buff.read(preferedchunksize)
832 chunk = buff.read(preferedchunksize)
810 while chunk:
833 while chunk:
811 yield chunk
834 yield chunk
812 chunk = buff.read(preferedchunksize)
835 chunk = buff.read(preferedchunksize)
813 elif len(self.data):
836 elif len(self.data):
814 yield self.data
837 yield self.data
815
838
816
839
817 flaginterrupt = -1
840 flaginterrupt = -1
818
841
819 class interrupthandler(unpackermixin):
842 class interrupthandler(unpackermixin):
820 """read one part and process it with restricted capability
843 """read one part and process it with restricted capability
821
844
822 This allows to transmit exception raised on the producer size during part
845 This allows to transmit exception raised on the producer size during part
823 iteration while the consumer is reading a part.
846 iteration while the consumer is reading a part.
824
847
825 Part processed in this manner only have access to a ui object,"""
848 Part processed in this manner only have access to a ui object,"""
826
849
827 def __init__(self, ui, fp):
850 def __init__(self, ui, fp):
828 super(interrupthandler, self).__init__(fp)
851 super(interrupthandler, self).__init__(fp)
829 self.ui = ui
852 self.ui = ui
830
853
831 def _readpartheader(self):
854 def _readpartheader(self):
832 """reads a part header size and return the bytes blob
855 """reads a part header size and return the bytes blob
833
856
834 returns None if empty"""
857 returns None if empty"""
835 headersize = self._unpack(_fpartheadersize)[0]
858 headersize = self._unpack(_fpartheadersize)[0]
836 if headersize < 0:
859 if headersize < 0:
837 raise error.BundleValueError('negative part header size: %i'
860 raise error.BundleValueError('negative part header size: %i'
838 % headersize)
861 % headersize)
839 indebug(self.ui, 'part header size: %i\n' % headersize)
862 indebug(self.ui, 'part header size: %i\n' % headersize)
840 if headersize:
863 if headersize:
841 return self._readexact(headersize)
864 return self._readexact(headersize)
842 return None
865 return None
843
866
844 def __call__(self):
867 def __call__(self):
845 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
868 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
846 headerblock = self._readpartheader()
869 headerblock = self._readpartheader()
847 if headerblock is None:
870 if headerblock is None:
848 indebug(self.ui, 'no part found during interruption.')
871 indebug(self.ui, 'no part found during interruption.')
849 return
872 return
850 part = unbundlepart(self.ui, headerblock, self._fp)
873 part = unbundlepart(self.ui, headerblock, self._fp)
851 op = interruptoperation(self.ui)
874 op = interruptoperation(self.ui)
852 _processpart(op, part)
875 _processpart(op, part)
853
876
854 class interruptoperation(object):
877 class interruptoperation(object):
855 """A limited operation to be use by part handler during interruption
878 """A limited operation to be use by part handler during interruption
856
879
857 It only have access to an ui object.
880 It only have access to an ui object.
858 """
881 """
859
882
860 def __init__(self, ui):
883 def __init__(self, ui):
861 self.ui = ui
884 self.ui = ui
862 self.reply = None
885 self.reply = None
863 self.captureoutput = False
886 self.captureoutput = False
864
887
865 @property
888 @property
866 def repo(self):
889 def repo(self):
867 raise RuntimeError('no repo access from stream interruption')
890 raise RuntimeError('no repo access from stream interruption')
868
891
869 def gettransaction(self):
892 def gettransaction(self):
870 raise TransactionUnavailable('no repo access from stream interruption')
893 raise TransactionUnavailable('no repo access from stream interruption')
871
894
872 class unbundlepart(unpackermixin):
895 class unbundlepart(unpackermixin):
873 """a bundle part read from a bundle"""
896 """a bundle part read from a bundle"""
874
897
875 def __init__(self, ui, header, fp):
898 def __init__(self, ui, header, fp):
876 super(unbundlepart, self).__init__(fp)
899 super(unbundlepart, self).__init__(fp)
877 self.ui = ui
900 self.ui = ui
878 # unbundle state attr
901 # unbundle state attr
879 self._headerdata = header
902 self._headerdata = header
880 self._headeroffset = 0
903 self._headeroffset = 0
881 self._initialized = False
904 self._initialized = False
882 self.consumed = False
905 self.consumed = False
883 # part data
906 # part data
884 self.id = None
907 self.id = None
885 self.type = None
908 self.type = None
886 self.mandatoryparams = None
909 self.mandatoryparams = None
887 self.advisoryparams = None
910 self.advisoryparams = None
888 self.params = None
911 self.params = None
889 self.mandatorykeys = ()
912 self.mandatorykeys = ()
890 self._payloadstream = None
913 self._payloadstream = None
891 self._readheader()
914 self._readheader()
892 self._mandatory = None
915 self._mandatory = None
893 self._chunkindex = [] #(payload, file) position tuples for chunk starts
916 self._chunkindex = [] #(payload, file) position tuples for chunk starts
894 self._pos = 0
917 self._pos = 0
895
918
896 def _fromheader(self, size):
919 def _fromheader(self, size):
897 """return the next <size> byte from the header"""
920 """return the next <size> byte from the header"""
898 offset = self._headeroffset
921 offset = self._headeroffset
899 data = self._headerdata[offset:(offset + size)]
922 data = self._headerdata[offset:(offset + size)]
900 self._headeroffset = offset + size
923 self._headeroffset = offset + size
901 return data
924 return data
902
925
903 def _unpackheader(self, format):
926 def _unpackheader(self, format):
904 """read given format from header
927 """read given format from header
905
928
906 This automatically compute the size of the format to read."""
929 This automatically compute the size of the format to read."""
907 data = self._fromheader(struct.calcsize(format))
930 data = self._fromheader(struct.calcsize(format))
908 return _unpack(format, data)
931 return _unpack(format, data)
909
932
910 def _initparams(self, mandatoryparams, advisoryparams):
933 def _initparams(self, mandatoryparams, advisoryparams):
911 """internal function to setup all logic related parameters"""
934 """internal function to setup all logic related parameters"""
912 # make it read only to prevent people touching it by mistake.
935 # make it read only to prevent people touching it by mistake.
913 self.mandatoryparams = tuple(mandatoryparams)
936 self.mandatoryparams = tuple(mandatoryparams)
914 self.advisoryparams = tuple(advisoryparams)
937 self.advisoryparams = tuple(advisoryparams)
915 # user friendly UI
938 # user friendly UI
916 self.params = dict(self.mandatoryparams)
939 self.params = dict(self.mandatoryparams)
917 self.params.update(dict(self.advisoryparams))
940 self.params.update(dict(self.advisoryparams))
918 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
941 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
919
942
920 def _payloadchunks(self, chunknum=0):
943 def _payloadchunks(self, chunknum=0):
921 '''seek to specified chunk and start yielding data'''
944 '''seek to specified chunk and start yielding data'''
922 if len(self._chunkindex) == 0:
945 if len(self._chunkindex) == 0:
923 assert chunknum == 0, 'Must start with chunk 0'
946 assert chunknum == 0, 'Must start with chunk 0'
924 self._chunkindex.append((0, super(unbundlepart, self).tell()))
947 self._chunkindex.append((0, super(unbundlepart, self).tell()))
925 else:
948 else:
926 assert chunknum < len(self._chunkindex), \
949 assert chunknum < len(self._chunkindex), \
927 'Unknown chunk %d' % chunknum
950 'Unknown chunk %d' % chunknum
928 super(unbundlepart, self).seek(self._chunkindex[chunknum][1])
951 super(unbundlepart, self).seek(self._chunkindex[chunknum][1])
929
952
930 pos = self._chunkindex[chunknum][0]
953 pos = self._chunkindex[chunknum][0]
931 payloadsize = self._unpack(_fpayloadsize)[0]
954 payloadsize = self._unpack(_fpayloadsize)[0]
932 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
955 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
933 while payloadsize:
956 while payloadsize:
934 if payloadsize == flaginterrupt:
957 if payloadsize == flaginterrupt:
935 # interruption detection, the handler will now read a
958 # interruption detection, the handler will now read a
936 # single part and process it.
959 # single part and process it.
937 interrupthandler(self.ui, self._fp)()
960 interrupthandler(self.ui, self._fp)()
938 elif payloadsize < 0:
961 elif payloadsize < 0:
939 msg = 'negative payload chunk size: %i' % payloadsize
962 msg = 'negative payload chunk size: %i' % payloadsize
940 raise error.BundleValueError(msg)
963 raise error.BundleValueError(msg)
941 else:
964 else:
942 result = self._readexact(payloadsize)
965 result = self._readexact(payloadsize)
943 chunknum += 1
966 chunknum += 1
944 pos += payloadsize
967 pos += payloadsize
945 if chunknum == len(self._chunkindex):
968 if chunknum == len(self._chunkindex):
946 self._chunkindex.append((pos,
969 self._chunkindex.append((pos,
947 super(unbundlepart, self).tell()))
970 super(unbundlepart, self).tell()))
948 yield result
971 yield result
949 payloadsize = self._unpack(_fpayloadsize)[0]
972 payloadsize = self._unpack(_fpayloadsize)[0]
950 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
973 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
951
974
952 def _findchunk(self, pos):
975 def _findchunk(self, pos):
953 '''for a given payload position, return a chunk number and offset'''
976 '''for a given payload position, return a chunk number and offset'''
954 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
977 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
955 if ppos == pos:
978 if ppos == pos:
956 return chunk, 0
979 return chunk, 0
957 elif ppos > pos:
980 elif ppos > pos:
958 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
981 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
959 raise ValueError('Unknown chunk')
982 raise ValueError('Unknown chunk')
960
983
961 def _readheader(self):
984 def _readheader(self):
962 """read the header and setup the object"""
985 """read the header and setup the object"""
963 typesize = self._unpackheader(_fparttypesize)[0]
986 typesize = self._unpackheader(_fparttypesize)[0]
964 self.type = self._fromheader(typesize)
987 self.type = self._fromheader(typesize)
965 indebug(self.ui, 'part type: "%s"' % self.type)
988 indebug(self.ui, 'part type: "%s"' % self.type)
966 self.id = self._unpackheader(_fpartid)[0]
989 self.id = self._unpackheader(_fpartid)[0]
967 indebug(self.ui, 'part id: "%s"' % self.id)
990 indebug(self.ui, 'part id: "%s"' % self.id)
968 # extract mandatory bit from type
991 # extract mandatory bit from type
969 self.mandatory = (self.type != self.type.lower())
992 self.mandatory = (self.type != self.type.lower())
970 self.type = self.type.lower()
993 self.type = self.type.lower()
971 ## reading parameters
994 ## reading parameters
972 # param count
995 # param count
973 mancount, advcount = self._unpackheader(_fpartparamcount)
996 mancount, advcount = self._unpackheader(_fpartparamcount)
974 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
997 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
975 # param size
998 # param size
976 fparamsizes = _makefpartparamsizes(mancount + advcount)
999 fparamsizes = _makefpartparamsizes(mancount + advcount)
977 paramsizes = self._unpackheader(fparamsizes)
1000 paramsizes = self._unpackheader(fparamsizes)
978 # make it a list of couple again
1001 # make it a list of couple again
979 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
1002 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
980 # split mandatory from advisory
1003 # split mandatory from advisory
981 mansizes = paramsizes[:mancount]
1004 mansizes = paramsizes[:mancount]
982 advsizes = paramsizes[mancount:]
1005 advsizes = paramsizes[mancount:]
983 # retrieve param value
1006 # retrieve param value
984 manparams = []
1007 manparams = []
985 for key, value in mansizes:
1008 for key, value in mansizes:
986 manparams.append((self._fromheader(key), self._fromheader(value)))
1009 manparams.append((self._fromheader(key), self._fromheader(value)))
987 advparams = []
1010 advparams = []
988 for key, value in advsizes:
1011 for key, value in advsizes:
989 advparams.append((self._fromheader(key), self._fromheader(value)))
1012 advparams.append((self._fromheader(key), self._fromheader(value)))
990 self._initparams(manparams, advparams)
1013 self._initparams(manparams, advparams)
991 ## part payload
1014 ## part payload
992 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1015 self._payloadstream = util.chunkbuffer(self._payloadchunks())
993 # we read the data, tell it
1016 # we read the data, tell it
994 self._initialized = True
1017 self._initialized = True
995
1018
996 def read(self, size=None):
1019 def read(self, size=None):
997 """read payload data"""
1020 """read payload data"""
998 if not self._initialized:
1021 if not self._initialized:
999 self._readheader()
1022 self._readheader()
1000 if size is None:
1023 if size is None:
1001 data = self._payloadstream.read()
1024 data = self._payloadstream.read()
1002 else:
1025 else:
1003 data = self._payloadstream.read(size)
1026 data = self._payloadstream.read(size)
1004 if size is None or len(data) < size:
1027 if size is None or len(data) < size:
1005 self.consumed = True
1028 self.consumed = True
1006 self._pos += len(data)
1029 self._pos += len(data)
1007 return data
1030 return data
1008
1031
1009 def tell(self):
1032 def tell(self):
1010 return self._pos
1033 return self._pos
1011
1034
1012 def seek(self, offset, whence=0):
1035 def seek(self, offset, whence=0):
1013 if whence == 0:
1036 if whence == 0:
1014 newpos = offset
1037 newpos = offset
1015 elif whence == 1:
1038 elif whence == 1:
1016 newpos = self._pos + offset
1039 newpos = self._pos + offset
1017 elif whence == 2:
1040 elif whence == 2:
1018 if not self.consumed:
1041 if not self.consumed:
1019 self.read()
1042 self.read()
1020 newpos = self._chunkindex[-1][0] - offset
1043 newpos = self._chunkindex[-1][0] - offset
1021 else:
1044 else:
1022 raise ValueError('Unknown whence value: %r' % (whence,))
1045 raise ValueError('Unknown whence value: %r' % (whence,))
1023
1046
1024 if newpos > self._chunkindex[-1][0] and not self.consumed:
1047 if newpos > self._chunkindex[-1][0] and not self.consumed:
1025 self.read()
1048 self.read()
1026 if not 0 <= newpos <= self._chunkindex[-1][0]:
1049 if not 0 <= newpos <= self._chunkindex[-1][0]:
1027 raise ValueError('Offset out of range')
1050 raise ValueError('Offset out of range')
1028
1051
1029 if self._pos != newpos:
1052 if self._pos != newpos:
1030 chunk, internaloffset = self._findchunk(newpos)
1053 chunk, internaloffset = self._findchunk(newpos)
1031 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1054 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1032 adjust = self.read(internaloffset)
1055 adjust = self.read(internaloffset)
1033 if len(adjust) != internaloffset:
1056 if len(adjust) != internaloffset:
1034 raise util.Abort(_('Seek failed\n'))
1057 raise util.Abort(_('Seek failed\n'))
1035 self._pos = newpos
1058 self._pos = newpos
1036
1059
1037 # These are only the static capabilities.
1060 # These are only the static capabilities.
1038 # Check the 'getrepocaps' function for the rest.
1061 # Check the 'getrepocaps' function for the rest.
1039 capabilities = {'HG20': (),
1062 capabilities = {'HG20': (),
1040 'listkeys': (),
1063 'listkeys': (),
1041 'pushkey': (),
1064 'pushkey': (),
1042 'digests': tuple(sorted(util.DIGESTS.keys())),
1065 'digests': tuple(sorted(util.DIGESTS.keys())),
1043 'remote-changegroup': ('http', 'https'),
1066 'remote-changegroup': ('http', 'https'),
1044 }
1067 }
1045
1068
1046 def getrepocaps(repo, allowpushback=False):
1069 def getrepocaps(repo, allowpushback=False):
1047 """return the bundle2 capabilities for a given repo
1070 """return the bundle2 capabilities for a given repo
1048
1071
1049 Exists to allow extensions (like evolution) to mutate the capabilities.
1072 Exists to allow extensions (like evolution) to mutate the capabilities.
1050 """
1073 """
1051 caps = capabilities.copy()
1074 caps = capabilities.copy()
1052 caps['changegroup'] = tuple(sorted(changegroup.packermap.keys()))
1075 caps['changegroup'] = tuple(sorted(changegroup.packermap.keys()))
1053 if obsolete.isenabled(repo, obsolete.exchangeopt):
1076 if obsolete.isenabled(repo, obsolete.exchangeopt):
1054 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1077 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1055 caps['obsmarkers'] = supportedformat
1078 caps['obsmarkers'] = supportedformat
1056 if allowpushback:
1079 if allowpushback:
1057 caps['pushback'] = ()
1080 caps['pushback'] = ()
1058 return caps
1081 return caps
1059
1082
1060 def bundle2caps(remote):
1083 def bundle2caps(remote):
1061 """return the bundle capabilities of a peer as dict"""
1084 """return the bundle capabilities of a peer as dict"""
1062 raw = remote.capable('bundle2')
1085 raw = remote.capable('bundle2')
1063 if not raw and raw != '':
1086 if not raw and raw != '':
1064 return {}
1087 return {}
1065 capsblob = urllib.unquote(remote.capable('bundle2'))
1088 capsblob = urllib.unquote(remote.capable('bundle2'))
1066 return decodecaps(capsblob)
1089 return decodecaps(capsblob)
1067
1090
1068 def obsmarkersversion(caps):
1091 def obsmarkersversion(caps):
1069 """extract the list of supported obsmarkers versions from a bundle2caps dict
1092 """extract the list of supported obsmarkers versions from a bundle2caps dict
1070 """
1093 """
1071 obscaps = caps.get('obsmarkers', ())
1094 obscaps = caps.get('obsmarkers', ())
1072 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1095 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1073
1096
1074 @parthandler('changegroup', ('version',))
1097 @parthandler('changegroup', ('version',))
1075 def handlechangegroup(op, inpart):
1098 def handlechangegroup(op, inpart):
1076 """apply a changegroup part on the repo
1099 """apply a changegroup part on the repo
1077
1100
1078 This is a very early implementation that will massive rework before being
1101 This is a very early implementation that will massive rework before being
1079 inflicted to any end-user.
1102 inflicted to any end-user.
1080 """
1103 """
1081 # Make sure we trigger a transaction creation
1104 # Make sure we trigger a transaction creation
1082 #
1105 #
1083 # The addchangegroup function will get a transaction object by itself, but
1106 # The addchangegroup function will get a transaction object by itself, but
1084 # we need to make sure we trigger the creation of a transaction object used
1107 # we need to make sure we trigger the creation of a transaction object used
1085 # for the whole processing scope.
1108 # for the whole processing scope.
1086 op.gettransaction()
1109 op.gettransaction()
1087 unpackerversion = inpart.params.get('version', '01')
1110 unpackerversion = inpart.params.get('version', '01')
1088 # We should raise an appropriate exception here
1111 # We should raise an appropriate exception here
1089 unpacker = changegroup.packermap[unpackerversion][1]
1112 unpacker = changegroup.packermap[unpackerversion][1]
1090 cg = unpacker(inpart, 'UN')
1113 cg = unpacker(inpart, 'UN')
1091 # the source and url passed here are overwritten by the one contained in
1114 # the source and url passed here are overwritten by the one contained in
1092 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1115 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1093 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
1116 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
1094 op.records.add('changegroup', {'return': ret})
1117 op.records.add('changegroup', {'return': ret})
1095 if op.reply is not None:
1118 if op.reply is not None:
1096 # This is definitely not the final form of this
1119 # This is definitely not the final form of this
1097 # return. But one need to start somewhere.
1120 # return. But one need to start somewhere.
1098 part = op.reply.newpart('reply:changegroup', mandatory=False)
1121 part = op.reply.newpart('reply:changegroup', mandatory=False)
1099 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1122 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1100 part.addparam('return', '%i' % ret, mandatory=False)
1123 part.addparam('return', '%i' % ret, mandatory=False)
1101 assert not inpart.read()
1124 assert not inpart.read()
1102
1125
1103 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1126 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1104 ['digest:%s' % k for k in util.DIGESTS.keys()])
1127 ['digest:%s' % k for k in util.DIGESTS.keys()])
1105 @parthandler('remote-changegroup', _remotechangegroupparams)
1128 @parthandler('remote-changegroup', _remotechangegroupparams)
1106 def handleremotechangegroup(op, inpart):
1129 def handleremotechangegroup(op, inpart):
1107 """apply a bundle10 on the repo, given an url and validation information
1130 """apply a bundle10 on the repo, given an url and validation information
1108
1131
1109 All the information about the remote bundle to import are given as
1132 All the information about the remote bundle to import are given as
1110 parameters. The parameters include:
1133 parameters. The parameters include:
1111 - url: the url to the bundle10.
1134 - url: the url to the bundle10.
1112 - size: the bundle10 file size. It is used to validate what was
1135 - size: the bundle10 file size. It is used to validate what was
1113 retrieved by the client matches the server knowledge about the bundle.
1136 retrieved by the client matches the server knowledge about the bundle.
1114 - digests: a space separated list of the digest types provided as
1137 - digests: a space separated list of the digest types provided as
1115 parameters.
1138 parameters.
1116 - digest:<digest-type>: the hexadecimal representation of the digest with
1139 - digest:<digest-type>: the hexadecimal representation of the digest with
1117 that name. Like the size, it is used to validate what was retrieved by
1140 that name. Like the size, it is used to validate what was retrieved by
1118 the client matches what the server knows about the bundle.
1141 the client matches what the server knows about the bundle.
1119
1142
1120 When multiple digest types are given, all of them are checked.
1143 When multiple digest types are given, all of them are checked.
1121 """
1144 """
1122 try:
1145 try:
1123 raw_url = inpart.params['url']
1146 raw_url = inpart.params['url']
1124 except KeyError:
1147 except KeyError:
1125 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1148 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1126 parsed_url = util.url(raw_url)
1149 parsed_url = util.url(raw_url)
1127 if parsed_url.scheme not in capabilities['remote-changegroup']:
1150 if parsed_url.scheme not in capabilities['remote-changegroup']:
1128 raise util.Abort(_('remote-changegroup does not support %s urls') %
1151 raise util.Abort(_('remote-changegroup does not support %s urls') %
1129 parsed_url.scheme)
1152 parsed_url.scheme)
1130
1153
1131 try:
1154 try:
1132 size = int(inpart.params['size'])
1155 size = int(inpart.params['size'])
1133 except ValueError:
1156 except ValueError:
1134 raise util.Abort(_('remote-changegroup: invalid value for param "%s"')
1157 raise util.Abort(_('remote-changegroup: invalid value for param "%s"')
1135 % 'size')
1158 % 'size')
1136 except KeyError:
1159 except KeyError:
1137 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1160 raise util.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1138
1161
1139 digests = {}
1162 digests = {}
1140 for typ in inpart.params.get('digests', '').split():
1163 for typ in inpart.params.get('digests', '').split():
1141 param = 'digest:%s' % typ
1164 param = 'digest:%s' % typ
1142 try:
1165 try:
1143 value = inpart.params[param]
1166 value = inpart.params[param]
1144 except KeyError:
1167 except KeyError:
1145 raise util.Abort(_('remote-changegroup: missing "%s" param') %
1168 raise util.Abort(_('remote-changegroup: missing "%s" param') %
1146 param)
1169 param)
1147 digests[typ] = value
1170 digests[typ] = value
1148
1171
1149 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1172 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1150
1173
1151 # Make sure we trigger a transaction creation
1174 # Make sure we trigger a transaction creation
1152 #
1175 #
1153 # The addchangegroup function will get a transaction object by itself, but
1176 # The addchangegroup function will get a transaction object by itself, but
1154 # we need to make sure we trigger the creation of a transaction object used
1177 # we need to make sure we trigger the creation of a transaction object used
1155 # for the whole processing scope.
1178 # for the whole processing scope.
1156 op.gettransaction()
1179 op.gettransaction()
1157 import exchange
1180 import exchange
1158 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1181 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1159 if not isinstance(cg, changegroup.cg1unpacker):
1182 if not isinstance(cg, changegroup.cg1unpacker):
1160 raise util.Abort(_('%s: not a bundle version 1.0') %
1183 raise util.Abort(_('%s: not a bundle version 1.0') %
1161 util.hidepassword(raw_url))
1184 util.hidepassword(raw_url))
1162 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
1185 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
1163 op.records.add('changegroup', {'return': ret})
1186 op.records.add('changegroup', {'return': ret})
1164 if op.reply is not None:
1187 if op.reply is not None:
1165 # This is definitely not the final form of this
1188 # This is definitely not the final form of this
1166 # return. But one need to start somewhere.
1189 # return. But one need to start somewhere.
1167 part = op.reply.newpart('reply:changegroup')
1190 part = op.reply.newpart('reply:changegroup')
1168 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1191 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1169 part.addparam('return', '%i' % ret, mandatory=False)
1192 part.addparam('return', '%i' % ret, mandatory=False)
1170 try:
1193 try:
1171 real_part.validate()
1194 real_part.validate()
1172 except util.Abort, e:
1195 except util.Abort, e:
1173 raise util.Abort(_('bundle at %s is corrupted:\n%s') %
1196 raise util.Abort(_('bundle at %s is corrupted:\n%s') %
1174 (util.hidepassword(raw_url), str(e)))
1197 (util.hidepassword(raw_url), str(e)))
1175 assert not inpart.read()
1198 assert not inpart.read()
1176
1199
1177 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1200 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1178 def handlereplychangegroup(op, inpart):
1201 def handlereplychangegroup(op, inpart):
1179 ret = int(inpart.params['return'])
1202 ret = int(inpart.params['return'])
1180 replyto = int(inpart.params['in-reply-to'])
1203 replyto = int(inpart.params['in-reply-to'])
1181 op.records.add('changegroup', {'return': ret}, replyto)
1204 op.records.add('changegroup', {'return': ret}, replyto)
1182
1205
1183 @parthandler('check:heads')
1206 @parthandler('check:heads')
1184 def handlecheckheads(op, inpart):
1207 def handlecheckheads(op, inpart):
1185 """check that head of the repo did not change
1208 """check that head of the repo did not change
1186
1209
1187 This is used to detect a push race when using unbundle.
1210 This is used to detect a push race when using unbundle.
1188 This replaces the "heads" argument of unbundle."""
1211 This replaces the "heads" argument of unbundle."""
1189 h = inpart.read(20)
1212 h = inpart.read(20)
1190 heads = []
1213 heads = []
1191 while len(h) == 20:
1214 while len(h) == 20:
1192 heads.append(h)
1215 heads.append(h)
1193 h = inpart.read(20)
1216 h = inpart.read(20)
1194 assert not h
1217 assert not h
1195 if heads != op.repo.heads():
1218 if heads != op.repo.heads():
1196 raise error.PushRaced('repository changed while pushing - '
1219 raise error.PushRaced('repository changed while pushing - '
1197 'please try again')
1220 'please try again')
1198
1221
1199 @parthandler('output')
1222 @parthandler('output')
1200 def handleoutput(op, inpart):
1223 def handleoutput(op, inpart):
1201 """forward output captured on the server to the client"""
1224 """forward output captured on the server to the client"""
1202 for line in inpart.read().splitlines():
1225 for line in inpart.read().splitlines():
1203 op.ui.status(('remote: %s\n' % line))
1226 op.ui.status(('remote: %s\n' % line))
1204
1227
1205 @parthandler('replycaps')
1228 @parthandler('replycaps')
1206 def handlereplycaps(op, inpart):
1229 def handlereplycaps(op, inpart):
1207 """Notify that a reply bundle should be created
1230 """Notify that a reply bundle should be created
1208
1231
1209 The payload contains the capabilities information for the reply"""
1232 The payload contains the capabilities information for the reply"""
1210 caps = decodecaps(inpart.read())
1233 caps = decodecaps(inpart.read())
1211 if op.reply is None:
1234 if op.reply is None:
1212 op.reply = bundle20(op.ui, caps)
1235 op.reply = bundle20(op.ui, caps)
1213
1236
1214 @parthandler('error:abort', ('message', 'hint'))
1237 @parthandler('error:abort', ('message', 'hint'))
1215 def handleerrorabort(op, inpart):
1238 def handleerrorabort(op, inpart):
1216 """Used to transmit abort error over the wire"""
1239 """Used to transmit abort error over the wire"""
1217 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
1240 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
1218
1241
1219 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1242 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1220 def handleerrorunsupportedcontent(op, inpart):
1243 def handleerrorunsupportedcontent(op, inpart):
1221 """Used to transmit unknown content error over the wire"""
1244 """Used to transmit unknown content error over the wire"""
1222 kwargs = {}
1245 kwargs = {}
1223 parttype = inpart.params.get('parttype')
1246 parttype = inpart.params.get('parttype')
1224 if parttype is not None:
1247 if parttype is not None:
1225 kwargs['parttype'] = parttype
1248 kwargs['parttype'] = parttype
1226 params = inpart.params.get('params')
1249 params = inpart.params.get('params')
1227 if params is not None:
1250 if params is not None:
1228 kwargs['params'] = params.split('\0')
1251 kwargs['params'] = params.split('\0')
1229
1252
1230 raise error.UnsupportedPartError(**kwargs)
1253 raise error.UnsupportedPartError(**kwargs)
1231
1254
1232 @parthandler('error:pushraced', ('message',))
1255 @parthandler('error:pushraced', ('message',))
1233 def handleerrorpushraced(op, inpart):
1256 def handleerrorpushraced(op, inpart):
1234 """Used to transmit push race error over the wire"""
1257 """Used to transmit push race error over the wire"""
1235 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1258 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1236
1259
1237 @parthandler('listkeys', ('namespace',))
1260 @parthandler('listkeys', ('namespace',))
1238 def handlelistkeys(op, inpart):
1261 def handlelistkeys(op, inpart):
1239 """retrieve pushkey namespace content stored in a bundle2"""
1262 """retrieve pushkey namespace content stored in a bundle2"""
1240 namespace = inpart.params['namespace']
1263 namespace = inpart.params['namespace']
1241 r = pushkey.decodekeys(inpart.read())
1264 r = pushkey.decodekeys(inpart.read())
1242 op.records.add('listkeys', (namespace, r))
1265 op.records.add('listkeys', (namespace, r))
1243
1266
1244 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1267 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1245 def handlepushkey(op, inpart):
1268 def handlepushkey(op, inpart):
1246 """process a pushkey request"""
1269 """process a pushkey request"""
1247 dec = pushkey.decode
1270 dec = pushkey.decode
1248 namespace = dec(inpart.params['namespace'])
1271 namespace = dec(inpart.params['namespace'])
1249 key = dec(inpart.params['key'])
1272 key = dec(inpart.params['key'])
1250 old = dec(inpart.params['old'])
1273 old = dec(inpart.params['old'])
1251 new = dec(inpart.params['new'])
1274 new = dec(inpart.params['new'])
1252 ret = op.repo.pushkey(namespace, key, old, new)
1275 ret = op.repo.pushkey(namespace, key, old, new)
1253 record = {'namespace': namespace,
1276 record = {'namespace': namespace,
1254 'key': key,
1277 'key': key,
1255 'old': old,
1278 'old': old,
1256 'new': new}
1279 'new': new}
1257 op.records.add('pushkey', record)
1280 op.records.add('pushkey', record)
1258 if op.reply is not None:
1281 if op.reply is not None:
1259 rpart = op.reply.newpart('reply:pushkey')
1282 rpart = op.reply.newpart('reply:pushkey')
1260 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1283 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1261 rpart.addparam('return', '%i' % ret, mandatory=False)
1284 rpart.addparam('return', '%i' % ret, mandatory=False)
1262
1285
1263 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
1286 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
1264 def handlepushkeyreply(op, inpart):
1287 def handlepushkeyreply(op, inpart):
1265 """retrieve the result of a pushkey request"""
1288 """retrieve the result of a pushkey request"""
1266 ret = int(inpart.params['return'])
1289 ret = int(inpart.params['return'])
1267 partid = int(inpart.params['in-reply-to'])
1290 partid = int(inpart.params['in-reply-to'])
1268 op.records.add('pushkey', {'return': ret}, partid)
1291 op.records.add('pushkey', {'return': ret}, partid)
1269
1292
1270 @parthandler('obsmarkers')
1293 @parthandler('obsmarkers')
1271 def handleobsmarker(op, inpart):
1294 def handleobsmarker(op, inpart):
1272 """add a stream of obsmarkers to the repo"""
1295 """add a stream of obsmarkers to the repo"""
1273 tr = op.gettransaction()
1296 tr = op.gettransaction()
1274 markerdata = inpart.read()
1297 markerdata = inpart.read()
1275 if op.ui.config('experimental', 'obsmarkers-exchange-debug', False):
1298 if op.ui.config('experimental', 'obsmarkers-exchange-debug', False):
1276 op.ui.write(('obsmarker-exchange: %i bytes received\n')
1299 op.ui.write(('obsmarker-exchange: %i bytes received\n')
1277 % len(markerdata))
1300 % len(markerdata))
1278 new = op.repo.obsstore.mergemarkers(tr, markerdata)
1301 new = op.repo.obsstore.mergemarkers(tr, markerdata)
1279 if new:
1302 if new:
1280 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
1303 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
1281 op.records.add('obsmarkers', {'new': new})
1304 op.records.add('obsmarkers', {'new': new})
1282 if op.reply is not None:
1305 if op.reply is not None:
1283 rpart = op.reply.newpart('reply:obsmarkers')
1306 rpart = op.reply.newpart('reply:obsmarkers')
1284 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1307 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1285 rpart.addparam('new', '%i' % new, mandatory=False)
1308 rpart.addparam('new', '%i' % new, mandatory=False)
1286
1309
1287
1310
1288 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
1311 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
1289 def handlepushkeyreply(op, inpart):
1312 def handlepushkeyreply(op, inpart):
1290 """retrieve the result of a pushkey request"""
1313 """retrieve the result of a pushkey request"""
1291 ret = int(inpart.params['new'])
1314 ret = int(inpart.params['new'])
1292 partid = int(inpart.params['in-reply-to'])
1315 partid = int(inpart.params['in-reply-to'])
1293 op.records.add('obsmarkers', {'new': ret}, partid)
1316 op.records.add('obsmarkers', {'new': ret}, partid)
@@ -1,833 +1,841
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 0: "test:empty"
394 bundle2-output: part 0: "test:empty"
394 bundle2-output: header chunk size: 17
395 bundle2-output: header chunk size: 17
395 bundle2-output: closing payload chunk
396 bundle2-output: closing payload chunk
396 bundle2-output: bundle part: "test:empty"
397 bundle2-output: bundle part: "test:empty"
398 bundle2-output-part: "test:empty" (advisory) empty payload
397 bundle2-output: part 1: "test:empty"
399 bundle2-output: part 1: "test:empty"
398 bundle2-output: header chunk size: 17
400 bundle2-output: header chunk size: 17
399 bundle2-output: closing payload chunk
401 bundle2-output: closing payload chunk
400 bundle2-output: bundle part: "test:song"
402 bundle2-output: bundle part: "test:song"
403 bundle2-output-part: "test:song" (advisory) 178 bytes payload
401 bundle2-output: part 2: "test:song"
404 bundle2-output: part 2: "test:song"
402 bundle2-output: header chunk size: 16
405 bundle2-output: header chunk size: 16
403 bundle2-output: payload chunk size: 178
406 bundle2-output: payload chunk size: 178
404 bundle2-output: closing payload chunk
407 bundle2-output: closing payload chunk
405 bundle2-output: bundle part: "test:debugreply"
408 bundle2-output: bundle part: "test:debugreply"
409 bundle2-output-part: "test:debugreply" (advisory) empty payload
406 bundle2-output: part 3: "test:debugreply"
410 bundle2-output: part 3: "test:debugreply"
407 bundle2-output: header chunk size: 22
411 bundle2-output: header chunk size: 22
408 bundle2-output: closing payload chunk
412 bundle2-output: closing payload chunk
409 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
410 bundle2-output: part 4: "test:math"
415 bundle2-output: part 4: "test:math"
411 bundle2-output: header chunk size: 43
416 bundle2-output: header chunk size: 43
412 bundle2-output: payload chunk size: 2
417 bundle2-output: payload chunk size: 2
413 bundle2-output: closing payload chunk
418 bundle2-output: closing payload chunk
414 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
415 bundle2-output: part 5: "test:song"
421 bundle2-output: part 5: "test:song"
416 bundle2-output: header chunk size: 29
422 bundle2-output: header chunk size: 29
417 bundle2-output: closing payload chunk
423 bundle2-output: closing payload chunk
418 bundle2-output: bundle part: "test:ping"
424 bundle2-output: bundle part: "test:ping"
425 bundle2-output-part: "test:ping" (advisory) empty payload
419 bundle2-output: part 6: "test:ping"
426 bundle2-output: part 6: "test:ping"
420 bundle2-output: header chunk size: 16
427 bundle2-output: header chunk size: 16
421 bundle2-output: closing payload chunk
428 bundle2-output: closing payload chunk
422 bundle2-output: end of bundle
429 bundle2-output: end of bundle
423
430
424 $ cat ../parts.hg2
431 $ cat ../parts.hg2
425 HG20\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
432 HG20\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
426 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)
427 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)
428 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
435 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
429 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)
430
437
431
438
432 $ hg statbundle2 < ../parts.hg2
439 $ hg statbundle2 < ../parts.hg2
433 options count: 0
440 options count: 0
434 :test:empty:
441 :test:empty:
435 mandatory: 0
442 mandatory: 0
436 advisory: 0
443 advisory: 0
437 payload: 0 bytes
444 payload: 0 bytes
438 :test:empty:
445 :test:empty:
439 mandatory: 0
446 mandatory: 0
440 advisory: 0
447 advisory: 0
441 payload: 0 bytes
448 payload: 0 bytes
442 :test:song:
449 :test:song:
443 mandatory: 0
450 mandatory: 0
444 advisory: 0
451 advisory: 0
445 payload: 178 bytes
452 payload: 178 bytes
446 :test:debugreply:
453 :test:debugreply:
447 mandatory: 0
454 mandatory: 0
448 advisory: 0
455 advisory: 0
449 payload: 0 bytes
456 payload: 0 bytes
450 :test:math:
457 :test:math:
451 mandatory: 2
458 mandatory: 2
452 advisory: 1
459 advisory: 1
453 payload: 2 bytes
460 payload: 2 bytes
454 :test:song:
461 :test:song:
455 mandatory: 1
462 mandatory: 1
456 advisory: 0
463 advisory: 0
457 payload: 0 bytes
464 payload: 0 bytes
458 :test:ping:
465 :test:ping:
459 mandatory: 0
466 mandatory: 0
460 advisory: 0
467 advisory: 0
461 payload: 0 bytes
468 payload: 0 bytes
462 parts count: 7
469 parts count: 7
463
470
464 $ hg statbundle2 --debug --config progress.debug=true < ../parts.hg2
471 $ hg statbundle2 --debug --config progress.debug=true < ../parts.hg2
465 bundle2-input: start processing of HG20 stream
472 bundle2-input: start processing of HG20 stream
466 bundle2-input: reading bundle2 stream parameters
473 bundle2-input: reading bundle2 stream parameters
467 options count: 0
474 options count: 0
468 bundle2-input: start extraction of bundle2 parts
475 bundle2-input: start extraction of bundle2 parts
469 bundle2-input: part header size: 17
476 bundle2-input: part header size: 17
470 bundle2-input: part type: "test:empty"
477 bundle2-input: part type: "test:empty"
471 bundle2-input: part id: "0"
478 bundle2-input: part id: "0"
472 bundle2-input: part parameters: 0
479 bundle2-input: part parameters: 0
473 :test:empty:
480 :test:empty:
474 mandatory: 0
481 mandatory: 0
475 advisory: 0
482 advisory: 0
476 bundle2-input: payload chunk size: 0
483 bundle2-input: payload chunk size: 0
477 payload: 0 bytes
484 payload: 0 bytes
478 bundle2-input: part header size: 17
485 bundle2-input: part header size: 17
479 bundle2-input: part type: "test:empty"
486 bundle2-input: part type: "test:empty"
480 bundle2-input: part id: "1"
487 bundle2-input: part id: "1"
481 bundle2-input: part parameters: 0
488 bundle2-input: part parameters: 0
482 :test:empty:
489 :test:empty:
483 mandatory: 0
490 mandatory: 0
484 advisory: 0
491 advisory: 0
485 bundle2-input: payload chunk size: 0
492 bundle2-input: payload chunk size: 0
486 payload: 0 bytes
493 payload: 0 bytes
487 bundle2-input: part header size: 16
494 bundle2-input: part header size: 16
488 bundle2-input: part type: "test:song"
495 bundle2-input: part type: "test:song"
489 bundle2-input: part id: "2"
496 bundle2-input: part id: "2"
490 bundle2-input: part parameters: 0
497 bundle2-input: part parameters: 0
491 :test:song:
498 :test:song:
492 mandatory: 0
499 mandatory: 0
493 advisory: 0
500 advisory: 0
494 bundle2-input: payload chunk size: 178
501 bundle2-input: payload chunk size: 178
495 bundle2-input: payload chunk size: 0
502 bundle2-input: payload chunk size: 0
496 payload: 178 bytes
503 payload: 178 bytes
497 bundle2-input: part header size: 22
504 bundle2-input: part header size: 22
498 bundle2-input: part type: "test:debugreply"
505 bundle2-input: part type: "test:debugreply"
499 bundle2-input: part id: "3"
506 bundle2-input: part id: "3"
500 bundle2-input: part parameters: 0
507 bundle2-input: part parameters: 0
501 :test:debugreply:
508 :test:debugreply:
502 mandatory: 0
509 mandatory: 0
503 advisory: 0
510 advisory: 0
504 bundle2-input: payload chunk size: 0
511 bundle2-input: payload chunk size: 0
505 payload: 0 bytes
512 payload: 0 bytes
506 bundle2-input: part header size: 43
513 bundle2-input: part header size: 43
507 bundle2-input: part type: "test:math"
514 bundle2-input: part type: "test:math"
508 bundle2-input: part id: "4"
515 bundle2-input: part id: "4"
509 bundle2-input: part parameters: 3
516 bundle2-input: part parameters: 3
510 :test:math:
517 :test:math:
511 mandatory: 2
518 mandatory: 2
512 advisory: 1
519 advisory: 1
513 bundle2-input: payload chunk size: 2
520 bundle2-input: payload chunk size: 2
514 bundle2-input: payload chunk size: 0
521 bundle2-input: payload chunk size: 0
515 payload: 2 bytes
522 payload: 2 bytes
516 bundle2-input: part header size: 29
523 bundle2-input: part header size: 29
517 bundle2-input: part type: "test:song"
524 bundle2-input: part type: "test:song"
518 bundle2-input: part id: "5"
525 bundle2-input: part id: "5"
519 bundle2-input: part parameters: 1
526 bundle2-input: part parameters: 1
520 :test:song:
527 :test:song:
521 mandatory: 1
528 mandatory: 1
522 advisory: 0
529 advisory: 0
523 bundle2-input: payload chunk size: 0
530 bundle2-input: payload chunk size: 0
524 payload: 0 bytes
531 payload: 0 bytes
525 bundle2-input: part header size: 16
532 bundle2-input: part header size: 16
526 bundle2-input: part type: "test:ping"
533 bundle2-input: part type: "test:ping"
527 bundle2-input: part id: "6"
534 bundle2-input: part id: "6"
528 bundle2-input: part parameters: 0
535 bundle2-input: part parameters: 0
529 :test:ping:
536 :test:ping:
530 mandatory: 0
537 mandatory: 0
531 advisory: 0
538 advisory: 0
532 bundle2-input: payload chunk size: 0
539 bundle2-input: payload chunk size: 0
533 payload: 0 bytes
540 payload: 0 bytes
534 bundle2-input: part header size: 0
541 bundle2-input: part header size: 0
535 bundle2-input: end of bundle2 stream
542 bundle2-input: end of bundle2 stream
536 parts count: 7
543 parts count: 7
537
544
538 Test actual unbundling of test part
545 Test actual unbundling of test part
539 =======================================
546 =======================================
540
547
541 Process the bundle
548 Process the bundle
542
549
543 $ hg unbundle2 --debug --config progress.debug=true < ../parts.hg2
550 $ hg unbundle2 --debug --config progress.debug=true < ../parts.hg2
544 bundle2-input: start processing of HG20 stream
551 bundle2-input: start processing of HG20 stream
545 bundle2-input: reading bundle2 stream parameters
552 bundle2-input: reading bundle2 stream parameters
546 bundle2-input: start extraction of bundle2 parts
553 bundle2-input: start extraction of bundle2 parts
547 bundle2-input: part header size: 17
554 bundle2-input: part header size: 17
548 bundle2-input: part type: "test:empty"
555 bundle2-input: part type: "test:empty"
549 bundle2-input: part id: "0"
556 bundle2-input: part id: "0"
550 bundle2-input: part parameters: 0
557 bundle2-input: part parameters: 0
551 bundle2-input: ignoring unsupported advisory part test:empty
558 bundle2-input: ignoring unsupported advisory part test:empty
552 bundle2-input: payload chunk size: 0
559 bundle2-input: payload chunk size: 0
553 bundle2-input: part header size: 17
560 bundle2-input: part header size: 17
554 bundle2-input: part type: "test:empty"
561 bundle2-input: part type: "test:empty"
555 bundle2-input: part id: "1"
562 bundle2-input: part id: "1"
556 bundle2-input: part parameters: 0
563 bundle2-input: part parameters: 0
557 bundle2-input: ignoring unsupported advisory part test:empty
564 bundle2-input: ignoring unsupported advisory part test:empty
558 bundle2-input: payload chunk size: 0
565 bundle2-input: payload chunk size: 0
559 bundle2-input: part header size: 16
566 bundle2-input: part header size: 16
560 bundle2-input: part type: "test:song"
567 bundle2-input: part type: "test:song"
561 bundle2-input: part id: "2"
568 bundle2-input: part id: "2"
562 bundle2-input: part parameters: 0
569 bundle2-input: part parameters: 0
563 bundle2-input: found a handler for part 'test:song'
570 bundle2-input: found a handler for part 'test:song'
564 The choir starts singing:
571 The choir starts singing:
565 bundle2-input: payload chunk size: 178
572 bundle2-input: payload chunk size: 178
566 bundle2-input: payload chunk size: 0
573 bundle2-input: payload chunk size: 0
567 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
574 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
568 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
575 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
569 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
576 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
570 bundle2-input: part header size: 22
577 bundle2-input: part header size: 22
571 bundle2-input: part type: "test:debugreply"
578 bundle2-input: part type: "test:debugreply"
572 bundle2-input: part id: "3"
579 bundle2-input: part id: "3"
573 bundle2-input: part parameters: 0
580 bundle2-input: part parameters: 0
574 bundle2-input: found a handler for part 'test:debugreply'
581 bundle2-input: found a handler for part 'test:debugreply'
575 debugreply: no reply
582 debugreply: no reply
576 bundle2-input: payload chunk size: 0
583 bundle2-input: payload chunk size: 0
577 bundle2-input: part header size: 43
584 bundle2-input: part header size: 43
578 bundle2-input: part type: "test:math"
585 bundle2-input: part type: "test:math"
579 bundle2-input: part id: "4"
586 bundle2-input: part id: "4"
580 bundle2-input: part parameters: 3
587 bundle2-input: part parameters: 3
581 bundle2-input: ignoring unsupported advisory part test:math
588 bundle2-input: ignoring unsupported advisory part test:math
582 bundle2-input: payload chunk size: 2
589 bundle2-input: payload chunk size: 2
583 bundle2-input: payload chunk size: 0
590 bundle2-input: payload chunk size: 0
584 bundle2-input: part header size: 29
591 bundle2-input: part header size: 29
585 bundle2-input: part type: "test:song"
592 bundle2-input: part type: "test:song"
586 bundle2-input: part id: "5"
593 bundle2-input: part id: "5"
587 bundle2-input: part parameters: 1
594 bundle2-input: part parameters: 1
588 bundle2-input: found a handler for part 'test:song'
595 bundle2-input: found a handler for part 'test:song'
589 bundle2-input: ignoring unsupported advisory part test:song - randomparam
596 bundle2-input: ignoring unsupported advisory part test:song - randomparam
590 bundle2-input: payload chunk size: 0
597 bundle2-input: payload chunk size: 0
591 bundle2-input: part header size: 16
598 bundle2-input: part header size: 16
592 bundle2-input: part type: "test:ping"
599 bundle2-input: part type: "test:ping"
593 bundle2-input: part id: "6"
600 bundle2-input: part id: "6"
594 bundle2-input: part parameters: 0
601 bundle2-input: part parameters: 0
595 bundle2-input: found a handler for part 'test:ping'
602 bundle2-input: found a handler for part 'test:ping'
596 received ping request (id 6)
603 received ping request (id 6)
597 bundle2-input: payload chunk size: 0
604 bundle2-input: payload chunk size: 0
598 bundle2-input: part header size: 0
605 bundle2-input: part header size: 0
599 bundle2-input: end of bundle2 stream
606 bundle2-input: end of bundle2 stream
600 0 unread bytes
607 0 unread bytes
601 3 total verses sung
608 3 total verses sung
602
609
603 Unbundle with an unknown mandatory part
610 Unbundle with an unknown mandatory part
604 (should abort)
611 (should abort)
605
612
606 $ hg bundle2 --parts --unknown ../unknown.hg2
613 $ hg bundle2 --parts --unknown ../unknown.hg2
607
614
608 $ hg unbundle2 < ../unknown.hg2
615 $ hg unbundle2 < ../unknown.hg2
609 The choir starts singing:
616 The choir starts singing:
610 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
617 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
611 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
618 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
612 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
619 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
613 debugreply: no reply
620 debugreply: no reply
614 0 unread bytes
621 0 unread bytes
615 abort: missing support for test:unknown
622 abort: missing support for test:unknown
616 [255]
623 [255]
617
624
618 Unbundle with an unknown mandatory part parameters
625 Unbundle with an unknown mandatory part parameters
619 (should abort)
626 (should abort)
620
627
621 $ hg bundle2 --unknownparams ../unknown.hg2
628 $ hg bundle2 --unknownparams ../unknown.hg2
622
629
623 $ hg unbundle2 < ../unknown.hg2
630 $ hg unbundle2 < ../unknown.hg2
624 0 unread bytes
631 0 unread bytes
625 abort: missing support for test:song - randomparams
632 abort: missing support for test:song - randomparams
626 [255]
633 [255]
627
634
628 unbundle with a reply
635 unbundle with a reply
629
636
630 $ hg bundle2 --parts --reply ../parts-reply.hg2
637 $ hg bundle2 --parts --reply ../parts-reply.hg2
631 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
638 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
632 0 unread bytes
639 0 unread bytes
633 3 total verses sung
640 3 total verses sung
634
641
635 The reply is a bundle
642 The reply is a bundle
636
643
637 $ cat ../reply.hg2
644 $ cat ../reply.hg2
638 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)
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)
639 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
646 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
640 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
647 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
641 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
648 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
642 \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)
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)
643 debugreply: 'city=!'
650 debugreply: 'city=!'
644 debugreply: 'celeste,ville'
651 debugreply: 'celeste,ville'
645 debugreply: 'elephants'
652 debugreply: 'elephants'
646 debugreply: 'babar'
653 debugreply: 'babar'
647 debugreply: 'celeste'
654 debugreply: 'celeste'
648 debugreply: 'ping-pong'
655 debugreply: 'ping-pong'
649 \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)
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)
650 replying to ping request (id 7)
657 replying to ping request (id 7)
651 \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
658 \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
652
659
653 The reply is valid
660 The reply is valid
654
661
655 $ hg statbundle2 < ../reply.hg2
662 $ hg statbundle2 < ../reply.hg2
656 options count: 0
663 options count: 0
657 :output:
664 :output:
658 mandatory: 0
665 mandatory: 0
659 advisory: 1
666 advisory: 1
660 payload: 217 bytes
667 payload: 217 bytes
661 :output:
668 :output:
662 mandatory: 0
669 mandatory: 0
663 advisory: 1
670 advisory: 1
664 payload: 201 bytes
671 payload: 201 bytes
665 :test:pong:
672 :test:pong:
666 mandatory: 1
673 mandatory: 1
667 advisory: 0
674 advisory: 0
668 payload: 0 bytes
675 payload: 0 bytes
669 :output:
676 :output:
670 mandatory: 0
677 mandatory: 0
671 advisory: 1
678 advisory: 1
672 payload: 61 bytes
679 payload: 61 bytes
673 parts count: 4
680 parts count: 4
674
681
675 Unbundle the reply to get the output:
682 Unbundle the reply to get the output:
676
683
677 $ hg unbundle2 < ../reply.hg2
684 $ hg unbundle2 < ../reply.hg2
678 remote: The choir starts singing:
685 remote: The choir starts singing:
679 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
686 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
680 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
687 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
681 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
688 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
682 remote: debugreply: capabilities:
689 remote: debugreply: capabilities:
683 remote: debugreply: 'city=!'
690 remote: debugreply: 'city=!'
684 remote: debugreply: 'celeste,ville'
691 remote: debugreply: 'celeste,ville'
685 remote: debugreply: 'elephants'
692 remote: debugreply: 'elephants'
686 remote: debugreply: 'babar'
693 remote: debugreply: 'babar'
687 remote: debugreply: 'celeste'
694 remote: debugreply: 'celeste'
688 remote: debugreply: 'ping-pong'
695 remote: debugreply: 'ping-pong'
689 remote: received ping request (id 7)
696 remote: received ping request (id 7)
690 remote: replying to ping request (id 7)
697 remote: replying to ping request (id 7)
691 0 unread bytes
698 0 unread bytes
692
699
693 Test push race detection
700 Test push race detection
694
701
695 $ hg bundle2 --pushrace ../part-race.hg2
702 $ hg bundle2 --pushrace ../part-race.hg2
696
703
697 $ hg unbundle2 < ../part-race.hg2
704 $ hg unbundle2 < ../part-race.hg2
698 0 unread bytes
705 0 unread bytes
699 abort: push race: repository changed while pushing - please try again
706 abort: push race: repository changed while pushing - please try again
700 [255]
707 [255]
701
708
702 Support for changegroup
709 Support for changegroup
703 ===================================
710 ===================================
704
711
705 $ hg unbundle $TESTDIR/bundles/rebase.hg
712 $ hg unbundle $TESTDIR/bundles/rebase.hg
706 adding changesets
713 adding changesets
707 adding manifests
714 adding manifests
708 adding file changes
715 adding file changes
709 added 8 changesets with 7 changes to 7 files (+3 heads)
716 added 8 changesets with 7 changes to 7 files (+3 heads)
710 (run 'hg heads' to see heads, 'hg merge' to merge)
717 (run 'hg heads' to see heads, 'hg merge' to merge)
711
718
712 $ hg log -G
719 $ hg log -G
713 o 8:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
720 o 8:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
714 |
721 |
715 | o 7:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com> G
722 | o 7:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com> G
716 |/|
723 |/|
717 o | 6:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
724 o | 6:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
718 | |
725 | |
719 | o 5:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
726 | o 5:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
720 |/
727 |/
721 | o 4:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com> D
728 | o 4:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com> D
722 | |
729 | |
723 | o 3:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C
730 | o 3:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C
724 | |
731 | |
725 | o 2:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B
732 | o 2:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B
726 |/
733 |/
727 o 1:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com> A
734 o 1:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com> A
728
735
729 @ 0:3903775176ed draft test a
736 @ 0:3903775176ed draft test a
730
737
731
738
732 $ hg bundle2 --debug --config progress.debug=true --rev '8+7+5+4' ../rev.hg2
739 $ hg bundle2 --debug --config progress.debug=true --rev '8+7+5+4' ../rev.hg2
733 4 changesets found
740 4 changesets found
734 list of changesets:
741 list of changesets:
735 32af7686d403cf45b5d95f2d70cebea587ac806a
742 32af7686d403cf45b5d95f2d70cebea587ac806a
736 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
743 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
737 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
744 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
738 02de42196ebee42ef284b6780a87cdc96e8eaab6
745 02de42196ebee42ef284b6780a87cdc96e8eaab6
739 bundle2-output-bundle: "HG20", 1 parts total
746 bundle2-output-bundle: "HG20", 1 parts total
740 bundle2-output: start emission of HG20 stream
747 bundle2-output: start emission of HG20 stream
741 bundle2-output: bundle parameter:
748 bundle2-output: bundle parameter:
742 bundle2-output: start of parts
749 bundle2-output: start of parts
743 bundle2-output: bundle part: "changegroup"
750 bundle2-output: bundle part: "changegroup"
751 bundle2-output-part: "changegroup" (advisory) streamed payload
744 bundle2-output: part 0: "changegroup"
752 bundle2-output: part 0: "changegroup"
745 bundle2-output: header chunk size: 18
753 bundle2-output: header chunk size: 18
746 bundling: 1/4 changesets (25.00%)
754 bundling: 1/4 changesets (25.00%)
747 bundling: 2/4 changesets (50.00%)
755 bundling: 2/4 changesets (50.00%)
748 bundling: 3/4 changesets (75.00%)
756 bundling: 3/4 changesets (75.00%)
749 bundling: 4/4 changesets (100.00%)
757 bundling: 4/4 changesets (100.00%)
750 bundling: 1/4 manifests (25.00%)
758 bundling: 1/4 manifests (25.00%)
751 bundling: 2/4 manifests (50.00%)
759 bundling: 2/4 manifests (50.00%)
752 bundling: 3/4 manifests (75.00%)
760 bundling: 3/4 manifests (75.00%)
753 bundling: 4/4 manifests (100.00%)
761 bundling: 4/4 manifests (100.00%)
754 bundling: D 1/3 files (33.33%)
762 bundling: D 1/3 files (33.33%)
755 bundling: E 2/3 files (66.67%)
763 bundling: E 2/3 files (66.67%)
756 bundling: H 3/3 files (100.00%)
764 bundling: H 3/3 files (100.00%)
757 bundle2-output: payload chunk size: 1555
765 bundle2-output: payload chunk size: 1555
758 bundle2-output: closing payload chunk
766 bundle2-output: closing payload chunk
759 bundle2-output: end of bundle
767 bundle2-output: end of bundle
760
768
761 $ cat ../rev.hg2
769 $ cat ../rev.hg2
762 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)
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)
763 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
771 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
764 \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)
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)
765 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
773 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
766 \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)
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)
767 \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)
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)
768 \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)
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)
769 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
777 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
770 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
778 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
771 \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)
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)
772 \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)
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)
773 \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)
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)
774 \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)
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)
775 \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)
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)
776 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
784 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
777 \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)
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)
778 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
786 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
779 l\r (no-eol) (esc)
787 l\r (no-eol) (esc)
780 \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)
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)
781 \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)
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)
782 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
790 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
783 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
791 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
784
792
785 $ hg debugbundle ../rev.hg2
793 $ hg debugbundle ../rev.hg2
786 Stream params: {}
794 Stream params: {}
787 changegroup -- '{}'
795 changegroup -- '{}'
788 32af7686d403cf45b5d95f2d70cebea587ac806a
796 32af7686d403cf45b5d95f2d70cebea587ac806a
789 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
797 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
790 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
798 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
791 02de42196ebee42ef284b6780a87cdc96e8eaab6
799 02de42196ebee42ef284b6780a87cdc96e8eaab6
792 $ hg unbundle ../rev.hg2
800 $ hg unbundle ../rev.hg2
793 adding changesets
801 adding changesets
794 adding manifests
802 adding manifests
795 adding file changes
803 adding file changes
796 added 0 changesets with 0 changes to 3 files
804 added 0 changesets with 0 changes to 3 files
797
805
798 with reply
806 with reply
799
807
800 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
808 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
801 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
809 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
802 0 unread bytes
810 0 unread bytes
803 addchangegroup return: 1
811 addchangegroup return: 1
804
812
805 $ cat ../rev-reply.hg2
813 $ cat ../rev-reply.hg2
806 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)
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)
807 adding manifests
815 adding manifests
808 adding file changes
816 adding file changes
809 added 0 changesets with 0 changes to 3 files
817 added 0 changesets with 0 changes to 3 files
810 \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
818 \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
811
819
812 Check handling of exception during generation.
820 Check handling of exception during generation.
813 ----------------------------------------------
821 ----------------------------------------------
814
822
815 $ hg bundle2 --genraise > ../genfailed.hg2
823 $ hg bundle2 --genraise > ../genfailed.hg2
816 abort: Someone set up us the bomb!
824 abort: Someone set up us the bomb!
817 [255]
825 [255]
818
826
819 Should still be a valid bundle
827 Should still be a valid bundle
820
828
821 $ cat ../genfailed.hg2
829 $ cat ../genfailed.hg2
822 HG20\x00\x00\x00\x00\x00\x00\x00\r (no-eol) (esc)
830 HG20\x00\x00\x00\x00\x00\x00\x00\r (no-eol) (esc)
823 \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)
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)
824
832
825 And its handling on the other size raise a clean exception
833 And its handling on the other size raise a clean exception
826
834
827 $ cat ../genfailed.hg2 | hg unbundle2
835 $ cat ../genfailed.hg2 | hg unbundle2
828 0 unread bytes
836 0 unread bytes
829 abort: unexpected error: Someone set up us the bomb!
837 abort: unexpected error: Someone set up us the bomb!
830 [255]
838 [255]
831
839
832
840
833 $ cd ..
841 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now