##// END OF EJS Templates
bundle2: produce a bundle2 reply...
Pierre-Yves David -
r20997:d7df4b73 default
parent child Browse files
Show More
@@ -1,575 +1,579
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. parameter with value
43 The blob contains a space separated list of parameters. parameter 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 safefly ignored. However when the first
49 parameter is advisory and can be safefly 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 remains simple and we want to discourage any
55 - Stream level parameters should remains simple and we want to discourage any
56 crazy usage.
56 crazy usage.
57 - Textual data allow easy human inspection of a the bundle2 header in case of
57 - Textual data allow easy human inspection of a the 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 :typename: alphanumerical part name
88 :typename: 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 arbitraty content, the binary structure is::
95 Part's parameter may have arbitraty 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 ultimatly be lifted.
124 This is an implementation limitation that will ultimatly 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 import StringIO
147 import StringIO
148
148
149 import changegroup
149 import changegroup
150 from i18n import _
150 from i18n import _
151
151
152 _pack = struct.pack
152 _pack = struct.pack
153 _unpack = struct.unpack
153 _unpack = struct.unpack
154
154
155 _magicstring = 'HG20'
155 _magicstring = 'HG20'
156
156
157 _fstreamparamsize = '>H'
157 _fstreamparamsize = '>H'
158 _fpartheadersize = '>H'
158 _fpartheadersize = '>H'
159 _fparttypesize = '>B'
159 _fparttypesize = '>B'
160 _fpartid = '>I'
160 _fpartid = '>I'
161 _fpayloadsize = '>I'
161 _fpayloadsize = '>I'
162 _fpartparamcount = '>BB'
162 _fpartparamcount = '>BB'
163
163
164 def _makefpartparamsizes(nbparams):
164 def _makefpartparamsizes(nbparams):
165 """return a struct format to read part parameter sizes
165 """return a struct format to read part parameter sizes
166
166
167 The number parameters is variable so we need to build that format
167 The number parameters is variable so we need to build that format
168 dynamically.
168 dynamically.
169 """
169 """
170 return '>'+('BB'*nbparams)
170 return '>'+('BB'*nbparams)
171
171
172 parthandlermapping = {}
172 parthandlermapping = {}
173
173
174 def parthandler(parttype):
174 def parthandler(parttype):
175 """decorator that register a function as a bundle2 part handler
175 """decorator that register a function as a bundle2 part handler
176
176
177 eg::
177 eg::
178
178
179 @parthandler('myparttype')
179 @parthandler('myparttype')
180 def myparttypehandler(...):
180 def myparttypehandler(...):
181 '''process a part of type "my part".'''
181 '''process a part of type "my part".'''
182 ...
182 ...
183 """
183 """
184 def _decorator(func):
184 def _decorator(func):
185 lparttype = parttype.lower() # enforce lower case matching.
185 lparttype = parttype.lower() # enforce lower case matching.
186 assert lparttype not in parthandlermapping
186 assert lparttype not in parthandlermapping
187 parthandlermapping[lparttype] = func
187 parthandlermapping[lparttype] = func
188 return func
188 return func
189 return _decorator
189 return _decorator
190
190
191 class unbundlerecords(object):
191 class unbundlerecords(object):
192 """keep record of what happens during and unbundle
192 """keep record of what happens during and unbundle
193
193
194 New records are added using `records.add('cat', obj)`. Where 'cat' is a
194 New records are added using `records.add('cat', obj)`. Where 'cat' is a
195 category of record and obj is an arbitraty object.
195 category of record and obj is an arbitraty object.
196
196
197 `records['cat']` will return all entries of this category 'cat'.
197 `records['cat']` will return all entries of this category 'cat'.
198
198
199 Iterating on the object itself will yield `('category', obj)` tuples
199 Iterating on the object itself will yield `('category', obj)` tuples
200 for all entries.
200 for all entries.
201
201
202 All iterations happens in chronological order.
202 All iterations happens in chronological order.
203 """
203 """
204
204
205 def __init__(self):
205 def __init__(self):
206 self._categories = {}
206 self._categories = {}
207 self._sequences = []
207 self._sequences = []
208 self._replies = {}
208 self._replies = {}
209
209
210 def add(self, category, entry, inreplyto=None):
210 def add(self, category, entry, inreplyto=None):
211 """add a new record of a given category.
211 """add a new record of a given category.
212
212
213 The entry can then be retrieved in the list returned by
213 The entry can then be retrieved in the list returned by
214 self['category']."""
214 self['category']."""
215 self._categories.setdefault(category, []).append(entry)
215 self._categories.setdefault(category, []).append(entry)
216 self._sequences.append((category, entry))
216 self._sequences.append((category, entry))
217 if inreplyto is not None:
217 if inreplyto is not None:
218 self.getreplies(inreplyto).add(category, entry)
218 self.getreplies(inreplyto).add(category, entry)
219
219
220 def getreplies(self, partid):
220 def getreplies(self, partid):
221 """get the subrecords that replies to a specific part"""
221 """get the subrecords that replies to a specific part"""
222 return self._replies.setdefault(partid, unbundlerecords())
222 return self._replies.setdefault(partid, unbundlerecords())
223
223
224 def __getitem__(self, cat):
224 def __getitem__(self, cat):
225 return tuple(self._categories.get(cat, ()))
225 return tuple(self._categories.get(cat, ()))
226
226
227 def __iter__(self):
227 def __iter__(self):
228 return iter(self._sequences)
228 return iter(self._sequences)
229
229
230 def __len__(self):
230 def __len__(self):
231 return len(self._sequences)
231 return len(self._sequences)
232
232
233 def __nonzero__(self):
233 def __nonzero__(self):
234 return bool(self._sequences)
234 return bool(self._sequences)
235
235
236 class bundleoperation(object):
236 class bundleoperation(object):
237 """an object that represents a single bundling process
237 """an object that represents a single bundling process
238
238
239 Its purpose is to carry unbundle-related objects and states.
239 Its purpose is to carry unbundle-related objects and states.
240
240
241 A new object should be created at the beginning of each bundle processing.
241 A new object should be created at the beginning of each bundle processing.
242 The object is to be returned by the processing function.
242 The object is to be returned by the processing function.
243
243
244 The object has very little content now it will ultimately contain:
244 The object has very little content now it will ultimately contain:
245 * an access to the repo the bundle is applied to,
245 * an access to the repo the bundle is applied to,
246 * a ui object,
246 * a ui object,
247 * a way to retrieve a transaction to add changes to the repo,
247 * a way to retrieve a transaction to add changes to the repo,
248 * a way to record the result of processing each part,
248 * a way to record the result of processing each part,
249 * a way to construct a bundle response when applicable.
249 * a way to construct a bundle response when applicable.
250 """
250 """
251
251
252 def __init__(self, repo, transactiongetter):
252 def __init__(self, repo, transactiongetter):
253 self.repo = repo
253 self.repo = repo
254 self.ui = repo.ui
254 self.ui = repo.ui
255 self.records = unbundlerecords()
255 self.records = unbundlerecords()
256 self.gettransaction = transactiongetter
256 self.gettransaction = transactiongetter
257 self.reply = None
257
258
258 class TransactionUnavailable(RuntimeError):
259 class TransactionUnavailable(RuntimeError):
259 pass
260 pass
260
261
261 def _notransaction():
262 def _notransaction():
262 """default method to get a transaction while processing a bundle
263 """default method to get a transaction while processing a bundle
263
264
264 Raise an exception to highlight the fact that no transaction was expected
265 Raise an exception to highlight the fact that no transaction was expected
265 to be created"""
266 to be created"""
266 raise TransactionUnavailable()
267 raise TransactionUnavailable()
267
268
268 def processbundle(repo, unbundler, transactiongetter=_notransaction):
269 def processbundle(repo, unbundler, transactiongetter=_notransaction):
269 """This function process a bundle, apply effect to/from a repo
270 """This function process a bundle, apply effect to/from a repo
270
271
271 It iterates over each part then searches for and uses the proper handling
272 It iterates over each part then searches for and uses the proper handling
272 code to process the part. Parts are processed in order.
273 code to process the part. Parts are processed in order.
273
274
274 This is very early version of this function that will be strongly reworked
275 This is very early version of this function that will be strongly reworked
275 before final usage.
276 before final usage.
276
277
277 Unknown Mandatory part will abort the process.
278 Unknown Mandatory part will abort the process.
278 """
279 """
279 op = bundleoperation(repo, transactiongetter)
280 op = bundleoperation(repo, transactiongetter)
280 # todo:
281 # todo:
282 # - only create reply bundle if requested.
283 op.reply = bundle20(op.ui)
284 # todo:
281 # - replace this is a init function soon.
285 # - replace this is a init function soon.
282 # - exception catching
286 # - exception catching
283 unbundler.params
287 unbundler.params
284 iterparts = iter(unbundler)
288 iterparts = iter(unbundler)
285 try:
289 try:
286 for part in iterparts:
290 for part in iterparts:
287 parttype = part.type
291 parttype = part.type
288 # part key are matched lower case
292 # part key are matched lower case
289 key = parttype.lower()
293 key = parttype.lower()
290 try:
294 try:
291 handler = parthandlermapping[key]
295 handler = parthandlermapping[key]
292 op.ui.debug('found a handler for part %r\n' % parttype)
296 op.ui.debug('found a handler for part %r\n' % parttype)
293 except KeyError:
297 except KeyError:
294 if key != parttype: # mandatory parts
298 if key != parttype: # mandatory parts
295 # todo:
299 # todo:
296 # - use a more precise exception
300 # - use a more precise exception
297 raise
301 raise
298 op.ui.debug('ignoring unknown advisory part %r\n' % key)
302 op.ui.debug('ignoring unknown advisory part %r\n' % key)
299 # todo:
303 # todo:
300 # - consume the part once we use streaming
304 # - consume the part once we use streaming
301 continue
305 continue
302 handler(op, part)
306 handler(op, part)
303 except Exception:
307 except Exception:
304 for part in iterparts:
308 for part in iterparts:
305 pass # consume the bundle content
309 pass # consume the bundle content
306 raise
310 raise
307 return op
311 return op
308
312
309 class bundle20(object):
313 class bundle20(object):
310 """represent an outgoing bundle2 container
314 """represent an outgoing bundle2 container
311
315
312 Use the `addparam` method to add stream level parameter. and `addpart` to
316 Use the `addparam` method to add stream level parameter. and `addpart` to
313 populate it. Then call `getchunks` to retrieve all the binary chunks of
317 populate it. Then call `getchunks` to retrieve all the binary chunks of
314 datathat compose the bundle2 container."""
318 datathat compose the bundle2 container."""
315
319
316 def __init__(self, ui):
320 def __init__(self, ui):
317 self.ui = ui
321 self.ui = ui
318 self._params = []
322 self._params = []
319 self._parts = []
323 self._parts = []
320
324
321 def addparam(self, name, value=None):
325 def addparam(self, name, value=None):
322 """add a stream level parameter"""
326 """add a stream level parameter"""
323 if not name:
327 if not name:
324 raise ValueError('empty parameter name')
328 raise ValueError('empty parameter name')
325 if name[0] not in string.letters:
329 if name[0] not in string.letters:
326 raise ValueError('non letter first character: %r' % name)
330 raise ValueError('non letter first character: %r' % name)
327 self._params.append((name, value))
331 self._params.append((name, value))
328
332
329 def addpart(self, part):
333 def addpart(self, part):
330 """add a new part to the bundle2 container
334 """add a new part to the bundle2 container
331
335
332 Parts contains the actuall applicative payload."""
336 Parts contains the actuall applicative payload."""
333 assert part.id is None
337 assert part.id is None
334 part.id = len(self._parts) # very cheap counter
338 part.id = len(self._parts) # very cheap counter
335 self._parts.append(part)
339 self._parts.append(part)
336
340
337 def getchunks(self):
341 def getchunks(self):
338 self.ui.debug('start emission of %s stream\n' % _magicstring)
342 self.ui.debug('start emission of %s stream\n' % _magicstring)
339 yield _magicstring
343 yield _magicstring
340 param = self._paramchunk()
344 param = self._paramchunk()
341 self.ui.debug('bundle parameter: %s\n' % param)
345 self.ui.debug('bundle parameter: %s\n' % param)
342 yield _pack(_fstreamparamsize, len(param))
346 yield _pack(_fstreamparamsize, len(param))
343 if param:
347 if param:
344 yield param
348 yield param
345
349
346 self.ui.debug('start of parts\n')
350 self.ui.debug('start of parts\n')
347 for part in self._parts:
351 for part in self._parts:
348 self.ui.debug('bundle part: "%s"\n' % part.type)
352 self.ui.debug('bundle part: "%s"\n' % part.type)
349 for chunk in part.getchunks():
353 for chunk in part.getchunks():
350 yield chunk
354 yield chunk
351 self.ui.debug('end of bundle\n')
355 self.ui.debug('end of bundle\n')
352 yield '\0\0'
356 yield '\0\0'
353
357
354 def _paramchunk(self):
358 def _paramchunk(self):
355 """return a encoded version of all stream parameters"""
359 """return a encoded version of all stream parameters"""
356 blocks = []
360 blocks = []
357 for par, value in self._params:
361 for par, value in self._params:
358 par = urllib.quote(par)
362 par = urllib.quote(par)
359 if value is not None:
363 if value is not None:
360 value = urllib.quote(value)
364 value = urllib.quote(value)
361 par = '%s=%s' % (par, value)
365 par = '%s=%s' % (par, value)
362 blocks.append(par)
366 blocks.append(par)
363 return ' '.join(blocks)
367 return ' '.join(blocks)
364
368
365 class unbundle20(object):
369 class unbundle20(object):
366 """interpret a bundle2 stream
370 """interpret a bundle2 stream
367
371
368 (this will eventually yield parts)"""
372 (this will eventually yield parts)"""
369
373
370 def __init__(self, ui, fp):
374 def __init__(self, ui, fp):
371 self.ui = ui
375 self.ui = ui
372 self._fp = fp
376 self._fp = fp
373 header = self._readexact(4)
377 header = self._readexact(4)
374 magic, version = header[0:2], header[2:4]
378 magic, version = header[0:2], header[2:4]
375 if magic != 'HG':
379 if magic != 'HG':
376 raise util.Abort(_('not a Mercurial bundle'))
380 raise util.Abort(_('not a Mercurial bundle'))
377 if version != '20':
381 if version != '20':
378 raise util.Abort(_('unknown bundle version %s') % version)
382 raise util.Abort(_('unknown bundle version %s') % version)
379 self.ui.debug('start processing of %s stream\n' % header)
383 self.ui.debug('start processing of %s stream\n' % header)
380
384
381 def _unpack(self, format):
385 def _unpack(self, format):
382 """unpack this struct format from the stream"""
386 """unpack this struct format from the stream"""
383 data = self._readexact(struct.calcsize(format))
387 data = self._readexact(struct.calcsize(format))
384 return _unpack(format, data)
388 return _unpack(format, data)
385
389
386 def _readexact(self, size):
390 def _readexact(self, size):
387 """read exactly <size> bytes from the stream"""
391 """read exactly <size> bytes from the stream"""
388 return changegroup.readexactly(self._fp, size)
392 return changegroup.readexactly(self._fp, size)
389
393
390 @util.propertycache
394 @util.propertycache
391 def params(self):
395 def params(self):
392 """dictionnary of stream level parameters"""
396 """dictionnary of stream level parameters"""
393 self.ui.debug('reading bundle2 stream parameters\n')
397 self.ui.debug('reading bundle2 stream parameters\n')
394 params = {}
398 params = {}
395 paramssize = self._unpack(_fstreamparamsize)[0]
399 paramssize = self._unpack(_fstreamparamsize)[0]
396 if paramssize:
400 if paramssize:
397 for p in self._readexact(paramssize).split(' '):
401 for p in self._readexact(paramssize).split(' '):
398 p = p.split('=', 1)
402 p = p.split('=', 1)
399 p = [urllib.unquote(i) for i in p]
403 p = [urllib.unquote(i) for i in p]
400 if len(p) < 2:
404 if len(p) < 2:
401 p.append(None)
405 p.append(None)
402 self._processparam(*p)
406 self._processparam(*p)
403 params[p[0]] = p[1]
407 params[p[0]] = p[1]
404 return params
408 return params
405
409
406 def _processparam(self, name, value):
410 def _processparam(self, name, value):
407 """process a parameter, applying its effect if needed
411 """process a parameter, applying its effect if needed
408
412
409 Parameter starting with a lower case letter are advisory and will be
413 Parameter starting with a lower case letter are advisory and will be
410 ignored when unknown. Those starting with an upper case letter are
414 ignored when unknown. Those starting with an upper case letter are
411 mandatory and will this function will raise a KeyError when unknown.
415 mandatory and will this function will raise a KeyError when unknown.
412
416
413 Note: no option are currently supported. Any input will be either
417 Note: no option are currently supported. Any input will be either
414 ignored or failing.
418 ignored or failing.
415 """
419 """
416 if not name:
420 if not name:
417 raise ValueError('empty parameter name')
421 raise ValueError('empty parameter name')
418 if name[0] not in string.letters:
422 if name[0] not in string.letters:
419 raise ValueError('non letter first character: %r' % name)
423 raise ValueError('non letter first character: %r' % name)
420 # Some logic will be later added here to try to process the option for
424 # Some logic will be later added here to try to process the option for
421 # a dict of known parameter.
425 # a dict of known parameter.
422 if name[0].islower():
426 if name[0].islower():
423 self.ui.debug("ignoring unknown parameter %r\n" % name)
427 self.ui.debug("ignoring unknown parameter %r\n" % name)
424 else:
428 else:
425 raise KeyError(name)
429 raise KeyError(name)
426
430
427
431
428 def __iter__(self):
432 def __iter__(self):
429 """yield all parts contained in the stream"""
433 """yield all parts contained in the stream"""
430 # make sure param have been loaded
434 # make sure param have been loaded
431 self.params
435 self.params
432 self.ui.debug('start extraction of bundle2 parts\n')
436 self.ui.debug('start extraction of bundle2 parts\n')
433 part = self._readpart()
437 part = self._readpart()
434 while part is not None:
438 while part is not None:
435 yield part
439 yield part
436 part = self._readpart()
440 part = self._readpart()
437 self.ui.debug('end of bundle2 stream\n')
441 self.ui.debug('end of bundle2 stream\n')
438
442
439 def _readpart(self):
443 def _readpart(self):
440 """return None when an end of stream markers is reach"""
444 """return None when an end of stream markers is reach"""
441
445
442 headersize = self._unpack(_fpartheadersize)[0]
446 headersize = self._unpack(_fpartheadersize)[0]
443 self.ui.debug('part header size: %i\n' % headersize)
447 self.ui.debug('part header size: %i\n' % headersize)
444 if not headersize:
448 if not headersize:
445 return None
449 return None
446 headerblock = self._readexact(headersize)
450 headerblock = self._readexact(headersize)
447 # some utility to help reading from the header block
451 # some utility to help reading from the header block
448 self._offset = 0 # layer violation to have something easy to understand
452 self._offset = 0 # layer violation to have something easy to understand
449 def fromheader(size):
453 def fromheader(size):
450 """return the next <size> byte from the header"""
454 """return the next <size> byte from the header"""
451 offset = self._offset
455 offset = self._offset
452 data = headerblock[offset:(offset + size)]
456 data = headerblock[offset:(offset + size)]
453 self._offset = offset + size
457 self._offset = offset + size
454 return data
458 return data
455 def unpackheader(format):
459 def unpackheader(format):
456 """read given format from header
460 """read given format from header
457
461
458 This automatically compute the size of the format to read."""
462 This automatically compute the size of the format to read."""
459 data = fromheader(struct.calcsize(format))
463 data = fromheader(struct.calcsize(format))
460 return _unpack(format, data)
464 return _unpack(format, data)
461
465
462 typesize = unpackheader(_fparttypesize)[0]
466 typesize = unpackheader(_fparttypesize)[0]
463 parttype = fromheader(typesize)
467 parttype = fromheader(typesize)
464 self.ui.debug('part type: "%s"\n' % parttype)
468 self.ui.debug('part type: "%s"\n' % parttype)
465 partid = unpackheader(_fpartid)[0]
469 partid = unpackheader(_fpartid)[0]
466 self.ui.debug('part id: "%s"\n' % partid)
470 self.ui.debug('part id: "%s"\n' % partid)
467 ## reading parameters
471 ## reading parameters
468 # param count
472 # param count
469 mancount, advcount = unpackheader(_fpartparamcount)
473 mancount, advcount = unpackheader(_fpartparamcount)
470 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
474 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
471 # param size
475 # param size
472 paramsizes = unpackheader(_makefpartparamsizes(mancount + advcount))
476 paramsizes = unpackheader(_makefpartparamsizes(mancount + advcount))
473 # make it a list of couple again
477 # make it a list of couple again
474 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
478 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
475 # split mandatory from advisory
479 # split mandatory from advisory
476 mansizes = paramsizes[:mancount]
480 mansizes = paramsizes[:mancount]
477 advsizes = paramsizes[mancount:]
481 advsizes = paramsizes[mancount:]
478 # retrive param value
482 # retrive param value
479 manparams = []
483 manparams = []
480 for key, value in mansizes:
484 for key, value in mansizes:
481 manparams.append((fromheader(key), fromheader(value)))
485 manparams.append((fromheader(key), fromheader(value)))
482 advparams = []
486 advparams = []
483 for key, value in advsizes:
487 for key, value in advsizes:
484 advparams.append((fromheader(key), fromheader(value)))
488 advparams.append((fromheader(key), fromheader(value)))
485 del self._offset # clean up layer, nobody saw anything.
489 del self._offset # clean up layer, nobody saw anything.
486 ## part payload
490 ## part payload
487 payload = []
491 payload = []
488 payloadsize = self._unpack(_fpayloadsize)[0]
492 payloadsize = self._unpack(_fpayloadsize)[0]
489 self.ui.debug('payload chunk size: %i\n' % payloadsize)
493 self.ui.debug('payload chunk size: %i\n' % payloadsize)
490 while payloadsize:
494 while payloadsize:
491 payload.append(self._readexact(payloadsize))
495 payload.append(self._readexact(payloadsize))
492 payloadsize = self._unpack(_fpayloadsize)[0]
496 payloadsize = self._unpack(_fpayloadsize)[0]
493 self.ui.debug('payload chunk size: %i\n' % payloadsize)
497 self.ui.debug('payload chunk size: %i\n' % payloadsize)
494 payload = ''.join(payload)
498 payload = ''.join(payload)
495 current = part(parttype, manparams, advparams, data=payload)
499 current = part(parttype, manparams, advparams, data=payload)
496 current.id = partid
500 current.id = partid
497 return current
501 return current
498
502
499
503
500 class part(object):
504 class part(object):
501 """A bundle2 part contains application level payload
505 """A bundle2 part contains application level payload
502
506
503 The part `type` is used to route the part to the application level
507 The part `type` is used to route the part to the application level
504 handler.
508 handler.
505 """
509 """
506
510
507 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
511 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
508 data=''):
512 data=''):
509 self.id = None
513 self.id = None
510 self.type = parttype
514 self.type = parttype
511 self.data = data
515 self.data = data
512 self.mandatoryparams = mandatoryparams
516 self.mandatoryparams = mandatoryparams
513 self.advisoryparams = advisoryparams
517 self.advisoryparams = advisoryparams
514
518
515 def getchunks(self):
519 def getchunks(self):
516 #### header
520 #### header
517 ## parttype
521 ## parttype
518 header = [_pack(_fparttypesize, len(self.type)),
522 header = [_pack(_fparttypesize, len(self.type)),
519 self.type, _pack(_fpartid, self.id),
523 self.type, _pack(_fpartid, self.id),
520 ]
524 ]
521 ## parameters
525 ## parameters
522 # count
526 # count
523 manpar = self.mandatoryparams
527 manpar = self.mandatoryparams
524 advpar = self.advisoryparams
528 advpar = self.advisoryparams
525 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
529 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
526 # size
530 # size
527 parsizes = []
531 parsizes = []
528 for key, value in manpar:
532 for key, value in manpar:
529 parsizes.append(len(key))
533 parsizes.append(len(key))
530 parsizes.append(len(value))
534 parsizes.append(len(value))
531 for key, value in advpar:
535 for key, value in advpar:
532 parsizes.append(len(key))
536 parsizes.append(len(key))
533 parsizes.append(len(value))
537 parsizes.append(len(value))
534 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
538 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
535 header.append(paramsizes)
539 header.append(paramsizes)
536 # key, value
540 # key, value
537 for key, value in manpar:
541 for key, value in manpar:
538 header.append(key)
542 header.append(key)
539 header.append(value)
543 header.append(value)
540 for key, value in advpar:
544 for key, value in advpar:
541 header.append(key)
545 header.append(key)
542 header.append(value)
546 header.append(value)
543 ## finalize header
547 ## finalize header
544 headerchunk = ''.join(header)
548 headerchunk = ''.join(header)
545 yield _pack(_fpartheadersize, len(headerchunk))
549 yield _pack(_fpartheadersize, len(headerchunk))
546 yield headerchunk
550 yield headerchunk
547 ## payload
551 ## payload
548 # we only support fixed size data now.
552 # we only support fixed size data now.
549 # This will be improved in the future.
553 # This will be improved in the future.
550 if len(self.data):
554 if len(self.data):
551 yield _pack(_fpayloadsize, len(self.data))
555 yield _pack(_fpayloadsize, len(self.data))
552 yield self.data
556 yield self.data
553 # end of payload
557 # end of payload
554 yield _pack(_fpayloadsize, 0)
558 yield _pack(_fpayloadsize, 0)
555
559
556 @parthandler('changegroup')
560 @parthandler('changegroup')
557 def handlechangegroup(op, part):
561 def handlechangegroup(op, part):
558 """apply a changegroup part on the repo
562 """apply a changegroup part on the repo
559
563
560 This is a very early implementation that will massive rework before being
564 This is a very early implementation that will massive rework before being
561 inflicted to any end-user.
565 inflicted to any end-user.
562 """
566 """
563 # Make sure we trigger a transaction creation
567 # Make sure we trigger a transaction creation
564 #
568 #
565 # The addchangegroup function will get a transaction object by itself, but
569 # The addchangegroup function will get a transaction object by itself, but
566 # we need to make sure we trigger the creation of a transaction object used
570 # we need to make sure we trigger the creation of a transaction object used
567 # for the whole processing scope.
571 # for the whole processing scope.
568 op.gettransaction()
572 op.gettransaction()
569 data = StringIO.StringIO(part.data)
573 data = StringIO.StringIO(part.data)
570 data.seek(0)
574 data.seek(0)
571 cg = changegroup.readbundle(data, 'bundle2part')
575 cg = changegroup.readbundle(data, 'bundle2part')
572 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
576 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
573 op.records.add('changegroup', {'return': ret})
577 op.records.add('changegroup', {'return': ret})
574
578
575
579
@@ -1,610 +1,673
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 > import sys
11 > import sys
12 > from mercurial import cmdutil
12 > from mercurial import cmdutil
13 > from mercurial import util
13 > from mercurial import util
14 > from mercurial import bundle2
14 > from mercurial import bundle2
15 > from mercurial import scmutil
15 > from mercurial import scmutil
16 > from mercurial import discovery
16 > from mercurial import discovery
17 > from mercurial import changegroup
17 > from mercurial import changegroup
18 > cmdtable = {}
18 > cmdtable = {}
19 > command = cmdutil.command(cmdtable)
19 > command = cmdutil.command(cmdtable)
20 >
20 >
21 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
21 > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
22 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
22 > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
23 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
23 > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
24 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
24 > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
25 >
25 >
26 > @bundle2.parthandler('test:song')
26 > @bundle2.parthandler('test:song')
27 > def songhandler(op, part):
27 > def songhandler(op, part):
28 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
28 > """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
29 > op.ui.write('The choir starts singing:\n')
29 > op.ui.write('The choir starts singing:\n')
30 > verses = 0
30 > verses = 0
31 > for line in part.data.split('\n'):
31 > for line in part.data.split('\n'):
32 > op.ui.write(' %s\n' % line)
32 > op.ui.write(' %s\n' % line)
33 > verses += 1
33 > verses += 1
34 > op.records.add('song', {'verses': verses})
34 > op.records.add('song', {'verses': verses})
35 >
35 >
36 > @bundle2.parthandler('test:ping')
37 > def pinghandler(op, part):
38 > op.ui.write('received ping request (id %i)\n' % part.id)
39 > if op.reply is not None:
40 > op.reply.addpart(bundle2.part('test:pong',
41 > [('in-reply-to', str(part.id))]))
42 >
36 > @command('bundle2',
43 > @command('bundle2',
37 > [('', 'param', [], 'stream level parameter'),
44 > [('', 'param', [], 'stream level parameter'),
38 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
45 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
39 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
46 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
40 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
47 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
41 > '[OUTPUTFILE]')
48 > '[OUTPUTFILE]')
42 > def cmdbundle2(ui, repo, path=None, **opts):
49 > def cmdbundle2(ui, repo, path=None, **opts):
43 > """write a bundle2 container on standard ouput"""
50 > """write a bundle2 container on standard ouput"""
44 > bundler = bundle2.bundle20(ui)
51 > bundler = bundle2.bundle20(ui)
45 > for p in opts['param']:
52 > for p in opts['param']:
46 > p = p.split('=', 1)
53 > p = p.split('=', 1)
47 > try:
54 > try:
48 > bundler.addparam(*p)
55 > bundler.addparam(*p)
49 > except ValueError, exc:
56 > except ValueError, exc:
50 > raise util.Abort('%s' % exc)
57 > raise util.Abort('%s' % exc)
51 >
58 >
52 > revs = opts['rev']
59 > revs = opts['rev']
53 > if 'rev' in opts:
60 > if 'rev' in opts:
54 > revs = scmutil.revrange(repo, opts['rev'])
61 > revs = scmutil.revrange(repo, opts['rev'])
55 > if revs:
62 > if revs:
56 > # very crude version of a changegroup part creation
63 > # very crude version of a changegroup part creation
57 > bundled = repo.revs('%ld::%ld', revs, revs)
64 > bundled = repo.revs('%ld::%ld', revs, revs)
58 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
65 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
59 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
66 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
60 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
67 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
61 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
68 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
62 > assert cg is not None
69 > assert cg is not None
63 > # make me lazy later
70 > # make me lazy later
64 > tempname = changegroup.writebundle(cg, None, 'HG10UN')
71 > tempname = changegroup.writebundle(cg, None, 'HG10UN')
65 > data = open(tempname).read()
72 > data = open(tempname).read()
66 > part = bundle2.part('changegroup', data=data)
73 > part = bundle2.part('changegroup', data=data)
67 > bundler.addpart(part)
74 > bundler.addpart(part)
68 >
75 >
69 > if opts['parts']:
76 > if opts['parts']:
70 > part = bundle2.part('test:empty')
77 > part = bundle2.part('test:empty')
71 > bundler.addpart(part)
78 > bundler.addpart(part)
72 > # add a second one to make sure we handle multiple parts
79 > # add a second one to make sure we handle multiple parts
73 > part = bundle2.part('test:empty')
80 > part = bundle2.part('test:empty')
74 > bundler.addpart(part)
81 > bundler.addpart(part)
75 > part = bundle2.part('test:song', data=ELEPHANTSSONG)
82 > part = bundle2.part('test:song', data=ELEPHANTSSONG)
76 > bundler.addpart(part)
83 > bundler.addpart(part)
77 > part = bundle2.part('test:math',
84 > part = bundle2.part('test:math',
78 > [('pi', '3.14'), ('e', '2.72')],
85 > [('pi', '3.14'), ('e', '2.72')],
79 > [('cooking', 'raw')],
86 > [('cooking', 'raw')],
80 > '42')
87 > '42')
81 > bundler.addpart(part)
88 > bundler.addpart(part)
82 > if opts['unknown']:
89 > if opts['unknown']:
83 > part = bundle2.part('test:UNKNOWN',
90 > part = bundle2.part('test:UNKNOWN',
84 > data='some random content')
91 > data='some random content')
85 > bundler.addpart(part)
92 > bundler.addpart(part)
93 > if opts['parts']:
94 > part = bundle2.part('test:ping')
95 > bundler.addpart(part)
86 >
96 >
87 > if path is None:
97 > if path is None:
88 > file = sys.stdout
98 > file = sys.stdout
89 > else:
99 > else:
90 > file = open(path, 'w')
100 > file = open(path, 'w')
91 >
101 >
92 > for chunk in bundler.getchunks():
102 > for chunk in bundler.getchunks():
93 > file.write(chunk)
103 > file.write(chunk)
94 >
104 >
95 > @command('unbundle2', [], '')
105 > @command('unbundle2', [], '')
96 > def cmdunbundle2(ui, repo):
106 > def cmdunbundle2(ui, repo, replypath=None):
97 > """process a bundle2 stream from stdin on the current repo"""
107 > """process a bundle2 stream from stdin on the current repo"""
98 > try:
108 > try:
99 > tr = None
109 > tr = None
100 > lock = repo.lock()
110 > lock = repo.lock()
101 > tr = repo.transaction('processbundle')
111 > tr = repo.transaction('processbundle')
102 > try:
112 > try:
103 > unbundler = bundle2.unbundle20(ui, sys.stdin)
113 > unbundler = bundle2.unbundle20(ui, sys.stdin)
104 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
114 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
105 > tr.close()
115 > tr.close()
106 > except KeyError, exc:
116 > except KeyError, exc:
107 > raise util.Abort('missing support for %s' % exc)
117 > raise util.Abort('missing support for %s' % exc)
108 > finally:
118 > finally:
109 > if tr is not None:
119 > if tr is not None:
110 > tr.release()
120 > tr.release()
111 > lock.release()
121 > lock.release()
112 > remains = sys.stdin.read()
122 > remains = sys.stdin.read()
113 > ui.write('%i unread bytes\n' % len(remains))
123 > ui.write('%i unread bytes\n' % len(remains))
114 > if op.records['song']:
124 > if op.records['song']:
115 > totalverses = sum(r['verses'] for r in op.records['song'])
125 > totalverses = sum(r['verses'] for r in op.records['song'])
116 > ui.write('%i total verses sung\n' % totalverses)
126 > ui.write('%i total verses sung\n' % totalverses)
117 > for rec in op.records['changegroup']:
127 > for rec in op.records['changegroup']:
118 > ui.write('addchangegroup return: %i\n' % rec['return'])
128 > ui.write('addchangegroup return: %i\n' % rec['return'])
129 > if op.reply is not None and replypath is not None:
130 > file = open(replypath, 'w')
131 > for chunk in op.reply.getchunks():
132 > file.write(chunk)
119 >
133 >
120 > @command('statbundle2', [], '')
134 > @command('statbundle2', [], '')
121 > def cmdstatbundle2(ui, repo):
135 > def cmdstatbundle2(ui, repo):
122 > """print statistic on the bundle2 container read from stdin"""
136 > """print statistic on the bundle2 container read from stdin"""
123 > unbundler = bundle2.unbundle20(ui, sys.stdin)
137 > unbundler = bundle2.unbundle20(ui, sys.stdin)
124 > try:
138 > try:
125 > params = unbundler.params
139 > params = unbundler.params
126 > except KeyError, exc:
140 > except KeyError, exc:
127 > raise util.Abort('unknown parameters: %s' % exc)
141 > raise util.Abort('unknown parameters: %s' % exc)
128 > ui.write('options count: %i\n' % len(params))
142 > ui.write('options count: %i\n' % len(params))
129 > for key in sorted(params):
143 > for key in sorted(params):
130 > ui.write('- %s\n' % key)
144 > ui.write('- %s\n' % key)
131 > value = params[key]
145 > value = params[key]
132 > if value is not None:
146 > if value is not None:
133 > ui.write(' %s\n' % value)
147 > ui.write(' %s\n' % value)
134 > parts = list(unbundler)
148 > parts = list(unbundler)
135 > ui.write('parts count: %i\n' % len(parts))
149 > ui.write('parts count: %i\n' % len(parts))
136 > for p in parts:
150 > for p in parts:
137 > ui.write(' :%s:\n' % p.type)
151 > ui.write(' :%s:\n' % p.type)
138 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
152 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
139 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
153 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
140 > ui.write(' payload: %i bytes\n' % len(p.data))
154 > ui.write(' payload: %i bytes\n' % len(p.data))
141 > EOF
155 > EOF
142 $ cat >> $HGRCPATH << EOF
156 $ cat >> $HGRCPATH << EOF
143 > [extensions]
157 > [extensions]
144 > bundle2=$TESTTMP/bundle2.py
158 > bundle2=$TESTTMP/bundle2.py
145 > [server]
159 > [server]
146 > bundle2=True
160 > bundle2=True
147 > EOF
161 > EOF
148
162
149 The extension requires a repo (currently unused)
163 The extension requires a repo (currently unused)
150
164
151 $ hg init main
165 $ hg init main
152 $ cd main
166 $ cd main
153 $ touch a
167 $ touch a
154 $ hg add a
168 $ hg add a
155 $ hg commit -m 'a'
169 $ hg commit -m 'a'
156
170
157
171
158 Empty bundle
172 Empty bundle
159 =================
173 =================
160
174
161 - no option
175 - no option
162 - no parts
176 - no parts
163
177
164 Test bundling
178 Test bundling
165
179
166 $ hg bundle2
180 $ hg bundle2
167 HG20\x00\x00\x00\x00 (no-eol) (esc)
181 HG20\x00\x00\x00\x00 (no-eol) (esc)
168
182
169 Test unbundling
183 Test unbundling
170
184
171 $ hg bundle2 | hg statbundle2
185 $ hg bundle2 | hg statbundle2
172 options count: 0
186 options count: 0
173 parts count: 0
187 parts count: 0
174
188
175 Test old style bundle are detected and refused
189 Test old style bundle are detected and refused
176
190
177 $ hg bundle --all ../bundle.hg
191 $ hg bundle --all ../bundle.hg
178 1 changesets found
192 1 changesets found
179 $ hg statbundle2 < ../bundle.hg
193 $ hg statbundle2 < ../bundle.hg
180 abort: unknown bundle version 10
194 abort: unknown bundle version 10
181 [255]
195 [255]
182
196
183 Test parameters
197 Test parameters
184 =================
198 =================
185
199
186 - some options
200 - some options
187 - no parts
201 - no parts
188
202
189 advisory parameters, no value
203 advisory parameters, no value
190 -------------------------------
204 -------------------------------
191
205
192 Simplest possible parameters form
206 Simplest possible parameters form
193
207
194 Test generation simple option
208 Test generation simple option
195
209
196 $ hg bundle2 --param 'caution'
210 $ hg bundle2 --param 'caution'
197 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
211 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
198
212
199 Test unbundling
213 Test unbundling
200
214
201 $ hg bundle2 --param 'caution' | hg statbundle2
215 $ hg bundle2 --param 'caution' | hg statbundle2
202 options count: 1
216 options count: 1
203 - caution
217 - caution
204 parts count: 0
218 parts count: 0
205
219
206 Test generation multiple option
220 Test generation multiple option
207
221
208 $ hg bundle2 --param 'caution' --param 'meal'
222 $ hg bundle2 --param 'caution' --param 'meal'
209 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
223 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
210
224
211 Test unbundling
225 Test unbundling
212
226
213 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
227 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
214 options count: 2
228 options count: 2
215 - caution
229 - caution
216 - meal
230 - meal
217 parts count: 0
231 parts count: 0
218
232
219 advisory parameters, with value
233 advisory parameters, with value
220 -------------------------------
234 -------------------------------
221
235
222 Test generation
236 Test generation
223
237
224 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
238 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
225 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
239 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
226
240
227 Test unbundling
241 Test unbundling
228
242
229 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
243 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
230 options count: 3
244 options count: 3
231 - caution
245 - caution
232 - elephants
246 - elephants
233 - meal
247 - meal
234 vegan
248 vegan
235 parts count: 0
249 parts count: 0
236
250
237 parameter with special char in value
251 parameter with special char in value
238 ---------------------------------------------------
252 ---------------------------------------------------
239
253
240 Test generation
254 Test generation
241
255
242 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
256 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
243 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
257 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
244
258
245 Test unbundling
259 Test unbundling
246
260
247 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
261 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
248 options count: 2
262 options count: 2
249 - e|! 7/
263 - e|! 7/
250 babar%#==tutu
264 babar%#==tutu
251 - simple
265 - simple
252 parts count: 0
266 parts count: 0
253
267
254 Test unknown mandatory option
268 Test unknown mandatory option
255 ---------------------------------------------------
269 ---------------------------------------------------
256
270
257 $ hg bundle2 --param 'Gravity' | hg statbundle2
271 $ hg bundle2 --param 'Gravity' | hg statbundle2
258 abort: unknown parameters: 'Gravity'
272 abort: unknown parameters: 'Gravity'
259 [255]
273 [255]
260
274
261 Test debug output
275 Test debug output
262 ---------------------------------------------------
276 ---------------------------------------------------
263
277
264 bundling debug
278 bundling debug
265
279
266 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
280 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
267 start emission of HG20 stream
281 start emission of HG20 stream
268 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
282 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
269 start of parts
283 start of parts
270 end of bundle
284 end of bundle
271
285
272 file content is ok
286 file content is ok
273
287
274 $ cat ../out.hg2
288 $ cat ../out.hg2
275 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
289 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
276
290
277 unbundling debug
291 unbundling debug
278
292
279 $ hg statbundle2 --debug < ../out.hg2
293 $ hg statbundle2 --debug < ../out.hg2
280 start processing of HG20 stream
294 start processing of HG20 stream
281 reading bundle2 stream parameters
295 reading bundle2 stream parameters
282 ignoring unknown parameter 'e|! 7/'
296 ignoring unknown parameter 'e|! 7/'
283 ignoring unknown parameter 'simple'
297 ignoring unknown parameter 'simple'
284 options count: 2
298 options count: 2
285 - e|! 7/
299 - e|! 7/
286 babar%#==tutu
300 babar%#==tutu
287 - simple
301 - simple
288 start extraction of bundle2 parts
302 start extraction of bundle2 parts
289 part header size: 0
303 part header size: 0
290 end of bundle2 stream
304 end of bundle2 stream
291 parts count: 0
305 parts count: 0
292
306
293
307
294 Test buggy input
308 Test buggy input
295 ---------------------------------------------------
309 ---------------------------------------------------
296
310
297 empty parameter name
311 empty parameter name
298
312
299 $ hg bundle2 --param '' --quiet
313 $ hg bundle2 --param '' --quiet
300 abort: empty parameter name
314 abort: empty parameter name
301 [255]
315 [255]
302
316
303 bad parameter name
317 bad parameter name
304
318
305 $ hg bundle2 --param 42babar
319 $ hg bundle2 --param 42babar
306 abort: non letter first character: '42babar'
320 abort: non letter first character: '42babar'
307 [255]
321 [255]
308
322
309
323
310 Test part
324 Test part
311 =================
325 =================
312
326
313 $ hg bundle2 --parts ../parts.hg2 --debug
327 $ hg bundle2 --parts ../parts.hg2 --debug
314 start emission of HG20 stream
328 start emission of HG20 stream
315 bundle parameter:
329 bundle parameter:
316 start of parts
330 start of parts
317 bundle part: "test:empty"
331 bundle part: "test:empty"
318 bundle part: "test:empty"
332 bundle part: "test:empty"
319 bundle part: "test:song"
333 bundle part: "test:song"
320 bundle part: "test:math"
334 bundle part: "test:math"
335 bundle part: "test:ping"
321 end of bundle
336 end of bundle
322
337
323 $ cat ../parts.hg2
338 $ cat ../parts.hg2
324 HG20\x00\x00\x00\x11 (esc)
339 HG20\x00\x00\x00\x11 (esc)
325 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
340 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
326 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)
341 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)
327 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
342 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
328 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x03\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
343 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00+ test:math\x00\x00\x00\x03\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\x04\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
329
344
330
345
331 $ hg statbundle2 < ../parts.hg2
346 $ hg statbundle2 < ../parts.hg2
332 options count: 0
347 options count: 0
333 parts count: 4
348 parts count: 5
334 :test:empty:
349 :test:empty:
335 mandatory: 0
350 mandatory: 0
336 advisory: 0
351 advisory: 0
337 payload: 0 bytes
352 payload: 0 bytes
338 :test:empty:
353 :test:empty:
339 mandatory: 0
354 mandatory: 0
340 advisory: 0
355 advisory: 0
341 payload: 0 bytes
356 payload: 0 bytes
342 :test:song:
357 :test:song:
343 mandatory: 0
358 mandatory: 0
344 advisory: 0
359 advisory: 0
345 payload: 178 bytes
360 payload: 178 bytes
346 :test:math:
361 :test:math:
347 mandatory: 2
362 mandatory: 2
348 advisory: 1
363 advisory: 1
349 payload: 2 bytes
364 payload: 2 bytes
365 :test:ping:
366 mandatory: 0
367 advisory: 0
368 payload: 0 bytes
350
369
351 $ hg statbundle2 --debug < ../parts.hg2
370 $ hg statbundle2 --debug < ../parts.hg2
352 start processing of HG20 stream
371 start processing of HG20 stream
353 reading bundle2 stream parameters
372 reading bundle2 stream parameters
354 options count: 0
373 options count: 0
355 start extraction of bundle2 parts
374 start extraction of bundle2 parts
356 part header size: 17
375 part header size: 17
357 part type: "test:empty"
376 part type: "test:empty"
358 part id: "0"
377 part id: "0"
359 part parameters: 0
378 part parameters: 0
360 payload chunk size: 0
379 payload chunk size: 0
361 part header size: 17
380 part header size: 17
362 part type: "test:empty"
381 part type: "test:empty"
363 part id: "1"
382 part id: "1"
364 part parameters: 0
383 part parameters: 0
365 payload chunk size: 0
384 payload chunk size: 0
366 part header size: 16
385 part header size: 16
367 part type: "test:song"
386 part type: "test:song"
368 part id: "2"
387 part id: "2"
369 part parameters: 0
388 part parameters: 0
370 payload chunk size: 178
389 payload chunk size: 178
371 payload chunk size: 0
390 payload chunk size: 0
372 part header size: 43
391 part header size: 43
373 part type: "test:math"
392 part type: "test:math"
374 part id: "3"
393 part id: "3"
375 part parameters: 3
394 part parameters: 3
376 payload chunk size: 2
395 payload chunk size: 2
377 payload chunk size: 0
396 payload chunk size: 0
397 part header size: 16
398 part type: "test:ping"
399 part id: "4"
400 part parameters: 0
401 payload chunk size: 0
378 part header size: 0
402 part header size: 0
379 end of bundle2 stream
403 end of bundle2 stream
380 parts count: 4
404 parts count: 5
381 :test:empty:
405 :test:empty:
382 mandatory: 0
406 mandatory: 0
383 advisory: 0
407 advisory: 0
384 payload: 0 bytes
408 payload: 0 bytes
385 :test:empty:
409 :test:empty:
386 mandatory: 0
410 mandatory: 0
387 advisory: 0
411 advisory: 0
388 payload: 0 bytes
412 payload: 0 bytes
389 :test:song:
413 :test:song:
390 mandatory: 0
414 mandatory: 0
391 advisory: 0
415 advisory: 0
392 payload: 178 bytes
416 payload: 178 bytes
393 :test:math:
417 :test:math:
394 mandatory: 2
418 mandatory: 2
395 advisory: 1
419 advisory: 1
396 payload: 2 bytes
420 payload: 2 bytes
421 :test:ping:
422 mandatory: 0
423 advisory: 0
424 payload: 0 bytes
397
425
398 Test actual unbundling of test part
426 Test actual unbundling of test part
399 =======================================
427 =======================================
400
428
401 Process the bundle
429 Process the bundle
402
430
403 $ hg unbundle2 --debug < ../parts.hg2
431 $ hg unbundle2 --debug < ../parts.hg2
404 start processing of HG20 stream
432 start processing of HG20 stream
405 reading bundle2 stream parameters
433 reading bundle2 stream parameters
406 start extraction of bundle2 parts
434 start extraction of bundle2 parts
407 part header size: 17
435 part header size: 17
408 part type: "test:empty"
436 part type: "test:empty"
409 part id: "0"
437 part id: "0"
410 part parameters: 0
438 part parameters: 0
411 payload chunk size: 0
439 payload chunk size: 0
412 ignoring unknown advisory part 'test:empty'
440 ignoring unknown advisory part 'test:empty'
413 part header size: 17
441 part header size: 17
414 part type: "test:empty"
442 part type: "test:empty"
415 part id: "1"
443 part id: "1"
416 part parameters: 0
444 part parameters: 0
417 payload chunk size: 0
445 payload chunk size: 0
418 ignoring unknown advisory part 'test:empty'
446 ignoring unknown advisory part 'test:empty'
419 part header size: 16
447 part header size: 16
420 part type: "test:song"
448 part type: "test:song"
421 part id: "2"
449 part id: "2"
422 part parameters: 0
450 part parameters: 0
423 payload chunk size: 178
451 payload chunk size: 178
424 payload chunk size: 0
452 payload chunk size: 0
425 found a handler for part 'test:song'
453 found a handler for part 'test:song'
426 The choir starts singing:
454 The choir starts singing:
427 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
455 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
428 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
456 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
429 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
457 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
430 part header size: 43
458 part header size: 43
431 part type: "test:math"
459 part type: "test:math"
432 part id: "3"
460 part id: "3"
433 part parameters: 3
461 part parameters: 3
434 payload chunk size: 2
462 payload chunk size: 2
435 payload chunk size: 0
463 payload chunk size: 0
436 ignoring unknown advisory part 'test:math'
464 ignoring unknown advisory part 'test:math'
465 part header size: 16
466 part type: "test:ping"
467 part id: "4"
468 part parameters: 0
469 payload chunk size: 0
470 found a handler for part 'test:ping'
471 received ping request (id 4)
437 part header size: 0
472 part header size: 0
438 end of bundle2 stream
473 end of bundle2 stream
439 0 unread bytes
474 0 unread bytes
440 3 total verses sung
475 3 total verses sung
441
476
477 Unbundle with an unknown mandatory part
478 (should abort)
442
479
443 $ hg bundle2 --parts --unknown ../unknown.hg2
480 $ hg bundle2 --parts --unknown ../unknown.hg2
444
481
445 $ hg unbundle2 < ../unknown.hg2
482 $ hg unbundle2 < ../unknown.hg2
446 The choir starts singing:
483 The choir starts singing:
447 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
484 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
448 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
485 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
449 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
486 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
450 0 unread bytes
487 0 unread bytes
451 abort: missing support for 'test:unknown'
488 abort: missing support for 'test:unknown'
452 [255]
489 [255]
453
490
491 unbundle with a reply
492
493 $ hg unbundle2 ../reply.hg2 < ../parts.hg2
494 The choir starts singing:
495 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
496 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
497 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
498 received ping request (id 4)
499 0 unread bytes
500 3 total verses sung
501
502 The reply is a bundle
503
504 $ cat ../reply.hg2
505 HG20\x00\x00\x00\x1e test:pong\x00\x00\x00\x00\x01\x00\x0b\x01in-reply-to4\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
506
507 The reply is valid
508
509 $ hg statbundle2 < ../reply.hg2
510 options count: 0
511 parts count: 1
512 :test:pong:
513 mandatory: 1
514 advisory: 0
515 payload: 0 bytes
516
454 Support for changegroup
517 Support for changegroup
455 ===================================
518 ===================================
456
519
457 $ hg unbundle $TESTDIR/bundles/rebase.hg
520 $ hg unbundle $TESTDIR/bundles/rebase.hg
458 adding changesets
521 adding changesets
459 adding manifests
522 adding manifests
460 adding file changes
523 adding file changes
461 added 8 changesets with 7 changes to 7 files (+3 heads)
524 added 8 changesets with 7 changes to 7 files (+3 heads)
462 (run 'hg heads' to see heads, 'hg merge' to merge)
525 (run 'hg heads' to see heads, 'hg merge' to merge)
463
526
464 $ hg log -G
527 $ hg log -G
465 o changeset: 8:02de42196ebe
528 o changeset: 8:02de42196ebe
466 | tag: tip
529 | tag: tip
467 | parent: 6:24b6387c8c8c
530 | parent: 6:24b6387c8c8c
468 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
531 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
469 | date: Sat Apr 30 15:24:48 2011 +0200
532 | date: Sat Apr 30 15:24:48 2011 +0200
470 | summary: H
533 | summary: H
471 |
534 |
472 | o changeset: 7:eea13746799a
535 | o changeset: 7:eea13746799a
473 |/| parent: 6:24b6387c8c8c
536 |/| parent: 6:24b6387c8c8c
474 | | parent: 5:9520eea781bc
537 | | parent: 5:9520eea781bc
475 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
538 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
476 | | date: Sat Apr 30 15:24:48 2011 +0200
539 | | date: Sat Apr 30 15:24:48 2011 +0200
477 | | summary: G
540 | | summary: G
478 | |
541 | |
479 o | changeset: 6:24b6387c8c8c
542 o | changeset: 6:24b6387c8c8c
480 | | parent: 1:cd010b8cd998
543 | | parent: 1:cd010b8cd998
481 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
544 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
482 | | date: Sat Apr 30 15:24:48 2011 +0200
545 | | date: Sat Apr 30 15:24:48 2011 +0200
483 | | summary: F
546 | | summary: F
484 | |
547 | |
485 | o changeset: 5:9520eea781bc
548 | o changeset: 5:9520eea781bc
486 |/ parent: 1:cd010b8cd998
549 |/ parent: 1:cd010b8cd998
487 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
550 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
488 | date: Sat Apr 30 15:24:48 2011 +0200
551 | date: Sat Apr 30 15:24:48 2011 +0200
489 | summary: E
552 | summary: E
490 |
553 |
491 | o changeset: 4:32af7686d403
554 | o changeset: 4:32af7686d403
492 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
555 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
493 | | date: Sat Apr 30 15:24:48 2011 +0200
556 | | date: Sat Apr 30 15:24:48 2011 +0200
494 | | summary: D
557 | | summary: D
495 | |
558 | |
496 | o changeset: 3:5fddd98957c8
559 | o changeset: 3:5fddd98957c8
497 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
560 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
498 | | date: Sat Apr 30 15:24:48 2011 +0200
561 | | date: Sat Apr 30 15:24:48 2011 +0200
499 | | summary: C
562 | | summary: C
500 | |
563 | |
501 | o changeset: 2:42ccdea3bb16
564 | o changeset: 2:42ccdea3bb16
502 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
565 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
503 | date: Sat Apr 30 15:24:48 2011 +0200
566 | date: Sat Apr 30 15:24:48 2011 +0200
504 | summary: B
567 | summary: B
505 |
568 |
506 o changeset: 1:cd010b8cd998
569 o changeset: 1:cd010b8cd998
507 parent: -1:000000000000
570 parent: -1:000000000000
508 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
571 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
509 date: Sat Apr 30 15:24:48 2011 +0200
572 date: Sat Apr 30 15:24:48 2011 +0200
510 summary: A
573 summary: A
511
574
512 @ changeset: 0:3903775176ed
575 @ changeset: 0:3903775176ed
513 user: test
576 user: test
514 date: Thu Jan 01 00:00:00 1970 +0000
577 date: Thu Jan 01 00:00:00 1970 +0000
515 summary: a
578 summary: a
516
579
517
580
518 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
581 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
519 4 changesets found
582 4 changesets found
520 list of changesets:
583 list of changesets:
521 32af7686d403cf45b5d95f2d70cebea587ac806a
584 32af7686d403cf45b5d95f2d70cebea587ac806a
522 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
585 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
523 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
586 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
524 02de42196ebee42ef284b6780a87cdc96e8eaab6
587 02de42196ebee42ef284b6780a87cdc96e8eaab6
525 bundling: 1/4 changesets (25.00%)
588 bundling: 1/4 changesets (25.00%)
526 bundling: 2/4 changesets (50.00%)
589 bundling: 2/4 changesets (50.00%)
527 bundling: 3/4 changesets (75.00%)
590 bundling: 3/4 changesets (75.00%)
528 bundling: 4/4 changesets (100.00%)
591 bundling: 4/4 changesets (100.00%)
529 bundling: 1/4 manifests (25.00%)
592 bundling: 1/4 manifests (25.00%)
530 bundling: 2/4 manifests (50.00%)
593 bundling: 2/4 manifests (50.00%)
531 bundling: 3/4 manifests (75.00%)
594 bundling: 3/4 manifests (75.00%)
532 bundling: 4/4 manifests (100.00%)
595 bundling: 4/4 manifests (100.00%)
533 bundling: D 1/3 files (33.33%)
596 bundling: D 1/3 files (33.33%)
534 bundling: E 2/3 files (66.67%)
597 bundling: E 2/3 files (66.67%)
535 bundling: H 3/3 files (100.00%)
598 bundling: H 3/3 files (100.00%)
536 start emission of HG20 stream
599 start emission of HG20 stream
537 bundle parameter:
600 bundle parameter:
538 start of parts
601 start of parts
539 bundle part: "changegroup"
602 bundle part: "changegroup"
540 end of bundle
603 end of bundle
541
604
542 $ cat ../rev.hg2
605 $ cat ../rev.hg2
543 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x19HG10UN\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)
606 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x19HG10UN\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)
544 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
607 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
545 \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)
608 \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)
546 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
609 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
547 \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)
610 \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)
548 \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)
611 \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)
549 \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)
612 \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)
550 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
613 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
551 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
614 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
552 \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)
615 \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)
553 \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)
616 \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)
554 \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)
617 \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)
555 \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)
618 \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)
556 \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)
619 \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)
557 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
620 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
558 \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)
621 \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)
559 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
622 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
560 l\r (no-eol) (esc)
623 l\r (no-eol) (esc)
561 \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)
624 \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)
562 \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)
625 \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)
563 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
626 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
564 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
627 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
565
628
566 $ hg unbundle2 < ../rev.hg2
629 $ hg unbundle2 < ../rev.hg2
567 adding changesets
630 adding changesets
568 adding manifests
631 adding manifests
569 adding file changes
632 adding file changes
570 added 0 changesets with 0 changes to 3 files
633 added 0 changesets with 0 changes to 3 files
571 0 unread bytes
634 0 unread bytes
572 addchangegroup return: 1
635 addchangegroup return: 1
573
636
574 Real world exchange
637 Real world exchange
575 =====================
638 =====================
576
639
577
640
578 clone --pull
641 clone --pull
579
642
580 $ cd ..
643 $ cd ..
581 $ hg clone main other --pull --rev 9520eea781bc
644 $ hg clone main other --pull --rev 9520eea781bc
582 adding changesets
645 adding changesets
583 adding manifests
646 adding manifests
584 adding file changes
647 adding file changes
585 added 2 changesets with 2 changes to 2 files
648 added 2 changesets with 2 changes to 2 files
586 updating to branch default
649 updating to branch default
587 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
650 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
588 $ hg -R other log -G
651 $ hg -R other log -G
589 @ changeset: 1:9520eea781bc
652 @ changeset: 1:9520eea781bc
590 | tag: tip
653 | tag: tip
591 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
654 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
592 | date: Sat Apr 30 15:24:48 2011 +0200
655 | date: Sat Apr 30 15:24:48 2011 +0200
593 | summary: E
656 | summary: E
594 |
657 |
595 o changeset: 0:cd010b8cd998
658 o changeset: 0:cd010b8cd998
596 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
659 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
597 date: Sat Apr 30 15:24:48 2011 +0200
660 date: Sat Apr 30 15:24:48 2011 +0200
598 summary: A
661 summary: A
599
662
600
663
601 pull
664 pull
602
665
603 $ hg -R other pull
666 $ hg -R other pull
604 pulling from $TESTTMP/main (glob)
667 pulling from $TESTTMP/main (glob)
605 searching for changes
668 searching for changes
606 adding changesets
669 adding changesets
607 adding manifests
670 adding manifests
608 adding file changes
671 adding file changes
609 added 7 changesets with 6 changes to 6 files (+3 heads)
672 added 7 changesets with 6 changes to 6 files (+3 heads)
610 (run 'hg heads' to see heads, 'hg merge' to merge)
673 (run 'hg heads' to see heads, 'hg merge' to merge)
General Comments 0
You need to be logged in to leave comments. Login now