##// END OF EJS Templates
bundle2: protect capabilities name and values with url quoting...
Pierre-Yves David -
r21137:341a0836 default
parent child Browse files
Show More
@@ -1,718 +1,720 b''
1 # bundle2.py - generic container format to transmit arbitrary data.
1 # bundle2.py - generic container format to transmit arbitrary data.
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """Handling of the new bundle2 format
7 """Handling of the new bundle2 format
8
8
9 The goal of bundle2 is to act as an atomically packet to transmit a set of
9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 payloads in an application agnostic way. It consist in a sequence of "parts"
10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 that will be handed to and processed by the application layer.
11 that will be handed to and processed by the application layer.
12
12
13
13
14 General format architecture
14 General format architecture
15 ===========================
15 ===========================
16
16
17 The format is architectured as follow
17 The format is architectured as follow
18
18
19 - magic string
19 - magic string
20 - stream level parameters
20 - stream level parameters
21 - payload parts (any number)
21 - payload parts (any number)
22 - end of stream marker.
22 - end of stream marker.
23
23
24 the Binary format
24 the Binary format
25 ============================
25 ============================
26
26
27 All numbers are unsigned and big-endian.
27 All numbers are unsigned and big-endian.
28
28
29 stream level parameters
29 stream level parameters
30 ------------------------
30 ------------------------
31
31
32 Binary format is as follow
32 Binary format is as follow
33
33
34 :params size: (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
148 import changegroup
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 = 'HG20'
154 _magicstring = 'HG20'
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 parthandlermapping = {}
173 parthandlermapping = {}
174
174
175 def parthandler(parttype):
175 def parthandler(parttype):
176 """decorator that register a function as a bundle2 part handler
176 """decorator that register a function as a bundle2 part handler
177
177
178 eg::
178 eg::
179
179
180 @parthandler('myparttype')
180 @parthandler('myparttype')
181 def myparttypehandler(...):
181 def myparttypehandler(...):
182 '''process a part of type "my part".'''
182 '''process a part of type "my part".'''
183 ...
183 ...
184 """
184 """
185 def _decorator(func):
185 def _decorator(func):
186 lparttype = parttype.lower() # enforce lower case matching.
186 lparttype = parttype.lower() # enforce lower case matching.
187 assert lparttype not in parthandlermapping
187 assert lparttype not in parthandlermapping
188 parthandlermapping[lparttype] = func
188 parthandlermapping[lparttype] = func
189 return func
189 return func
190 return _decorator
190 return _decorator
191
191
192 class unbundlerecords(object):
192 class unbundlerecords(object):
193 """keep record of what happens during and unbundle
193 """keep record of what happens during and unbundle
194
194
195 New records are added using `records.add('cat', obj)`. Where 'cat' is a
195 New records are added using `records.add('cat', obj)`. Where 'cat' is a
196 category of record and obj is an arbitrary object.
196 category of record and obj is an arbitrary object.
197
197
198 `records['cat']` will return all entries of this category 'cat'.
198 `records['cat']` will return all entries of this category 'cat'.
199
199
200 Iterating on the object itself will yield `('category', obj)` tuples
200 Iterating on the object itself will yield `('category', obj)` tuples
201 for all entries.
201 for all entries.
202
202
203 All iterations happens in chronological order.
203 All iterations happens in chronological order.
204 """
204 """
205
205
206 def __init__(self):
206 def __init__(self):
207 self._categories = {}
207 self._categories = {}
208 self._sequences = []
208 self._sequences = []
209 self._replies = {}
209 self._replies = {}
210
210
211 def add(self, category, entry, inreplyto=None):
211 def add(self, category, entry, inreplyto=None):
212 """add a new record of a given category.
212 """add a new record of a given category.
213
213
214 The entry can then be retrieved in the list returned by
214 The entry can then be retrieved in the list returned by
215 self['category']."""
215 self['category']."""
216 self._categories.setdefault(category, []).append(entry)
216 self._categories.setdefault(category, []).append(entry)
217 self._sequences.append((category, entry))
217 self._sequences.append((category, entry))
218 if inreplyto is not None:
218 if inreplyto is not None:
219 self.getreplies(inreplyto).add(category, entry)
219 self.getreplies(inreplyto).add(category, entry)
220
220
221 def getreplies(self, partid):
221 def getreplies(self, partid):
222 """get the subrecords that replies to a specific part"""
222 """get the subrecords that replies to a specific part"""
223 return self._replies.setdefault(partid, unbundlerecords())
223 return self._replies.setdefault(partid, unbundlerecords())
224
224
225 def __getitem__(self, cat):
225 def __getitem__(self, cat):
226 return tuple(self._categories.get(cat, ()))
226 return tuple(self._categories.get(cat, ()))
227
227
228 def __iter__(self):
228 def __iter__(self):
229 return iter(self._sequences)
229 return iter(self._sequences)
230
230
231 def __len__(self):
231 def __len__(self):
232 return len(self._sequences)
232 return len(self._sequences)
233
233
234 def __nonzero__(self):
234 def __nonzero__(self):
235 return bool(self._sequences)
235 return bool(self._sequences)
236
236
237 class bundleoperation(object):
237 class bundleoperation(object):
238 """an object that represents a single bundling process
238 """an object that represents a single bundling process
239
239
240 Its purpose is to carry unbundle-related objects and states.
240 Its purpose is to carry unbundle-related objects and states.
241
241
242 A new object should be created at the beginning of each bundle processing.
242 A new object should be created at the beginning of each bundle processing.
243 The object is to be returned by the processing function.
243 The object is to be returned by the processing function.
244
244
245 The object has very little content now it will ultimately contain:
245 The object has very little content now it will ultimately contain:
246 * an access to the repo the bundle is applied to,
246 * an access to the repo the bundle is applied to,
247 * a ui object,
247 * a ui object,
248 * a way to retrieve a transaction to add changes to the repo,
248 * a way to retrieve a transaction to add changes to the repo,
249 * a way to record the result of processing each part,
249 * a way to record the result of processing each part,
250 * a way to construct a bundle response when applicable.
250 * a way to construct a bundle response when applicable.
251 """
251 """
252
252
253 def __init__(self, repo, transactiongetter):
253 def __init__(self, repo, transactiongetter):
254 self.repo = repo
254 self.repo = repo
255 self.ui = repo.ui
255 self.ui = repo.ui
256 self.records = unbundlerecords()
256 self.records = unbundlerecords()
257 self.gettransaction = transactiongetter
257 self.gettransaction = transactiongetter
258 self.reply = None
258 self.reply = None
259
259
260 class TransactionUnavailable(RuntimeError):
260 class TransactionUnavailable(RuntimeError):
261 pass
261 pass
262
262
263 def _notransaction():
263 def _notransaction():
264 """default method to get a transaction while processing a bundle
264 """default method to get a transaction while processing a bundle
265
265
266 Raise an exception to highlight the fact that no transaction was expected
266 Raise an exception to highlight the fact that no transaction was expected
267 to be created"""
267 to be created"""
268 raise TransactionUnavailable()
268 raise TransactionUnavailable()
269
269
270 def processbundle(repo, unbundler, transactiongetter=_notransaction):
270 def processbundle(repo, unbundler, transactiongetter=_notransaction):
271 """This function process a bundle, apply effect to/from a repo
271 """This function process a bundle, apply effect to/from a repo
272
272
273 It iterates over each part then searches for and uses the proper handling
273 It iterates over each part then searches for and uses the proper handling
274 code to process the part. Parts are processed in order.
274 code to process the part. Parts are processed in order.
275
275
276 This is very early version of this function that will be strongly reworked
276 This is very early version of this function that will be strongly reworked
277 before final usage.
277 before final usage.
278
278
279 Unknown Mandatory part will abort the process.
279 Unknown Mandatory part will abort the process.
280 """
280 """
281 op = bundleoperation(repo, transactiongetter)
281 op = bundleoperation(repo, transactiongetter)
282 # todo:
282 # todo:
283 # - replace this is a init function soon.
283 # - replace this is a init function soon.
284 # - exception catching
284 # - exception catching
285 unbundler.params
285 unbundler.params
286 iterparts = unbundler.iterparts()
286 iterparts = unbundler.iterparts()
287 part = None
287 part = None
288 try:
288 try:
289 for part in iterparts:
289 for part in iterparts:
290 parttype = part.type
290 parttype = part.type
291 # part key are matched lower case
291 # part key are matched lower case
292 key = parttype.lower()
292 key = parttype.lower()
293 try:
293 try:
294 handler = parthandlermapping[key]
294 handler = parthandlermapping[key]
295 op.ui.debug('found a handler for part %r\n' % parttype)
295 op.ui.debug('found a handler for part %r\n' % parttype)
296 except KeyError:
296 except KeyError:
297 if key != parttype: # mandatory parts
297 if key != parttype: # mandatory parts
298 # todo:
298 # todo:
299 # - use a more precise exception
299 # - use a more precise exception
300 raise
300 raise
301 op.ui.debug('ignoring unknown advisory part %r\n' % key)
301 op.ui.debug('ignoring unknown advisory part %r\n' % key)
302 # consuming the part
302 # consuming the part
303 part.read()
303 part.read()
304 continue
304 continue
305
305
306 # handler is called outside the above try block so that we don't
306 # handler is called outside the above try block so that we don't
307 # risk catching KeyErrors from anything other than the
307 # risk catching KeyErrors from anything other than the
308 # parthandlermapping lookup (any KeyError raised by handler()
308 # parthandlermapping lookup (any KeyError raised by handler()
309 # itself represents a defect of a different variety).
309 # itself represents a defect of a different variety).
310 output = None
310 output = None
311 if op.reply is not None:
311 if op.reply is not None:
312 op.ui.pushbuffer(error=True)
312 op.ui.pushbuffer(error=True)
313 output = ''
313 output = ''
314 try:
314 try:
315 handler(op, part)
315 handler(op, part)
316 finally:
316 finally:
317 if output is not None:
317 if output is not None:
318 output = op.ui.popbuffer()
318 output = op.ui.popbuffer()
319 if output:
319 if output:
320 outpart = bundlepart('output',
320 outpart = bundlepart('output',
321 advisoryparams=[('in-reply-to',
321 advisoryparams=[('in-reply-to',
322 str(part.id))],
322 str(part.id))],
323 data=output)
323 data=output)
324 op.reply.addpart(outpart)
324 op.reply.addpart(outpart)
325 part.read()
325 part.read()
326 except Exception:
326 except Exception:
327 if part is not None:
327 if part is not None:
328 # consume the bundle content
328 # consume the bundle content
329 part.read()
329 part.read()
330 for part in iterparts:
330 for part in iterparts:
331 # consume the bundle content
331 # consume the bundle content
332 part.read()
332 part.read()
333 raise
333 raise
334 return op
334 return op
335
335
336 class bundle20(object):
336 class bundle20(object):
337 """represent an outgoing bundle2 container
337 """represent an outgoing bundle2 container
338
338
339 Use the `addparam` method to add stream level parameter. and `addpart` to
339 Use the `addparam` method to add stream level parameter. and `addpart` to
340 populate it. Then call `getchunks` to retrieve all the binary chunks of
340 populate it. Then call `getchunks` to retrieve all the binary chunks of
341 data that compose the bundle2 container."""
341 data that compose the bundle2 container."""
342
342
343 def __init__(self, ui, capabilities=()):
343 def __init__(self, ui, capabilities=()):
344 self.ui = ui
344 self.ui = ui
345 self._params = []
345 self._params = []
346 self._parts = []
346 self._parts = []
347 self.capabilities = dict(capabilities)
347 self.capabilities = dict(capabilities)
348
348
349 def addparam(self, name, value=None):
349 def addparam(self, name, value=None):
350 """add a stream level parameter"""
350 """add a stream level parameter"""
351 if not name:
351 if not name:
352 raise ValueError('empty parameter name')
352 raise ValueError('empty parameter name')
353 if name[0] not in string.letters:
353 if name[0] not in string.letters:
354 raise ValueError('non letter first character: %r' % name)
354 raise ValueError('non letter first character: %r' % name)
355 self._params.append((name, value))
355 self._params.append((name, value))
356
356
357 def addpart(self, part):
357 def addpart(self, part):
358 """add a new part to the bundle2 container
358 """add a new part to the bundle2 container
359
359
360 Parts contains the actual applicative payload."""
360 Parts contains the actual applicative payload."""
361 assert part.id is None
361 assert part.id is None
362 part.id = len(self._parts) # very cheap counter
362 part.id = len(self._parts) # very cheap counter
363 self._parts.append(part)
363 self._parts.append(part)
364
364
365 def getchunks(self):
365 def getchunks(self):
366 self.ui.debug('start emission of %s stream\n' % _magicstring)
366 self.ui.debug('start emission of %s stream\n' % _magicstring)
367 yield _magicstring
367 yield _magicstring
368 param = self._paramchunk()
368 param = self._paramchunk()
369 self.ui.debug('bundle parameter: %s\n' % param)
369 self.ui.debug('bundle parameter: %s\n' % param)
370 yield _pack(_fstreamparamsize, len(param))
370 yield _pack(_fstreamparamsize, len(param))
371 if param:
371 if param:
372 yield param
372 yield param
373
373
374 self.ui.debug('start of parts\n')
374 self.ui.debug('start of parts\n')
375 for part in self._parts:
375 for part in self._parts:
376 self.ui.debug('bundle part: "%s"\n' % part.type)
376 self.ui.debug('bundle part: "%s"\n' % part.type)
377 for chunk in part.getchunks():
377 for chunk in part.getchunks():
378 yield chunk
378 yield chunk
379 self.ui.debug('end of bundle\n')
379 self.ui.debug('end of bundle\n')
380 yield '\0\0'
380 yield '\0\0'
381
381
382 def _paramchunk(self):
382 def _paramchunk(self):
383 """return a encoded version of all stream parameters"""
383 """return a encoded version of all stream parameters"""
384 blocks = []
384 blocks = []
385 for par, value in self._params:
385 for par, value in self._params:
386 par = urllib.quote(par)
386 par = urllib.quote(par)
387 if value is not None:
387 if value is not None:
388 value = urllib.quote(value)
388 value = urllib.quote(value)
389 par = '%s=%s' % (par, value)
389 par = '%s=%s' % (par, value)
390 blocks.append(par)
390 blocks.append(par)
391 return ' '.join(blocks)
391 return ' '.join(blocks)
392
392
393 class unpackermixin(object):
393 class unpackermixin(object):
394 """A mixin to extract bytes and struct data from a stream"""
394 """A mixin to extract bytes and struct data from a stream"""
395
395
396 def __init__(self, fp):
396 def __init__(self, fp):
397 self._fp = fp
397 self._fp = fp
398
398
399 def _unpack(self, format):
399 def _unpack(self, format):
400 """unpack this struct format from the stream"""
400 """unpack this struct format from the stream"""
401 data = self._readexact(struct.calcsize(format))
401 data = self._readexact(struct.calcsize(format))
402 return _unpack(format, data)
402 return _unpack(format, data)
403
403
404 def _readexact(self, size):
404 def _readexact(self, size):
405 """read exactly <size> bytes from the stream"""
405 """read exactly <size> bytes from the stream"""
406 return changegroup.readexactly(self._fp, size)
406 return changegroup.readexactly(self._fp, size)
407
407
408
408
409 class unbundle20(unpackermixin):
409 class unbundle20(unpackermixin):
410 """interpret a bundle2 stream
410 """interpret a bundle2 stream
411
411
412 This class is fed with a binary stream and yields parts through its
412 This class is fed with a binary stream and yields parts through its
413 `iterparts` methods."""
413 `iterparts` methods."""
414
414
415 def __init__(self, ui, fp, header=None):
415 def __init__(self, ui, fp, header=None):
416 """If header is specified, we do not read it out of the stream."""
416 """If header is specified, we do not read it out of the stream."""
417 self.ui = ui
417 self.ui = ui
418 super(unbundle20, self).__init__(fp)
418 super(unbundle20, self).__init__(fp)
419 if header is None:
419 if header is None:
420 header = self._readexact(4)
420 header = self._readexact(4)
421 magic, version = header[0:2], header[2:4]
421 magic, version = header[0:2], header[2:4]
422 if magic != 'HG':
422 if magic != 'HG':
423 raise util.Abort(_('not a Mercurial bundle'))
423 raise util.Abort(_('not a Mercurial bundle'))
424 if version != '20':
424 if version != '20':
425 raise util.Abort(_('unknown bundle version %s') % version)
425 raise util.Abort(_('unknown bundle version %s') % version)
426 self.ui.debug('start processing of %s stream\n' % header)
426 self.ui.debug('start processing of %s stream\n' % header)
427
427
428 @util.propertycache
428 @util.propertycache
429 def params(self):
429 def params(self):
430 """dictionary of stream level parameters"""
430 """dictionary of stream level parameters"""
431 self.ui.debug('reading bundle2 stream parameters\n')
431 self.ui.debug('reading bundle2 stream parameters\n')
432 params = {}
432 params = {}
433 paramssize = self._unpack(_fstreamparamsize)[0]
433 paramssize = self._unpack(_fstreamparamsize)[0]
434 if paramssize:
434 if paramssize:
435 for p in self._readexact(paramssize).split(' '):
435 for p in self._readexact(paramssize).split(' '):
436 p = p.split('=', 1)
436 p = p.split('=', 1)
437 p = [urllib.unquote(i) for i in p]
437 p = [urllib.unquote(i) for i in p]
438 if len(p) < 2:
438 if len(p) < 2:
439 p.append(None)
439 p.append(None)
440 self._processparam(*p)
440 self._processparam(*p)
441 params[p[0]] = p[1]
441 params[p[0]] = p[1]
442 return params
442 return params
443
443
444 def _processparam(self, name, value):
444 def _processparam(self, name, value):
445 """process a parameter, applying its effect if needed
445 """process a parameter, applying its effect if needed
446
446
447 Parameter starting with a lower case letter are advisory and will be
447 Parameter starting with a lower case letter are advisory and will be
448 ignored when unknown. Those starting with an upper case letter are
448 ignored when unknown. Those starting with an upper case letter are
449 mandatory and will this function will raise a KeyError when unknown.
449 mandatory and will this function will raise a KeyError when unknown.
450
450
451 Note: no option are currently supported. Any input will be either
451 Note: no option are currently supported. Any input will be either
452 ignored or failing.
452 ignored or failing.
453 """
453 """
454 if not name:
454 if not name:
455 raise ValueError('empty parameter name')
455 raise ValueError('empty parameter name')
456 if name[0] not in string.letters:
456 if name[0] not in string.letters:
457 raise ValueError('non letter first character: %r' % name)
457 raise ValueError('non letter first character: %r' % name)
458 # Some logic will be later added here to try to process the option for
458 # Some logic will be later added here to try to process the option for
459 # a dict of known parameter.
459 # a dict of known parameter.
460 if name[0].islower():
460 if name[0].islower():
461 self.ui.debug("ignoring unknown parameter %r\n" % name)
461 self.ui.debug("ignoring unknown parameter %r\n" % name)
462 else:
462 else:
463 raise KeyError(name)
463 raise KeyError(name)
464
464
465
465
466 def iterparts(self):
466 def iterparts(self):
467 """yield all parts contained in the stream"""
467 """yield all parts contained in the stream"""
468 # make sure param have been loaded
468 # make sure param have been loaded
469 self.params
469 self.params
470 self.ui.debug('start extraction of bundle2 parts\n')
470 self.ui.debug('start extraction of bundle2 parts\n')
471 headerblock = self._readpartheader()
471 headerblock = self._readpartheader()
472 while headerblock is not None:
472 while headerblock is not None:
473 part = unbundlepart(self.ui, headerblock, self._fp)
473 part = unbundlepart(self.ui, headerblock, self._fp)
474 yield part
474 yield part
475 headerblock = self._readpartheader()
475 headerblock = self._readpartheader()
476 self.ui.debug('end of bundle2 stream\n')
476 self.ui.debug('end of bundle2 stream\n')
477
477
478 def _readpartheader(self):
478 def _readpartheader(self):
479 """reads a part header size and return the bytes blob
479 """reads a part header size and return the bytes blob
480
480
481 returns None if empty"""
481 returns None if empty"""
482 headersize = self._unpack(_fpartheadersize)[0]
482 headersize = self._unpack(_fpartheadersize)[0]
483 self.ui.debug('part header size: %i\n' % headersize)
483 self.ui.debug('part header size: %i\n' % headersize)
484 if headersize:
484 if headersize:
485 return self._readexact(headersize)
485 return self._readexact(headersize)
486 return None
486 return None
487
487
488
488
489 class bundlepart(object):
489 class bundlepart(object):
490 """A bundle2 part contains application level payload
490 """A bundle2 part contains application level payload
491
491
492 The part `type` is used to route the part to the application level
492 The part `type` is used to route the part to the application level
493 handler.
493 handler.
494 """
494 """
495
495
496 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
496 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
497 data=''):
497 data=''):
498 self.id = None
498 self.id = None
499 self.type = parttype
499 self.type = parttype
500 self.data = data
500 self.data = data
501 self.mandatoryparams = mandatoryparams
501 self.mandatoryparams = mandatoryparams
502 self.advisoryparams = advisoryparams
502 self.advisoryparams = advisoryparams
503
503
504 def getchunks(self):
504 def getchunks(self):
505 #### header
505 #### header
506 ## parttype
506 ## parttype
507 header = [_pack(_fparttypesize, len(self.type)),
507 header = [_pack(_fparttypesize, len(self.type)),
508 self.type, _pack(_fpartid, self.id),
508 self.type, _pack(_fpartid, self.id),
509 ]
509 ]
510 ## parameters
510 ## parameters
511 # count
511 # count
512 manpar = self.mandatoryparams
512 manpar = self.mandatoryparams
513 advpar = self.advisoryparams
513 advpar = self.advisoryparams
514 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
514 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
515 # size
515 # size
516 parsizes = []
516 parsizes = []
517 for key, value in manpar:
517 for key, value in manpar:
518 parsizes.append(len(key))
518 parsizes.append(len(key))
519 parsizes.append(len(value))
519 parsizes.append(len(value))
520 for key, value in advpar:
520 for key, value in advpar:
521 parsizes.append(len(key))
521 parsizes.append(len(key))
522 parsizes.append(len(value))
522 parsizes.append(len(value))
523 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
523 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
524 header.append(paramsizes)
524 header.append(paramsizes)
525 # key, value
525 # key, value
526 for key, value in manpar:
526 for key, value in manpar:
527 header.append(key)
527 header.append(key)
528 header.append(value)
528 header.append(value)
529 for key, value in advpar:
529 for key, value in advpar:
530 header.append(key)
530 header.append(key)
531 header.append(value)
531 header.append(value)
532 ## finalize header
532 ## finalize header
533 headerchunk = ''.join(header)
533 headerchunk = ''.join(header)
534 yield _pack(_fpartheadersize, len(headerchunk))
534 yield _pack(_fpartheadersize, len(headerchunk))
535 yield headerchunk
535 yield headerchunk
536 ## payload
536 ## payload
537 for chunk in self._payloadchunks():
537 for chunk in self._payloadchunks():
538 yield _pack(_fpayloadsize, len(chunk))
538 yield _pack(_fpayloadsize, len(chunk))
539 yield chunk
539 yield chunk
540 # end of payload
540 # end of payload
541 yield _pack(_fpayloadsize, 0)
541 yield _pack(_fpayloadsize, 0)
542
542
543 def _payloadchunks(self):
543 def _payloadchunks(self):
544 """yield chunks of a the part payload
544 """yield chunks of a the part payload
545
545
546 Exists to handle the different methods to provide data to a part."""
546 Exists to handle the different methods to provide data to a part."""
547 # we only support fixed size data now.
547 # we only support fixed size data now.
548 # This will be improved in the future.
548 # This will be improved in the future.
549 if util.safehasattr(self.data, 'next'):
549 if util.safehasattr(self.data, 'next'):
550 buff = util.chunkbuffer(self.data)
550 buff = util.chunkbuffer(self.data)
551 chunk = buff.read(preferedchunksize)
551 chunk = buff.read(preferedchunksize)
552 while chunk:
552 while chunk:
553 yield chunk
553 yield chunk
554 chunk = buff.read(preferedchunksize)
554 chunk = buff.read(preferedchunksize)
555 elif len(self.data):
555 elif len(self.data):
556 yield self.data
556 yield self.data
557
557
558 class unbundlepart(unpackermixin):
558 class unbundlepart(unpackermixin):
559 """a bundle part read from a bundle"""
559 """a bundle part read from a bundle"""
560
560
561 def __init__(self, ui, header, fp):
561 def __init__(self, ui, header, fp):
562 super(unbundlepart, self).__init__(fp)
562 super(unbundlepart, self).__init__(fp)
563 self.ui = ui
563 self.ui = ui
564 # unbundle state attr
564 # unbundle state attr
565 self._headerdata = header
565 self._headerdata = header
566 self._headeroffset = 0
566 self._headeroffset = 0
567 self._initialized = False
567 self._initialized = False
568 self.consumed = False
568 self.consumed = False
569 # part data
569 # part data
570 self.id = None
570 self.id = None
571 self.type = None
571 self.type = None
572 self.mandatoryparams = None
572 self.mandatoryparams = None
573 self.advisoryparams = None
573 self.advisoryparams = None
574 self._payloadstream = None
574 self._payloadstream = None
575 self._readheader()
575 self._readheader()
576
576
577 def _fromheader(self, size):
577 def _fromheader(self, size):
578 """return the next <size> byte from the header"""
578 """return the next <size> byte from the header"""
579 offset = self._headeroffset
579 offset = self._headeroffset
580 data = self._headerdata[offset:(offset + size)]
580 data = self._headerdata[offset:(offset + size)]
581 self._headeroffset = offset + size
581 self._headeroffset = offset + size
582 return data
582 return data
583
583
584 def _unpackheader(self, format):
584 def _unpackheader(self, format):
585 """read given format from header
585 """read given format from header
586
586
587 This automatically compute the size of the format to read."""
587 This automatically compute the size of the format to read."""
588 data = self._fromheader(struct.calcsize(format))
588 data = self._fromheader(struct.calcsize(format))
589 return _unpack(format, data)
589 return _unpack(format, data)
590
590
591 def _readheader(self):
591 def _readheader(self):
592 """read the header and setup the object"""
592 """read the header and setup the object"""
593 typesize = self._unpackheader(_fparttypesize)[0]
593 typesize = self._unpackheader(_fparttypesize)[0]
594 self.type = self._fromheader(typesize)
594 self.type = self._fromheader(typesize)
595 self.ui.debug('part type: "%s"\n' % self.type)
595 self.ui.debug('part type: "%s"\n' % self.type)
596 self.id = self._unpackheader(_fpartid)[0]
596 self.id = self._unpackheader(_fpartid)[0]
597 self.ui.debug('part id: "%s"\n' % self.id)
597 self.ui.debug('part id: "%s"\n' % self.id)
598 ## reading parameters
598 ## reading parameters
599 # param count
599 # param count
600 mancount, advcount = self._unpackheader(_fpartparamcount)
600 mancount, advcount = self._unpackheader(_fpartparamcount)
601 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
601 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
602 # param size
602 # param size
603 fparamsizes = _makefpartparamsizes(mancount + advcount)
603 fparamsizes = _makefpartparamsizes(mancount + advcount)
604 paramsizes = self._unpackheader(fparamsizes)
604 paramsizes = self._unpackheader(fparamsizes)
605 # make it a list of couple again
605 # make it a list of couple again
606 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
606 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
607 # split mandatory from advisory
607 # split mandatory from advisory
608 mansizes = paramsizes[:mancount]
608 mansizes = paramsizes[:mancount]
609 advsizes = paramsizes[mancount:]
609 advsizes = paramsizes[mancount:]
610 # retrive param value
610 # retrive param value
611 manparams = []
611 manparams = []
612 for key, value in mansizes:
612 for key, value in mansizes:
613 manparams.append((self._fromheader(key), self._fromheader(value)))
613 manparams.append((self._fromheader(key), self._fromheader(value)))
614 advparams = []
614 advparams = []
615 for key, value in advsizes:
615 for key, value in advsizes:
616 advparams.append((self._fromheader(key), self._fromheader(value)))
616 advparams.append((self._fromheader(key), self._fromheader(value)))
617 self.mandatoryparams = manparams
617 self.mandatoryparams = manparams
618 self.advisoryparams = advparams
618 self.advisoryparams = advparams
619 ## part payload
619 ## part payload
620 def payloadchunks():
620 def payloadchunks():
621 payloadsize = self._unpack(_fpayloadsize)[0]
621 payloadsize = self._unpack(_fpayloadsize)[0]
622 self.ui.debug('payload chunk size: %i\n' % payloadsize)
622 self.ui.debug('payload chunk size: %i\n' % payloadsize)
623 while payloadsize:
623 while payloadsize:
624 yield self._readexact(payloadsize)
624 yield self._readexact(payloadsize)
625 payloadsize = self._unpack(_fpayloadsize)[0]
625 payloadsize = self._unpack(_fpayloadsize)[0]
626 self.ui.debug('payload chunk size: %i\n' % payloadsize)
626 self.ui.debug('payload chunk size: %i\n' % payloadsize)
627 self._payloadstream = util.chunkbuffer(payloadchunks())
627 self._payloadstream = util.chunkbuffer(payloadchunks())
628 # we read the data, tell it
628 # we read the data, tell it
629 self._initialized = True
629 self._initialized = True
630
630
631 def read(self, size=None):
631 def read(self, size=None):
632 """read payload data"""
632 """read payload data"""
633 if not self._initialized:
633 if not self._initialized:
634 self._readheader()
634 self._readheader()
635 if size is None:
635 if size is None:
636 data = self._payloadstream.read()
636 data = self._payloadstream.read()
637 else:
637 else:
638 data = self._payloadstream.read(size)
638 data = self._payloadstream.read(size)
639 if size is None or len(data) < size:
639 if size is None or len(data) < size:
640 self.consumed = True
640 self.consumed = True
641 return data
641 return data
642
642
643
643
644 @parthandler('changegroup')
644 @parthandler('changegroup')
645 def handlechangegroup(op, inpart):
645 def handlechangegroup(op, inpart):
646 """apply a changegroup part on the repo
646 """apply a changegroup part on the repo
647
647
648 This is a very early implementation that will massive rework before being
648 This is a very early implementation that will massive rework before being
649 inflicted to any end-user.
649 inflicted to any end-user.
650 """
650 """
651 # Make sure we trigger a transaction creation
651 # Make sure we trigger a transaction creation
652 #
652 #
653 # The addchangegroup function will get a transaction object by itself, but
653 # The addchangegroup function will get a transaction object by itself, but
654 # we need to make sure we trigger the creation of a transaction object used
654 # we need to make sure we trigger the creation of a transaction object used
655 # for the whole processing scope.
655 # for the whole processing scope.
656 op.gettransaction()
656 op.gettransaction()
657 cg = changegroup.unbundle10(inpart, 'UN')
657 cg = changegroup.unbundle10(inpart, 'UN')
658 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
658 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
659 op.records.add('changegroup', {'return': ret})
659 op.records.add('changegroup', {'return': ret})
660 if op.reply is not None:
660 if op.reply is not None:
661 # This is definitly not the final form of this
661 # This is definitly not the final form of this
662 # return. But one need to start somewhere.
662 # return. But one need to start somewhere.
663 part = bundlepart('reply:changegroup', (),
663 part = bundlepart('reply:changegroup', (),
664 [('in-reply-to', str(inpart.id)),
664 [('in-reply-to', str(inpart.id)),
665 ('return', '%i' % ret)])
665 ('return', '%i' % ret)])
666 op.reply.addpart(part)
666 op.reply.addpart(part)
667 assert not inpart.read()
667 assert not inpart.read()
668
668
669 @parthandler('reply:changegroup')
669 @parthandler('reply:changegroup')
670 def handlechangegroup(op, inpart):
670 def handlechangegroup(op, inpart):
671 p = dict(inpart.advisoryparams)
671 p = dict(inpart.advisoryparams)
672 ret = int(p['return'])
672 ret = int(p['return'])
673 op.records.add('changegroup', {'return': ret}, int(p['in-reply-to']))
673 op.records.add('changegroup', {'return': ret}, int(p['in-reply-to']))
674
674
675 @parthandler('check:heads')
675 @parthandler('check:heads')
676 def handlechangegroup(op, inpart):
676 def handlechangegroup(op, inpart):
677 """check that head of the repo did not change
677 """check that head of the repo did not change
678
678
679 This is used to detect a push race when using unbundle.
679 This is used to detect a push race when using unbundle.
680 This replaces the "heads" argument of unbundle."""
680 This replaces the "heads" argument of unbundle."""
681 h = inpart.read(20)
681 h = inpart.read(20)
682 heads = []
682 heads = []
683 while len(h) == 20:
683 while len(h) == 20:
684 heads.append(h)
684 heads.append(h)
685 h = inpart.read(20)
685 h = inpart.read(20)
686 assert not h
686 assert not h
687 if heads != op.repo.heads():
687 if heads != op.repo.heads():
688 raise exchange.PushRaced()
688 raise exchange.PushRaced()
689
689
690 @parthandler('output')
690 @parthandler('output')
691 def handleoutput(op, inpart):
691 def handleoutput(op, inpart):
692 """forward output captured on the server to the client"""
692 """forward output captured on the server to the client"""
693 for line in inpart.read().splitlines():
693 for line in inpart.read().splitlines():
694 op.ui.write(('remote: %s\n' % line))
694 op.ui.write(('remote: %s\n' % line))
695
695
696 @parthandler('replycaps')
696 @parthandler('replycaps')
697 def handlereplycaps(op, inpart):
697 def handlereplycaps(op, inpart):
698 """Notify that a reply bundle should be created
698 """Notify that a reply bundle should be created
699
699
700 The part payload is a list of capabilities (one per line)
700 The part payload is a list of capabilities (one per line)
701 Capabilities may have values using a line of form::
701 Capabilities may have values using a line of form::
702
702
703 capability=value1,value2,value3
703 capability=value1,value2,value3
704
704
705 The value are alway a list."""
705 The value are alway a list."""
706 caps = {}
706 caps = {}
707 for line in inpart.read().splitlines():
707 for line in inpart.read().splitlines():
708 if not line:
708 if not line:
709 continue
709 continue
710 if '=' not in line:
710 if '=' not in line:
711 key, vals = line, ()
711 key, vals = line, ()
712 else:
712 else:
713 key, vals = line.split('=', 1)
713 key, vals = line.split('=', 1)
714 vals = vals.split(',')
714 vals = vals.split(',')
715 key = urllib.unquote(key)
716 vals = [urllib.unquote(v) for v in vals]
715 caps[key] = vals
717 caps[key] = vals
716 if op.reply is None:
718 if op.reply is None:
717 op.reply = bundle20(op.ui, caps)
719 op.reply = bundle20(op.ui, caps)
718
720
@@ -1,881 +1,881 b''
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.read().split('\n'):
31 > for line in part.read().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')
36 > @bundle2.parthandler('test:ping')
37 > def pinghandler(op, part):
37 > def pinghandler(op, part):
38 > op.ui.write('received ping request (id %i)\n' % part.id)
38 > op.ui.write('received ping request (id %i)\n' % part.id)
39 > if op.reply is not None and 'ping-pong' in op.reply.capabilities:
39 > if op.reply is not None and 'ping-pong' in op.reply.capabilities:
40 > op.ui.write_err('replying to ping request (id %i)\n' % part.id)
40 > op.ui.write_err('replying to ping request (id %i)\n' % part.id)
41 > rpart = bundle2.bundlepart('test:pong',
41 > rpart = bundle2.bundlepart('test:pong',
42 > [('in-reply-to', str(part.id))])
42 > [('in-reply-to', str(part.id))])
43 > op.reply.addpart(rpart)
43 > op.reply.addpart(rpart)
44 >
44 >
45 > @bundle2.parthandler('test:debugreply')
45 > @bundle2.parthandler('test:debugreply')
46 > def debugreply(op, part):
46 > def debugreply(op, part):
47 > """print data about the capacity of the bundle reply"""
47 > """print data about the capacity of the bundle reply"""
48 > if op.reply is None:
48 > if op.reply is None:
49 > op.ui.write('debugreply: no reply\n')
49 > op.ui.write('debugreply: no reply\n')
50 > else:
50 > else:
51 > op.ui.write('debugreply: capabilities:\n')
51 > op.ui.write('debugreply: capabilities:\n')
52 > for cap in sorted(op.reply.capabilities):
52 > for cap in sorted(op.reply.capabilities):
53 > op.ui.write('debugreply: %r\n' % cap)
53 > op.ui.write('debugreply: %r\n' % cap)
54 > for val in op.reply.capabilities[cap]:
54 > for val in op.reply.capabilities[cap]:
55 > op.ui.write('debugreply: %r\n' % val)
55 > op.ui.write('debugreply: %r\n' % val)
56 >
56 >
57 > @command('bundle2',
57 > @command('bundle2',
58 > [('', 'param', [], 'stream level parameter'),
58 > [('', 'param', [], 'stream level parameter'),
59 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
59 > ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
60 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
60 > ('', 'parts', False, 'include some arbitrary parts to the bundle'),
61 > ('', 'reply', False, 'produce a reply bundle'),
61 > ('', 'reply', False, 'produce a reply bundle'),
62 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
62 > ('r', 'rev', [], 'includes those changeset in the bundle'),],
63 > '[OUTPUTFILE]')
63 > '[OUTPUTFILE]')
64 > def cmdbundle2(ui, repo, path=None, **opts):
64 > def cmdbundle2(ui, repo, path=None, **opts):
65 > """write a bundle2 container on standard ouput"""
65 > """write a bundle2 container on standard ouput"""
66 > bundler = bundle2.bundle20(ui)
66 > bundler = bundle2.bundle20(ui)
67 > for p in opts['param']:
67 > for p in opts['param']:
68 > p = p.split('=', 1)
68 > p = p.split('=', 1)
69 > try:
69 > try:
70 > bundler.addparam(*p)
70 > bundler.addparam(*p)
71 > except ValueError, exc:
71 > except ValueError, exc:
72 > raise util.Abort('%s' % exc)
72 > raise util.Abort('%s' % exc)
73 >
73 >
74 > if opts['reply']:
74 > if opts['reply']:
75 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity=celesteville'
75 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
76 > bundler.addpart(bundle2.bundlepart('replycaps', data=capsstring))
76 > bundler.addpart(bundle2.bundlepart('replycaps', data=capsstring))
77 >
77 >
78 > revs = opts['rev']
78 > revs = opts['rev']
79 > if 'rev' in opts:
79 > if 'rev' in opts:
80 > revs = scmutil.revrange(repo, opts['rev'])
80 > revs = scmutil.revrange(repo, opts['rev'])
81 > if revs:
81 > if revs:
82 > # very crude version of a changegroup part creation
82 > # very crude version of a changegroup part creation
83 > bundled = repo.revs('%ld::%ld', revs, revs)
83 > bundled = repo.revs('%ld::%ld', revs, revs)
84 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
84 > headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
85 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
85 > headcommon = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
86 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
86 > outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
87 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
87 > cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
88 > part = bundle2.bundlepart('changegroup', data=cg.getchunks())
88 > part = bundle2.bundlepart('changegroup', data=cg.getchunks())
89 > bundler.addpart(part)
89 > bundler.addpart(part)
90 >
90 >
91 > if opts['parts']:
91 > if opts['parts']:
92 > part = bundle2.bundlepart('test:empty')
92 > part = bundle2.bundlepart('test:empty')
93 > bundler.addpart(part)
93 > bundler.addpart(part)
94 > # add a second one to make sure we handle multiple parts
94 > # add a second one to make sure we handle multiple parts
95 > part = bundle2.bundlepart('test:empty')
95 > part = bundle2.bundlepart('test:empty')
96 > bundler.addpart(part)
96 > bundler.addpart(part)
97 > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG)
97 > part = bundle2.bundlepart('test:song', data=ELEPHANTSSONG)
98 > bundler.addpart(part)
98 > bundler.addpart(part)
99 > part = bundle2.bundlepart('test:debugreply')
99 > part = bundle2.bundlepart('test:debugreply')
100 > bundler.addpart(part)
100 > bundler.addpart(part)
101 > part = bundle2.bundlepart('test:math',
101 > part = bundle2.bundlepart('test:math',
102 > [('pi', '3.14'), ('e', '2.72')],
102 > [('pi', '3.14'), ('e', '2.72')],
103 > [('cooking', 'raw')],
103 > [('cooking', 'raw')],
104 > '42')
104 > '42')
105 > bundler.addpart(part)
105 > bundler.addpart(part)
106 > if opts['unknown']:
106 > if opts['unknown']:
107 > part = bundle2.bundlepart('test:UNKNOWN',
107 > part = bundle2.bundlepart('test:UNKNOWN',
108 > data='some random content')
108 > data='some random content')
109 > bundler.addpart(part)
109 > bundler.addpart(part)
110 > if opts['parts']:
110 > if opts['parts']:
111 > part = bundle2.bundlepart('test:ping')
111 > part = bundle2.bundlepart('test:ping')
112 > bundler.addpart(part)
112 > bundler.addpart(part)
113 >
113 >
114 > if path is None:
114 > if path is None:
115 > file = sys.stdout
115 > file = sys.stdout
116 > else:
116 > else:
117 > file = open(path, 'w')
117 > file = open(path, 'w')
118 >
118 >
119 > for chunk in bundler.getchunks():
119 > for chunk in bundler.getchunks():
120 > file.write(chunk)
120 > file.write(chunk)
121 >
121 >
122 > @command('unbundle2', [], '')
122 > @command('unbundle2', [], '')
123 > def cmdunbundle2(ui, repo, replypath=None):
123 > def cmdunbundle2(ui, repo, replypath=None):
124 > """process a bundle2 stream from stdin on the current repo"""
124 > """process a bundle2 stream from stdin on the current repo"""
125 > try:
125 > try:
126 > tr = None
126 > tr = None
127 > lock = repo.lock()
127 > lock = repo.lock()
128 > tr = repo.transaction('processbundle')
128 > tr = repo.transaction('processbundle')
129 > try:
129 > try:
130 > unbundler = bundle2.unbundle20(ui, sys.stdin)
130 > unbundler = bundle2.unbundle20(ui, sys.stdin)
131 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
131 > op = bundle2.processbundle(repo, unbundler, lambda: tr)
132 > tr.close()
132 > tr.close()
133 > except KeyError, exc:
133 > except KeyError, exc:
134 > raise util.Abort('missing support for %s' % exc)
134 > raise util.Abort('missing support for %s' % exc)
135 > finally:
135 > finally:
136 > if tr is not None:
136 > if tr is not None:
137 > tr.release()
137 > tr.release()
138 > lock.release()
138 > lock.release()
139 > remains = sys.stdin.read()
139 > remains = sys.stdin.read()
140 > ui.write('%i unread bytes\n' % len(remains))
140 > ui.write('%i unread bytes\n' % len(remains))
141 > if op.records['song']:
141 > if op.records['song']:
142 > totalverses = sum(r['verses'] for r in op.records['song'])
142 > totalverses = sum(r['verses'] for r in op.records['song'])
143 > ui.write('%i total verses sung\n' % totalverses)
143 > ui.write('%i total verses sung\n' % totalverses)
144 > for rec in op.records['changegroup']:
144 > for rec in op.records['changegroup']:
145 > ui.write('addchangegroup return: %i\n' % rec['return'])
145 > ui.write('addchangegroup return: %i\n' % rec['return'])
146 > if op.reply is not None and replypath is not None:
146 > if op.reply is not None and replypath is not None:
147 > file = open(replypath, 'w')
147 > file = open(replypath, 'w')
148 > for chunk in op.reply.getchunks():
148 > for chunk in op.reply.getchunks():
149 > file.write(chunk)
149 > file.write(chunk)
150 >
150 >
151 > @command('statbundle2', [], '')
151 > @command('statbundle2', [], '')
152 > def cmdstatbundle2(ui, repo):
152 > def cmdstatbundle2(ui, repo):
153 > """print statistic on the bundle2 container read from stdin"""
153 > """print statistic on the bundle2 container read from stdin"""
154 > unbundler = bundle2.unbundle20(ui, sys.stdin)
154 > unbundler = bundle2.unbundle20(ui, sys.stdin)
155 > try:
155 > try:
156 > params = unbundler.params
156 > params = unbundler.params
157 > except KeyError, exc:
157 > except KeyError, exc:
158 > raise util.Abort('unknown parameters: %s' % exc)
158 > raise util.Abort('unknown parameters: %s' % exc)
159 > ui.write('options count: %i\n' % len(params))
159 > ui.write('options count: %i\n' % len(params))
160 > for key in sorted(params):
160 > for key in sorted(params):
161 > ui.write('- %s\n' % key)
161 > ui.write('- %s\n' % key)
162 > value = params[key]
162 > value = params[key]
163 > if value is not None:
163 > if value is not None:
164 > ui.write(' %s\n' % value)
164 > ui.write(' %s\n' % value)
165 > count = 0
165 > count = 0
166 > for p in unbundler.iterparts():
166 > for p in unbundler.iterparts():
167 > count += 1
167 > count += 1
168 > ui.write(' :%s:\n' % p.type)
168 > ui.write(' :%s:\n' % p.type)
169 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
169 > ui.write(' mandatory: %i\n' % len(p.mandatoryparams))
170 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
170 > ui.write(' advisory: %i\n' % len(p.advisoryparams))
171 > ui.write(' payload: %i bytes\n' % len(p.read()))
171 > ui.write(' payload: %i bytes\n' % len(p.read()))
172 > ui.write('parts count: %i\n' % count)
172 > ui.write('parts count: %i\n' % count)
173 > EOF
173 > EOF
174 $ cat >> $HGRCPATH << EOF
174 $ cat >> $HGRCPATH << EOF
175 > [extensions]
175 > [extensions]
176 > bundle2=$TESTTMP/bundle2.py
176 > bundle2=$TESTTMP/bundle2.py
177 > [server]
177 > [server]
178 > bundle2=True
178 > bundle2=True
179 > [ui]
179 > [ui]
180 > ssh=python "$TESTDIR/dummyssh"
180 > ssh=python "$TESTDIR/dummyssh"
181 > [web]
181 > [web]
182 > push_ssl = false
182 > push_ssl = false
183 > allow_push = *
183 > allow_push = *
184 > EOF
184 > EOF
185
185
186 The extension requires a repo (currently unused)
186 The extension requires a repo (currently unused)
187
187
188 $ hg init main
188 $ hg init main
189 $ cd main
189 $ cd main
190 $ touch a
190 $ touch a
191 $ hg add a
191 $ hg add a
192 $ hg commit -m 'a'
192 $ hg commit -m 'a'
193
193
194
194
195 Empty bundle
195 Empty bundle
196 =================
196 =================
197
197
198 - no option
198 - no option
199 - no parts
199 - no parts
200
200
201 Test bundling
201 Test bundling
202
202
203 $ hg bundle2
203 $ hg bundle2
204 HG20\x00\x00\x00\x00 (no-eol) (esc)
204 HG20\x00\x00\x00\x00 (no-eol) (esc)
205
205
206 Test unbundling
206 Test unbundling
207
207
208 $ hg bundle2 | hg statbundle2
208 $ hg bundle2 | hg statbundle2
209 options count: 0
209 options count: 0
210 parts count: 0
210 parts count: 0
211
211
212 Test old style bundle are detected and refused
212 Test old style bundle are detected and refused
213
213
214 $ hg bundle --all ../bundle.hg
214 $ hg bundle --all ../bundle.hg
215 1 changesets found
215 1 changesets found
216 $ hg statbundle2 < ../bundle.hg
216 $ hg statbundle2 < ../bundle.hg
217 abort: unknown bundle version 10
217 abort: unknown bundle version 10
218 [255]
218 [255]
219
219
220 Test parameters
220 Test parameters
221 =================
221 =================
222
222
223 - some options
223 - some options
224 - no parts
224 - no parts
225
225
226 advisory parameters, no value
226 advisory parameters, no value
227 -------------------------------
227 -------------------------------
228
228
229 Simplest possible parameters form
229 Simplest possible parameters form
230
230
231 Test generation simple option
231 Test generation simple option
232
232
233 $ hg bundle2 --param 'caution'
233 $ hg bundle2 --param 'caution'
234 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
234 HG20\x00\x07caution\x00\x00 (no-eol) (esc)
235
235
236 Test unbundling
236 Test unbundling
237
237
238 $ hg bundle2 --param 'caution' | hg statbundle2
238 $ hg bundle2 --param 'caution' | hg statbundle2
239 options count: 1
239 options count: 1
240 - caution
240 - caution
241 parts count: 0
241 parts count: 0
242
242
243 Test generation multiple option
243 Test generation multiple option
244
244
245 $ hg bundle2 --param 'caution' --param 'meal'
245 $ hg bundle2 --param 'caution' --param 'meal'
246 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
246 HG20\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
247
247
248 Test unbundling
248 Test unbundling
249
249
250 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
250 $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
251 options count: 2
251 options count: 2
252 - caution
252 - caution
253 - meal
253 - meal
254 parts count: 0
254 parts count: 0
255
255
256 advisory parameters, with value
256 advisory parameters, with value
257 -------------------------------
257 -------------------------------
258
258
259 Test generation
259 Test generation
260
260
261 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
261 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
262 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
262 HG20\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
263
263
264 Test unbundling
264 Test unbundling
265
265
266 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
266 $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
267 options count: 3
267 options count: 3
268 - caution
268 - caution
269 - elephants
269 - elephants
270 - meal
270 - meal
271 vegan
271 vegan
272 parts count: 0
272 parts count: 0
273
273
274 parameter with special char in value
274 parameter with special char in value
275 ---------------------------------------------------
275 ---------------------------------------------------
276
276
277 Test generation
277 Test generation
278
278
279 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
279 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
280 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
280 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
281
281
282 Test unbundling
282 Test unbundling
283
283
284 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
284 $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
285 options count: 2
285 options count: 2
286 - e|! 7/
286 - e|! 7/
287 babar%#==tutu
287 babar%#==tutu
288 - simple
288 - simple
289 parts count: 0
289 parts count: 0
290
290
291 Test unknown mandatory option
291 Test unknown mandatory option
292 ---------------------------------------------------
292 ---------------------------------------------------
293
293
294 $ hg bundle2 --param 'Gravity' | hg statbundle2
294 $ hg bundle2 --param 'Gravity' | hg statbundle2
295 abort: unknown parameters: 'Gravity'
295 abort: unknown parameters: 'Gravity'
296 [255]
296 [255]
297
297
298 Test debug output
298 Test debug output
299 ---------------------------------------------------
299 ---------------------------------------------------
300
300
301 bundling debug
301 bundling debug
302
302
303 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
303 $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
304 start emission of HG20 stream
304 start emission of HG20 stream
305 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
305 bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
306 start of parts
306 start of parts
307 end of bundle
307 end of bundle
308
308
309 file content is ok
309 file content is ok
310
310
311 $ cat ../out.hg2
311 $ cat ../out.hg2
312 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
312 HG20\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
313
313
314 unbundling debug
314 unbundling debug
315
315
316 $ hg statbundle2 --debug < ../out.hg2
316 $ hg statbundle2 --debug < ../out.hg2
317 start processing of HG20 stream
317 start processing of HG20 stream
318 reading bundle2 stream parameters
318 reading bundle2 stream parameters
319 ignoring unknown parameter 'e|! 7/'
319 ignoring unknown parameter 'e|! 7/'
320 ignoring unknown parameter 'simple'
320 ignoring unknown parameter 'simple'
321 options count: 2
321 options count: 2
322 - e|! 7/
322 - e|! 7/
323 babar%#==tutu
323 babar%#==tutu
324 - simple
324 - simple
325 start extraction of bundle2 parts
325 start extraction of bundle2 parts
326 part header size: 0
326 part header size: 0
327 end of bundle2 stream
327 end of bundle2 stream
328 parts count: 0
328 parts count: 0
329
329
330
330
331 Test buggy input
331 Test buggy input
332 ---------------------------------------------------
332 ---------------------------------------------------
333
333
334 empty parameter name
334 empty parameter name
335
335
336 $ hg bundle2 --param '' --quiet
336 $ hg bundle2 --param '' --quiet
337 abort: empty parameter name
337 abort: empty parameter name
338 [255]
338 [255]
339
339
340 bad parameter name
340 bad parameter name
341
341
342 $ hg bundle2 --param 42babar
342 $ hg bundle2 --param 42babar
343 abort: non letter first character: '42babar'
343 abort: non letter first character: '42babar'
344 [255]
344 [255]
345
345
346
346
347 Test part
347 Test part
348 =================
348 =================
349
349
350 $ hg bundle2 --parts ../parts.hg2 --debug
350 $ hg bundle2 --parts ../parts.hg2 --debug
351 start emission of HG20 stream
351 start emission of HG20 stream
352 bundle parameter:
352 bundle parameter:
353 start of parts
353 start of parts
354 bundle part: "test:empty"
354 bundle part: "test:empty"
355 bundle part: "test:empty"
355 bundle part: "test:empty"
356 bundle part: "test:song"
356 bundle part: "test:song"
357 bundle part: "test:debugreply"
357 bundle part: "test:debugreply"
358 bundle part: "test:math"
358 bundle part: "test:math"
359 bundle part: "test:ping"
359 bundle part: "test:ping"
360 end of bundle
360 end of bundle
361
361
362 $ cat ../parts.hg2
362 $ cat ../parts.hg2
363 HG20\x00\x00\x00\x11 (esc)
363 HG20\x00\x00\x00\x11 (esc)
364 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
364 test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
365 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)
365 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)
366 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
366 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
367 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)
367 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)
368
368
369
369
370 $ hg statbundle2 < ../parts.hg2
370 $ hg statbundle2 < ../parts.hg2
371 options count: 0
371 options count: 0
372 :test:empty:
372 :test:empty:
373 mandatory: 0
373 mandatory: 0
374 advisory: 0
374 advisory: 0
375 payload: 0 bytes
375 payload: 0 bytes
376 :test:empty:
376 :test:empty:
377 mandatory: 0
377 mandatory: 0
378 advisory: 0
378 advisory: 0
379 payload: 0 bytes
379 payload: 0 bytes
380 :test:song:
380 :test:song:
381 mandatory: 0
381 mandatory: 0
382 advisory: 0
382 advisory: 0
383 payload: 178 bytes
383 payload: 178 bytes
384 :test:debugreply:
384 :test:debugreply:
385 mandatory: 0
385 mandatory: 0
386 advisory: 0
386 advisory: 0
387 payload: 0 bytes
387 payload: 0 bytes
388 :test:math:
388 :test:math:
389 mandatory: 2
389 mandatory: 2
390 advisory: 1
390 advisory: 1
391 payload: 2 bytes
391 payload: 2 bytes
392 :test:ping:
392 :test:ping:
393 mandatory: 0
393 mandatory: 0
394 advisory: 0
394 advisory: 0
395 payload: 0 bytes
395 payload: 0 bytes
396 parts count: 6
396 parts count: 6
397
397
398 $ hg statbundle2 --debug < ../parts.hg2
398 $ hg statbundle2 --debug < ../parts.hg2
399 start processing of HG20 stream
399 start processing of HG20 stream
400 reading bundle2 stream parameters
400 reading bundle2 stream parameters
401 options count: 0
401 options count: 0
402 start extraction of bundle2 parts
402 start extraction of bundle2 parts
403 part header size: 17
403 part header size: 17
404 part type: "test:empty"
404 part type: "test:empty"
405 part id: "0"
405 part id: "0"
406 part parameters: 0
406 part parameters: 0
407 :test:empty:
407 :test:empty:
408 mandatory: 0
408 mandatory: 0
409 advisory: 0
409 advisory: 0
410 payload chunk size: 0
410 payload chunk size: 0
411 payload: 0 bytes
411 payload: 0 bytes
412 part header size: 17
412 part header size: 17
413 part type: "test:empty"
413 part type: "test:empty"
414 part id: "1"
414 part id: "1"
415 part parameters: 0
415 part parameters: 0
416 :test:empty:
416 :test:empty:
417 mandatory: 0
417 mandatory: 0
418 advisory: 0
418 advisory: 0
419 payload chunk size: 0
419 payload chunk size: 0
420 payload: 0 bytes
420 payload: 0 bytes
421 part header size: 16
421 part header size: 16
422 part type: "test:song"
422 part type: "test:song"
423 part id: "2"
423 part id: "2"
424 part parameters: 0
424 part parameters: 0
425 :test:song:
425 :test:song:
426 mandatory: 0
426 mandatory: 0
427 advisory: 0
427 advisory: 0
428 payload chunk size: 178
428 payload chunk size: 178
429 payload chunk size: 0
429 payload chunk size: 0
430 payload: 178 bytes
430 payload: 178 bytes
431 part header size: 22
431 part header size: 22
432 part type: "test:debugreply"
432 part type: "test:debugreply"
433 part id: "3"
433 part id: "3"
434 part parameters: 0
434 part parameters: 0
435 :test:debugreply:
435 :test:debugreply:
436 mandatory: 0
436 mandatory: 0
437 advisory: 0
437 advisory: 0
438 payload chunk size: 0
438 payload chunk size: 0
439 payload: 0 bytes
439 payload: 0 bytes
440 part header size: 43
440 part header size: 43
441 part type: "test:math"
441 part type: "test:math"
442 part id: "4"
442 part id: "4"
443 part parameters: 3
443 part parameters: 3
444 :test:math:
444 :test:math:
445 mandatory: 2
445 mandatory: 2
446 advisory: 1
446 advisory: 1
447 payload chunk size: 2
447 payload chunk size: 2
448 payload chunk size: 0
448 payload chunk size: 0
449 payload: 2 bytes
449 payload: 2 bytes
450 part header size: 16
450 part header size: 16
451 part type: "test:ping"
451 part type: "test:ping"
452 part id: "5"
452 part id: "5"
453 part parameters: 0
453 part parameters: 0
454 :test:ping:
454 :test:ping:
455 mandatory: 0
455 mandatory: 0
456 advisory: 0
456 advisory: 0
457 payload chunk size: 0
457 payload chunk size: 0
458 payload: 0 bytes
458 payload: 0 bytes
459 part header size: 0
459 part header size: 0
460 end of bundle2 stream
460 end of bundle2 stream
461 parts count: 6
461 parts count: 6
462
462
463 Test actual unbundling of test part
463 Test actual unbundling of test part
464 =======================================
464 =======================================
465
465
466 Process the bundle
466 Process the bundle
467
467
468 $ hg unbundle2 --debug < ../parts.hg2
468 $ hg unbundle2 --debug < ../parts.hg2
469 start processing of HG20 stream
469 start processing of HG20 stream
470 reading bundle2 stream parameters
470 reading bundle2 stream parameters
471 start extraction of bundle2 parts
471 start extraction of bundle2 parts
472 part header size: 17
472 part header size: 17
473 part type: "test:empty"
473 part type: "test:empty"
474 part id: "0"
474 part id: "0"
475 part parameters: 0
475 part parameters: 0
476 ignoring unknown advisory part 'test:empty'
476 ignoring unknown advisory part 'test:empty'
477 payload chunk size: 0
477 payload chunk size: 0
478 part header size: 17
478 part header size: 17
479 part type: "test:empty"
479 part type: "test:empty"
480 part id: "1"
480 part id: "1"
481 part parameters: 0
481 part parameters: 0
482 ignoring unknown advisory part 'test:empty'
482 ignoring unknown advisory part 'test:empty'
483 payload chunk size: 0
483 payload chunk size: 0
484 part header size: 16
484 part header size: 16
485 part type: "test:song"
485 part type: "test:song"
486 part id: "2"
486 part id: "2"
487 part parameters: 0
487 part parameters: 0
488 found a handler for part 'test:song'
488 found a handler for part 'test:song'
489 The choir starts singing:
489 The choir starts singing:
490 payload chunk size: 178
490 payload chunk size: 178
491 payload chunk size: 0
491 payload chunk size: 0
492 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
492 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
493 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
493 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
494 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
494 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
495 part header size: 22
495 part header size: 22
496 part type: "test:debugreply"
496 part type: "test:debugreply"
497 part id: "3"
497 part id: "3"
498 part parameters: 0
498 part parameters: 0
499 found a handler for part 'test:debugreply'
499 found a handler for part 'test:debugreply'
500 debugreply: no reply
500 debugreply: no reply
501 payload chunk size: 0
501 payload chunk size: 0
502 part header size: 43
502 part header size: 43
503 part type: "test:math"
503 part type: "test:math"
504 part id: "4"
504 part id: "4"
505 part parameters: 3
505 part parameters: 3
506 ignoring unknown advisory part 'test:math'
506 ignoring unknown advisory part 'test:math'
507 payload chunk size: 2
507 payload chunk size: 2
508 payload chunk size: 0
508 payload chunk size: 0
509 part header size: 16
509 part header size: 16
510 part type: "test:ping"
510 part type: "test:ping"
511 part id: "5"
511 part id: "5"
512 part parameters: 0
512 part parameters: 0
513 found a handler for part 'test:ping'
513 found a handler for part 'test:ping'
514 received ping request (id 5)
514 received ping request (id 5)
515 payload chunk size: 0
515 payload chunk size: 0
516 part header size: 0
516 part header size: 0
517 end of bundle2 stream
517 end of bundle2 stream
518 0 unread bytes
518 0 unread bytes
519 3 total verses sung
519 3 total verses sung
520
520
521 Unbundle with an unknown mandatory part
521 Unbundle with an unknown mandatory part
522 (should abort)
522 (should abort)
523
523
524 $ hg bundle2 --parts --unknown ../unknown.hg2
524 $ hg bundle2 --parts --unknown ../unknown.hg2
525
525
526 $ hg unbundle2 < ../unknown.hg2
526 $ hg unbundle2 < ../unknown.hg2
527 The choir starts singing:
527 The choir starts singing:
528 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
528 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
529 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
529 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
530 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
530 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
531 debugreply: no reply
531 debugreply: no reply
532 0 unread bytes
532 0 unread bytes
533 abort: missing support for 'test:unknown'
533 abort: missing support for 'test:unknown'
534 [255]
534 [255]
535
535
536 unbundle with a reply
536 unbundle with a reply
537
537
538 $ hg bundle2 --parts --reply ../parts-reply.hg2
538 $ hg bundle2 --parts --reply ../parts-reply.hg2
539 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
539 $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
540 0 unread bytes
540 0 unread bytes
541 3 total verses sung
541 3 total verses sung
542
542
543 The reply is a bundle
543 The reply is a bundle
544
544
545 $ cat ../reply.hg2
545 $ cat ../reply.hg2
546 HG20\x00\x00\x00\x1b\x06output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
546 HG20\x00\x00\x00\x1b\x06output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
547 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
547 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
548 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
548 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
549 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
549 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
550 \x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc6debugreply: capabilities: (esc)
550 \x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
551 debugreply: 'city'
551 debugreply: 'city=!'
552 debugreply: 'celesteville'
552 debugreply: 'celeste,ville'
553 debugreply: 'elephants'
553 debugreply: 'elephants'
554 debugreply: 'babar'
554 debugreply: 'babar'
555 debugreply: 'celeste'
555 debugreply: 'celeste'
556 debugreply: 'ping-pong'
556 debugreply: 'ping-pong'
557 \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to6\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to6\x00\x00\x00=received ping request (id 6) (esc)
557 \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to6\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to6\x00\x00\x00=received ping request (id 6) (esc)
558 replying to ping request (id 6)
558 replying to ping request (id 6)
559 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
559 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
560
560
561 The reply is valid
561 The reply is valid
562
562
563 $ hg statbundle2 < ../reply.hg2
563 $ hg statbundle2 < ../reply.hg2
564 options count: 0
564 options count: 0
565 :output:
565 :output:
566 mandatory: 0
566 mandatory: 0
567 advisory: 1
567 advisory: 1
568 payload: 217 bytes
568 payload: 217 bytes
569 :output:
569 :output:
570 mandatory: 0
570 mandatory: 0
571 advisory: 1
571 advisory: 1
572 payload: 198 bytes
572 payload: 201 bytes
573 :test:pong:
573 :test:pong:
574 mandatory: 1
574 mandatory: 1
575 advisory: 0
575 advisory: 0
576 payload: 0 bytes
576 payload: 0 bytes
577 :output:
577 :output:
578 mandatory: 0
578 mandatory: 0
579 advisory: 1
579 advisory: 1
580 payload: 61 bytes
580 payload: 61 bytes
581 parts count: 4
581 parts count: 4
582
582
583 Unbundle the reply to get the output:
583 Unbundle the reply to get the output:
584
584
585 $ hg unbundle2 < ../reply.hg2
585 $ hg unbundle2 < ../reply.hg2
586 remote: The choir starts singing:
586 remote: The choir starts singing:
587 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
587 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
588 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
588 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
589 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
589 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
590 remote: debugreply: capabilities:
590 remote: debugreply: capabilities:
591 remote: debugreply: 'city'
591 remote: debugreply: 'city=!'
592 remote: debugreply: 'celesteville'
592 remote: debugreply: 'celeste,ville'
593 remote: debugreply: 'elephants'
593 remote: debugreply: 'elephants'
594 remote: debugreply: 'babar'
594 remote: debugreply: 'babar'
595 remote: debugreply: 'celeste'
595 remote: debugreply: 'celeste'
596 remote: debugreply: 'ping-pong'
596 remote: debugreply: 'ping-pong'
597 remote: received ping request (id 6)
597 remote: received ping request (id 6)
598 remote: replying to ping request (id 6)
598 remote: replying to ping request (id 6)
599 0 unread bytes
599 0 unread bytes
600
600
601 Support for changegroup
601 Support for changegroup
602 ===================================
602 ===================================
603
603
604 $ hg unbundle $TESTDIR/bundles/rebase.hg
604 $ hg unbundle $TESTDIR/bundles/rebase.hg
605 adding changesets
605 adding changesets
606 adding manifests
606 adding manifests
607 adding file changes
607 adding file changes
608 added 8 changesets with 7 changes to 7 files (+3 heads)
608 added 8 changesets with 7 changes to 7 files (+3 heads)
609 (run 'hg heads' to see heads, 'hg merge' to merge)
609 (run 'hg heads' to see heads, 'hg merge' to merge)
610
610
611 $ hg log -G
611 $ hg log -G
612 o changeset: 8:02de42196ebe
612 o changeset: 8:02de42196ebe
613 | tag: tip
613 | tag: tip
614 | parent: 6:24b6387c8c8c
614 | parent: 6:24b6387c8c8c
615 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
615 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
616 | date: Sat Apr 30 15:24:48 2011 +0200
616 | date: Sat Apr 30 15:24:48 2011 +0200
617 | summary: H
617 | summary: H
618 |
618 |
619 | o changeset: 7:eea13746799a
619 | o changeset: 7:eea13746799a
620 |/| parent: 6:24b6387c8c8c
620 |/| parent: 6:24b6387c8c8c
621 | | parent: 5:9520eea781bc
621 | | parent: 5:9520eea781bc
622 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
622 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
623 | | date: Sat Apr 30 15:24:48 2011 +0200
623 | | date: Sat Apr 30 15:24:48 2011 +0200
624 | | summary: G
624 | | summary: G
625 | |
625 | |
626 o | changeset: 6:24b6387c8c8c
626 o | changeset: 6:24b6387c8c8c
627 | | parent: 1:cd010b8cd998
627 | | parent: 1:cd010b8cd998
628 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
628 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
629 | | date: Sat Apr 30 15:24:48 2011 +0200
629 | | date: Sat Apr 30 15:24:48 2011 +0200
630 | | summary: F
630 | | summary: F
631 | |
631 | |
632 | o changeset: 5:9520eea781bc
632 | o changeset: 5:9520eea781bc
633 |/ parent: 1:cd010b8cd998
633 |/ parent: 1:cd010b8cd998
634 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
634 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
635 | date: Sat Apr 30 15:24:48 2011 +0200
635 | date: Sat Apr 30 15:24:48 2011 +0200
636 | summary: E
636 | summary: E
637 |
637 |
638 | o changeset: 4:32af7686d403
638 | o changeset: 4:32af7686d403
639 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
639 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
640 | | date: Sat Apr 30 15:24:48 2011 +0200
640 | | date: Sat Apr 30 15:24:48 2011 +0200
641 | | summary: D
641 | | summary: D
642 | |
642 | |
643 | o changeset: 3:5fddd98957c8
643 | o changeset: 3:5fddd98957c8
644 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
644 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
645 | | date: Sat Apr 30 15:24:48 2011 +0200
645 | | date: Sat Apr 30 15:24:48 2011 +0200
646 | | summary: C
646 | | summary: C
647 | |
647 | |
648 | o changeset: 2:42ccdea3bb16
648 | o changeset: 2:42ccdea3bb16
649 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
649 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
650 | date: Sat Apr 30 15:24:48 2011 +0200
650 | date: Sat Apr 30 15:24:48 2011 +0200
651 | summary: B
651 | summary: B
652 |
652 |
653 o changeset: 1:cd010b8cd998
653 o changeset: 1:cd010b8cd998
654 parent: -1:000000000000
654 parent: -1:000000000000
655 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
655 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
656 date: Sat Apr 30 15:24:48 2011 +0200
656 date: Sat Apr 30 15:24:48 2011 +0200
657 summary: A
657 summary: A
658
658
659 @ changeset: 0:3903775176ed
659 @ changeset: 0:3903775176ed
660 user: test
660 user: test
661 date: Thu Jan 01 00:00:00 1970 +0000
661 date: Thu Jan 01 00:00:00 1970 +0000
662 summary: a
662 summary: a
663
663
664
664
665 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
665 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
666 4 changesets found
666 4 changesets found
667 list of changesets:
667 list of changesets:
668 32af7686d403cf45b5d95f2d70cebea587ac806a
668 32af7686d403cf45b5d95f2d70cebea587ac806a
669 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
669 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
670 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
670 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
671 02de42196ebee42ef284b6780a87cdc96e8eaab6
671 02de42196ebee42ef284b6780a87cdc96e8eaab6
672 start emission of HG20 stream
672 start emission of HG20 stream
673 bundle parameter:
673 bundle parameter:
674 start of parts
674 start of parts
675 bundle part: "changegroup"
675 bundle part: "changegroup"
676 bundling: 1/4 changesets (25.00%)
676 bundling: 1/4 changesets (25.00%)
677 bundling: 2/4 changesets (50.00%)
677 bundling: 2/4 changesets (50.00%)
678 bundling: 3/4 changesets (75.00%)
678 bundling: 3/4 changesets (75.00%)
679 bundling: 4/4 changesets (100.00%)
679 bundling: 4/4 changesets (100.00%)
680 bundling: 1/4 manifests (25.00%)
680 bundling: 1/4 manifests (25.00%)
681 bundling: 2/4 manifests (50.00%)
681 bundling: 2/4 manifests (50.00%)
682 bundling: 3/4 manifests (75.00%)
682 bundling: 3/4 manifests (75.00%)
683 bundling: 4/4 manifests (100.00%)
683 bundling: 4/4 manifests (100.00%)
684 bundling: D 1/3 files (33.33%)
684 bundling: D 1/3 files (33.33%)
685 bundling: E 2/3 files (66.67%)
685 bundling: E 2/3 files (66.67%)
686 bundling: H 3/3 files (100.00%)
686 bundling: H 3/3 files (100.00%)
687 end of bundle
687 end of bundle
688
688
689 $ cat ../rev.hg2
689 $ cat ../rev.hg2
690 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
690 HG20\x00\x00\x00\x12\x0bchangegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
691 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
691 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
692 \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)
692 \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)
693 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
693 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
694 \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)
694 \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)
695 \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)
695 \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)
696 \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)
696 \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)
697 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
697 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
698 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
698 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
699 \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)
699 \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)
700 \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)
700 \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)
701 \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)
701 \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)
702 \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)
702 \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)
703 \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)
703 \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)
704 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
704 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
705 \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)
705 \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)
706 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
706 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
707 l\r (no-eol) (esc)
707 l\r (no-eol) (esc)
708 \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)
708 \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)
709 \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)
709 \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)
710 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
710 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
711 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
711 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
712
712
713 $ hg unbundle2 < ../rev.hg2
713 $ hg unbundle2 < ../rev.hg2
714 adding changesets
714 adding changesets
715 adding manifests
715 adding manifests
716 adding file changes
716 adding file changes
717 added 0 changesets with 0 changes to 3 files
717 added 0 changesets with 0 changes to 3 files
718 0 unread bytes
718 0 unread bytes
719 addchangegroup return: 1
719 addchangegroup return: 1
720
720
721 with reply
721 with reply
722
722
723 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
723 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
724 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
724 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
725 0 unread bytes
725 0 unread bytes
726 addchangegroup return: 1
726 addchangegroup return: 1
727
727
728 $ cat ../rev-reply.hg2
728 $ cat ../rev-reply.hg2
729 HG20\x00\x00\x00/\x11reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
729 HG20\x00\x00\x00/\x11reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1b\x06output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
730 adding manifests
730 adding manifests
731 adding file changes
731 adding file changes
732 added 0 changesets with 0 changes to 3 files
732 added 0 changesets with 0 changes to 3 files
733 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
733 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
734
734
735 Real world exchange
735 Real world exchange
736 =====================
736 =====================
737
737
738
738
739 clone --pull
739 clone --pull
740
740
741 $ cd ..
741 $ cd ..
742 $ hg clone main other --pull --rev 9520eea781bc
742 $ hg clone main other --pull --rev 9520eea781bc
743 adding changesets
743 adding changesets
744 adding manifests
744 adding manifests
745 adding file changes
745 adding file changes
746 added 2 changesets with 2 changes to 2 files
746 added 2 changesets with 2 changes to 2 files
747 updating to branch default
747 updating to branch default
748 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
748 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
749 $ hg -R other log -G
749 $ hg -R other log -G
750 @ changeset: 1:9520eea781bc
750 @ changeset: 1:9520eea781bc
751 | tag: tip
751 | tag: tip
752 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
752 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
753 | date: Sat Apr 30 15:24:48 2011 +0200
753 | date: Sat Apr 30 15:24:48 2011 +0200
754 | summary: E
754 | summary: E
755 |
755 |
756 o changeset: 0:cd010b8cd998
756 o changeset: 0:cd010b8cd998
757 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
757 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
758 date: Sat Apr 30 15:24:48 2011 +0200
758 date: Sat Apr 30 15:24:48 2011 +0200
759 summary: A
759 summary: A
760
760
761
761
762 pull
762 pull
763
763
764 $ hg -R other pull -r 24b6387c8c8c
764 $ hg -R other pull -r 24b6387c8c8c
765 pulling from $TESTTMP/main (glob)
765 pulling from $TESTTMP/main (glob)
766 searching for changes
766 searching for changes
767 adding changesets
767 adding changesets
768 adding manifests
768 adding manifests
769 adding file changes
769 adding file changes
770 added 1 changesets with 1 changes to 1 files (+1 heads)
770 added 1 changesets with 1 changes to 1 files (+1 heads)
771 (run 'hg heads' to see heads, 'hg merge' to merge)
771 (run 'hg heads' to see heads, 'hg merge' to merge)
772
772
773 push
773 push
774
774
775 $ hg -R main push other --rev eea13746799a
775 $ hg -R main push other --rev eea13746799a
776 pushing to other
776 pushing to other
777 searching for changes
777 searching for changes
778 remote: adding changesets
778 remote: adding changesets
779 remote: adding manifests
779 remote: adding manifests
780 remote: adding file changes
780 remote: adding file changes
781 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
781 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
782
782
783 pull over ssh
783 pull over ssh
784
784
785 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
785 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
786 pulling from ssh://user@dummy/main
786 pulling from ssh://user@dummy/main
787 searching for changes
787 searching for changes
788 adding changesets
788 adding changesets
789 adding manifests
789 adding manifests
790 adding file changes
790 adding file changes
791 added 1 changesets with 1 changes to 1 files (+1 heads)
791 added 1 changesets with 1 changes to 1 files (+1 heads)
792 (run 'hg heads' to see heads, 'hg merge' to merge)
792 (run 'hg heads' to see heads, 'hg merge' to merge)
793
793
794 pull over http
794 pull over http
795
795
796 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
796 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
797 $ cat main.pid >> $DAEMON_PIDS
797 $ cat main.pid >> $DAEMON_PIDS
798
798
799 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
799 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
800 pulling from http://localhost:$HGPORT/
800 pulling from http://localhost:$HGPORT/
801 searching for changes
801 searching for changes
802 adding changesets
802 adding changesets
803 adding manifests
803 adding manifests
804 adding file changes
804 adding file changes
805 added 1 changesets with 1 changes to 1 files (+1 heads)
805 added 1 changesets with 1 changes to 1 files (+1 heads)
806 (run 'hg heads .' to see heads, 'hg merge' to merge)
806 (run 'hg heads .' to see heads, 'hg merge' to merge)
807 $ cat main-error.log
807 $ cat main-error.log
808
808
809 push over ssh
809 push over ssh
810
810
811 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
811 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
812 pushing to ssh://user@dummy/other
812 pushing to ssh://user@dummy/other
813 searching for changes
813 searching for changes
814 remote: adding changesets
814 remote: adding changesets
815 remote: adding manifests
815 remote: adding manifests
816 remote: adding file changes
816 remote: adding file changes
817 remote: added 1 changesets with 1 changes to 1 files
817 remote: added 1 changesets with 1 changes to 1 files
818
818
819 push over http
819 push over http
820
820
821 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
821 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
822 $ cat other.pid >> $DAEMON_PIDS
822 $ cat other.pid >> $DAEMON_PIDS
823
823
824 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
824 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
825 pushing to http://localhost:$HGPORT2/
825 pushing to http://localhost:$HGPORT2/
826 searching for changes
826 searching for changes
827 remote: adding changesets
827 remote: adding changesets
828 remote: adding manifests
828 remote: adding manifests
829 remote: adding file changes
829 remote: adding file changes
830 remote: added 1 changesets with 1 changes to 1 files
830 remote: added 1 changesets with 1 changes to 1 files
831 $ cat other-error.log
831 $ cat other-error.log
832
832
833 Check final content.
833 Check final content.
834
834
835 $ hg -R other log -G
835 $ hg -R other log -G
836 o changeset: 7:32af7686d403
836 o changeset: 7:32af7686d403
837 | tag: tip
837 | tag: tip
838 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
838 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
839 | date: Sat Apr 30 15:24:48 2011 +0200
839 | date: Sat Apr 30 15:24:48 2011 +0200
840 | summary: D
840 | summary: D
841 |
841 |
842 o changeset: 6:5fddd98957c8
842 o changeset: 6:5fddd98957c8
843 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
843 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
844 | date: Sat Apr 30 15:24:48 2011 +0200
844 | date: Sat Apr 30 15:24:48 2011 +0200
845 | summary: C
845 | summary: C
846 |
846 |
847 o changeset: 5:42ccdea3bb16
847 o changeset: 5:42ccdea3bb16
848 | parent: 0:cd010b8cd998
848 | parent: 0:cd010b8cd998
849 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
849 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
850 | date: Sat Apr 30 15:24:48 2011 +0200
850 | date: Sat Apr 30 15:24:48 2011 +0200
851 | summary: B
851 | summary: B
852 |
852 |
853 | o changeset: 4:02de42196ebe
853 | o changeset: 4:02de42196ebe
854 | | parent: 2:24b6387c8c8c
854 | | parent: 2:24b6387c8c8c
855 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
855 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
856 | | date: Sat Apr 30 15:24:48 2011 +0200
856 | | date: Sat Apr 30 15:24:48 2011 +0200
857 | | summary: H
857 | | summary: H
858 | |
858 | |
859 | | o changeset: 3:eea13746799a
859 | | o changeset: 3:eea13746799a
860 | |/| parent: 2:24b6387c8c8c
860 | |/| parent: 2:24b6387c8c8c
861 | | | parent: 1:9520eea781bc
861 | | | parent: 1:9520eea781bc
862 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
862 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
863 | | | date: Sat Apr 30 15:24:48 2011 +0200
863 | | | date: Sat Apr 30 15:24:48 2011 +0200
864 | | | summary: G
864 | | | summary: G
865 | | |
865 | | |
866 | o | changeset: 2:24b6387c8c8c
866 | o | changeset: 2:24b6387c8c8c
867 |/ / parent: 0:cd010b8cd998
867 |/ / parent: 0:cd010b8cd998
868 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
868 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
869 | | date: Sat Apr 30 15:24:48 2011 +0200
869 | | date: Sat Apr 30 15:24:48 2011 +0200
870 | | summary: F
870 | | summary: F
871 | |
871 | |
872 | @ changeset: 1:9520eea781bc
872 | @ changeset: 1:9520eea781bc
873 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
873 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
874 | date: Sat Apr 30 15:24:48 2011 +0200
874 | date: Sat Apr 30 15:24:48 2011 +0200
875 | summary: E
875 | summary: E
876 |
876 |
877 o changeset: 0:cd010b8cd998
877 o changeset: 0:cd010b8cd998
878 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
878 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
879 date: Sat Apr 30 15:24:48 2011 +0200
879 date: Sat Apr 30 15:24:48 2011 +0200
880 summary: A
880 summary: A
881
881
General Comments 0
You need to be logged in to leave comments. Login now