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