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