##// END OF EJS Templates
bundle2: raise BundleValueError error for stream level unsupported params...
Pierre-Yves David -
r21628:7c5a8561 default
parent child Browse files
Show More
@@ -1,855 +1,855
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: (16 bits integer)
34 :params size: (16 bits integer)
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: (16 bits inter)
67 :header size: (16 bits inter)
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
88 :parttype: alphanumerical part name
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 a 32 bits integer, `chunkdata` are plain bytes (as much as
122 `chunksize` is a 32 bits integer, `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 Bundle processing
128 Bundle processing
129 ============================
129 ============================
130
130
131 Each part is processed in order using a "part handler". Handler are registered
131 Each part is processed in order using a "part handler". Handler are registered
132 for a certain part type.
132 for a certain part type.
133
133
134 The matching of a part to its handler is case insensitive. The case of the
134 The matching of a part to its handler is case insensitive. The case of the
135 part type is used to know if a part is mandatory or advisory. If the Part type
135 part type is used to know if a part is mandatory or advisory. If the Part type
136 contains any uppercase char it is considered mandatory. When no handler is
136 contains any uppercase char it is considered mandatory. When no handler is
137 known for a Mandatory part, the process is aborted and an exception is raised.
137 known for a Mandatory part, the process is aborted and an exception is raised.
138 If the part is advisory and no handler is known, the part is ignored. When the
138 If the part is advisory and no handler is known, the part is ignored. When the
139 process is aborted, the full bundle is still read from the stream to keep the
139 process is aborted, the full bundle is still read from the stream to keep the
140 channel usable. But none of the part read from an abort are processed. In the
140 channel usable. But none of the part read from an abort are processed. In the
141 future, dropping the stream may become an option for channel we do not care to
141 future, dropping the stream may become an option for channel we do not care to
142 preserve.
142 preserve.
143 """
143 """
144
144
145 import util
145 import util
146 import struct
146 import struct
147 import urllib
147 import urllib
148 import string
148 import string
149
149
150 import changegroup, error
150 import changegroup, error
151 from i18n import _
151 from i18n import _
152
152
153 _pack = struct.pack
153 _pack = struct.pack
154 _unpack = struct.unpack
154 _unpack = struct.unpack
155
155
156 _magicstring = 'HG2X'
156 _magicstring = 'HG2X'
157
157
158 _fstreamparamsize = '>H'
158 _fstreamparamsize = '>H'
159 _fpartheadersize = '>H'
159 _fpartheadersize = '>H'
160 _fparttypesize = '>B'
160 _fparttypesize = '>B'
161 _fpartid = '>I'
161 _fpartid = '>I'
162 _fpayloadsize = '>I'
162 _fpayloadsize = '>I'
163 _fpartparamcount = '>BB'
163 _fpartparamcount = '>BB'
164
164
165 preferedchunksize = 4096
165 preferedchunksize = 4096
166
166
167 def _makefpartparamsizes(nbparams):
167 def _makefpartparamsizes(nbparams):
168 """return a struct format to read part parameter sizes
168 """return a struct format to read part parameter sizes
169
169
170 The number parameters is variable so we need to build that format
170 The number parameters is variable so we need to build that format
171 dynamically.
171 dynamically.
172 """
172 """
173 return '>'+('BB'*nbparams)
173 return '>'+('BB'*nbparams)
174
174
175 parthandlermapping = {}
175 parthandlermapping = {}
176
176
177 def parthandler(parttype, params=()):
177 def parthandler(parttype, params=()):
178 """decorator that register a function as a bundle2 part handler
178 """decorator that register a function as a bundle2 part handler
179
179
180 eg::
180 eg::
181
181
182 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
182 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
183 def myparttypehandler(...):
183 def myparttypehandler(...):
184 '''process a part of type "my part".'''
184 '''process a part of type "my part".'''
185 ...
185 ...
186 """
186 """
187 def _decorator(func):
187 def _decorator(func):
188 lparttype = parttype.lower() # enforce lower case matching.
188 lparttype = parttype.lower() # enforce lower case matching.
189 assert lparttype not in parthandlermapping
189 assert lparttype not in parthandlermapping
190 parthandlermapping[lparttype] = func
190 parthandlermapping[lparttype] = func
191 func.params = frozenset(params)
191 func.params = frozenset(params)
192 return func
192 return func
193 return _decorator
193 return _decorator
194
194
195 class unbundlerecords(object):
195 class unbundlerecords(object):
196 """keep record of what happens during and unbundle
196 """keep record of what happens during and unbundle
197
197
198 New records are added using `records.add('cat', obj)`. Where 'cat' is a
198 New records are added using `records.add('cat', obj)`. Where 'cat' is a
199 category of record and obj is an arbitrary object.
199 category of record and obj is an arbitrary object.
200
200
201 `records['cat']` will return all entries of this category 'cat'.
201 `records['cat']` will return all entries of this category 'cat'.
202
202
203 Iterating on the object itself will yield `('category', obj)` tuples
203 Iterating on the object itself will yield `('category', obj)` tuples
204 for all entries.
204 for all entries.
205
205
206 All iterations happens in chronological order.
206 All iterations happens in chronological order.
207 """
207 """
208
208
209 def __init__(self):
209 def __init__(self):
210 self._categories = {}
210 self._categories = {}
211 self._sequences = []
211 self._sequences = []
212 self._replies = {}
212 self._replies = {}
213
213
214 def add(self, category, entry, inreplyto=None):
214 def add(self, category, entry, inreplyto=None):
215 """add a new record of a given category.
215 """add a new record of a given category.
216
216
217 The entry can then be retrieved in the list returned by
217 The entry can then be retrieved in the list returned by
218 self['category']."""
218 self['category']."""
219 self._categories.setdefault(category, []).append(entry)
219 self._categories.setdefault(category, []).append(entry)
220 self._sequences.append((category, entry))
220 self._sequences.append((category, entry))
221 if inreplyto is not None:
221 if inreplyto is not None:
222 self.getreplies(inreplyto).add(category, entry)
222 self.getreplies(inreplyto).add(category, entry)
223
223
224 def getreplies(self, partid):
224 def getreplies(self, partid):
225 """get the subrecords that replies to a specific part"""
225 """get the subrecords that replies to a specific part"""
226 return self._replies.setdefault(partid, unbundlerecords())
226 return self._replies.setdefault(partid, unbundlerecords())
227
227
228 def __getitem__(self, cat):
228 def __getitem__(self, cat):
229 return tuple(self._categories.get(cat, ()))
229 return tuple(self._categories.get(cat, ()))
230
230
231 def __iter__(self):
231 def __iter__(self):
232 return iter(self._sequences)
232 return iter(self._sequences)
233
233
234 def __len__(self):
234 def __len__(self):
235 return len(self._sequences)
235 return len(self._sequences)
236
236
237 def __nonzero__(self):
237 def __nonzero__(self):
238 return bool(self._sequences)
238 return bool(self._sequences)
239
239
240 class bundleoperation(object):
240 class bundleoperation(object):
241 """an object that represents a single bundling process
241 """an object that represents a single bundling process
242
242
243 Its purpose is to carry unbundle-related objects and states.
243 Its purpose is to carry unbundle-related objects and states.
244
244
245 A new object should be created at the beginning of each bundle processing.
245 A new object should be created at the beginning of each bundle processing.
246 The object is to be returned by the processing function.
246 The object is to be returned by the processing function.
247
247
248 The object has very little content now it will ultimately contain:
248 The object has very little content now it will ultimately contain:
249 * an access to the repo the bundle is applied to,
249 * an access to the repo the bundle is applied to,
250 * a ui object,
250 * a ui object,
251 * a way to retrieve a transaction to add changes to the repo,
251 * a way to retrieve a transaction to add changes to the repo,
252 * a way to record the result of processing each part,
252 * a way to record the result of processing each part,
253 * a way to construct a bundle response when applicable.
253 * a way to construct a bundle response when applicable.
254 """
254 """
255
255
256 def __init__(self, repo, transactiongetter):
256 def __init__(self, repo, transactiongetter):
257 self.repo = repo
257 self.repo = repo
258 self.ui = repo.ui
258 self.ui = repo.ui
259 self.records = unbundlerecords()
259 self.records = unbundlerecords()
260 self.gettransaction = transactiongetter
260 self.gettransaction = transactiongetter
261 self.reply = None
261 self.reply = None
262
262
263 class TransactionUnavailable(RuntimeError):
263 class TransactionUnavailable(RuntimeError):
264 pass
264 pass
265
265
266 def _notransaction():
266 def _notransaction():
267 """default method to get a transaction while processing a bundle
267 """default method to get a transaction while processing a bundle
268
268
269 Raise an exception to highlight the fact that no transaction was expected
269 Raise an exception to highlight the fact that no transaction was expected
270 to be created"""
270 to be created"""
271 raise TransactionUnavailable()
271 raise TransactionUnavailable()
272
272
273 def processbundle(repo, unbundler, transactiongetter=_notransaction):
273 def processbundle(repo, unbundler, transactiongetter=_notransaction):
274 """This function process a bundle, apply effect to/from a repo
274 """This function process a bundle, apply effect to/from a repo
275
275
276 It iterates over each part then searches for and uses the proper handling
276 It iterates over each part then searches for and uses the proper handling
277 code to process the part. Parts are processed in order.
277 code to process the part. Parts are processed in order.
278
278
279 This is very early version of this function that will be strongly reworked
279 This is very early version of this function that will be strongly reworked
280 before final usage.
280 before final usage.
281
281
282 Unknown Mandatory part will abort the process.
282 Unknown Mandatory part will abort the process.
283 """
283 """
284 op = bundleoperation(repo, transactiongetter)
284 op = bundleoperation(repo, transactiongetter)
285 # todo:
285 # todo:
286 # - replace this is a init function soon.
286 # - replace this is a init function soon.
287 # - exception catching
287 # - exception catching
288 unbundler.params
288 unbundler.params
289 iterparts = unbundler.iterparts()
289 iterparts = unbundler.iterparts()
290 part = None
290 part = None
291 try:
291 try:
292 for part in iterparts:
292 for part in iterparts:
293 parttype = part.type
293 parttype = part.type
294 # part key are matched lower case
294 # part key are matched lower case
295 key = parttype.lower()
295 key = parttype.lower()
296 try:
296 try:
297 handler = parthandlermapping.get(key)
297 handler = parthandlermapping.get(key)
298 if handler is None:
298 if handler is None:
299 raise error.BundleValueError(parttype=key)
299 raise error.BundleValueError(parttype=key)
300 op.ui.debug('found a handler for part %r\n' % parttype)
300 op.ui.debug('found a handler for part %r\n' % parttype)
301 unknownparams = part.mandatorykeys - handler.params
301 unknownparams = part.mandatorykeys - handler.params
302 if unknownparams:
302 if unknownparams:
303 unknownparams = list(unknownparams)
303 unknownparams = list(unknownparams)
304 unknownparams.sort()
304 unknownparams.sort()
305 raise error.BundleValueError(parttype=key,
305 raise error.BundleValueError(parttype=key,
306 params=unknownparams)
306 params=unknownparams)
307 except error.BundleValueError, exc:
307 except error.BundleValueError, exc:
308 if key != parttype: # mandatory parts
308 if key != parttype: # mandatory parts
309 raise
309 raise
310 op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
310 op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
311 # consuming the part
311 # consuming the part
312 part.read()
312 part.read()
313 continue
313 continue
314
314
315
315
316 # handler is called outside the above try block so that we don't
316 # handler is called outside the above try block so that we don't
317 # risk catching KeyErrors from anything other than the
317 # risk catching KeyErrors from anything other than the
318 # parthandlermapping lookup (any KeyError raised by handler()
318 # parthandlermapping lookup (any KeyError raised by handler()
319 # itself represents a defect of a different variety).
319 # itself represents a defect of a different variety).
320 output = None
320 output = None
321 if op.reply is not None:
321 if op.reply is not None:
322 op.ui.pushbuffer(error=True)
322 op.ui.pushbuffer(error=True)
323 output = ''
323 output = ''
324 try:
324 try:
325 handler(op, part)
325 handler(op, part)
326 finally:
326 finally:
327 if output is not None:
327 if output is not None:
328 output = op.ui.popbuffer()
328 output = op.ui.popbuffer()
329 if output:
329 if output:
330 outpart = op.reply.newpart('b2x:output', data=output)
330 outpart = op.reply.newpart('b2x:output', data=output)
331 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
331 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
332 part.read()
332 part.read()
333 except Exception, exc:
333 except Exception, exc:
334 if part is not None:
334 if part is not None:
335 # consume the bundle content
335 # consume the bundle content
336 part.read()
336 part.read()
337 for part in iterparts:
337 for part in iterparts:
338 # consume the bundle content
338 # consume the bundle content
339 part.read()
339 part.read()
340 # Small hack to let caller code distinguish exceptions from bundle2
340 # Small hack to let caller code distinguish exceptions from bundle2
341 # processing fron the ones from bundle1 processing. This is mostly
341 # processing fron the ones from bundle1 processing. This is mostly
342 # needed to handle different return codes to unbundle according to the
342 # needed to handle different return codes to unbundle according to the
343 # type of bundle. We should probably clean up or drop this return code
343 # type of bundle. We should probably clean up or drop this return code
344 # craziness in a future version.
344 # craziness in a future version.
345 exc.duringunbundle2 = True
345 exc.duringunbundle2 = True
346 raise
346 raise
347 return op
347 return op
348
348
349 def decodecaps(blob):
349 def decodecaps(blob):
350 """decode a bundle2 caps bytes blob into a dictionnary
350 """decode a bundle2 caps bytes blob into a dictionnary
351
351
352 The blob is a list of capabilities (one per line)
352 The blob is a list of capabilities (one per line)
353 Capabilities may have values using a line of the form::
353 Capabilities may have values using a line of the form::
354
354
355 capability=value1,value2,value3
355 capability=value1,value2,value3
356
356
357 The values are always a list."""
357 The values are always a list."""
358 caps = {}
358 caps = {}
359 for line in blob.splitlines():
359 for line in blob.splitlines():
360 if not line:
360 if not line:
361 continue
361 continue
362 if '=' not in line:
362 if '=' not in line:
363 key, vals = line, ()
363 key, vals = line, ()
364 else:
364 else:
365 key, vals = line.split('=', 1)
365 key, vals = line.split('=', 1)
366 vals = vals.split(',')
366 vals = vals.split(',')
367 key = urllib.unquote(key)
367 key = urllib.unquote(key)
368 vals = [urllib.unquote(v) for v in vals]
368 vals = [urllib.unquote(v) for v in vals]
369 caps[key] = vals
369 caps[key] = vals
370 return caps
370 return caps
371
371
372 def encodecaps(caps):
372 def encodecaps(caps):
373 """encode a bundle2 caps dictionary into a bytes blob"""
373 """encode a bundle2 caps dictionary into a bytes blob"""
374 chunks = []
374 chunks = []
375 for ca in sorted(caps):
375 for ca in sorted(caps):
376 vals = caps[ca]
376 vals = caps[ca]
377 ca = urllib.quote(ca)
377 ca = urllib.quote(ca)
378 vals = [urllib.quote(v) for v in vals]
378 vals = [urllib.quote(v) for v in vals]
379 if vals:
379 if vals:
380 ca = "%s=%s" % (ca, ','.join(vals))
380 ca = "%s=%s" % (ca, ','.join(vals))
381 chunks.append(ca)
381 chunks.append(ca)
382 return '\n'.join(chunks)
382 return '\n'.join(chunks)
383
383
384 class bundle20(object):
384 class bundle20(object):
385 """represent an outgoing bundle2 container
385 """represent an outgoing bundle2 container
386
386
387 Use the `addparam` method to add stream level parameter. and `newpart` to
387 Use the `addparam` method to add stream level parameter. and `newpart` to
388 populate it. Then call `getchunks` to retrieve all the binary chunks of
388 populate it. Then call `getchunks` to retrieve all the binary chunks of
389 data that compose the bundle2 container."""
389 data that compose the bundle2 container."""
390
390
391 def __init__(self, ui, capabilities=()):
391 def __init__(self, ui, capabilities=()):
392 self.ui = ui
392 self.ui = ui
393 self._params = []
393 self._params = []
394 self._parts = []
394 self._parts = []
395 self.capabilities = dict(capabilities)
395 self.capabilities = dict(capabilities)
396
396
397 # methods used to defines the bundle2 content
397 # methods used to defines the bundle2 content
398 def addparam(self, name, value=None):
398 def addparam(self, name, value=None):
399 """add a stream level parameter"""
399 """add a stream level parameter"""
400 if not name:
400 if not name:
401 raise ValueError('empty parameter name')
401 raise ValueError('empty parameter name')
402 if name[0] not in string.letters:
402 if name[0] not in string.letters:
403 raise ValueError('non letter first character: %r' % name)
403 raise ValueError('non letter first character: %r' % name)
404 self._params.append((name, value))
404 self._params.append((name, value))
405
405
406 def addpart(self, part):
406 def addpart(self, part):
407 """add a new part to the bundle2 container
407 """add a new part to the bundle2 container
408
408
409 Parts contains the actual applicative payload."""
409 Parts contains the actual applicative payload."""
410 assert part.id is None
410 assert part.id is None
411 part.id = len(self._parts) # very cheap counter
411 part.id = len(self._parts) # very cheap counter
412 self._parts.append(part)
412 self._parts.append(part)
413
413
414 def newpart(self, typeid, *args, **kwargs):
414 def newpart(self, typeid, *args, **kwargs):
415 """create a new part and add it to the containers
415 """create a new part and add it to the containers
416
416
417 As the part is directly added to the containers. For now, this means
417 As the part is directly added to the containers. For now, this means
418 that any failure to properly initialize the part after calling
418 that any failure to properly initialize the part after calling
419 ``newpart`` should result in a failure of the whole bundling process.
419 ``newpart`` should result in a failure of the whole bundling process.
420
420
421 You can still fall back to manually create and add if you need better
421 You can still fall back to manually create and add if you need better
422 control."""
422 control."""
423 part = bundlepart(typeid, *args, **kwargs)
423 part = bundlepart(typeid, *args, **kwargs)
424 self.addpart(part)
424 self.addpart(part)
425 return part
425 return part
426
426
427 # methods used to generate the bundle2 stream
427 # methods used to generate the bundle2 stream
428 def getchunks(self):
428 def getchunks(self):
429 self.ui.debug('start emission of %s stream\n' % _magicstring)
429 self.ui.debug('start emission of %s stream\n' % _magicstring)
430 yield _magicstring
430 yield _magicstring
431 param = self._paramchunk()
431 param = self._paramchunk()
432 self.ui.debug('bundle parameter: %s\n' % param)
432 self.ui.debug('bundle parameter: %s\n' % param)
433 yield _pack(_fstreamparamsize, len(param))
433 yield _pack(_fstreamparamsize, len(param))
434 if param:
434 if param:
435 yield param
435 yield param
436
436
437 self.ui.debug('start of parts\n')
437 self.ui.debug('start of parts\n')
438 for part in self._parts:
438 for part in self._parts:
439 self.ui.debug('bundle part: "%s"\n' % part.type)
439 self.ui.debug('bundle part: "%s"\n' % part.type)
440 for chunk in part.getchunks():
440 for chunk in part.getchunks():
441 yield chunk
441 yield chunk
442 self.ui.debug('end of bundle\n')
442 self.ui.debug('end of bundle\n')
443 yield '\0\0'
443 yield '\0\0'
444
444
445 def _paramchunk(self):
445 def _paramchunk(self):
446 """return a encoded version of all stream parameters"""
446 """return a encoded version of all stream parameters"""
447 blocks = []
447 blocks = []
448 for par, value in self._params:
448 for par, value in self._params:
449 par = urllib.quote(par)
449 par = urllib.quote(par)
450 if value is not None:
450 if value is not None:
451 value = urllib.quote(value)
451 value = urllib.quote(value)
452 par = '%s=%s' % (par, value)
452 par = '%s=%s' % (par, value)
453 blocks.append(par)
453 blocks.append(par)
454 return ' '.join(blocks)
454 return ' '.join(blocks)
455
455
456 class unpackermixin(object):
456 class unpackermixin(object):
457 """A mixin to extract bytes and struct data from a stream"""
457 """A mixin to extract bytes and struct data from a stream"""
458
458
459 def __init__(self, fp):
459 def __init__(self, fp):
460 self._fp = fp
460 self._fp = fp
461
461
462 def _unpack(self, format):
462 def _unpack(self, format):
463 """unpack this struct format from the stream"""
463 """unpack this struct format from the stream"""
464 data = self._readexact(struct.calcsize(format))
464 data = self._readexact(struct.calcsize(format))
465 return _unpack(format, data)
465 return _unpack(format, data)
466
466
467 def _readexact(self, size):
467 def _readexact(self, size):
468 """read exactly <size> bytes from the stream"""
468 """read exactly <size> bytes from the stream"""
469 return changegroup.readexactly(self._fp, size)
469 return changegroup.readexactly(self._fp, size)
470
470
471
471
472 class unbundle20(unpackermixin):
472 class unbundle20(unpackermixin):
473 """interpret a bundle2 stream
473 """interpret a bundle2 stream
474
474
475 This class is fed with a binary stream and yields parts through its
475 This class is fed with a binary stream and yields parts through its
476 `iterparts` methods."""
476 `iterparts` methods."""
477
477
478 def __init__(self, ui, fp, header=None):
478 def __init__(self, ui, fp, header=None):
479 """If header is specified, we do not read it out of the stream."""
479 """If header is specified, we do not read it out of the stream."""
480 self.ui = ui
480 self.ui = ui
481 super(unbundle20, self).__init__(fp)
481 super(unbundle20, self).__init__(fp)
482 if header is None:
482 if header is None:
483 header = self._readexact(4)
483 header = self._readexact(4)
484 magic, version = header[0:2], header[2:4]
484 magic, version = header[0:2], header[2:4]
485 if magic != 'HG':
485 if magic != 'HG':
486 raise util.Abort(_('not a Mercurial bundle'))
486 raise util.Abort(_('not a Mercurial bundle'))
487 if version != '2X':
487 if version != '2X':
488 raise util.Abort(_('unknown bundle version %s') % version)
488 raise util.Abort(_('unknown bundle version %s') % version)
489 self.ui.debug('start processing of %s stream\n' % header)
489 self.ui.debug('start processing of %s stream\n' % header)
490
490
491 @util.propertycache
491 @util.propertycache
492 def params(self):
492 def params(self):
493 """dictionary of stream level parameters"""
493 """dictionary of stream level parameters"""
494 self.ui.debug('reading bundle2 stream parameters\n')
494 self.ui.debug('reading bundle2 stream parameters\n')
495 params = {}
495 params = {}
496 paramssize = self._unpack(_fstreamparamsize)[0]
496 paramssize = self._unpack(_fstreamparamsize)[0]
497 if paramssize:
497 if paramssize:
498 for p in self._readexact(paramssize).split(' '):
498 for p in self._readexact(paramssize).split(' '):
499 p = p.split('=', 1)
499 p = p.split('=', 1)
500 p = [urllib.unquote(i) for i in p]
500 p = [urllib.unquote(i) for i in p]
501 if len(p) < 2:
501 if len(p) < 2:
502 p.append(None)
502 p.append(None)
503 self._processparam(*p)
503 self._processparam(*p)
504 params[p[0]] = p[1]
504 params[p[0]] = p[1]
505 return params
505 return params
506
506
507 def _processparam(self, name, value):
507 def _processparam(self, name, value):
508 """process a parameter, applying its effect if needed
508 """process a parameter, applying its effect if needed
509
509
510 Parameter starting with a lower case letter are advisory and will be
510 Parameter starting with a lower case letter are advisory and will be
511 ignored when unknown. Those starting with an upper case letter are
511 ignored when unknown. Those starting with an upper case letter are
512 mandatory and will this function will raise a KeyError when unknown.
512 mandatory and will this function will raise a KeyError when unknown.
513
513
514 Note: no option are currently supported. Any input will be either
514 Note: no option are currently supported. Any input will be either
515 ignored or failing.
515 ignored or failing.
516 """
516 """
517 if not name:
517 if not name:
518 raise ValueError('empty parameter name')
518 raise ValueError('empty parameter name')
519 if name[0] not in string.letters:
519 if name[0] not in string.letters:
520 raise ValueError('non letter first character: %r' % name)
520 raise ValueError('non letter first character: %r' % name)
521 # Some logic will be later added here to try to process the option for
521 # Some logic will be later added here to try to process the option for
522 # a dict of known parameter.
522 # a dict of known parameter.
523 if name[0].islower():
523 if name[0].islower():
524 self.ui.debug("ignoring unknown parameter %r\n" % name)
524 self.ui.debug("ignoring unknown parameter %r\n" % name)
525 else:
525 else:
526 raise KeyError(name)
526 raise error.BundleValueError(params=(name,))
527
527
528
528
529 def iterparts(self):
529 def iterparts(self):
530 """yield all parts contained in the stream"""
530 """yield all parts contained in the stream"""
531 # make sure param have been loaded
531 # make sure param have been loaded
532 self.params
532 self.params
533 self.ui.debug('start extraction of bundle2 parts\n')
533 self.ui.debug('start extraction of bundle2 parts\n')
534 headerblock = self._readpartheader()
534 headerblock = self._readpartheader()
535 while headerblock is not None:
535 while headerblock is not None:
536 part = unbundlepart(self.ui, headerblock, self._fp)
536 part = unbundlepart(self.ui, headerblock, self._fp)
537 yield part
537 yield part
538 headerblock = self._readpartheader()
538 headerblock = self._readpartheader()
539 self.ui.debug('end of bundle2 stream\n')
539 self.ui.debug('end of bundle2 stream\n')
540
540
541 def _readpartheader(self):
541 def _readpartheader(self):
542 """reads a part header size and return the bytes blob
542 """reads a part header size and return the bytes blob
543
543
544 returns None if empty"""
544 returns None if empty"""
545 headersize = self._unpack(_fpartheadersize)[0]
545 headersize = self._unpack(_fpartheadersize)[0]
546 self.ui.debug('part header size: %i\n' % headersize)
546 self.ui.debug('part header size: %i\n' % headersize)
547 if headersize:
547 if headersize:
548 return self._readexact(headersize)
548 return self._readexact(headersize)
549 return None
549 return None
550
550
551
551
552 class bundlepart(object):
552 class bundlepart(object):
553 """A bundle2 part contains application level payload
553 """A bundle2 part contains application level payload
554
554
555 The part `type` is used to route the part to the application level
555 The part `type` is used to route the part to the application level
556 handler.
556 handler.
557
557
558 The part payload is contained in ``part.data``. It could be raw bytes or a
558 The part payload is contained in ``part.data``. It could be raw bytes or a
559 generator of byte chunks.
559 generator of byte chunks.
560
560
561 You can add parameters to the part using the ``addparam`` method.
561 You can add parameters to the part using the ``addparam`` method.
562 Parameters can be either mandatory (default) or advisory. Remote side
562 Parameters can be either mandatory (default) or advisory. Remote side
563 should be able to safely ignore the advisory ones.
563 should be able to safely ignore the advisory ones.
564
564
565 Both data and parameters cannot be modified after the generation has begun.
565 Both data and parameters cannot be modified after the generation has begun.
566 """
566 """
567
567
568 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
568 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
569 data=''):
569 data=''):
570 self.id = None
570 self.id = None
571 self.type = parttype
571 self.type = parttype
572 self._data = data
572 self._data = data
573 self._mandatoryparams = list(mandatoryparams)
573 self._mandatoryparams = list(mandatoryparams)
574 self._advisoryparams = list(advisoryparams)
574 self._advisoryparams = list(advisoryparams)
575 # checking for duplicated entries
575 # checking for duplicated entries
576 self._seenparams = set()
576 self._seenparams = set()
577 for pname, __ in self._mandatoryparams + self._advisoryparams:
577 for pname, __ in self._mandatoryparams + self._advisoryparams:
578 if pname in self._seenparams:
578 if pname in self._seenparams:
579 raise RuntimeError('duplicated params: %s' % pname)
579 raise RuntimeError('duplicated params: %s' % pname)
580 self._seenparams.add(pname)
580 self._seenparams.add(pname)
581 # status of the part's generation:
581 # status of the part's generation:
582 # - None: not started,
582 # - None: not started,
583 # - False: currently generated,
583 # - False: currently generated,
584 # - True: generation done.
584 # - True: generation done.
585 self._generated = None
585 self._generated = None
586
586
587 # methods used to defines the part content
587 # methods used to defines the part content
588 def __setdata(self, data):
588 def __setdata(self, data):
589 if self._generated is not None:
589 if self._generated is not None:
590 raise error.ReadOnlyPartError('part is being generated')
590 raise error.ReadOnlyPartError('part is being generated')
591 self._data = data
591 self._data = data
592 def __getdata(self):
592 def __getdata(self):
593 return self._data
593 return self._data
594 data = property(__getdata, __setdata)
594 data = property(__getdata, __setdata)
595
595
596 @property
596 @property
597 def mandatoryparams(self):
597 def mandatoryparams(self):
598 # make it an immutable tuple to force people through ``addparam``
598 # make it an immutable tuple to force people through ``addparam``
599 return tuple(self._mandatoryparams)
599 return tuple(self._mandatoryparams)
600
600
601 @property
601 @property
602 def advisoryparams(self):
602 def advisoryparams(self):
603 # make it an immutable tuple to force people through ``addparam``
603 # make it an immutable tuple to force people through ``addparam``
604 return tuple(self._advisoryparams)
604 return tuple(self._advisoryparams)
605
605
606 def addparam(self, name, value='', mandatory=True):
606 def addparam(self, name, value='', mandatory=True):
607 if self._generated is not None:
607 if self._generated is not None:
608 raise error.ReadOnlyPartError('part is being generated')
608 raise error.ReadOnlyPartError('part is being generated')
609 if name in self._seenparams:
609 if name in self._seenparams:
610 raise ValueError('duplicated params: %s' % name)
610 raise ValueError('duplicated params: %s' % name)
611 self._seenparams.add(name)
611 self._seenparams.add(name)
612 params = self._advisoryparams
612 params = self._advisoryparams
613 if mandatory:
613 if mandatory:
614 params = self._mandatoryparams
614 params = self._mandatoryparams
615 params.append((name, value))
615 params.append((name, value))
616
616
617 # methods used to generates the bundle2 stream
617 # methods used to generates the bundle2 stream
618 def getchunks(self):
618 def getchunks(self):
619 if self._generated is not None:
619 if self._generated is not None:
620 raise RuntimeError('part can only be consumed once')
620 raise RuntimeError('part can only be consumed once')
621 self._generated = False
621 self._generated = False
622 #### header
622 #### header
623 ## parttype
623 ## parttype
624 header = [_pack(_fparttypesize, len(self.type)),
624 header = [_pack(_fparttypesize, len(self.type)),
625 self.type, _pack(_fpartid, self.id),
625 self.type, _pack(_fpartid, self.id),
626 ]
626 ]
627 ## parameters
627 ## parameters
628 # count
628 # count
629 manpar = self.mandatoryparams
629 manpar = self.mandatoryparams
630 advpar = self.advisoryparams
630 advpar = self.advisoryparams
631 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
631 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
632 # size
632 # size
633 parsizes = []
633 parsizes = []
634 for key, value in manpar:
634 for key, value in manpar:
635 parsizes.append(len(key))
635 parsizes.append(len(key))
636 parsizes.append(len(value))
636 parsizes.append(len(value))
637 for key, value in advpar:
637 for key, value in advpar:
638 parsizes.append(len(key))
638 parsizes.append(len(key))
639 parsizes.append(len(value))
639 parsizes.append(len(value))
640 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
640 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
641 header.append(paramsizes)
641 header.append(paramsizes)
642 # key, value
642 # key, value
643 for key, value in manpar:
643 for key, value in manpar:
644 header.append(key)
644 header.append(key)
645 header.append(value)
645 header.append(value)
646 for key, value in advpar:
646 for key, value in advpar:
647 header.append(key)
647 header.append(key)
648 header.append(value)
648 header.append(value)
649 ## finalize header
649 ## finalize header
650 headerchunk = ''.join(header)
650 headerchunk = ''.join(header)
651 yield _pack(_fpartheadersize, len(headerchunk))
651 yield _pack(_fpartheadersize, len(headerchunk))
652 yield headerchunk
652 yield headerchunk
653 ## payload
653 ## payload
654 for chunk in self._payloadchunks():
654 for chunk in self._payloadchunks():
655 yield _pack(_fpayloadsize, len(chunk))
655 yield _pack(_fpayloadsize, len(chunk))
656 yield chunk
656 yield chunk
657 # end of payload
657 # end of payload
658 yield _pack(_fpayloadsize, 0)
658 yield _pack(_fpayloadsize, 0)
659 self._generated = True
659 self._generated = True
660
660
661 def _payloadchunks(self):
661 def _payloadchunks(self):
662 """yield chunks of a the part payload
662 """yield chunks of a the part payload
663
663
664 Exists to handle the different methods to provide data to a part."""
664 Exists to handle the different methods to provide data to a part."""
665 # we only support fixed size data now.
665 # we only support fixed size data now.
666 # This will be improved in the future.
666 # This will be improved in the future.
667 if util.safehasattr(self.data, 'next'):
667 if util.safehasattr(self.data, 'next'):
668 buff = util.chunkbuffer(self.data)
668 buff = util.chunkbuffer(self.data)
669 chunk = buff.read(preferedchunksize)
669 chunk = buff.read(preferedchunksize)
670 while chunk:
670 while chunk:
671 yield chunk
671 yield chunk
672 chunk = buff.read(preferedchunksize)
672 chunk = buff.read(preferedchunksize)
673 elif len(self.data):
673 elif len(self.data):
674 yield self.data
674 yield self.data
675
675
676 class unbundlepart(unpackermixin):
676 class unbundlepart(unpackermixin):
677 """a bundle part read from a bundle"""
677 """a bundle part read from a bundle"""
678
678
679 def __init__(self, ui, header, fp):
679 def __init__(self, ui, header, fp):
680 super(unbundlepart, self).__init__(fp)
680 super(unbundlepart, self).__init__(fp)
681 self.ui = ui
681 self.ui = ui
682 # unbundle state attr
682 # unbundle state attr
683 self._headerdata = header
683 self._headerdata = header
684 self._headeroffset = 0
684 self._headeroffset = 0
685 self._initialized = False
685 self._initialized = False
686 self.consumed = False
686 self.consumed = False
687 # part data
687 # part data
688 self.id = None
688 self.id = None
689 self.type = None
689 self.type = None
690 self.mandatoryparams = None
690 self.mandatoryparams = None
691 self.advisoryparams = None
691 self.advisoryparams = None
692 self.params = None
692 self.params = None
693 self.mandatorykeys = ()
693 self.mandatorykeys = ()
694 self._payloadstream = None
694 self._payloadstream = None
695 self._readheader()
695 self._readheader()
696
696
697 def _fromheader(self, size):
697 def _fromheader(self, size):
698 """return the next <size> byte from the header"""
698 """return the next <size> byte from the header"""
699 offset = self._headeroffset
699 offset = self._headeroffset
700 data = self._headerdata[offset:(offset + size)]
700 data = self._headerdata[offset:(offset + size)]
701 self._headeroffset = offset + size
701 self._headeroffset = offset + size
702 return data
702 return data
703
703
704 def _unpackheader(self, format):
704 def _unpackheader(self, format):
705 """read given format from header
705 """read given format from header
706
706
707 This automatically compute the size of the format to read."""
707 This automatically compute the size of the format to read."""
708 data = self._fromheader(struct.calcsize(format))
708 data = self._fromheader(struct.calcsize(format))
709 return _unpack(format, data)
709 return _unpack(format, data)
710
710
711 def _initparams(self, mandatoryparams, advisoryparams):
711 def _initparams(self, mandatoryparams, advisoryparams):
712 """internal function to setup all logic related parameters"""
712 """internal function to setup all logic related parameters"""
713 # make it read only to prevent people touching it by mistake.
713 # make it read only to prevent people touching it by mistake.
714 self.mandatoryparams = tuple(mandatoryparams)
714 self.mandatoryparams = tuple(mandatoryparams)
715 self.advisoryparams = tuple(advisoryparams)
715 self.advisoryparams = tuple(advisoryparams)
716 # user friendly UI
716 # user friendly UI
717 self.params = dict(self.mandatoryparams)
717 self.params = dict(self.mandatoryparams)
718 self.params.update(dict(self.advisoryparams))
718 self.params.update(dict(self.advisoryparams))
719 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
719 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
720
720
721 def _readheader(self):
721 def _readheader(self):
722 """read the header and setup the object"""
722 """read the header and setup the object"""
723 typesize = self._unpackheader(_fparttypesize)[0]
723 typesize = self._unpackheader(_fparttypesize)[0]
724 self.type = self._fromheader(typesize)
724 self.type = self._fromheader(typesize)
725 self.ui.debug('part type: "%s"\n' % self.type)
725 self.ui.debug('part type: "%s"\n' % self.type)
726 self.id = self._unpackheader(_fpartid)[0]
726 self.id = self._unpackheader(_fpartid)[0]
727 self.ui.debug('part id: "%s"\n' % self.id)
727 self.ui.debug('part id: "%s"\n' % self.id)
728 ## reading parameters
728 ## reading parameters
729 # param count
729 # param count
730 mancount, advcount = self._unpackheader(_fpartparamcount)
730 mancount, advcount = self._unpackheader(_fpartparamcount)
731 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
731 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
732 # param size
732 # param size
733 fparamsizes = _makefpartparamsizes(mancount + advcount)
733 fparamsizes = _makefpartparamsizes(mancount + advcount)
734 paramsizes = self._unpackheader(fparamsizes)
734 paramsizes = self._unpackheader(fparamsizes)
735 # make it a list of couple again
735 # make it a list of couple again
736 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
736 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
737 # split mandatory from advisory
737 # split mandatory from advisory
738 mansizes = paramsizes[:mancount]
738 mansizes = paramsizes[:mancount]
739 advsizes = paramsizes[mancount:]
739 advsizes = paramsizes[mancount:]
740 # retrive param value
740 # retrive param value
741 manparams = []
741 manparams = []
742 for key, value in mansizes:
742 for key, value in mansizes:
743 manparams.append((self._fromheader(key), self._fromheader(value)))
743 manparams.append((self._fromheader(key), self._fromheader(value)))
744 advparams = []
744 advparams = []
745 for key, value in advsizes:
745 for key, value in advsizes:
746 advparams.append((self._fromheader(key), self._fromheader(value)))
746 advparams.append((self._fromheader(key), self._fromheader(value)))
747 self._initparams(manparams, advparams)
747 self._initparams(manparams, advparams)
748 ## part payload
748 ## part payload
749 def payloadchunks():
749 def payloadchunks():
750 payloadsize = self._unpack(_fpayloadsize)[0]
750 payloadsize = self._unpack(_fpayloadsize)[0]
751 self.ui.debug('payload chunk size: %i\n' % payloadsize)
751 self.ui.debug('payload chunk size: %i\n' % payloadsize)
752 while payloadsize:
752 while payloadsize:
753 yield self._readexact(payloadsize)
753 yield self._readexact(payloadsize)
754 payloadsize = self._unpack(_fpayloadsize)[0]
754 payloadsize = self._unpack(_fpayloadsize)[0]
755 self.ui.debug('payload chunk size: %i\n' % payloadsize)
755 self.ui.debug('payload chunk size: %i\n' % payloadsize)
756 self._payloadstream = util.chunkbuffer(payloadchunks())
756 self._payloadstream = util.chunkbuffer(payloadchunks())
757 # we read the data, tell it
757 # we read the data, tell it
758 self._initialized = True
758 self._initialized = True
759
759
760 def read(self, size=None):
760 def read(self, size=None):
761 """read payload data"""
761 """read payload data"""
762 if not self._initialized:
762 if not self._initialized:
763 self._readheader()
763 self._readheader()
764 if size is None:
764 if size is None:
765 data = self._payloadstream.read()
765 data = self._payloadstream.read()
766 else:
766 else:
767 data = self._payloadstream.read(size)
767 data = self._payloadstream.read(size)
768 if size is None or len(data) < size:
768 if size is None or len(data) < size:
769 self.consumed = True
769 self.consumed = True
770 return data
770 return data
771
771
772
772
773 @parthandler('b2x:changegroup')
773 @parthandler('b2x:changegroup')
774 def handlechangegroup(op, inpart):
774 def handlechangegroup(op, inpart):
775 """apply a changegroup part on the repo
775 """apply a changegroup part on the repo
776
776
777 This is a very early implementation that will massive rework before being
777 This is a very early implementation that will massive rework before being
778 inflicted to any end-user.
778 inflicted to any end-user.
779 """
779 """
780 # Make sure we trigger a transaction creation
780 # Make sure we trigger a transaction creation
781 #
781 #
782 # The addchangegroup function will get a transaction object by itself, but
782 # The addchangegroup function will get a transaction object by itself, but
783 # we need to make sure we trigger the creation of a transaction object used
783 # we need to make sure we trigger the creation of a transaction object used
784 # for the whole processing scope.
784 # for the whole processing scope.
785 op.gettransaction()
785 op.gettransaction()
786 cg = changegroup.unbundle10(inpart, 'UN')
786 cg = changegroup.unbundle10(inpart, 'UN')
787 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
787 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
788 op.records.add('changegroup', {'return': ret})
788 op.records.add('changegroup', {'return': ret})
789 if op.reply is not None:
789 if op.reply is not None:
790 # This is definitly not the final form of this
790 # This is definitly not the final form of this
791 # return. But one need to start somewhere.
791 # return. But one need to start somewhere.
792 part = op.reply.newpart('b2x:reply:changegroup')
792 part = op.reply.newpart('b2x:reply:changegroup')
793 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
793 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
794 part.addparam('return', '%i' % ret, mandatory=False)
794 part.addparam('return', '%i' % ret, mandatory=False)
795 assert not inpart.read()
795 assert not inpart.read()
796
796
797 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to'))
797 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to'))
798 def handlechangegroup(op, inpart):
798 def handlechangegroup(op, inpart):
799 ret = int(inpart.params['return'])
799 ret = int(inpart.params['return'])
800 replyto = int(inpart.params['in-reply-to'])
800 replyto = int(inpart.params['in-reply-to'])
801 op.records.add('changegroup', {'return': ret}, replyto)
801 op.records.add('changegroup', {'return': ret}, replyto)
802
802
803 @parthandler('b2x:check:heads')
803 @parthandler('b2x:check:heads')
804 def handlechangegroup(op, inpart):
804 def handlechangegroup(op, inpart):
805 """check that head of the repo did not change
805 """check that head of the repo did not change
806
806
807 This is used to detect a push race when using unbundle.
807 This is used to detect a push race when using unbundle.
808 This replaces the "heads" argument of unbundle."""
808 This replaces the "heads" argument of unbundle."""
809 h = inpart.read(20)
809 h = inpart.read(20)
810 heads = []
810 heads = []
811 while len(h) == 20:
811 while len(h) == 20:
812 heads.append(h)
812 heads.append(h)
813 h = inpart.read(20)
813 h = inpart.read(20)
814 assert not h
814 assert not h
815 if heads != op.repo.heads():
815 if heads != op.repo.heads():
816 raise error.PushRaced('repository changed while pushing - '
816 raise error.PushRaced('repository changed while pushing - '
817 'please try again')
817 'please try again')
818
818
819 @parthandler('b2x:output')
819 @parthandler('b2x:output')
820 def handleoutput(op, inpart):
820 def handleoutput(op, inpart):
821 """forward output captured on the server to the client"""
821 """forward output captured on the server to the client"""
822 for line in inpart.read().splitlines():
822 for line in inpart.read().splitlines():
823 op.ui.write(('remote: %s\n' % line))
823 op.ui.write(('remote: %s\n' % line))
824
824
825 @parthandler('b2x:replycaps')
825 @parthandler('b2x:replycaps')
826 def handlereplycaps(op, inpart):
826 def handlereplycaps(op, inpart):
827 """Notify that a reply bundle should be created
827 """Notify that a reply bundle should be created
828
828
829 The payload contains the capabilities information for the reply"""
829 The payload contains the capabilities information for the reply"""
830 caps = decodecaps(inpart.read())
830 caps = decodecaps(inpart.read())
831 if op.reply is None:
831 if op.reply is None:
832 op.reply = bundle20(op.ui, caps)
832 op.reply = bundle20(op.ui, caps)
833
833
834 @parthandler('b2x:error:abort', ('message', 'hint'))
834 @parthandler('b2x:error:abort', ('message', 'hint'))
835 def handlereplycaps(op, inpart):
835 def handlereplycaps(op, inpart):
836 """Used to transmit abort error over the wire"""
836 """Used to transmit abort error over the wire"""
837 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
837 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
838
838
839 @parthandler('b2x:error:unsupportedcontent', ('parttype', 'params'))
839 @parthandler('b2x:error:unsupportedcontent', ('parttype', 'params'))
840 def handlereplycaps(op, inpart):
840 def handlereplycaps(op, inpart):
841 """Used to transmit unknown content error over the wire"""
841 """Used to transmit unknown content error over the wire"""
842 kwargs = {}
842 kwargs = {}
843 parttype = inpart.params.get('parttype')
843 parttype = inpart.params.get('parttype')
844 if parttype is not None:
844 if parttype is not None:
845 kwargs['parttype'] = parttype
845 kwargs['parttype'] = parttype
846 params = inpart.params.get('params')
846 params = inpart.params.get('params')
847 if params is not None:
847 if params is not None:
848 kwargs['params'] = params.split('\0')
848 kwargs['params'] = params.split('\0')
849
849
850 raise error.BundleValueError(**kwargs)
850 raise error.BundleValueError(**kwargs)
851
851
852 @parthandler('b2x:error:pushraced', ('message',))
852 @parthandler('b2x:error:pushraced', ('message',))
853 def handlereplycaps(op, inpart):
853 def handlereplycaps(op, inpart):
854 """Used to transmit push race error over the wire"""
854 """Used to transmit push race error over the wire"""
855 raise error.ResponseError(_('push failed:'), inpart.params['message'])
855 raise error.ResponseError(_('push failed:'), inpart.params['message'])
@@ -1,1121 +1,1121
1
1
2 Create an extension to test bundle2 API
2 Create an extension to test bundle2 API
3
3
4 $ cat > bundle2.py << EOF
4 $ cat > bundle2.py << EOF
5 > """A small extension to test bundle2 implementation
5 > """A small extension to test bundle2 implementation
6 >
6 >
7 > Current bundle2 implementation is far too limited to be used in any core
7 > Current bundle2 implementation is far too limited to be used in any core
8 > code. We still need to be able to test it while it grow up.
8 > code. We still need to be able to test it while it grow up.
9 > """
9 > """
10 >
10 >
11 > try:
11 > try:
12 > import msvcrt
12 > import msvcrt
13 > msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
13 > msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
14 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
14 > msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
15 > msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
15 > msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
16 > except ImportError:
16 > except ImportError:
17 > pass
17 > pass
18 >
18 >
19 > import sys
19 > import sys
20 > from mercurial import cmdutil
20 > from mercurial import cmdutil
21 > from mercurial import util
21 > from mercurial import util
22 > from mercurial import bundle2
22 > from mercurial import bundle2
23 > from mercurial import scmutil
23 > from mercurial import scmutil
24 > from mercurial import discovery
24 > from mercurial import discovery
25 > from mercurial import changegroup
25 > from mercurial import changegroup
26 > from mercurial import error
26 > from mercurial import error
27 > cmdtable = {}
27 > cmdtable = {}
28 > command = cmdutil.command(cmdtable)
28 > command = cmdutil.command(cmdtable)
29 >
29 >
30 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
30 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
31 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
31 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
32 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
32 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
33 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
33 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
34 >
34 >
35 > @bundle2.parthandler('test:song')
35 > @bundle2.parthandler('test:song')
36 > def songhandler(op, part):
36 > def songhandler(op, part):
37 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
37 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
38 > op.ui.write('The choir starts singing:\n')
38 > op.ui.write('The choir starts singing:\n')
39 > verses = 0
39 > verses = 0
40 > for line in part.read().split('\n'):
40 > for line in part.read().split('\n'):
41 > op.ui.write(' %s\n' % line)
41 > op.ui.write(' %s\n' % line)
42 > verses += 1
42 > verses += 1
43 > op.records.add('song', {'verses': verses})
43 > op.records.add('song', {'verses': verses})
44 >
44 >
45 > @bundle2.parthandler('test:ping')
45 > @bundle2.parthandler('test:ping')
46 > def pinghandler(op, part):
46 > def pinghandler(op, part):
47 > op.ui.write('received ping request (id %i)\n' % part.id)
47 > op.ui.write('received ping request (id %i)\n' % part.id)
48 > if op.reply is not None and 'ping-pong' in op.reply.capabilities:
48 > if op.reply is not None and 'ping-pong' in op.reply.capabilities:
49 > op.ui.write_err('replying to ping request (id %i)\n' % part.id)
49 > op.ui.write_err('replying to ping request (id %i)\n' % part.id)
50 > op.reply.newpart('test:pong', [('in-reply-to', str(part.id))])
50 > op.reply.newpart('test:pong', [('in-reply-to', str(part.id))])
51 >
51 >
52 > @bundle2.parthandler('test:debugreply')
52 > @bundle2.parthandler('test:debugreply')
53 > def debugreply(op, part):
53 > def debugreply(op, part):
54 > """print data about the capacity of the bundle reply"""
54 > """print data about the capacity of the bundle reply"""
55 > if op.reply is None:
55 > if op.reply is None:
56 > op.ui.write('debugreply: no reply\n')
56 > op.ui.write('debugreply: no reply\n')
57 > else:
57 > else:
58 > op.ui.write('debugreply: capabilities:\n')
58 > op.ui.write('debugreply: capabilities:\n')
59 > for cap in sorted(op.reply.capabilities):
59 > for cap in sorted(op.reply.capabilities):
60 > op.ui.write('debugreply: %r\n' % cap)
60 > op.ui.write('debugreply: %r\n' % cap)
61 > for val in op.reply.capabilities[cap]:
61 > for val in op.reply.capabilities[cap]:
62 > op.ui.write('debugreply: %r\n' % val)
62 > op.ui.write('debugreply: %r\n' % val)
63 >
63 >
64 > @command('bundle2',
64 > @command('bundle2',
65 > [('', 'param', [], 'stream level parameter'),
65 > [('', 'param', [], 'stream level parameter'),
66 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
66 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
67 > ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
67 > ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
68 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
68 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
69 > ('', 'reply', False, 'produce a reply bundle'),
69 > ('', 'reply', False, 'produce a reply bundle'),
70 > ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
70 > ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
71 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
71 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
72 > '[OUTPUTFILE]')
72 > '[OUTPUTFILE]')
73 > def cmdbundle2(ui, repo, path=None, **opts):
73 > def cmdbundle2(ui, repo, path=None, **opts):
74 > """write a bundle2 container on standard ouput"""
74 > """write a bundle2 container on standard ouput"""
75 > bundler = bundle2.bundle20(ui)
75 > bundler = bundle2.bundle20(ui)
76 > for p in opts['param']:
76 > for p in opts['param']:
77 > p = p.split('=', 1)
77 > p = p.split('=', 1)
78 > try:
78 > try:
79 > bundler.addparam(*p)
79 > bundler.addparam(*p)
80 > except ValueError, exc:
80 > except ValueError, exc:
81 > raise util.Abort('%s' % exc)
81 > raise util.Abort('%s' % exc)
82 >
82 >
83 > if opts['reply']:
83 > if opts['reply']:
84 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
84 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
85 > bundler.newpart('b2x:replycaps', data=capsstring)
85 > bundler.newpart('b2x:replycaps', data=capsstring)
86 >
86 >
87 > if opts['pushrace']:
87 > if opts['pushrace']:
88 > # also serve to test the assignement of data outside of init
88 > # also serve to test the assignement of data outside of init
89 > part = bundler.newpart('b2x:check:heads')
89 > part = bundler.newpart('b2x:check:heads')
90 > part.data = '01234567890123456789'
90 > part.data = '01234567890123456789'
91 >
91 >
92 > revs = opts['rev']
92 > revs = opts['rev']
93 > if 'rev' in opts:
93 > if 'rev' in opts:
94 > revs = scmutil.revrange(repo, opts['rev'])
94 > revs = scmutil.revrange(repo, opts['rev'])
95 > if revs:
95 > if revs:
96 > # very crude version of a changegroup part creation
96 > # very crude version of a changegroup part creation
97 > bundled = repo.revs('%ld::%ld', revs, revs)
97 > bundled = repo.revs('%ld::%ld', revs, revs)
98 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
98 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
99 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
99 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
100 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
100 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
101 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
101 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
102 > bundler.newpart('b2x:changegroup', data=cg.getchunks())
102 > bundler.newpart('b2x:changegroup', data=cg.getchunks())
103 >
103 >
104 > if opts['parts']:
104 > if opts['parts']:
105 > bundler.newpart('test:empty')
105 > bundler.newpart('test:empty')
106 > # add a second one to make sure we handle multiple parts
106 > # add a second one to make sure we handle multiple parts
107 > bundler.newpart('test:empty')
107 > bundler.newpart('test:empty')
108 > bundler.newpart('test:song', data=ELEPHANTSSONG)
108 > bundler.newpart('test:song', data=ELEPHANTSSONG)
109 > bundler.newpart('test:debugreply')
109 > bundler.newpart('test:debugreply')
110 > mathpart = bundler.newpart('test:math')
110 > mathpart = bundler.newpart('test:math')
111 > mathpart.addparam('pi', '3.14')
111 > mathpart.addparam('pi', '3.14')
112 > mathpart.addparam('e', '2.72')
112 > mathpart.addparam('e', '2.72')
113 > mathpart.addparam('cooking', 'raw', mandatory=False)
113 > mathpart.addparam('cooking', 'raw', mandatory=False)
114 > mathpart.data = '42'
114 > mathpart.data = '42'
115 > # advisory known part with unknown mandatory param
115 > # advisory known part with unknown mandatory param
116 > bundler.newpart('test:song', [('randomparam','')])
116 > bundler.newpart('test:song', [('randomparam','')])
117 > if opts['unknown']:
117 > if opts['unknown']:
118 > bundler.newpart('test:UNKNOWN', data='some random content')
118 > bundler.newpart('test:UNKNOWN', data='some random content')
119 > if opts['unknownparams']:
119 > if opts['unknownparams']:
120 > bundler.newpart('test:SONG', [('randomparams', '')])
120 > bundler.newpart('test:SONG', [('randomparams', '')])
121 > if opts['parts']:
121 > if opts['parts']:
122 > bundler.newpart('test:ping')
122 > bundler.newpart('test:ping')
123 >
123 >
124 > if path is None:
124 > if path is None:
125 > file = sys.stdout
125 > file = sys.stdout
126 > else:
126 > else:
127 > file = open(path, 'w')
127 > file = open(path, 'w')
128 >
128 >
129 > for chunk in bundler.getchunks():
129 > for chunk in bundler.getchunks():
130 > file.write(chunk)
130 > file.write(chunk)
131 >
131 >
132 > @command('unbundle2', [], '')
132 > @command('unbundle2', [], '')
133 > def cmdunbundle2(ui, repo, replypath=None):
133 > def cmdunbundle2(ui, repo, replypath=None):
134 > """process a bundle2 stream from stdin on the current repo"""
134 > """process a bundle2 stream from stdin on the current repo"""
135 > try:
135 > try:
136 > tr = None
136 > tr = None
137 > lock = repo.lock()
137 > lock = repo.lock()
138 > tr = repo.transaction('processbundle')
138 > tr = repo.transaction('processbundle')
139 > try:
139 > try:
140 > unbundler = bundle2.unbundle20(ui, sys.stdin)
140 > unbundler = bundle2.unbundle20(ui, sys.stdin)
141 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
141 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
142 > tr.close()
142 > tr.close()
143 > except error.BundleValueError, exc:
143 > except error.BundleValueError, exc:
144 > raise util.Abort('missing support for %s' % exc)
144 > raise util.Abort('missing support for %s' % exc)
145 > except error.PushRaced, exc:
145 > except error.PushRaced, exc:
146 > raise util.Abort('push race: %s' % exc)
146 > raise util.Abort('push race: %s' % exc)
147 > finally:
147 > finally:
148 > if tr is not None:
148 > if tr is not None:
149 > tr.release()
149 > tr.release()
150 > lock.release()
150 > lock.release()
151 > remains = sys.stdin.read()
151 > remains = sys.stdin.read()
152 > ui.write('%i unread bytes\n' % len(remains))
152 > ui.write('%i unread bytes\n' % len(remains))
153 > if op.records['song']:
153 > if op.records['song']:
154 > totalverses = sum(r['verses'] for r in op.records['song'])
154 > totalverses = sum(r['verses'] for r in op.records['song'])
155 > ui.write('%i total verses sung\n' % totalverses)
155 > ui.write('%i total verses sung\n' % totalverses)
156 > for rec in op.records['changegroup']:
156 > for rec in op.records['changegroup']:
157 > ui.write('addchangegroup return: %i\n' % rec['return'])
157 > ui.write('addchangegroup return: %i\n' % rec['return'])
158 > if op.reply is not None and replypath is not None:
158 > if op.reply is not None and replypath is not None:
159 > file = open(replypath, 'w')
159 > file = open(replypath, 'w')
160 > for chunk in op.reply.getchunks():
160 > for chunk in op.reply.getchunks():
161 > file.write(chunk)
161 > file.write(chunk)
162 >
162 >
163 > @command('statbundle2', [], '')
163 > @command('statbundle2', [], '')
164 > def cmdstatbundle2(ui, repo):
164 > def cmdstatbundle2(ui, repo):
165 > """print statistic on the bundle2 container read from stdin"""
165 > """print statistic on the bundle2 container read from stdin"""
166 > unbundler = bundle2.unbundle20(ui, sys.stdin)
166 > unbundler = bundle2.unbundle20(ui, sys.stdin)
167 > try:
167 > try:
168 > params = unbundler.params
168 > params = unbundler.params
169 > except KeyError, exc:
169 > except error.BundleValueError, exc:
170 > raise util.Abort('unknown parameters: %s' % exc)
170 > raise util.Abort('unknown parameters: %s' % exc)
171 > ui.write('options count: %i\n' % len(params))
171 > ui.write('options count: %i\n' % len(params))
172 > for key in sorted(params):
172 > for key in sorted(params):
173 > ui.write('- %s\n' % key)
173 > ui.write('- %s\n' % key)
174 > value = params[key]
174 > value = params[key]
175 > if value is not None:
175 > if value is not None:
176 > ui.write(' %s\n' % value)
176 > ui.write(' %s\n' % value)
177 > count = 0
177 > count = 0
178 > for p in unbundler.iterparts():
178 > for p in unbundler.iterparts():
179 > count += 1
179 > count += 1
180 > ui.write(' :%s:\n' % p.type)
180 > ui.write(' :%s:\n' % p.type)
181 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
181 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
182 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
182 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
183 > ui.write(' payload: %i bytes\n' % len(p.read()))
183 > ui.write(' payload: %i bytes\n' % len(p.read()))
184 > ui.write('parts count: %i\n' % count)
184 > ui.write('parts count: %i\n' % count)
185 > EOF
185 > EOF
186 $ cat >> $HGRCPATH << EOF
186 $ cat >> $HGRCPATH << EOF
187 > [extensions]
187 > [extensions]
188 > bundle2=$TESTTMP/bundle2.py
188 > bundle2=$TESTTMP/bundle2.py
189 > [experimental]
189 > [experimental]
190 > bundle2-exp=True
190 > bundle2-exp=True
191 > [ui]
191 > [ui]
192 > ssh=python "$TESTDIR/dummyssh"
192 > ssh=python "$TESTDIR/dummyssh"
193 > [web]
193 > [web]
194 > push_ssl = false
194 > push_ssl = false
195 > allow_push = *
195 > allow_push = *
196 > EOF
196 > EOF
197
197
198 The extension requires a repo (currently unused)
198 The extension requires a repo (currently unused)
199
199
200 $ hg init main
200 $ hg init main
201 $ cd main
201 $ cd main
202 $ touch a
202 $ touch a
203 $ hg add a
203 $ hg add a
204 $ hg commit -m 'a'
204 $ hg commit -m 'a'
205
205
206
206
207 Empty bundle
207 Empty bundle
208 =================
208 =================
209
209
210 - no option
210 - no option
211 - no parts
211 - no parts
212
212
213 Test bundling
213 Test bundling
214
214
215 $ hg bundle2
215 $ hg bundle2
216 HG2X\x00\x00\x00\x00 (no-eol) (esc)
216 HG2X\x00\x00\x00\x00 (no-eol) (esc)
217
217
218 Test unbundling
218 Test unbundling
219
219
220 $ hg bundle2 | hg statbundle2
220 $ hg bundle2 | hg statbundle2
221 options count: 0
221 options count: 0
222 parts count: 0
222 parts count: 0
223
223
224 Test old style bundle are detected and refused
224 Test old style bundle are detected and refused
225
225
226 $ hg bundle --all ../bundle.hg
226 $ hg bundle --all ../bundle.hg
227 1 changesets found
227 1 changesets found
228 $ hg statbundle2 < ../bundle.hg
228 $ hg statbundle2 < ../bundle.hg
229 abort: unknown bundle version 10
229 abort: unknown bundle version 10
230 [255]
230 [255]
231
231
232 Test parameters
232 Test parameters
233 =================
233 =================
234
234
235 - some options
235 - some options
236 - no parts
236 - no parts
237
237
238 advisory parameters, no value
238 advisory parameters, no value
239 -------------------------------
239 -------------------------------
240
240
241 Simplest possible parameters form
241 Simplest possible parameters form
242
242
243 Test generation simple option
243 Test generation simple option
244
244
245 $ hg bundle2 --param 'caution'
245 $ hg bundle2 --param 'caution'
246 HG2X\x00\x07caution\x00\x00 (no-eol) (esc)
246 HG2X\x00\x07caution\x00\x00 (no-eol) (esc)
247
247
248 Test unbundling
248 Test unbundling
249
249
250 $ hg bundle2 --param 'caution' | hg statbundle2
250 $ hg bundle2 --param 'caution' | hg statbundle2
251 options count: 1
251 options count: 1
252 - caution
252 - caution
253 parts count: 0
253 parts count: 0
254
254
255 Test generation multiple option
255 Test generation multiple option
256
256
257 $ hg bundle2 --param 'caution' --param 'meal'
257 $ hg bundle2 --param 'caution' --param 'meal'
258 HG2X\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
258 HG2X\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
259
259
260 Test unbundling
260 Test unbundling
261
261
262 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
262 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
263 options count: 2
263 options count: 2
264 - caution
264 - caution
265 - meal
265 - meal
266 parts count: 0
266 parts count: 0
267
267
268 advisory parameters, with value
268 advisory parameters, with value
269 -------------------------------
269 -------------------------------
270
270
271 Test generation
271 Test generation
272
272
273 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
273 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
274 HG2X\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
274 HG2X\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
275
275
276 Test unbundling
276 Test unbundling
277
277
278 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
278 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
279 options count: 3
279 options count: 3
280 - caution
280 - caution
281 - elephants
281 - elephants
282 - meal
282 - meal
283 vegan
283 vegan
284 parts count: 0
284 parts count: 0
285
285
286 parameter with special char in value
286 parameter with special char in value
287 ---------------------------------------------------
287 ---------------------------------------------------
288
288
289 Test generation
289 Test generation
290
290
291 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
291 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
292 HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
292 HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
293
293
294 Test unbundling
294 Test unbundling
295
295
296 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
296 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
297 options count: 2
297 options count: 2
298 - e|! 7/
298 - e|! 7/
299 babar%#==tutu
299 babar%#==tutu
300 - simple
300 - simple
301 parts count: 0
301 parts count: 0
302
302
303 Test unknown mandatory option
303 Test unknown mandatory option
304 ---------------------------------------------------
304 ---------------------------------------------------
305
305
306 $ hg bundle2 --param 'Gravity' | hg statbundle2
306 $ hg bundle2 --param 'Gravity' | hg statbundle2
307 abort: unknown parameters: 'Gravity'
307 abort: unknown parameters: Stream Parameter - Gravity
308 [255]
308 [255]
309
309
310 Test debug output
310 Test debug output
311 ---------------------------------------------------
311 ---------------------------------------------------
312
312
313 bundling debug
313 bundling debug
314
314
315 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
315 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
316 start emission of HG2X stream
316 start emission of HG2X stream
317 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
317 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
318 start of parts
318 start of parts
319 end of bundle
319 end of bundle
320
320
321 file content is ok
321 file content is ok
322
322
323 $ cat ../out.hg2
323 $ cat ../out.hg2
324 HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
324 HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
325
325
326 unbundling debug
326 unbundling debug
327
327
328 $ hg statbundle2 --debug < ../out.hg2
328 $ hg statbundle2 --debug < ../out.hg2
329 start processing of HG2X stream
329 start processing of HG2X stream
330 reading bundle2 stream parameters
330 reading bundle2 stream parameters
331 ignoring unknown parameter 'e|! 7/'
331 ignoring unknown parameter 'e|! 7/'
332 ignoring unknown parameter 'simple'
332 ignoring unknown parameter 'simple'
333 options count: 2
333 options count: 2
334 - e|! 7/
334 - e|! 7/
335 babar%#==tutu
335 babar%#==tutu
336 - simple
336 - simple
337 start extraction of bundle2 parts
337 start extraction of bundle2 parts
338 part header size: 0
338 part header size: 0
339 end of bundle2 stream
339 end of bundle2 stream
340 parts count: 0
340 parts count: 0
341
341
342
342
343 Test buggy input
343 Test buggy input
344 ---------------------------------------------------
344 ---------------------------------------------------
345
345
346 empty parameter name
346 empty parameter name
347
347
348 $ hg bundle2 --param '' --quiet
348 $ hg bundle2 --param '' --quiet
349 abort: empty parameter name
349 abort: empty parameter name
350 [255]
350 [255]
351
351
352 bad parameter name
352 bad parameter name
353
353
354 $ hg bundle2 --param 42babar
354 $ hg bundle2 --param 42babar
355 abort: non letter first character: '42babar'
355 abort: non letter first character: '42babar'
356 [255]
356 [255]
357
357
358
358
359 Test part
359 Test part
360 =================
360 =================
361
361
362 $ hg bundle2 --parts ../parts.hg2 --debug
362 $ hg bundle2 --parts ../parts.hg2 --debug
363 start emission of HG2X stream
363 start emission of HG2X stream
364 bundle parameter:
364 bundle parameter:
365 start of parts
365 start of parts
366 bundle part: "test:empty"
366 bundle part: "test:empty"
367 bundle part: "test:empty"
367 bundle part: "test:empty"
368 bundle part: "test:song"
368 bundle part: "test:song"
369 bundle part: "test:debugreply"
369 bundle part: "test:debugreply"
370 bundle part: "test:math"
370 bundle part: "test:math"
371 bundle part: "test:song"
371 bundle part: "test:song"
372 bundle part: "test:ping"
372 bundle part: "test:ping"
373 end of bundle
373 end of bundle
374
374
375 $ cat ../parts.hg2
375 $ cat ../parts.hg2
376 HG2X\x00\x00\x00\x11 (esc)
376 HG2X\x00\x00\x00\x11 (esc)
377 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
377 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
378 test:empty\x00\x00\x00\x01\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)
378 test:empty\x00\x00\x00\x01\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)
379 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
379 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
380 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\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\x1d test:song\x00\x00\x00\x05\x01\x00\x0b\x00randomparam\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
380 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\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\x1d test:song\x00\x00\x00\x05\x01\x00\x0b\x00randomparam\x00\x00\x00\x00\x00\x10 test:ping\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
381
381
382
382
383 $ hg statbundle2 < ../parts.hg2
383 $ hg statbundle2 < ../parts.hg2
384 options count: 0
384 options count: 0
385 :test:empty:
385 :test:empty:
386 mandatory: 0
386 mandatory: 0
387 advisory: 0
387 advisory: 0
388 payload: 0 bytes
388 payload: 0 bytes
389 :test:empty:
389 :test:empty:
390 mandatory: 0
390 mandatory: 0
391 advisory: 0
391 advisory: 0
392 payload: 0 bytes
392 payload: 0 bytes
393 :test:song:
393 :test:song:
394 mandatory: 0
394 mandatory: 0
395 advisory: 0
395 advisory: 0
396 payload: 178 bytes
396 payload: 178 bytes
397 :test:debugreply:
397 :test:debugreply:
398 mandatory: 0
398 mandatory: 0
399 advisory: 0
399 advisory: 0
400 payload: 0 bytes
400 payload: 0 bytes
401 :test:math:
401 :test:math:
402 mandatory: 2
402 mandatory: 2
403 advisory: 1
403 advisory: 1
404 payload: 2 bytes
404 payload: 2 bytes
405 :test:song:
405 :test:song:
406 mandatory: 1
406 mandatory: 1
407 advisory: 0
407 advisory: 0
408 payload: 0 bytes
408 payload: 0 bytes
409 :test:ping:
409 :test:ping:
410 mandatory: 0
410 mandatory: 0
411 advisory: 0
411 advisory: 0
412 payload: 0 bytes
412 payload: 0 bytes
413 parts count: 7
413 parts count: 7
414
414
415 $ hg statbundle2 --debug < ../parts.hg2
415 $ hg statbundle2 --debug < ../parts.hg2
416 start processing of HG2X stream
416 start processing of HG2X stream
417 reading bundle2 stream parameters
417 reading bundle2 stream parameters
418 options count: 0
418 options count: 0
419 start extraction of bundle2 parts
419 start extraction of bundle2 parts
420 part header size: 17
420 part header size: 17
421 part type: "test:empty"
421 part type: "test:empty"
422 part id: "0"
422 part id: "0"
423 part parameters: 0
423 part parameters: 0
424 :test:empty:
424 :test:empty:
425 mandatory: 0
425 mandatory: 0
426 advisory: 0
426 advisory: 0
427 payload chunk size: 0
427 payload chunk size: 0
428 payload: 0 bytes
428 payload: 0 bytes
429 part header size: 17
429 part header size: 17
430 part type: "test:empty"
430 part type: "test:empty"
431 part id: "1"
431 part id: "1"
432 part parameters: 0
432 part parameters: 0
433 :test:empty:
433 :test:empty:
434 mandatory: 0
434 mandatory: 0
435 advisory: 0
435 advisory: 0
436 payload chunk size: 0
436 payload chunk size: 0
437 payload: 0 bytes
437 payload: 0 bytes
438 part header size: 16
438 part header size: 16
439 part type: "test:song"
439 part type: "test:song"
440 part id: "2"
440 part id: "2"
441 part parameters: 0
441 part parameters: 0
442 :test:song:
442 :test:song:
443 mandatory: 0
443 mandatory: 0
444 advisory: 0
444 advisory: 0
445 payload chunk size: 178
445 payload chunk size: 178
446 payload chunk size: 0
446 payload chunk size: 0
447 payload: 178 bytes
447 payload: 178 bytes
448 part header size: 22
448 part header size: 22
449 part type: "test:debugreply"
449 part type: "test:debugreply"
450 part id: "3"
450 part id: "3"
451 part parameters: 0
451 part parameters: 0
452 :test:debugreply:
452 :test:debugreply:
453 mandatory: 0
453 mandatory: 0
454 advisory: 0
454 advisory: 0
455 payload chunk size: 0
455 payload chunk size: 0
456 payload: 0 bytes
456 payload: 0 bytes
457 part header size: 43
457 part header size: 43
458 part type: "test:math"
458 part type: "test:math"
459 part id: "4"
459 part id: "4"
460 part parameters: 3
460 part parameters: 3
461 :test:math:
461 :test:math:
462 mandatory: 2
462 mandatory: 2
463 advisory: 1
463 advisory: 1
464 payload chunk size: 2
464 payload chunk size: 2
465 payload chunk size: 0
465 payload chunk size: 0
466 payload: 2 bytes
466 payload: 2 bytes
467 part header size: 29
467 part header size: 29
468 part type: "test:song"
468 part type: "test:song"
469 part id: "5"
469 part id: "5"
470 part parameters: 1
470 part parameters: 1
471 :test:song:
471 :test:song:
472 mandatory: 1
472 mandatory: 1
473 advisory: 0
473 advisory: 0
474 payload chunk size: 0
474 payload chunk size: 0
475 payload: 0 bytes
475 payload: 0 bytes
476 part header size: 16
476 part header size: 16
477 part type: "test:ping"
477 part type: "test:ping"
478 part id: "6"
478 part id: "6"
479 part parameters: 0
479 part parameters: 0
480 :test:ping:
480 :test:ping:
481 mandatory: 0
481 mandatory: 0
482 advisory: 0
482 advisory: 0
483 payload chunk size: 0
483 payload chunk size: 0
484 payload: 0 bytes
484 payload: 0 bytes
485 part header size: 0
485 part header size: 0
486 end of bundle2 stream
486 end of bundle2 stream
487 parts count: 7
487 parts count: 7
488
488
489 Test actual unbundling of test part
489 Test actual unbundling of test part
490 =======================================
490 =======================================
491
491
492 Process the bundle
492 Process the bundle
493
493
494 $ hg unbundle2 --debug < ../parts.hg2
494 $ hg unbundle2 --debug < ../parts.hg2
495 start processing of HG2X stream
495 start processing of HG2X stream
496 reading bundle2 stream parameters
496 reading bundle2 stream parameters
497 start extraction of bundle2 parts
497 start extraction of bundle2 parts
498 part header size: 17
498 part header size: 17
499 part type: "test:empty"
499 part type: "test:empty"
500 part id: "0"
500 part id: "0"
501 part parameters: 0
501 part parameters: 0
502 ignoring unsupported advisory part test:empty
502 ignoring unsupported advisory part test:empty
503 payload chunk size: 0
503 payload chunk size: 0
504 part header size: 17
504 part header size: 17
505 part type: "test:empty"
505 part type: "test:empty"
506 part id: "1"
506 part id: "1"
507 part parameters: 0
507 part parameters: 0
508 ignoring unsupported advisory part test:empty
508 ignoring unsupported advisory part test:empty
509 payload chunk size: 0
509 payload chunk size: 0
510 part header size: 16
510 part header size: 16
511 part type: "test:song"
511 part type: "test:song"
512 part id: "2"
512 part id: "2"
513 part parameters: 0
513 part parameters: 0
514 found a handler for part 'test:song'
514 found a handler for part 'test:song'
515 The choir starts singing:
515 The choir starts singing:
516 payload chunk size: 178
516 payload chunk size: 178
517 payload chunk size: 0
517 payload chunk size: 0
518 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
518 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
519 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
519 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
520 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
520 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
521 part header size: 22
521 part header size: 22
522 part type: "test:debugreply"
522 part type: "test:debugreply"
523 part id: "3"
523 part id: "3"
524 part parameters: 0
524 part parameters: 0
525 found a handler for part 'test:debugreply'
525 found a handler for part 'test:debugreply'
526 debugreply: no reply
526 debugreply: no reply
527 payload chunk size: 0
527 payload chunk size: 0
528 part header size: 43
528 part header size: 43
529 part type: "test:math"
529 part type: "test:math"
530 part id: "4"
530 part id: "4"
531 part parameters: 3
531 part parameters: 3
532 ignoring unsupported advisory part test:math
532 ignoring unsupported advisory part test:math
533 payload chunk size: 2
533 payload chunk size: 2
534 payload chunk size: 0
534 payload chunk size: 0
535 part header size: 29
535 part header size: 29
536 part type: "test:song"
536 part type: "test:song"
537 part id: "5"
537 part id: "5"
538 part parameters: 1
538 part parameters: 1
539 found a handler for part 'test:song'
539 found a handler for part 'test:song'
540 ignoring unsupported advisory part test:song - randomparam
540 ignoring unsupported advisory part test:song - randomparam
541 payload chunk size: 0
541 payload chunk size: 0
542 part header size: 16
542 part header size: 16
543 part type: "test:ping"
543 part type: "test:ping"
544 part id: "6"
544 part id: "6"
545 part parameters: 0
545 part parameters: 0
546 found a handler for part 'test:ping'
546 found a handler for part 'test:ping'
547 received ping request (id 6)
547 received ping request (id 6)
548 payload chunk size: 0
548 payload chunk size: 0
549 part header size: 0
549 part header size: 0
550 end of bundle2 stream
550 end of bundle2 stream
551 0 unread bytes
551 0 unread bytes
552 3 total verses sung
552 3 total verses sung
553
553
554 Unbundle with an unknown mandatory part
554 Unbundle with an unknown mandatory part
555 (should abort)
555 (should abort)
556
556
557 $ hg bundle2 --parts --unknown ../unknown.hg2
557 $ hg bundle2 --parts --unknown ../unknown.hg2
558
558
559 $ hg unbundle2 < ../unknown.hg2
559 $ hg unbundle2 < ../unknown.hg2
560 The choir starts singing:
560 The choir starts singing:
561 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
561 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
562 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
562 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
563 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
563 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
564 debugreply: no reply
564 debugreply: no reply
565 0 unread bytes
565 0 unread bytes
566 abort: missing support for test:unknown
566 abort: missing support for test:unknown
567 [255]
567 [255]
568
568
569 Unbundle with an unknown mandatory part parameters
569 Unbundle with an unknown mandatory part parameters
570 (should abort)
570 (should abort)
571
571
572 $ hg bundle2 --unknownparams ../unknown.hg2
572 $ hg bundle2 --unknownparams ../unknown.hg2
573
573
574 $ hg unbundle2 < ../unknown.hg2
574 $ hg unbundle2 < ../unknown.hg2
575 0 unread bytes
575 0 unread bytes
576 abort: missing support for test:song - randomparams
576 abort: missing support for test:song - randomparams
577 [255]
577 [255]
578
578
579 unbundle with a reply
579 unbundle with a reply
580
580
581 $ hg bundle2 --parts --reply ../parts-reply.hg2
581 $ hg bundle2 --parts --reply ../parts-reply.hg2
582 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
582 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
583 0 unread bytes
583 0 unread bytes
584 3 total verses sung
584 3 total verses sung
585
585
586 The reply is a bundle
586 The reply is a bundle
587
587
588 $ cat ../reply.hg2
588 $ cat ../reply.hg2
589 HG2X\x00\x00\x00\x1f (esc)
589 HG2X\x00\x00\x00\x1f (esc)
590 b2x:output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
590 b2x:output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
591 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
591 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
592 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
592 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
593 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
593 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
594 \x00\x00\x00\x00\x00\x1f (esc)
594 \x00\x00\x00\x00\x00\x1f (esc)
595 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
595 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
596 debugreply: 'city=!'
596 debugreply: 'city=!'
597 debugreply: 'celeste,ville'
597 debugreply: 'celeste,ville'
598 debugreply: 'elephants'
598 debugreply: 'elephants'
599 debugreply: 'babar'
599 debugreply: 'babar'
600 debugreply: 'celeste'
600 debugreply: 'celeste'
601 debugreply: 'ping-pong'
601 debugreply: 'ping-pong'
602 \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to7\x00\x00\x00\x00\x00\x1f (esc)
602 \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to7\x00\x00\x00\x00\x00\x1f (esc)
603 b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to7\x00\x00\x00=received ping request (id 7) (esc)
603 b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to7\x00\x00\x00=received ping request (id 7) (esc)
604 replying to ping request (id 7)
604 replying to ping request (id 7)
605 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
605 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
606
606
607 The reply is valid
607 The reply is valid
608
608
609 $ hg statbundle2 < ../reply.hg2
609 $ hg statbundle2 < ../reply.hg2
610 options count: 0
610 options count: 0
611 :b2x:output:
611 :b2x:output:
612 mandatory: 0
612 mandatory: 0
613 advisory: 1
613 advisory: 1
614 payload: 217 bytes
614 payload: 217 bytes
615 :b2x:output:
615 :b2x:output:
616 mandatory: 0
616 mandatory: 0
617 advisory: 1
617 advisory: 1
618 payload: 201 bytes
618 payload: 201 bytes
619 :test:pong:
619 :test:pong:
620 mandatory: 1
620 mandatory: 1
621 advisory: 0
621 advisory: 0
622 payload: 0 bytes
622 payload: 0 bytes
623 :b2x:output:
623 :b2x:output:
624 mandatory: 0
624 mandatory: 0
625 advisory: 1
625 advisory: 1
626 payload: 61 bytes
626 payload: 61 bytes
627 parts count: 4
627 parts count: 4
628
628
629 Unbundle the reply to get the output:
629 Unbundle the reply to get the output:
630
630
631 $ hg unbundle2 < ../reply.hg2
631 $ hg unbundle2 < ../reply.hg2
632 remote: The choir starts singing:
632 remote: The choir starts singing:
633 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
633 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
634 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
634 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
635 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
635 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
636 remote: debugreply: capabilities:
636 remote: debugreply: capabilities:
637 remote: debugreply: 'city=!'
637 remote: debugreply: 'city=!'
638 remote: debugreply: 'celeste,ville'
638 remote: debugreply: 'celeste,ville'
639 remote: debugreply: 'elephants'
639 remote: debugreply: 'elephants'
640 remote: debugreply: 'babar'
640 remote: debugreply: 'babar'
641 remote: debugreply: 'celeste'
641 remote: debugreply: 'celeste'
642 remote: debugreply: 'ping-pong'
642 remote: debugreply: 'ping-pong'
643 remote: received ping request (id 7)
643 remote: received ping request (id 7)
644 remote: replying to ping request (id 7)
644 remote: replying to ping request (id 7)
645 0 unread bytes
645 0 unread bytes
646
646
647 Test push race detection
647 Test push race detection
648
648
649 $ hg bundle2 --pushrace ../part-race.hg2
649 $ hg bundle2 --pushrace ../part-race.hg2
650
650
651 $ hg unbundle2 < ../part-race.hg2
651 $ hg unbundle2 < ../part-race.hg2
652 0 unread bytes
652 0 unread bytes
653 abort: push race: repository changed while pushing - please try again
653 abort: push race: repository changed while pushing - please try again
654 [255]
654 [255]
655
655
656 Support for changegroup
656 Support for changegroup
657 ===================================
657 ===================================
658
658
659 $ hg unbundle $TESTDIR/bundles/rebase.hg
659 $ hg unbundle $TESTDIR/bundles/rebase.hg
660 adding changesets
660 adding changesets
661 adding manifests
661 adding manifests
662 adding file changes
662 adding file changes
663 added 8 changesets with 7 changes to 7 files (+3 heads)
663 added 8 changesets with 7 changes to 7 files (+3 heads)
664 (run 'hg heads' to see heads, 'hg merge' to merge)
664 (run 'hg heads' to see heads, 'hg merge' to merge)
665
665
666 $ hg log -G
666 $ hg log -G
667 o changeset: 8:02de42196ebe
667 o changeset: 8:02de42196ebe
668 | tag: tip
668 | tag: tip
669 | parent: 6:24b6387c8c8c
669 | parent: 6:24b6387c8c8c
670 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
670 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
671 | date: Sat Apr 30 15:24:48 2011 +0200
671 | date: Sat Apr 30 15:24:48 2011 +0200
672 | summary: H
672 | summary: H
673 |
673 |
674 | o changeset: 7:eea13746799a
674 | o changeset: 7:eea13746799a
675 |/| parent: 6:24b6387c8c8c
675 |/| parent: 6:24b6387c8c8c
676 | | parent: 5:9520eea781bc
676 | | parent: 5:9520eea781bc
677 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
677 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
678 | | date: Sat Apr 30 15:24:48 2011 +0200
678 | | date: Sat Apr 30 15:24:48 2011 +0200
679 | | summary: G
679 | | summary: G
680 | |
680 | |
681 o | changeset: 6:24b6387c8c8c
681 o | changeset: 6:24b6387c8c8c
682 | | parent: 1:cd010b8cd998
682 | | parent: 1:cd010b8cd998
683 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
683 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
684 | | date: Sat Apr 30 15:24:48 2011 +0200
684 | | date: Sat Apr 30 15:24:48 2011 +0200
685 | | summary: F
685 | | summary: F
686 | |
686 | |
687 | o changeset: 5:9520eea781bc
687 | o changeset: 5:9520eea781bc
688 |/ parent: 1:cd010b8cd998
688 |/ parent: 1:cd010b8cd998
689 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
689 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
690 | date: Sat Apr 30 15:24:48 2011 +0200
690 | date: Sat Apr 30 15:24:48 2011 +0200
691 | summary: E
691 | summary: E
692 |
692 |
693 | o changeset: 4:32af7686d403
693 | o changeset: 4:32af7686d403
694 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
694 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
695 | | date: Sat Apr 30 15:24:48 2011 +0200
695 | | date: Sat Apr 30 15:24:48 2011 +0200
696 | | summary: D
696 | | summary: D
697 | |
697 | |
698 | o changeset: 3:5fddd98957c8
698 | o changeset: 3:5fddd98957c8
699 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
699 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
700 | | date: Sat Apr 30 15:24:48 2011 +0200
700 | | date: Sat Apr 30 15:24:48 2011 +0200
701 | | summary: C
701 | | summary: C
702 | |
702 | |
703 | o changeset: 2:42ccdea3bb16
703 | o changeset: 2:42ccdea3bb16
704 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
704 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
705 | date: Sat Apr 30 15:24:48 2011 +0200
705 | date: Sat Apr 30 15:24:48 2011 +0200
706 | summary: B
706 | summary: B
707 |
707 |
708 o changeset: 1:cd010b8cd998
708 o changeset: 1:cd010b8cd998
709 parent: -1:000000000000
709 parent: -1:000000000000
710 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
710 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
711 date: Sat Apr 30 15:24:48 2011 +0200
711 date: Sat Apr 30 15:24:48 2011 +0200
712 summary: A
712 summary: A
713
713
714 @ changeset: 0:3903775176ed
714 @ changeset: 0:3903775176ed
715 user: test
715 user: test
716 date: Thu Jan 01 00:00:00 1970 +0000
716 date: Thu Jan 01 00:00:00 1970 +0000
717 summary: a
717 summary: a
718
718
719
719
720 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
720 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
721 4 changesets found
721 4 changesets found
722 list of changesets:
722 list of changesets:
723 32af7686d403cf45b5d95f2d70cebea587ac806a
723 32af7686d403cf45b5d95f2d70cebea587ac806a
724 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
724 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
725 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
725 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
726 02de42196ebee42ef284b6780a87cdc96e8eaab6
726 02de42196ebee42ef284b6780a87cdc96e8eaab6
727 start emission of HG2X stream
727 start emission of HG2X stream
728 bundle parameter:
728 bundle parameter:
729 start of parts
729 start of parts
730 bundle part: "b2x:changegroup"
730 bundle part: "b2x:changegroup"
731 bundling: 1/4 changesets (25.00%)
731 bundling: 1/4 changesets (25.00%)
732 bundling: 2/4 changesets (50.00%)
732 bundling: 2/4 changesets (50.00%)
733 bundling: 3/4 changesets (75.00%)
733 bundling: 3/4 changesets (75.00%)
734 bundling: 4/4 changesets (100.00%)
734 bundling: 4/4 changesets (100.00%)
735 bundling: 1/4 manifests (25.00%)
735 bundling: 1/4 manifests (25.00%)
736 bundling: 2/4 manifests (50.00%)
736 bundling: 2/4 manifests (50.00%)
737 bundling: 3/4 manifests (75.00%)
737 bundling: 3/4 manifests (75.00%)
738 bundling: 4/4 manifests (100.00%)
738 bundling: 4/4 manifests (100.00%)
739 bundling: D 1/3 files (33.33%)
739 bundling: D 1/3 files (33.33%)
740 bundling: E 2/3 files (66.67%)
740 bundling: E 2/3 files (66.67%)
741 bundling: H 3/3 files (100.00%)
741 bundling: H 3/3 files (100.00%)
742 end of bundle
742 end of bundle
743
743
744 $ cat ../rev.hg2
744 $ cat ../rev.hg2
745 HG2X\x00\x00\x00\x16\x0fb2x:changegroup\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)
745 HG2X\x00\x00\x00\x16\x0fb2x:changegroup\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)
746 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
746 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
747 \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)
747 \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)
748 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
748 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
749 \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)
749 \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)
750 \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)
750 \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)
751 \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)
751 \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)
752 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
752 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
753 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
753 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
754 \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)
754 \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)
755 \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)
755 \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)
756 \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)
756 \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)
757 \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)
757 \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)
758 \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)
758 \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)
759 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
759 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
760 \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)
760 \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)
761 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
761 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
762 l\r (no-eol) (esc)
762 l\r (no-eol) (esc)
763 \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)
763 \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)
764 \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)
764 \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)
765 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
765 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
766 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
766 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
767
767
768 $ hg unbundle2 < ../rev.hg2
768 $ hg unbundle2 < ../rev.hg2
769 adding changesets
769 adding changesets
770 adding manifests
770 adding manifests
771 adding file changes
771 adding file changes
772 added 0 changesets with 0 changes to 3 files
772 added 0 changesets with 0 changes to 3 files
773 0 unread bytes
773 0 unread bytes
774 addchangegroup return: 1
774 addchangegroup return: 1
775
775
776 with reply
776 with reply
777
777
778 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
778 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
779 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
779 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
780 0 unread bytes
780 0 unread bytes
781 addchangegroup return: 1
781 addchangegroup return: 1
782
782
783 $ cat ../rev-reply.hg2
783 $ cat ../rev-reply.hg2
784 HG2X\x00\x00\x003\x15b2x:reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1f (esc)
784 HG2X\x00\x00\x003\x15b2x:reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1f (esc)
785 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
785 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
786 adding manifests
786 adding manifests
787 adding file changes
787 adding file changes
788 added 0 changesets with 0 changes to 3 files
788 added 0 changesets with 0 changes to 3 files
789 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
789 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
790
790
791 Real world exchange
791 Real world exchange
792 =====================
792 =====================
793
793
794
794
795 clone --pull
795 clone --pull
796
796
797 $ cd ..
797 $ cd ..
798 $ hg clone main other --pull --rev 9520eea781bc
798 $ hg clone main other --pull --rev 9520eea781bc
799 adding changesets
799 adding changesets
800 adding manifests
800 adding manifests
801 adding file changes
801 adding file changes
802 added 2 changesets with 2 changes to 2 files
802 added 2 changesets with 2 changes to 2 files
803 updating to branch default
803 updating to branch default
804 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
804 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
805 $ hg -R other log -G
805 $ hg -R other log -G
806 @ changeset: 1:9520eea781bc
806 @ changeset: 1:9520eea781bc
807 | tag: tip
807 | tag: tip
808 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
808 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
809 | date: Sat Apr 30 15:24:48 2011 +0200
809 | date: Sat Apr 30 15:24:48 2011 +0200
810 | summary: E
810 | summary: E
811 |
811 |
812 o changeset: 0:cd010b8cd998
812 o changeset: 0:cd010b8cd998
813 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
813 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
814 date: Sat Apr 30 15:24:48 2011 +0200
814 date: Sat Apr 30 15:24:48 2011 +0200
815 summary: A
815 summary: A
816
816
817
817
818 pull
818 pull
819
819
820 $ hg -R other pull -r 24b6387c8c8c
820 $ hg -R other pull -r 24b6387c8c8c
821 pulling from $TESTTMP/main (glob)
821 pulling from $TESTTMP/main (glob)
822 searching for changes
822 searching for changes
823 adding changesets
823 adding changesets
824 adding manifests
824 adding manifests
825 adding file changes
825 adding file changes
826 added 1 changesets with 1 changes to 1 files (+1 heads)
826 added 1 changesets with 1 changes to 1 files (+1 heads)
827 (run 'hg heads' to see heads, 'hg merge' to merge)
827 (run 'hg heads' to see heads, 'hg merge' to merge)
828
828
829 pull empty
829 pull empty
830
830
831 $ hg -R other pull -r 24b6387c8c8c
831 $ hg -R other pull -r 24b6387c8c8c
832 pulling from $TESTTMP/main (glob)
832 pulling from $TESTTMP/main (glob)
833 no changes found
833 no changes found
834
834
835 push
835 push
836
836
837 $ hg -R main push other --rev eea13746799a
837 $ hg -R main push other --rev eea13746799a
838 pushing to other
838 pushing to other
839 searching for changes
839 searching for changes
840 remote: adding changesets
840 remote: adding changesets
841 remote: adding manifests
841 remote: adding manifests
842 remote: adding file changes
842 remote: adding file changes
843 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
843 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
844
844
845 pull over ssh
845 pull over ssh
846
846
847 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
847 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
848 pulling from ssh://user@dummy/main
848 pulling from ssh://user@dummy/main
849 searching for changes
849 searching for changes
850 adding changesets
850 adding changesets
851 adding manifests
851 adding manifests
852 adding file changes
852 adding file changes
853 added 1 changesets with 1 changes to 1 files (+1 heads)
853 added 1 changesets with 1 changes to 1 files (+1 heads)
854 (run 'hg heads' to see heads, 'hg merge' to merge)
854 (run 'hg heads' to see heads, 'hg merge' to merge)
855
855
856 pull over http
856 pull over http
857
857
858 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
858 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
859 $ cat main.pid >> $DAEMON_PIDS
859 $ cat main.pid >> $DAEMON_PIDS
860
860
861 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
861 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
862 pulling from http://localhost:$HGPORT/
862 pulling from http://localhost:$HGPORT/
863 searching for changes
863 searching for changes
864 adding changesets
864 adding changesets
865 adding manifests
865 adding manifests
866 adding file changes
866 adding file changes
867 added 1 changesets with 1 changes to 1 files (+1 heads)
867 added 1 changesets with 1 changes to 1 files (+1 heads)
868 (run 'hg heads .' to see heads, 'hg merge' to merge)
868 (run 'hg heads .' to see heads, 'hg merge' to merge)
869 $ cat main-error.log
869 $ cat main-error.log
870
870
871 push over ssh
871 push over ssh
872
872
873 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
873 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
874 pushing to ssh://user@dummy/other
874 pushing to ssh://user@dummy/other
875 searching for changes
875 searching for changes
876 remote: adding changesets
876 remote: adding changesets
877 remote: adding manifests
877 remote: adding manifests
878 remote: adding file changes
878 remote: adding file changes
879 remote: added 1 changesets with 1 changes to 1 files
879 remote: added 1 changesets with 1 changes to 1 files
880
880
881 push over http
881 push over http
882
882
883 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
883 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
884 $ cat other.pid >> $DAEMON_PIDS
884 $ cat other.pid >> $DAEMON_PIDS
885
885
886 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
886 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
887 pushing to http://localhost:$HGPORT2/
887 pushing to http://localhost:$HGPORT2/
888 searching for changes
888 searching for changes
889 remote: adding changesets
889 remote: adding changesets
890 remote: adding manifests
890 remote: adding manifests
891 remote: adding file changes
891 remote: adding file changes
892 remote: added 1 changesets with 1 changes to 1 files
892 remote: added 1 changesets with 1 changes to 1 files
893 $ cat other-error.log
893 $ cat other-error.log
894
894
895 Check final content.
895 Check final content.
896
896
897 $ hg -R other log -G
897 $ hg -R other log -G
898 o changeset: 7:32af7686d403
898 o changeset: 7:32af7686d403
899 | tag: tip
899 | tag: tip
900 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
900 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
901 | date: Sat Apr 30 15:24:48 2011 +0200
901 | date: Sat Apr 30 15:24:48 2011 +0200
902 | summary: D
902 | summary: D
903 |
903 |
904 o changeset: 6:5fddd98957c8
904 o changeset: 6:5fddd98957c8
905 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
905 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
906 | date: Sat Apr 30 15:24:48 2011 +0200
906 | date: Sat Apr 30 15:24:48 2011 +0200
907 | summary: C
907 | summary: C
908 |
908 |
909 o changeset: 5:42ccdea3bb16
909 o changeset: 5:42ccdea3bb16
910 | parent: 0:cd010b8cd998
910 | parent: 0:cd010b8cd998
911 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
911 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
912 | date: Sat Apr 30 15:24:48 2011 +0200
912 | date: Sat Apr 30 15:24:48 2011 +0200
913 | summary: B
913 | summary: B
914 |
914 |
915 | o changeset: 4:02de42196ebe
915 | o changeset: 4:02de42196ebe
916 | | parent: 2:24b6387c8c8c
916 | | parent: 2:24b6387c8c8c
917 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
917 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
918 | | date: Sat Apr 30 15:24:48 2011 +0200
918 | | date: Sat Apr 30 15:24:48 2011 +0200
919 | | summary: H
919 | | summary: H
920 | |
920 | |
921 | | o changeset: 3:eea13746799a
921 | | o changeset: 3:eea13746799a
922 | |/| parent: 2:24b6387c8c8c
922 | |/| parent: 2:24b6387c8c8c
923 | | | parent: 1:9520eea781bc
923 | | | parent: 1:9520eea781bc
924 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
924 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
925 | | | date: Sat Apr 30 15:24:48 2011 +0200
925 | | | date: Sat Apr 30 15:24:48 2011 +0200
926 | | | summary: G
926 | | | summary: G
927 | | |
927 | | |
928 | o | changeset: 2:24b6387c8c8c
928 | o | changeset: 2:24b6387c8c8c
929 |/ / parent: 0:cd010b8cd998
929 |/ / parent: 0:cd010b8cd998
930 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
930 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
931 | | date: Sat Apr 30 15:24:48 2011 +0200
931 | | date: Sat Apr 30 15:24:48 2011 +0200
932 | | summary: F
932 | | summary: F
933 | |
933 | |
934 | @ changeset: 1:9520eea781bc
934 | @ changeset: 1:9520eea781bc
935 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
935 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
936 | date: Sat Apr 30 15:24:48 2011 +0200
936 | date: Sat Apr 30 15:24:48 2011 +0200
937 | summary: E
937 | summary: E
938 |
938 |
939 o changeset: 0:cd010b8cd998
939 o changeset: 0:cd010b8cd998
940 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
940 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
941 date: Sat Apr 30 15:24:48 2011 +0200
941 date: Sat Apr 30 15:24:48 2011 +0200
942 summary: A
942 summary: A
943
943
944
944
945 Error Handling
945 Error Handling
946 ==============
946 ==============
947
947
948 Check that errors are properly returned to the client during push.
948 Check that errors are properly returned to the client during push.
949
949
950 Setting up
950 Setting up
951
951
952 $ cat > failpush.py << EOF
952 $ cat > failpush.py << EOF
953 > """A small extension that makes push fails when using bundle2
953 > """A small extension that makes push fails when using bundle2
954 >
954 >
955 > used to test error handling in bundle2
955 > used to test error handling in bundle2
956 > """
956 > """
957 >
957 >
958 > from mercurial import util
958 > from mercurial import util
959 > from mercurial import bundle2
959 > from mercurial import bundle2
960 > from mercurial import exchange
960 > from mercurial import exchange
961 > from mercurial import extensions
961 > from mercurial import extensions
962 >
962 >
963 > def _pushbundle2failpart(orig, pushop, bundler):
963 > def _pushbundle2failpart(orig, pushop, bundler):
964 > extradata = orig(pushop, bundler)
964 > extradata = orig(pushop, bundler)
965 > reason = pushop.ui.config('failpush', 'reason', None)
965 > reason = pushop.ui.config('failpush', 'reason', None)
966 > part = None
966 > part = None
967 > if reason == 'abort':
967 > if reason == 'abort':
968 > bundler.newpart('test:abort')
968 > bundler.newpart('test:abort')
969 > if reason == 'unknown':
969 > if reason == 'unknown':
970 > bundler.newpart('TEST:UNKNOWN')
970 > bundler.newpart('TEST:UNKNOWN')
971 > if reason == 'race':
971 > if reason == 'race':
972 > # 20 Bytes of crap
972 > # 20 Bytes of crap
973 > bundler.newpart('b2x:check:heads', data='01234567890123456789')
973 > bundler.newpart('b2x:check:heads', data='01234567890123456789')
974 > return extradata
974 > return extradata
975 >
975 >
976 > @bundle2.parthandler("test:abort")
976 > @bundle2.parthandler("test:abort")
977 > def handleabort(op, part):
977 > def handleabort(op, part):
978 > raise util.Abort('Abandon ship!', hint="don't panic")
978 > raise util.Abort('Abandon ship!', hint="don't panic")
979 >
979 >
980 > def uisetup(ui):
980 > def uisetup(ui):
981 > extensions.wrapfunction(exchange, '_pushbundle2extraparts', _pushbundle2failpart)
981 > extensions.wrapfunction(exchange, '_pushbundle2extraparts', _pushbundle2failpart)
982 >
982 >
983 > EOF
983 > EOF
984
984
985 $ cd main
985 $ cd main
986 $ hg up tip
986 $ hg up tip
987 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
987 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
988 $ echo 'I' > I
988 $ echo 'I' > I
989 $ hg add I
989 $ hg add I
990 $ hg ci -m 'I'
990 $ hg ci -m 'I'
991 $ hg id
991 $ hg id
992 e7ec4e813ba6 tip
992 e7ec4e813ba6 tip
993 $ cd ..
993 $ cd ..
994
994
995 $ cat << EOF >> $HGRCPATH
995 $ cat << EOF >> $HGRCPATH
996 > [extensions]
996 > [extensions]
997 > failpush=$TESTTMP/failpush.py
997 > failpush=$TESTTMP/failpush.py
998 > EOF
998 > EOF
999
999
1000 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
1000 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
1001 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
1001 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
1002 $ cat other.pid >> $DAEMON_PIDS
1002 $ cat other.pid >> $DAEMON_PIDS
1003
1003
1004 Doing the actual push: Abort error
1004 Doing the actual push: Abort error
1005
1005
1006 $ cat << EOF >> $HGRCPATH
1006 $ cat << EOF >> $HGRCPATH
1007 > [failpush]
1007 > [failpush]
1008 > reason = abort
1008 > reason = abort
1009 > EOF
1009 > EOF
1010
1010
1011 $ hg -R main push other -r e7ec4e813ba6
1011 $ hg -R main push other -r e7ec4e813ba6
1012 pushing to other
1012 pushing to other
1013 searching for changes
1013 searching for changes
1014 abort: Abandon ship!
1014 abort: Abandon ship!
1015 (don't panic)
1015 (don't panic)
1016 [255]
1016 [255]
1017
1017
1018 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1018 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1019 pushing to ssh://user@dummy/other
1019 pushing to ssh://user@dummy/other
1020 searching for changes
1020 searching for changes
1021 abort: Abandon ship!
1021 abort: Abandon ship!
1022 (don't panic)
1022 (don't panic)
1023 [255]
1023 [255]
1024
1024
1025 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1025 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1026 pushing to http://localhost:$HGPORT2/
1026 pushing to http://localhost:$HGPORT2/
1027 searching for changes
1027 searching for changes
1028 abort: Abandon ship!
1028 abort: Abandon ship!
1029 (don't panic)
1029 (don't panic)
1030 [255]
1030 [255]
1031
1031
1032
1032
1033 Doing the actual push: unknown mandatory parts
1033 Doing the actual push: unknown mandatory parts
1034
1034
1035 $ cat << EOF >> $HGRCPATH
1035 $ cat << EOF >> $HGRCPATH
1036 > [failpush]
1036 > [failpush]
1037 > reason = unknown
1037 > reason = unknown
1038 > EOF
1038 > EOF
1039
1039
1040 $ hg -R main push other -r e7ec4e813ba6
1040 $ hg -R main push other -r e7ec4e813ba6
1041 pushing to other
1041 pushing to other
1042 searching for changes
1042 searching for changes
1043 abort: missing support for test:unknown
1043 abort: missing support for test:unknown
1044 [255]
1044 [255]
1045
1045
1046 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1046 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1047 pushing to ssh://user@dummy/other
1047 pushing to ssh://user@dummy/other
1048 searching for changes
1048 searching for changes
1049 abort: missing support for test:unknown
1049 abort: missing support for test:unknown
1050 [255]
1050 [255]
1051
1051
1052 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1052 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1053 pushing to http://localhost:$HGPORT2/
1053 pushing to http://localhost:$HGPORT2/
1054 searching for changes
1054 searching for changes
1055 abort: missing support for test:unknown
1055 abort: missing support for test:unknown
1056 [255]
1056 [255]
1057
1057
1058 Doing the actual push: race
1058 Doing the actual push: race
1059
1059
1060 $ cat << EOF >> $HGRCPATH
1060 $ cat << EOF >> $HGRCPATH
1061 > [failpush]
1061 > [failpush]
1062 > reason = race
1062 > reason = race
1063 > EOF
1063 > EOF
1064
1064
1065 $ hg -R main push other -r e7ec4e813ba6
1065 $ hg -R main push other -r e7ec4e813ba6
1066 pushing to other
1066 pushing to other
1067 searching for changes
1067 searching for changes
1068 abort: push failed:
1068 abort: push failed:
1069 'repository changed while pushing - please try again'
1069 'repository changed while pushing - please try again'
1070 [255]
1070 [255]
1071
1071
1072 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1072 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1073 pushing to ssh://user@dummy/other
1073 pushing to ssh://user@dummy/other
1074 searching for changes
1074 searching for changes
1075 abort: push failed:
1075 abort: push failed:
1076 'repository changed while pushing - please try again'
1076 'repository changed while pushing - please try again'
1077 [255]
1077 [255]
1078
1078
1079 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1079 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1080 pushing to http://localhost:$HGPORT2/
1080 pushing to http://localhost:$HGPORT2/
1081 searching for changes
1081 searching for changes
1082 abort: push failed:
1082 abort: push failed:
1083 'repository changed while pushing - please try again'
1083 'repository changed while pushing - please try again'
1084 [255]
1084 [255]
1085
1085
1086 Doing the actual push: hook abort
1086 Doing the actual push: hook abort
1087
1087
1088 $ cat << EOF >> $HGRCPATH
1088 $ cat << EOF >> $HGRCPATH
1089 > [failpush]
1089 > [failpush]
1090 > reason =
1090 > reason =
1091 > [hooks]
1091 > [hooks]
1092 > b2x-pretransactionclose.failpush = false
1092 > b2x-pretransactionclose.failpush = false
1093 > EOF
1093 > EOF
1094
1094
1095 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
1095 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
1096 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
1096 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
1097 $ cat other.pid >> $DAEMON_PIDS
1097 $ cat other.pid >> $DAEMON_PIDS
1098
1098
1099 $ hg -R main push other -r e7ec4e813ba6
1099 $ hg -R main push other -r e7ec4e813ba6
1100 pushing to other
1100 pushing to other
1101 searching for changes
1101 searching for changes
1102 transaction abort!
1102 transaction abort!
1103 rollback completed
1103 rollback completed
1104 abort: b2x-pretransactionclose.failpush hook exited with status 1
1104 abort: b2x-pretransactionclose.failpush hook exited with status 1
1105 [255]
1105 [255]
1106
1106
1107 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1107 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
1108 pushing to ssh://user@dummy/other
1108 pushing to ssh://user@dummy/other
1109 searching for changes
1109 searching for changes
1110 abort: b2x-pretransactionclose.failpush hook exited with status 1
1110 abort: b2x-pretransactionclose.failpush hook exited with status 1
1111 remote: transaction abort!
1111 remote: transaction abort!
1112 remote: rollback completed
1112 remote: rollback completed
1113 [255]
1113 [255]
1114
1114
1115 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1115 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
1116 pushing to http://localhost:$HGPORT2/
1116 pushing to http://localhost:$HGPORT2/
1117 searching for changes
1117 searching for changes
1118 abort: b2x-pretransactionclose.failpush hook exited with status 1
1118 abort: b2x-pretransactionclose.failpush hook exited with status 1
1119 [255]
1119 [255]
1120
1120
1121
1121
General Comments 0
You need to be logged in to leave comments. Login now