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