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