##// END OF EJS Templates
bundle2: gracefully handle UnknownPartError during unbundle...
Pierre-Yves David -
r21183:4345274a stable
parent child Browse files
Show More
@@ -1,755 +1,761 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 = 'HG2X'
154 _magicstring = 'HG2X'
155
155
156 _fstreamparamsize = '>H'
156 _fstreamparamsize = '>H'
157 _fpartheadersize = '>H'
157 _fpartheadersize = '>H'
158 _fparttypesize = '>B'
158 _fparttypesize = '>B'
159 _fpartid = '>I'
159 _fpartid = '>I'
160 _fpayloadsize = '>I'
160 _fpayloadsize = '>I'
161 _fpartparamcount = '>BB'
161 _fpartparamcount = '>BB'
162
162
163 preferedchunksize = 4096
163 preferedchunksize = 4096
164
164
165 def _makefpartparamsizes(nbparams):
165 def _makefpartparamsizes(nbparams):
166 """return a struct format to read part parameter sizes
166 """return a struct format to read part parameter sizes
167
167
168 The number parameters is variable so we need to build that format
168 The number parameters is variable so we need to build that format
169 dynamically.
169 dynamically.
170 """
170 """
171 return '>'+('BB'*nbparams)
171 return '>'+('BB'*nbparams)
172
172
173 class UnknownPartError(KeyError):
173 class UnknownPartError(KeyError):
174 """error raised when no handler is found for a Mandatory part"""
174 """error raised when no handler is found for a Mandatory part"""
175 pass
175 pass
176
176
177 parthandlermapping = {}
177 parthandlermapping = {}
178
178
179 def parthandler(parttype):
179 def parthandler(parttype):
180 """decorator that register a function as a bundle2 part handler
180 """decorator that register a function as a bundle2 part handler
181
181
182 eg::
182 eg::
183
183
184 @parthandler('myparttype')
184 @parthandler('myparttype')
185 def myparttypehandler(...):
185 def myparttypehandler(...):
186 '''process a part of type "my part".'''
186 '''process a part of type "my part".'''
187 ...
187 ...
188 """
188 """
189 def _decorator(func):
189 def _decorator(func):
190 lparttype = parttype.lower() # enforce lower case matching.
190 lparttype = parttype.lower() # enforce lower case matching.
191 assert lparttype not in parthandlermapping
191 assert lparttype not in parthandlermapping
192 parthandlermapping[lparttype] = func
192 parthandlermapping[lparttype] = func
193 return func
193 return func
194 return _decorator
194 return _decorator
195
195
196 class unbundlerecords(object):
196 class unbundlerecords(object):
197 """keep record of what happens during and unbundle
197 """keep record of what happens during and unbundle
198
198
199 New records are added using `records.add('cat', obj)`. Where 'cat' is a
199 New records are added using `records.add('cat', obj)`. Where 'cat' is a
200 category of record and obj is an arbitrary object.
200 category of record and obj is an arbitrary object.
201
201
202 `records['cat']` will return all entries of this category 'cat'.
202 `records['cat']` will return all entries of this category 'cat'.
203
203
204 Iterating on the object itself will yield `('category', obj)` tuples
204 Iterating on the object itself will yield `('category', obj)` tuples
205 for all entries.
205 for all entries.
206
206
207 All iterations happens in chronological order.
207 All iterations happens in chronological order.
208 """
208 """
209
209
210 def __init__(self):
210 def __init__(self):
211 self._categories = {}
211 self._categories = {}
212 self._sequences = []
212 self._sequences = []
213 self._replies = {}
213 self._replies = {}
214
214
215 def add(self, category, entry, inreplyto=None):
215 def add(self, category, entry, inreplyto=None):
216 """add a new record of a given category.
216 """add a new record of a given category.
217
217
218 The entry can then be retrieved in the list returned by
218 The entry can then be retrieved in the list returned by
219 self['category']."""
219 self['category']."""
220 self._categories.setdefault(category, []).append(entry)
220 self._categories.setdefault(category, []).append(entry)
221 self._sequences.append((category, entry))
221 self._sequences.append((category, entry))
222 if inreplyto is not None:
222 if inreplyto is not None:
223 self.getreplies(inreplyto).add(category, entry)
223 self.getreplies(inreplyto).add(category, entry)
224
224
225 def getreplies(self, partid):
225 def getreplies(self, partid):
226 """get the subrecords that replies to a specific part"""
226 """get the subrecords that replies to a specific part"""
227 return self._replies.setdefault(partid, unbundlerecords())
227 return self._replies.setdefault(partid, unbundlerecords())
228
228
229 def __getitem__(self, cat):
229 def __getitem__(self, cat):
230 return tuple(self._categories.get(cat, ()))
230 return tuple(self._categories.get(cat, ()))
231
231
232 def __iter__(self):
232 def __iter__(self):
233 return iter(self._sequences)
233 return iter(self._sequences)
234
234
235 def __len__(self):
235 def __len__(self):
236 return len(self._sequences)
236 return len(self._sequences)
237
237
238 def __nonzero__(self):
238 def __nonzero__(self):
239 return bool(self._sequences)
239 return bool(self._sequences)
240
240
241 class bundleoperation(object):
241 class bundleoperation(object):
242 """an object that represents a single bundling process
242 """an object that represents a single bundling process
243
243
244 Its purpose is to carry unbundle-related objects and states.
244 Its purpose is to carry unbundle-related objects and states.
245
245
246 A new object should be created at the beginning of each bundle processing.
246 A new object should be created at the beginning of each bundle processing.
247 The object is to be returned by the processing function.
247 The object is to be returned by the processing function.
248
248
249 The object has very little content now it will ultimately contain:
249 The object has very little content now it will ultimately contain:
250 * an access to the repo the bundle is applied to,
250 * an access to the repo the bundle is applied to,
251 * a ui object,
251 * a ui object,
252 * a way to retrieve a transaction to add changes to the repo,
252 * a way to retrieve a transaction to add changes to the repo,
253 * a way to record the result of processing each part,
253 * a way to record the result of processing each part,
254 * a way to construct a bundle response when applicable.
254 * a way to construct a bundle response when applicable.
255 """
255 """
256
256
257 def __init__(self, repo, transactiongetter):
257 def __init__(self, repo, transactiongetter):
258 self.repo = repo
258 self.repo = repo
259 self.ui = repo.ui
259 self.ui = repo.ui
260 self.records = unbundlerecords()
260 self.records = unbundlerecords()
261 self.gettransaction = transactiongetter
261 self.gettransaction = transactiongetter
262 self.reply = None
262 self.reply = None
263
263
264 class TransactionUnavailable(RuntimeError):
264 class TransactionUnavailable(RuntimeError):
265 pass
265 pass
266
266
267 def _notransaction():
267 def _notransaction():
268 """default method to get a transaction while processing a bundle
268 """default method to get a transaction while processing a bundle
269
269
270 Raise an exception to highlight the fact that no transaction was expected
270 Raise an exception to highlight the fact that no transaction was expected
271 to be created"""
271 to be created"""
272 raise TransactionUnavailable()
272 raise TransactionUnavailable()
273
273
274 def processbundle(repo, unbundler, transactiongetter=_notransaction):
274 def processbundle(repo, unbundler, transactiongetter=_notransaction):
275 """This function process a bundle, apply effect to/from a repo
275 """This function process a bundle, apply effect to/from a repo
276
276
277 It iterates over each part then searches for and uses the proper handling
277 It iterates over each part then searches for and uses the proper handling
278 code to process the part. Parts are processed in order.
278 code to process the part. Parts are processed in order.
279
279
280 This is very early version of this function that will be strongly reworked
280 This is very early version of this function that will be strongly reworked
281 before final usage.
281 before final usage.
282
282
283 Unknown Mandatory part will abort the process.
283 Unknown Mandatory part will abort the process.
284 """
284 """
285 op = bundleoperation(repo, transactiongetter)
285 op = bundleoperation(repo, transactiongetter)
286 # todo:
286 # todo:
287 # - replace this is a init function soon.
287 # - replace this is a init function soon.
288 # - exception catching
288 # - exception catching
289 unbundler.params
289 unbundler.params
290 iterparts = unbundler.iterparts()
290 iterparts = unbundler.iterparts()
291 part = None
291 part = None
292 try:
292 try:
293 for part in iterparts:
293 for part in iterparts:
294 parttype = part.type
294 parttype = part.type
295 # part key are matched lower case
295 # part key are matched lower case
296 key = parttype.lower()
296 key = parttype.lower()
297 try:
297 try:
298 handler = parthandlermapping[key]
298 handler = parthandlermapping[key]
299 op.ui.debug('found a handler for part %r\n' % parttype)
299 op.ui.debug('found a handler for part %r\n' % parttype)
300 except KeyError:
300 except KeyError:
301 if key != parttype: # mandatory parts
301 if key != parttype: # mandatory parts
302 # todo:
302 # todo:
303 # - use a more precise exception
303 # - use a more precise exception
304 raise UnknownPartError(key)
304 raise UnknownPartError(key)
305 op.ui.debug('ignoring unknown advisory part %r\n' % key)
305 op.ui.debug('ignoring unknown advisory part %r\n' % key)
306 # consuming the part
306 # consuming the part
307 part.read()
307 part.read()
308 continue
308 continue
309
309
310 # handler is called outside the above try block so that we don't
310 # handler is called outside the above try block so that we don't
311 # risk catching KeyErrors from anything other than the
311 # risk catching KeyErrors from anything other than the
312 # parthandlermapping lookup (any KeyError raised by handler()
312 # parthandlermapping lookup (any KeyError raised by handler()
313 # itself represents a defect of a different variety).
313 # itself represents a defect of a different variety).
314 output = None
314 output = None
315 if op.reply is not None:
315 if op.reply is not None:
316 op.ui.pushbuffer(error=True)
316 op.ui.pushbuffer(error=True)
317 output = ''
317 output = ''
318 try:
318 try:
319 handler(op, part)
319 handler(op, part)
320 finally:
320 finally:
321 if output is not None:
321 if output is not None:
322 output = op.ui.popbuffer()
322 output = op.ui.popbuffer()
323 if output:
323 if output:
324 outpart = bundlepart('b2x:output',
324 outpart = bundlepart('b2x:output',
325 advisoryparams=[('in-reply-to',
325 advisoryparams=[('in-reply-to',
326 str(part.id))],
326 str(part.id))],
327 data=output)
327 data=output)
328 op.reply.addpart(outpart)
328 op.reply.addpart(outpart)
329 part.read()
329 part.read()
330 except Exception, exc:
330 except Exception, exc:
331 if part is not None:
331 if part is not None:
332 # consume the bundle content
332 # consume the bundle content
333 part.read()
333 part.read()
334 for part in iterparts:
334 for part in iterparts:
335 # consume the bundle content
335 # consume the bundle content
336 part.read()
336 part.read()
337 # Small hack to let caller code distinguish exceptions from bundle2
337 # Small hack to let caller code distinguish exceptions from bundle2
338 # processing fron the ones from bundle1 processing. This is mostly
338 # processing fron the ones from bundle1 processing. This is mostly
339 # needed to handle different return codes to unbundle according to the
339 # needed to handle different return codes to unbundle according to the
340 # type of bundle. We should probably clean up or drop this return code
340 # type of bundle. We should probably clean up or drop this return code
341 # craziness in a future version.
341 # craziness in a future version.
342 exc.duringunbundle2 = True
342 exc.duringunbundle2 = True
343 raise
343 raise
344 return op
344 return op
345
345
346 def decodecaps(blob):
346 def decodecaps(blob):
347 """decode a bundle2 caps bytes blob into a dictionnary
347 """decode a bundle2 caps bytes blob into a dictionnary
348
348
349 The blob is a list of capabilities (one per line)
349 The blob is a list of capabilities (one per line)
350 Capabilities may have values using a line of the form::
350 Capabilities may have values using a line of the form::
351
351
352 capability=value1,value2,value3
352 capability=value1,value2,value3
353
353
354 The values are always a list."""
354 The values are always a list."""
355 caps = {}
355 caps = {}
356 for line in blob.splitlines():
356 for line in blob.splitlines():
357 if not line:
357 if not line:
358 continue
358 continue
359 if '=' not in line:
359 if '=' not in line:
360 key, vals = line, ()
360 key, vals = line, ()
361 else:
361 else:
362 key, vals = line.split('=', 1)
362 key, vals = line.split('=', 1)
363 vals = vals.split(',')
363 vals = vals.split(',')
364 key = urllib.unquote(key)
364 key = urllib.unquote(key)
365 vals = [urllib.unquote(v) for v in vals]
365 vals = [urllib.unquote(v) for v in vals]
366 caps[key] = vals
366 caps[key] = vals
367 return caps
367 return caps
368
368
369 def encodecaps(caps):
369 def encodecaps(caps):
370 """encode a bundle2 caps dictionary into a bytes blob"""
370 """encode a bundle2 caps dictionary into a bytes blob"""
371 chunks = []
371 chunks = []
372 for ca in sorted(caps):
372 for ca in sorted(caps):
373 vals = caps[ca]
373 vals = caps[ca]
374 ca = urllib.quote(ca)
374 ca = urllib.quote(ca)
375 vals = [urllib.quote(v) for v in vals]
375 vals = [urllib.quote(v) for v in vals]
376 if vals:
376 if vals:
377 ca = "%s=%s" % (ca, ','.join(vals))
377 ca = "%s=%s" % (ca, ','.join(vals))
378 chunks.append(ca)
378 chunks.append(ca)
379 return '\n'.join(chunks)
379 return '\n'.join(chunks)
380
380
381 class bundle20(object):
381 class bundle20(object):
382 """represent an outgoing bundle2 container
382 """represent an outgoing bundle2 container
383
383
384 Use the `addparam` method to add stream level parameter. and `addpart` to
384 Use the `addparam` method to add stream level parameter. and `addpart` to
385 populate it. Then call `getchunks` to retrieve all the binary chunks of
385 populate it. Then call `getchunks` to retrieve all the binary chunks of
386 data that compose the bundle2 container."""
386 data that compose the bundle2 container."""
387
387
388 def __init__(self, ui, capabilities=()):
388 def __init__(self, ui, capabilities=()):
389 self.ui = ui
389 self.ui = ui
390 self._params = []
390 self._params = []
391 self._parts = []
391 self._parts = []
392 self.capabilities = dict(capabilities)
392 self.capabilities = dict(capabilities)
393
393
394 def addparam(self, name, value=None):
394 def addparam(self, name, value=None):
395 """add a stream level parameter"""
395 """add a stream level parameter"""
396 if not name:
396 if not name:
397 raise ValueError('empty parameter name')
397 raise ValueError('empty parameter name')
398 if name[0] not in string.letters:
398 if name[0] not in string.letters:
399 raise ValueError('non letter first character: %r' % name)
399 raise ValueError('non letter first character: %r' % name)
400 self._params.append((name, value))
400 self._params.append((name, value))
401
401
402 def addpart(self, part):
402 def addpart(self, part):
403 """add a new part to the bundle2 container
403 """add a new part to the bundle2 container
404
404
405 Parts contains the actual applicative payload."""
405 Parts contains the actual applicative payload."""
406 assert part.id is None
406 assert part.id is None
407 part.id = len(self._parts) # very cheap counter
407 part.id = len(self._parts) # very cheap counter
408 self._parts.append(part)
408 self._parts.append(part)
409
409
410 def getchunks(self):
410 def getchunks(self):
411 self.ui.debug('start emission of %s stream\n' % _magicstring)
411 self.ui.debug('start emission of %s stream\n' % _magicstring)
412 yield _magicstring
412 yield _magicstring
413 param = self._paramchunk()
413 param = self._paramchunk()
414 self.ui.debug('bundle parameter: %s\n' % param)
414 self.ui.debug('bundle parameter: %s\n' % param)
415 yield _pack(_fstreamparamsize, len(param))
415 yield _pack(_fstreamparamsize, len(param))
416 if param:
416 if param:
417 yield param
417 yield param
418
418
419 self.ui.debug('start of parts\n')
419 self.ui.debug('start of parts\n')
420 for part in self._parts:
420 for part in self._parts:
421 self.ui.debug('bundle part: "%s"\n' % part.type)
421 self.ui.debug('bundle part: "%s"\n' % part.type)
422 for chunk in part.getchunks():
422 for chunk in part.getchunks():
423 yield chunk
423 yield chunk
424 self.ui.debug('end of bundle\n')
424 self.ui.debug('end of bundle\n')
425 yield '\0\0'
425 yield '\0\0'
426
426
427 def _paramchunk(self):
427 def _paramchunk(self):
428 """return a encoded version of all stream parameters"""
428 """return a encoded version of all stream parameters"""
429 blocks = []
429 blocks = []
430 for par, value in self._params:
430 for par, value in self._params:
431 par = urllib.quote(par)
431 par = urllib.quote(par)
432 if value is not None:
432 if value is not None:
433 value = urllib.quote(value)
433 value = urllib.quote(value)
434 par = '%s=%s' % (par, value)
434 par = '%s=%s' % (par, value)
435 blocks.append(par)
435 blocks.append(par)
436 return ' '.join(blocks)
436 return ' '.join(blocks)
437
437
438 class unpackermixin(object):
438 class unpackermixin(object):
439 """A mixin to extract bytes and struct data from a stream"""
439 """A mixin to extract bytes and struct data from a stream"""
440
440
441 def __init__(self, fp):
441 def __init__(self, fp):
442 self._fp = fp
442 self._fp = fp
443
443
444 def _unpack(self, format):
444 def _unpack(self, format):
445 """unpack this struct format from the stream"""
445 """unpack this struct format from the stream"""
446 data = self._readexact(struct.calcsize(format))
446 data = self._readexact(struct.calcsize(format))
447 return _unpack(format, data)
447 return _unpack(format, data)
448
448
449 def _readexact(self, size):
449 def _readexact(self, size):
450 """read exactly <size> bytes from the stream"""
450 """read exactly <size> bytes from the stream"""
451 return changegroup.readexactly(self._fp, size)
451 return changegroup.readexactly(self._fp, size)
452
452
453
453
454 class unbundle20(unpackermixin):
454 class unbundle20(unpackermixin):
455 """interpret a bundle2 stream
455 """interpret a bundle2 stream
456
456
457 This class is fed with a binary stream and yields parts through its
457 This class is fed with a binary stream and yields parts through its
458 `iterparts` methods."""
458 `iterparts` methods."""
459
459
460 def __init__(self, ui, fp, header=None):
460 def __init__(self, ui, fp, header=None):
461 """If header is specified, we do not read it out of the stream."""
461 """If header is specified, we do not read it out of the stream."""
462 self.ui = ui
462 self.ui = ui
463 super(unbundle20, self).__init__(fp)
463 super(unbundle20, self).__init__(fp)
464 if header is None:
464 if header is None:
465 header = self._readexact(4)
465 header = self._readexact(4)
466 magic, version = header[0:2], header[2:4]
466 magic, version = header[0:2], header[2:4]
467 if magic != 'HG':
467 if magic != 'HG':
468 raise util.Abort(_('not a Mercurial bundle'))
468 raise util.Abort(_('not a Mercurial bundle'))
469 if version != '2X':
469 if version != '2X':
470 raise util.Abort(_('unknown bundle version %s') % version)
470 raise util.Abort(_('unknown bundle version %s') % version)
471 self.ui.debug('start processing of %s stream\n' % header)
471 self.ui.debug('start processing of %s stream\n' % header)
472
472
473 @util.propertycache
473 @util.propertycache
474 def params(self):
474 def params(self):
475 """dictionary of stream level parameters"""
475 """dictionary of stream level parameters"""
476 self.ui.debug('reading bundle2 stream parameters\n')
476 self.ui.debug('reading bundle2 stream parameters\n')
477 params = {}
477 params = {}
478 paramssize = self._unpack(_fstreamparamsize)[0]
478 paramssize = self._unpack(_fstreamparamsize)[0]
479 if paramssize:
479 if paramssize:
480 for p in self._readexact(paramssize).split(' '):
480 for p in self._readexact(paramssize).split(' '):
481 p = p.split('=', 1)
481 p = p.split('=', 1)
482 p = [urllib.unquote(i) for i in p]
482 p = [urllib.unquote(i) for i in p]
483 if len(p) < 2:
483 if len(p) < 2:
484 p.append(None)
484 p.append(None)
485 self._processparam(*p)
485 self._processparam(*p)
486 params[p[0]] = p[1]
486 params[p[0]] = p[1]
487 return params
487 return params
488
488
489 def _processparam(self, name, value):
489 def _processparam(self, name, value):
490 """process a parameter, applying its effect if needed
490 """process a parameter, applying its effect if needed
491
491
492 Parameter starting with a lower case letter are advisory and will be
492 Parameter starting with a lower case letter are advisory and will be
493 ignored when unknown. Those starting with an upper case letter are
493 ignored when unknown. Those starting with an upper case letter are
494 mandatory and will this function will raise a KeyError when unknown.
494 mandatory and will this function will raise a KeyError when unknown.
495
495
496 Note: no option are currently supported. Any input will be either
496 Note: no option are currently supported. Any input will be either
497 ignored or failing.
497 ignored or failing.
498 """
498 """
499 if not name:
499 if not name:
500 raise ValueError('empty parameter name')
500 raise ValueError('empty parameter name')
501 if name[0] not in string.letters:
501 if name[0] not in string.letters:
502 raise ValueError('non letter first character: %r' % name)
502 raise ValueError('non letter first character: %r' % name)
503 # Some logic will be later added here to try to process the option for
503 # Some logic will be later added here to try to process the option for
504 # a dict of known parameter.
504 # a dict of known parameter.
505 if name[0].islower():
505 if name[0].islower():
506 self.ui.debug("ignoring unknown parameter %r\n" % name)
506 self.ui.debug("ignoring unknown parameter %r\n" % name)
507 else:
507 else:
508 raise KeyError(name)
508 raise KeyError(name)
509
509
510
510
511 def iterparts(self):
511 def iterparts(self):
512 """yield all parts contained in the stream"""
512 """yield all parts contained in the stream"""
513 # make sure param have been loaded
513 # make sure param have been loaded
514 self.params
514 self.params
515 self.ui.debug('start extraction of bundle2 parts\n')
515 self.ui.debug('start extraction of bundle2 parts\n')
516 headerblock = self._readpartheader()
516 headerblock = self._readpartheader()
517 while headerblock is not None:
517 while headerblock is not None:
518 part = unbundlepart(self.ui, headerblock, self._fp)
518 part = unbundlepart(self.ui, headerblock, self._fp)
519 yield part
519 yield part
520 headerblock = self._readpartheader()
520 headerblock = self._readpartheader()
521 self.ui.debug('end of bundle2 stream\n')
521 self.ui.debug('end of bundle2 stream\n')
522
522
523 def _readpartheader(self):
523 def _readpartheader(self):
524 """reads a part header size and return the bytes blob
524 """reads a part header size and return the bytes blob
525
525
526 returns None if empty"""
526 returns None if empty"""
527 headersize = self._unpack(_fpartheadersize)[0]
527 headersize = self._unpack(_fpartheadersize)[0]
528 self.ui.debug('part header size: %i\n' % headersize)
528 self.ui.debug('part header size: %i\n' % headersize)
529 if headersize:
529 if headersize:
530 return self._readexact(headersize)
530 return self._readexact(headersize)
531 return None
531 return None
532
532
533
533
534 class bundlepart(object):
534 class bundlepart(object):
535 """A bundle2 part contains application level payload
535 """A bundle2 part contains application level payload
536
536
537 The part `type` is used to route the part to the application level
537 The part `type` is used to route the part to the application level
538 handler.
538 handler.
539 """
539 """
540
540
541 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
541 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
542 data=''):
542 data=''):
543 self.id = None
543 self.id = None
544 self.type = parttype
544 self.type = parttype
545 self.data = data
545 self.data = data
546 self.mandatoryparams = mandatoryparams
546 self.mandatoryparams = mandatoryparams
547 self.advisoryparams = advisoryparams
547 self.advisoryparams = advisoryparams
548
548
549 def getchunks(self):
549 def getchunks(self):
550 #### header
550 #### header
551 ## parttype
551 ## parttype
552 header = [_pack(_fparttypesize, len(self.type)),
552 header = [_pack(_fparttypesize, len(self.type)),
553 self.type, _pack(_fpartid, self.id),
553 self.type, _pack(_fpartid, self.id),
554 ]
554 ]
555 ## parameters
555 ## parameters
556 # count
556 # count
557 manpar = self.mandatoryparams
557 manpar = self.mandatoryparams
558 advpar = self.advisoryparams
558 advpar = self.advisoryparams
559 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
559 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
560 # size
560 # size
561 parsizes = []
561 parsizes = []
562 for key, value in manpar:
562 for key, value in manpar:
563 parsizes.append(len(key))
563 parsizes.append(len(key))
564 parsizes.append(len(value))
564 parsizes.append(len(value))
565 for key, value in advpar:
565 for key, value in advpar:
566 parsizes.append(len(key))
566 parsizes.append(len(key))
567 parsizes.append(len(value))
567 parsizes.append(len(value))
568 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
568 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
569 header.append(paramsizes)
569 header.append(paramsizes)
570 # key, value
570 # key, value
571 for key, value in manpar:
571 for key, value in manpar:
572 header.append(key)
572 header.append(key)
573 header.append(value)
573 header.append(value)
574 for key, value in advpar:
574 for key, value in advpar:
575 header.append(key)
575 header.append(key)
576 header.append(value)
576 header.append(value)
577 ## finalize header
577 ## finalize header
578 headerchunk = ''.join(header)
578 headerchunk = ''.join(header)
579 yield _pack(_fpartheadersize, len(headerchunk))
579 yield _pack(_fpartheadersize, len(headerchunk))
580 yield headerchunk
580 yield headerchunk
581 ## payload
581 ## payload
582 for chunk in self._payloadchunks():
582 for chunk in self._payloadchunks():
583 yield _pack(_fpayloadsize, len(chunk))
583 yield _pack(_fpayloadsize, len(chunk))
584 yield chunk
584 yield chunk
585 # end of payload
585 # end of payload
586 yield _pack(_fpayloadsize, 0)
586 yield _pack(_fpayloadsize, 0)
587
587
588 def _payloadchunks(self):
588 def _payloadchunks(self):
589 """yield chunks of a the part payload
589 """yield chunks of a the part payload
590
590
591 Exists to handle the different methods to provide data to a part."""
591 Exists to handle the different methods to provide data to a part."""
592 # we only support fixed size data now.
592 # we only support fixed size data now.
593 # This will be improved in the future.
593 # This will be improved in the future.
594 if util.safehasattr(self.data, 'next'):
594 if util.safehasattr(self.data, 'next'):
595 buff = util.chunkbuffer(self.data)
595 buff = util.chunkbuffer(self.data)
596 chunk = buff.read(preferedchunksize)
596 chunk = buff.read(preferedchunksize)
597 while chunk:
597 while chunk:
598 yield chunk
598 yield chunk
599 chunk = buff.read(preferedchunksize)
599 chunk = buff.read(preferedchunksize)
600 elif len(self.data):
600 elif len(self.data):
601 yield self.data
601 yield self.data
602
602
603 class unbundlepart(unpackermixin):
603 class unbundlepart(unpackermixin):
604 """a bundle part read from a bundle"""
604 """a bundle part read from a bundle"""
605
605
606 def __init__(self, ui, header, fp):
606 def __init__(self, ui, header, fp):
607 super(unbundlepart, self).__init__(fp)
607 super(unbundlepart, self).__init__(fp)
608 self.ui = ui
608 self.ui = ui
609 # unbundle state attr
609 # unbundle state attr
610 self._headerdata = header
610 self._headerdata = header
611 self._headeroffset = 0
611 self._headeroffset = 0
612 self._initialized = False
612 self._initialized = False
613 self.consumed = False
613 self.consumed = False
614 # part data
614 # part data
615 self.id = None
615 self.id = None
616 self.type = None
616 self.type = None
617 self.mandatoryparams = None
617 self.mandatoryparams = None
618 self.advisoryparams = None
618 self.advisoryparams = None
619 self._payloadstream = None
619 self._payloadstream = None
620 self._readheader()
620 self._readheader()
621
621
622 def _fromheader(self, size):
622 def _fromheader(self, size):
623 """return the next <size> byte from the header"""
623 """return the next <size> byte from the header"""
624 offset = self._headeroffset
624 offset = self._headeroffset
625 data = self._headerdata[offset:(offset + size)]
625 data = self._headerdata[offset:(offset + size)]
626 self._headeroffset = offset + size
626 self._headeroffset = offset + size
627 return data
627 return data
628
628
629 def _unpackheader(self, format):
629 def _unpackheader(self, format):
630 """read given format from header
630 """read given format from header
631
631
632 This automatically compute the size of the format to read."""
632 This automatically compute the size of the format to read."""
633 data = self._fromheader(struct.calcsize(format))
633 data = self._fromheader(struct.calcsize(format))
634 return _unpack(format, data)
634 return _unpack(format, data)
635
635
636 def _readheader(self):
636 def _readheader(self):
637 """read the header and setup the object"""
637 """read the header and setup the object"""
638 typesize = self._unpackheader(_fparttypesize)[0]
638 typesize = self._unpackheader(_fparttypesize)[0]
639 self.type = self._fromheader(typesize)
639 self.type = self._fromheader(typesize)
640 self.ui.debug('part type: "%s"\n' % self.type)
640 self.ui.debug('part type: "%s"\n' % self.type)
641 self.id = self._unpackheader(_fpartid)[0]
641 self.id = self._unpackheader(_fpartid)[0]
642 self.ui.debug('part id: "%s"\n' % self.id)
642 self.ui.debug('part id: "%s"\n' % self.id)
643 ## reading parameters
643 ## reading parameters
644 # param count
644 # param count
645 mancount, advcount = self._unpackheader(_fpartparamcount)
645 mancount, advcount = self._unpackheader(_fpartparamcount)
646 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
646 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
647 # param size
647 # param size
648 fparamsizes = _makefpartparamsizes(mancount + advcount)
648 fparamsizes = _makefpartparamsizes(mancount + advcount)
649 paramsizes = self._unpackheader(fparamsizes)
649 paramsizes = self._unpackheader(fparamsizes)
650 # make it a list of couple again
650 # make it a list of couple again
651 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
651 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
652 # split mandatory from advisory
652 # split mandatory from advisory
653 mansizes = paramsizes[:mancount]
653 mansizes = paramsizes[:mancount]
654 advsizes = paramsizes[mancount:]
654 advsizes = paramsizes[mancount:]
655 # retrive param value
655 # retrive param value
656 manparams = []
656 manparams = []
657 for key, value in mansizes:
657 for key, value in mansizes:
658 manparams.append((self._fromheader(key), self._fromheader(value)))
658 manparams.append((self._fromheader(key), self._fromheader(value)))
659 advparams = []
659 advparams = []
660 for key, value in advsizes:
660 for key, value in advsizes:
661 advparams.append((self._fromheader(key), self._fromheader(value)))
661 advparams.append((self._fromheader(key), self._fromheader(value)))
662 self.mandatoryparams = manparams
662 self.mandatoryparams = manparams
663 self.advisoryparams = advparams
663 self.advisoryparams = advparams
664 ## part payload
664 ## part payload
665 def payloadchunks():
665 def payloadchunks():
666 payloadsize = self._unpack(_fpayloadsize)[0]
666 payloadsize = self._unpack(_fpayloadsize)[0]
667 self.ui.debug('payload chunk size: %i\n' % payloadsize)
667 self.ui.debug('payload chunk size: %i\n' % payloadsize)
668 while payloadsize:
668 while payloadsize:
669 yield self._readexact(payloadsize)
669 yield self._readexact(payloadsize)
670 payloadsize = self._unpack(_fpayloadsize)[0]
670 payloadsize = self._unpack(_fpayloadsize)[0]
671 self.ui.debug('payload chunk size: %i\n' % payloadsize)
671 self.ui.debug('payload chunk size: %i\n' % payloadsize)
672 self._payloadstream = util.chunkbuffer(payloadchunks())
672 self._payloadstream = util.chunkbuffer(payloadchunks())
673 # we read the data, tell it
673 # we read the data, tell it
674 self._initialized = True
674 self._initialized = True
675
675
676 def read(self, size=None):
676 def read(self, size=None):
677 """read payload data"""
677 """read payload data"""
678 if not self._initialized:
678 if not self._initialized:
679 self._readheader()
679 self._readheader()
680 if size is None:
680 if size is None:
681 data = self._payloadstream.read()
681 data = self._payloadstream.read()
682 else:
682 else:
683 data = self._payloadstream.read(size)
683 data = self._payloadstream.read(size)
684 if size is None or len(data) < size:
684 if size is None or len(data) < size:
685 self.consumed = True
685 self.consumed = True
686 return data
686 return data
687
687
688
688
689 @parthandler('b2x:changegroup')
689 @parthandler('b2x:changegroup')
690 def handlechangegroup(op, inpart):
690 def handlechangegroup(op, inpart):
691 """apply a changegroup part on the repo
691 """apply a changegroup part on the repo
692
692
693 This is a very early implementation that will massive rework before being
693 This is a very early implementation that will massive rework before being
694 inflicted to any end-user.
694 inflicted to any end-user.
695 """
695 """
696 # Make sure we trigger a transaction creation
696 # Make sure we trigger a transaction creation
697 #
697 #
698 # The addchangegroup function will get a transaction object by itself, but
698 # The addchangegroup function will get a transaction object by itself, but
699 # we need to make sure we trigger the creation of a transaction object used
699 # we need to make sure we trigger the creation of a transaction object used
700 # for the whole processing scope.
700 # for the whole processing scope.
701 op.gettransaction()
701 op.gettransaction()
702 cg = changegroup.unbundle10(inpart, 'UN')
702 cg = changegroup.unbundle10(inpart, 'UN')
703 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
703 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
704 op.records.add('changegroup', {'return': ret})
704 op.records.add('changegroup', {'return': ret})
705 if op.reply is not None:
705 if op.reply is not None:
706 # This is definitly not the final form of this
706 # This is definitly not the final form of this
707 # return. But one need to start somewhere.
707 # return. But one need to start somewhere.
708 part = bundlepart('b2x:reply:changegroup', (),
708 part = bundlepart('b2x:reply:changegroup', (),
709 [('in-reply-to', str(inpart.id)),
709 [('in-reply-to', str(inpart.id)),
710 ('return', '%i' % ret)])
710 ('return', '%i' % ret)])
711 op.reply.addpart(part)
711 op.reply.addpart(part)
712 assert not inpart.read()
712 assert not inpart.read()
713
713
714 @parthandler('b2x:reply:changegroup')
714 @parthandler('b2x:reply:changegroup')
715 def handlechangegroup(op, inpart):
715 def handlechangegroup(op, inpart):
716 p = dict(inpart.advisoryparams)
716 p = dict(inpart.advisoryparams)
717 ret = int(p['return'])
717 ret = int(p['return'])
718 op.records.add('changegroup', {'return': ret}, int(p['in-reply-to']))
718 op.records.add('changegroup', {'return': ret}, int(p['in-reply-to']))
719
719
720 @parthandler('b2x:check:heads')
720 @parthandler('b2x:check:heads')
721 def handlechangegroup(op, inpart):
721 def handlechangegroup(op, inpart):
722 """check that head of the repo did not change
722 """check that head of the repo did not change
723
723
724 This is used to detect a push race when using unbundle.
724 This is used to detect a push race when using unbundle.
725 This replaces the "heads" argument of unbundle."""
725 This replaces the "heads" argument of unbundle."""
726 h = inpart.read(20)
726 h = inpart.read(20)
727 heads = []
727 heads = []
728 while len(h) == 20:
728 while len(h) == 20:
729 heads.append(h)
729 heads.append(h)
730 h = inpart.read(20)
730 h = inpart.read(20)
731 assert not h
731 assert not h
732 if heads != op.repo.heads():
732 if heads != op.repo.heads():
733 raise exchange.PushRaced()
733 raise exchange.PushRaced()
734
734
735 @parthandler('b2x:output')
735 @parthandler('b2x:output')
736 def handleoutput(op, inpart):
736 def handleoutput(op, inpart):
737 """forward output captured on the server to the client"""
737 """forward output captured on the server to the client"""
738 for line in inpart.read().splitlines():
738 for line in inpart.read().splitlines():
739 op.ui.write(('remote: %s\n' % line))
739 op.ui.write(('remote: %s\n' % line))
740
740
741 @parthandler('b2x:replycaps')
741 @parthandler('b2x:replycaps')
742 def handlereplycaps(op, inpart):
742 def handlereplycaps(op, inpart):
743 """Notify that a reply bundle should be created
743 """Notify that a reply bundle should be created
744
744
745 The payload contains the capabilities information for the reply"""
745 The payload contains the capabilities information for the reply"""
746 caps = decodecaps(inpart.read())
746 caps = decodecaps(inpart.read())
747 if op.reply is None:
747 if op.reply is None:
748 op.reply = bundle20(op.ui, caps)
748 op.reply = bundle20(op.ui, caps)
749
749
750 @parthandler('b2x:error:abort')
750 @parthandler('b2x:error:abort')
751 def handlereplycaps(op, inpart):
751 def handlereplycaps(op, inpart):
752 """Used to transmit abort error over the wire"""
752 """Used to transmit abort error over the wire"""
753 manargs = dict(inpart.mandatoryparams)
753 manargs = dict(inpart.mandatoryparams)
754 advargs = dict(inpart.advisoryparams)
754 advargs = dict(inpart.advisoryparams)
755 raise util.Abort(manargs['message'], hint=advargs.get('hint'))
755 raise util.Abort(manargs['message'], hint=advargs.get('hint'))
756
757 @parthandler('b2x:error:unknownpart')
758 def handlereplycaps(op, inpart):
759 """Used to transmit unknown part error over the wire"""
760 manargs = dict(inpart.mandatoryparams)
761 raise UnknownPartError(manargs['parttype'])
@@ -1,824 +1,830 b''
1 # wireproto.py - generic wire protocol support functions
1 # wireproto.py - generic wire protocol support functions
2 #
2 #
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
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
7
8 import urllib, tempfile, os, sys
8 import urllib, tempfile, os, sys
9 from i18n import _
9 from i18n import _
10 from node import bin, hex
10 from node import bin, hex
11 import changegroup as changegroupmod, bundle2
11 import changegroup as changegroupmod, bundle2
12 import peer, error, encoding, util, store, exchange
12 import peer, error, encoding, util, store, exchange
13
13
14
14
15 class abstractserverproto(object):
15 class abstractserverproto(object):
16 """abstract class that summarizes the protocol API
16 """abstract class that summarizes the protocol API
17
17
18 Used as reference and documentation.
18 Used as reference and documentation.
19 """
19 """
20
20
21 def getargs(self, args):
21 def getargs(self, args):
22 """return the value for arguments in <args>
22 """return the value for arguments in <args>
23
23
24 returns a list of values (same order as <args>)"""
24 returns a list of values (same order as <args>)"""
25 raise NotImplementedError()
25 raise NotImplementedError()
26
26
27 def getfile(self, fp):
27 def getfile(self, fp):
28 """write the whole content of a file into a file like object
28 """write the whole content of a file into a file like object
29
29
30 The file is in the form::
30 The file is in the form::
31
31
32 (<chunk-size>\n<chunk>)+0\n
32 (<chunk-size>\n<chunk>)+0\n
33
33
34 chunk size is the ascii version of the int.
34 chunk size is the ascii version of the int.
35 """
35 """
36 raise NotImplementedError()
36 raise NotImplementedError()
37
37
38 def redirect(self):
38 def redirect(self):
39 """may setup interception for stdout and stderr
39 """may setup interception for stdout and stderr
40
40
41 See also the `restore` method."""
41 See also the `restore` method."""
42 raise NotImplementedError()
42 raise NotImplementedError()
43
43
44 # If the `redirect` function does install interception, the `restore`
44 # If the `redirect` function does install interception, the `restore`
45 # function MUST be defined. If interception is not used, this function
45 # function MUST be defined. If interception is not used, this function
46 # MUST NOT be defined.
46 # MUST NOT be defined.
47 #
47 #
48 # left commented here on purpose
48 # left commented here on purpose
49 #
49 #
50 #def restore(self):
50 #def restore(self):
51 # """reinstall previous stdout and stderr and return intercepted stdout
51 # """reinstall previous stdout and stderr and return intercepted stdout
52 # """
52 # """
53 # raise NotImplementedError()
53 # raise NotImplementedError()
54
54
55 def groupchunks(self, cg):
55 def groupchunks(self, cg):
56 """return 4096 chunks from a changegroup object
56 """return 4096 chunks from a changegroup object
57
57
58 Some protocols may have compressed the contents."""
58 Some protocols may have compressed the contents."""
59 raise NotImplementedError()
59 raise NotImplementedError()
60
60
61 # abstract batching support
61 # abstract batching support
62
62
63 class future(object):
63 class future(object):
64 '''placeholder for a value to be set later'''
64 '''placeholder for a value to be set later'''
65 def set(self, value):
65 def set(self, value):
66 if util.safehasattr(self, 'value'):
66 if util.safehasattr(self, 'value'):
67 raise error.RepoError("future is already set")
67 raise error.RepoError("future is already set")
68 self.value = value
68 self.value = value
69
69
70 class batcher(object):
70 class batcher(object):
71 '''base class for batches of commands submittable in a single request
71 '''base class for batches of commands submittable in a single request
72
72
73 All methods invoked on instances of this class are simply queued and
73 All methods invoked on instances of this class are simply queued and
74 return a a future for the result. Once you call submit(), all the queued
74 return a a future for the result. Once you call submit(), all the queued
75 calls are performed and the results set in their respective futures.
75 calls are performed and the results set in their respective futures.
76 '''
76 '''
77 def __init__(self):
77 def __init__(self):
78 self.calls = []
78 self.calls = []
79 def __getattr__(self, name):
79 def __getattr__(self, name):
80 def call(*args, **opts):
80 def call(*args, **opts):
81 resref = future()
81 resref = future()
82 self.calls.append((name, args, opts, resref,))
82 self.calls.append((name, args, opts, resref,))
83 return resref
83 return resref
84 return call
84 return call
85 def submit(self):
85 def submit(self):
86 pass
86 pass
87
87
88 class localbatch(batcher):
88 class localbatch(batcher):
89 '''performs the queued calls directly'''
89 '''performs the queued calls directly'''
90 def __init__(self, local):
90 def __init__(self, local):
91 batcher.__init__(self)
91 batcher.__init__(self)
92 self.local = local
92 self.local = local
93 def submit(self):
93 def submit(self):
94 for name, args, opts, resref in self.calls:
94 for name, args, opts, resref in self.calls:
95 resref.set(getattr(self.local, name)(*args, **opts))
95 resref.set(getattr(self.local, name)(*args, **opts))
96
96
97 class remotebatch(batcher):
97 class remotebatch(batcher):
98 '''batches the queued calls; uses as few roundtrips as possible'''
98 '''batches the queued calls; uses as few roundtrips as possible'''
99 def __init__(self, remote):
99 def __init__(self, remote):
100 '''remote must support _submitbatch(encbatch) and
100 '''remote must support _submitbatch(encbatch) and
101 _submitone(op, encargs)'''
101 _submitone(op, encargs)'''
102 batcher.__init__(self)
102 batcher.__init__(self)
103 self.remote = remote
103 self.remote = remote
104 def submit(self):
104 def submit(self):
105 req, rsp = [], []
105 req, rsp = [], []
106 for name, args, opts, resref in self.calls:
106 for name, args, opts, resref in self.calls:
107 mtd = getattr(self.remote, name)
107 mtd = getattr(self.remote, name)
108 batchablefn = getattr(mtd, 'batchable', None)
108 batchablefn = getattr(mtd, 'batchable', None)
109 if batchablefn is not None:
109 if batchablefn is not None:
110 batchable = batchablefn(mtd.im_self, *args, **opts)
110 batchable = batchablefn(mtd.im_self, *args, **opts)
111 encargsorres, encresref = batchable.next()
111 encargsorres, encresref = batchable.next()
112 if encresref:
112 if encresref:
113 req.append((name, encargsorres,))
113 req.append((name, encargsorres,))
114 rsp.append((batchable, encresref, resref,))
114 rsp.append((batchable, encresref, resref,))
115 else:
115 else:
116 resref.set(encargsorres)
116 resref.set(encargsorres)
117 else:
117 else:
118 if req:
118 if req:
119 self._submitreq(req, rsp)
119 self._submitreq(req, rsp)
120 req, rsp = [], []
120 req, rsp = [], []
121 resref.set(mtd(*args, **opts))
121 resref.set(mtd(*args, **opts))
122 if req:
122 if req:
123 self._submitreq(req, rsp)
123 self._submitreq(req, rsp)
124 def _submitreq(self, req, rsp):
124 def _submitreq(self, req, rsp):
125 encresults = self.remote._submitbatch(req)
125 encresults = self.remote._submitbatch(req)
126 for encres, r in zip(encresults, rsp):
126 for encres, r in zip(encresults, rsp):
127 batchable, encresref, resref = r
127 batchable, encresref, resref = r
128 encresref.set(encres)
128 encresref.set(encres)
129 resref.set(batchable.next())
129 resref.set(batchable.next())
130
130
131 def batchable(f):
131 def batchable(f):
132 '''annotation for batchable methods
132 '''annotation for batchable methods
133
133
134 Such methods must implement a coroutine as follows:
134 Such methods must implement a coroutine as follows:
135
135
136 @batchable
136 @batchable
137 def sample(self, one, two=None):
137 def sample(self, one, two=None):
138 # Handle locally computable results first:
138 # Handle locally computable results first:
139 if not one:
139 if not one:
140 yield "a local result", None
140 yield "a local result", None
141 # Build list of encoded arguments suitable for your wire protocol:
141 # Build list of encoded arguments suitable for your wire protocol:
142 encargs = [('one', encode(one),), ('two', encode(two),)]
142 encargs = [('one', encode(one),), ('two', encode(two),)]
143 # Create future for injection of encoded result:
143 # Create future for injection of encoded result:
144 encresref = future()
144 encresref = future()
145 # Return encoded arguments and future:
145 # Return encoded arguments and future:
146 yield encargs, encresref
146 yield encargs, encresref
147 # Assuming the future to be filled with the result from the batched
147 # Assuming the future to be filled with the result from the batched
148 # request now. Decode it:
148 # request now. Decode it:
149 yield decode(encresref.value)
149 yield decode(encresref.value)
150
150
151 The decorator returns a function which wraps this coroutine as a plain
151 The decorator returns a function which wraps this coroutine as a plain
152 method, but adds the original method as an attribute called "batchable",
152 method, but adds the original method as an attribute called "batchable",
153 which is used by remotebatch to split the call into separate encoding and
153 which is used by remotebatch to split the call into separate encoding and
154 decoding phases.
154 decoding phases.
155 '''
155 '''
156 def plain(*args, **opts):
156 def plain(*args, **opts):
157 batchable = f(*args, **opts)
157 batchable = f(*args, **opts)
158 encargsorres, encresref = batchable.next()
158 encargsorres, encresref = batchable.next()
159 if not encresref:
159 if not encresref:
160 return encargsorres # a local result in this case
160 return encargsorres # a local result in this case
161 self = args[0]
161 self = args[0]
162 encresref.set(self._submitone(f.func_name, encargsorres))
162 encresref.set(self._submitone(f.func_name, encargsorres))
163 return batchable.next()
163 return batchable.next()
164 setattr(plain, 'batchable', f)
164 setattr(plain, 'batchable', f)
165 return plain
165 return plain
166
166
167 # list of nodes encoding / decoding
167 # list of nodes encoding / decoding
168
168
169 def decodelist(l, sep=' '):
169 def decodelist(l, sep=' '):
170 if l:
170 if l:
171 return map(bin, l.split(sep))
171 return map(bin, l.split(sep))
172 return []
172 return []
173
173
174 def encodelist(l, sep=' '):
174 def encodelist(l, sep=' '):
175 return sep.join(map(hex, l))
175 return sep.join(map(hex, l))
176
176
177 # batched call argument encoding
177 # batched call argument encoding
178
178
179 def escapearg(plain):
179 def escapearg(plain):
180 return (plain
180 return (plain
181 .replace(':', '::')
181 .replace(':', '::')
182 .replace(',', ':,')
182 .replace(',', ':,')
183 .replace(';', ':;')
183 .replace(';', ':;')
184 .replace('=', ':='))
184 .replace('=', ':='))
185
185
186 def unescapearg(escaped):
186 def unescapearg(escaped):
187 return (escaped
187 return (escaped
188 .replace(':=', '=')
188 .replace(':=', '=')
189 .replace(':;', ';')
189 .replace(':;', ';')
190 .replace(':,', ',')
190 .replace(':,', ',')
191 .replace('::', ':'))
191 .replace('::', ':'))
192
192
193 # client side
193 # client side
194
194
195 class wirepeer(peer.peerrepository):
195 class wirepeer(peer.peerrepository):
196
196
197 def batch(self):
197 def batch(self):
198 return remotebatch(self)
198 return remotebatch(self)
199 def _submitbatch(self, req):
199 def _submitbatch(self, req):
200 cmds = []
200 cmds = []
201 for op, argsdict in req:
201 for op, argsdict in req:
202 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
202 args = ','.join('%s=%s' % p for p in argsdict.iteritems())
203 cmds.append('%s %s' % (op, args))
203 cmds.append('%s %s' % (op, args))
204 rsp = self._call("batch", cmds=';'.join(cmds))
204 rsp = self._call("batch", cmds=';'.join(cmds))
205 return rsp.split(';')
205 return rsp.split(';')
206 def _submitone(self, op, args):
206 def _submitone(self, op, args):
207 return self._call(op, **args)
207 return self._call(op, **args)
208
208
209 @batchable
209 @batchable
210 def lookup(self, key):
210 def lookup(self, key):
211 self.requirecap('lookup', _('look up remote revision'))
211 self.requirecap('lookup', _('look up remote revision'))
212 f = future()
212 f = future()
213 yield {'key': encoding.fromlocal(key)}, f
213 yield {'key': encoding.fromlocal(key)}, f
214 d = f.value
214 d = f.value
215 success, data = d[:-1].split(" ", 1)
215 success, data = d[:-1].split(" ", 1)
216 if int(success):
216 if int(success):
217 yield bin(data)
217 yield bin(data)
218 self._abort(error.RepoError(data))
218 self._abort(error.RepoError(data))
219
219
220 @batchable
220 @batchable
221 def heads(self):
221 def heads(self):
222 f = future()
222 f = future()
223 yield {}, f
223 yield {}, f
224 d = f.value
224 d = f.value
225 try:
225 try:
226 yield decodelist(d[:-1])
226 yield decodelist(d[:-1])
227 except ValueError:
227 except ValueError:
228 self._abort(error.ResponseError(_("unexpected response:"), d))
228 self._abort(error.ResponseError(_("unexpected response:"), d))
229
229
230 @batchable
230 @batchable
231 def known(self, nodes):
231 def known(self, nodes):
232 f = future()
232 f = future()
233 yield {'nodes': encodelist(nodes)}, f
233 yield {'nodes': encodelist(nodes)}, f
234 d = f.value
234 d = f.value
235 try:
235 try:
236 yield [bool(int(f)) for f in d]
236 yield [bool(int(f)) for f in d]
237 except ValueError:
237 except ValueError:
238 self._abort(error.ResponseError(_("unexpected response:"), d))
238 self._abort(error.ResponseError(_("unexpected response:"), d))
239
239
240 @batchable
240 @batchable
241 def branchmap(self):
241 def branchmap(self):
242 f = future()
242 f = future()
243 yield {}, f
243 yield {}, f
244 d = f.value
244 d = f.value
245 try:
245 try:
246 branchmap = {}
246 branchmap = {}
247 for branchpart in d.splitlines():
247 for branchpart in d.splitlines():
248 branchname, branchheads = branchpart.split(' ', 1)
248 branchname, branchheads = branchpart.split(' ', 1)
249 branchname = encoding.tolocal(urllib.unquote(branchname))
249 branchname = encoding.tolocal(urllib.unquote(branchname))
250 branchheads = decodelist(branchheads)
250 branchheads = decodelist(branchheads)
251 branchmap[branchname] = branchheads
251 branchmap[branchname] = branchheads
252 yield branchmap
252 yield branchmap
253 except TypeError:
253 except TypeError:
254 self._abort(error.ResponseError(_("unexpected response:"), d))
254 self._abort(error.ResponseError(_("unexpected response:"), d))
255
255
256 def branches(self, nodes):
256 def branches(self, nodes):
257 n = encodelist(nodes)
257 n = encodelist(nodes)
258 d = self._call("branches", nodes=n)
258 d = self._call("branches", nodes=n)
259 try:
259 try:
260 br = [tuple(decodelist(b)) for b in d.splitlines()]
260 br = [tuple(decodelist(b)) for b in d.splitlines()]
261 return br
261 return br
262 except ValueError:
262 except ValueError:
263 self._abort(error.ResponseError(_("unexpected response:"), d))
263 self._abort(error.ResponseError(_("unexpected response:"), d))
264
264
265 def between(self, pairs):
265 def between(self, pairs):
266 batch = 8 # avoid giant requests
266 batch = 8 # avoid giant requests
267 r = []
267 r = []
268 for i in xrange(0, len(pairs), batch):
268 for i in xrange(0, len(pairs), batch):
269 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
269 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
270 d = self._call("between", pairs=n)
270 d = self._call("between", pairs=n)
271 try:
271 try:
272 r.extend(l and decodelist(l) or [] for l in d.splitlines())
272 r.extend(l and decodelist(l) or [] for l in d.splitlines())
273 except ValueError:
273 except ValueError:
274 self._abort(error.ResponseError(_("unexpected response:"), d))
274 self._abort(error.ResponseError(_("unexpected response:"), d))
275 return r
275 return r
276
276
277 @batchable
277 @batchable
278 def pushkey(self, namespace, key, old, new):
278 def pushkey(self, namespace, key, old, new):
279 if not self.capable('pushkey'):
279 if not self.capable('pushkey'):
280 yield False, None
280 yield False, None
281 f = future()
281 f = future()
282 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
282 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
283 yield {'namespace': encoding.fromlocal(namespace),
283 yield {'namespace': encoding.fromlocal(namespace),
284 'key': encoding.fromlocal(key),
284 'key': encoding.fromlocal(key),
285 'old': encoding.fromlocal(old),
285 'old': encoding.fromlocal(old),
286 'new': encoding.fromlocal(new)}, f
286 'new': encoding.fromlocal(new)}, f
287 d = f.value
287 d = f.value
288 d, output = d.split('\n', 1)
288 d, output = d.split('\n', 1)
289 try:
289 try:
290 d = bool(int(d))
290 d = bool(int(d))
291 except ValueError:
291 except ValueError:
292 raise error.ResponseError(
292 raise error.ResponseError(
293 _('push failed (unexpected response):'), d)
293 _('push failed (unexpected response):'), d)
294 for l in output.splitlines(True):
294 for l in output.splitlines(True):
295 self.ui.status(_('remote: '), l)
295 self.ui.status(_('remote: '), l)
296 yield d
296 yield d
297
297
298 @batchable
298 @batchable
299 def listkeys(self, namespace):
299 def listkeys(self, namespace):
300 if not self.capable('pushkey'):
300 if not self.capable('pushkey'):
301 yield {}, None
301 yield {}, None
302 f = future()
302 f = future()
303 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
303 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
304 yield {'namespace': encoding.fromlocal(namespace)}, f
304 yield {'namespace': encoding.fromlocal(namespace)}, f
305 d = f.value
305 d = f.value
306 r = {}
306 r = {}
307 for l in d.splitlines():
307 for l in d.splitlines():
308 k, v = l.split('\t')
308 k, v = l.split('\t')
309 r[encoding.tolocal(k)] = encoding.tolocal(v)
309 r[encoding.tolocal(k)] = encoding.tolocal(v)
310 yield r
310 yield r
311
311
312 def stream_out(self):
312 def stream_out(self):
313 return self._callstream('stream_out')
313 return self._callstream('stream_out')
314
314
315 def changegroup(self, nodes, kind):
315 def changegroup(self, nodes, kind):
316 n = encodelist(nodes)
316 n = encodelist(nodes)
317 f = self._callcompressable("changegroup", roots=n)
317 f = self._callcompressable("changegroup", roots=n)
318 return changegroupmod.unbundle10(f, 'UN')
318 return changegroupmod.unbundle10(f, 'UN')
319
319
320 def changegroupsubset(self, bases, heads, kind):
320 def changegroupsubset(self, bases, heads, kind):
321 self.requirecap('changegroupsubset', _('look up remote changes'))
321 self.requirecap('changegroupsubset', _('look up remote changes'))
322 bases = encodelist(bases)
322 bases = encodelist(bases)
323 heads = encodelist(heads)
323 heads = encodelist(heads)
324 f = self._callcompressable("changegroupsubset",
324 f = self._callcompressable("changegroupsubset",
325 bases=bases, heads=heads)
325 bases=bases, heads=heads)
326 return changegroupmod.unbundle10(f, 'UN')
326 return changegroupmod.unbundle10(f, 'UN')
327
327
328 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
328 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
329 **kwargs):
329 **kwargs):
330 self.requirecap('getbundle', _('look up remote changes'))
330 self.requirecap('getbundle', _('look up remote changes'))
331 opts = {}
331 opts = {}
332 if heads is not None:
332 if heads is not None:
333 opts['heads'] = encodelist(heads)
333 opts['heads'] = encodelist(heads)
334 if common is not None:
334 if common is not None:
335 opts['common'] = encodelist(common)
335 opts['common'] = encodelist(common)
336 if bundlecaps is not None:
336 if bundlecaps is not None:
337 opts['bundlecaps'] = ','.join(bundlecaps)
337 opts['bundlecaps'] = ','.join(bundlecaps)
338 opts.update(kwargs)
338 opts.update(kwargs)
339 f = self._callcompressable("getbundle", **opts)
339 f = self._callcompressable("getbundle", **opts)
340 if bundlecaps is not None and 'HG2X' in bundlecaps:
340 if bundlecaps is not None and 'HG2X' in bundlecaps:
341 return bundle2.unbundle20(self.ui, f)
341 return bundle2.unbundle20(self.ui, f)
342 else:
342 else:
343 return changegroupmod.unbundle10(f, 'UN')
343 return changegroupmod.unbundle10(f, 'UN')
344
344
345 def unbundle(self, cg, heads, source):
345 def unbundle(self, cg, heads, source):
346 '''Send cg (a readable file-like object representing the
346 '''Send cg (a readable file-like object representing the
347 changegroup to push, typically a chunkbuffer object) to the
347 changegroup to push, typically a chunkbuffer object) to the
348 remote server as a bundle.
348 remote server as a bundle.
349
349
350 When pushing a bundle10 stream, return an integer indicating the
350 When pushing a bundle10 stream, return an integer indicating the
351 result of the push (see localrepository.addchangegroup()).
351 result of the push (see localrepository.addchangegroup()).
352
352
353 When pushing a bundle20 stream, return a bundle20 stream.'''
353 When pushing a bundle20 stream, return a bundle20 stream.'''
354
354
355 if heads != ['force'] and self.capable('unbundlehash'):
355 if heads != ['force'] and self.capable('unbundlehash'):
356 heads = encodelist(['hashed',
356 heads = encodelist(['hashed',
357 util.sha1(''.join(sorted(heads))).digest()])
357 util.sha1(''.join(sorted(heads))).digest()])
358 else:
358 else:
359 heads = encodelist(heads)
359 heads = encodelist(heads)
360
360
361 if util.safehasattr(cg, 'deltaheader'):
361 if util.safehasattr(cg, 'deltaheader'):
362 # this a bundle10, do the old style call sequence
362 # this a bundle10, do the old style call sequence
363 ret, output = self._callpush("unbundle", cg, heads=heads)
363 ret, output = self._callpush("unbundle", cg, heads=heads)
364 if ret == "":
364 if ret == "":
365 raise error.ResponseError(
365 raise error.ResponseError(
366 _('push failed:'), output)
366 _('push failed:'), output)
367 try:
367 try:
368 ret = int(ret)
368 ret = int(ret)
369 except ValueError:
369 except ValueError:
370 raise error.ResponseError(
370 raise error.ResponseError(
371 _('push failed (unexpected response):'), ret)
371 _('push failed (unexpected response):'), ret)
372
372
373 for l in output.splitlines(True):
373 for l in output.splitlines(True):
374 self.ui.status(_('remote: '), l)
374 self.ui.status(_('remote: '), l)
375 else:
375 else:
376 # bundle2 push. Send a stream, fetch a stream.
376 # bundle2 push. Send a stream, fetch a stream.
377 stream = self._calltwowaystream('unbundle', cg, heads=heads)
377 stream = self._calltwowaystream('unbundle', cg, heads=heads)
378 ret = bundle2.unbundle20(self.ui, stream)
378 ret = bundle2.unbundle20(self.ui, stream)
379 return ret
379 return ret
380
380
381 def debugwireargs(self, one, two, three=None, four=None, five=None):
381 def debugwireargs(self, one, two, three=None, four=None, five=None):
382 # don't pass optional arguments left at their default value
382 # don't pass optional arguments left at their default value
383 opts = {}
383 opts = {}
384 if three is not None:
384 if three is not None:
385 opts['three'] = three
385 opts['three'] = three
386 if four is not None:
386 if four is not None:
387 opts['four'] = four
387 opts['four'] = four
388 return self._call('debugwireargs', one=one, two=two, **opts)
388 return self._call('debugwireargs', one=one, two=two, **opts)
389
389
390 def _call(self, cmd, **args):
390 def _call(self, cmd, **args):
391 """execute <cmd> on the server
391 """execute <cmd> on the server
392
392
393 The command is expected to return a simple string.
393 The command is expected to return a simple string.
394
394
395 returns the server reply as a string."""
395 returns the server reply as a string."""
396 raise NotImplementedError()
396 raise NotImplementedError()
397
397
398 def _callstream(self, cmd, **args):
398 def _callstream(self, cmd, **args):
399 """execute <cmd> on the server
399 """execute <cmd> on the server
400
400
401 The command is expected to return a stream.
401 The command is expected to return a stream.
402
402
403 returns the server reply as a file like object."""
403 returns the server reply as a file like object."""
404 raise NotImplementedError()
404 raise NotImplementedError()
405
405
406 def _callcompressable(self, cmd, **args):
406 def _callcompressable(self, cmd, **args):
407 """execute <cmd> on the server
407 """execute <cmd> on the server
408
408
409 The command is expected to return a stream.
409 The command is expected to return a stream.
410
410
411 The stream may have been compressed in some implementations. This
411 The stream may have been compressed in some implementations. This
412 function takes care of the decompression. This is the only difference
412 function takes care of the decompression. This is the only difference
413 with _callstream.
413 with _callstream.
414
414
415 returns the server reply as a file like object.
415 returns the server reply as a file like object.
416 """
416 """
417 raise NotImplementedError()
417 raise NotImplementedError()
418
418
419 def _callpush(self, cmd, fp, **args):
419 def _callpush(self, cmd, fp, **args):
420 """execute a <cmd> on server
420 """execute a <cmd> on server
421
421
422 The command is expected to be related to a push. Push has a special
422 The command is expected to be related to a push. Push has a special
423 return method.
423 return method.
424
424
425 returns the server reply as a (ret, output) tuple. ret is either
425 returns the server reply as a (ret, output) tuple. ret is either
426 empty (error) or a stringified int.
426 empty (error) or a stringified int.
427 """
427 """
428 raise NotImplementedError()
428 raise NotImplementedError()
429
429
430 def _calltwowaystream(self, cmd, fp, **args):
430 def _calltwowaystream(self, cmd, fp, **args):
431 """execute <cmd> on server
431 """execute <cmd> on server
432
432
433 The command will send a stream to the server and get a stream in reply.
433 The command will send a stream to the server and get a stream in reply.
434 """
434 """
435 raise NotImplementedError()
435 raise NotImplementedError()
436
436
437 def _abort(self, exception):
437 def _abort(self, exception):
438 """clearly abort the wire protocol connection and raise the exception
438 """clearly abort the wire protocol connection and raise the exception
439 """
439 """
440 raise NotImplementedError()
440 raise NotImplementedError()
441
441
442 # server side
442 # server side
443
443
444 # wire protocol command can either return a string or one of these classes.
444 # wire protocol command can either return a string or one of these classes.
445 class streamres(object):
445 class streamres(object):
446 """wireproto reply: binary stream
446 """wireproto reply: binary stream
447
447
448 The call was successful and the result is a stream.
448 The call was successful and the result is a stream.
449 Iterate on the `self.gen` attribute to retrieve chunks.
449 Iterate on the `self.gen` attribute to retrieve chunks.
450 """
450 """
451 def __init__(self, gen):
451 def __init__(self, gen):
452 self.gen = gen
452 self.gen = gen
453
453
454 class pushres(object):
454 class pushres(object):
455 """wireproto reply: success with simple integer return
455 """wireproto reply: success with simple integer return
456
456
457 The call was successful and returned an integer contained in `self.res`.
457 The call was successful and returned an integer contained in `self.res`.
458 """
458 """
459 def __init__(self, res):
459 def __init__(self, res):
460 self.res = res
460 self.res = res
461
461
462 class pusherr(object):
462 class pusherr(object):
463 """wireproto reply: failure
463 """wireproto reply: failure
464
464
465 The call failed. The `self.res` attribute contains the error message.
465 The call failed. The `self.res` attribute contains the error message.
466 """
466 """
467 def __init__(self, res):
467 def __init__(self, res):
468 self.res = res
468 self.res = res
469
469
470 class ooberror(object):
470 class ooberror(object):
471 """wireproto reply: failure of a batch of operation
471 """wireproto reply: failure of a batch of operation
472
472
473 Something failed during a batch call. The error message is stored in
473 Something failed during a batch call. The error message is stored in
474 `self.message`.
474 `self.message`.
475 """
475 """
476 def __init__(self, message):
476 def __init__(self, message):
477 self.message = message
477 self.message = message
478
478
479 def dispatch(repo, proto, command):
479 def dispatch(repo, proto, command):
480 repo = repo.filtered("served")
480 repo = repo.filtered("served")
481 func, spec = commands[command]
481 func, spec = commands[command]
482 args = proto.getargs(spec)
482 args = proto.getargs(spec)
483 return func(repo, proto, *args)
483 return func(repo, proto, *args)
484
484
485 def options(cmd, keys, others):
485 def options(cmd, keys, others):
486 opts = {}
486 opts = {}
487 for k in keys:
487 for k in keys:
488 if k in others:
488 if k in others:
489 opts[k] = others[k]
489 opts[k] = others[k]
490 del others[k]
490 del others[k]
491 if others:
491 if others:
492 sys.stderr.write("abort: %s got unexpected arguments %s\n"
492 sys.stderr.write("abort: %s got unexpected arguments %s\n"
493 % (cmd, ",".join(others)))
493 % (cmd, ",".join(others)))
494 return opts
494 return opts
495
495
496 # list of commands
496 # list of commands
497 commands = {}
497 commands = {}
498
498
499 def wireprotocommand(name, args=''):
499 def wireprotocommand(name, args=''):
500 """decorator for wire protocol command"""
500 """decorator for wire protocol command"""
501 def register(func):
501 def register(func):
502 commands[name] = (func, args)
502 commands[name] = (func, args)
503 return func
503 return func
504 return register
504 return register
505
505
506 @wireprotocommand('batch', 'cmds *')
506 @wireprotocommand('batch', 'cmds *')
507 def batch(repo, proto, cmds, others):
507 def batch(repo, proto, cmds, others):
508 repo = repo.filtered("served")
508 repo = repo.filtered("served")
509 res = []
509 res = []
510 for pair in cmds.split(';'):
510 for pair in cmds.split(';'):
511 op, args = pair.split(' ', 1)
511 op, args = pair.split(' ', 1)
512 vals = {}
512 vals = {}
513 for a in args.split(','):
513 for a in args.split(','):
514 if a:
514 if a:
515 n, v = a.split('=')
515 n, v = a.split('=')
516 vals[n] = unescapearg(v)
516 vals[n] = unescapearg(v)
517 func, spec = commands[op]
517 func, spec = commands[op]
518 if spec:
518 if spec:
519 keys = spec.split()
519 keys = spec.split()
520 data = {}
520 data = {}
521 for k in keys:
521 for k in keys:
522 if k == '*':
522 if k == '*':
523 star = {}
523 star = {}
524 for key in vals.keys():
524 for key in vals.keys():
525 if key not in keys:
525 if key not in keys:
526 star[key] = vals[key]
526 star[key] = vals[key]
527 data['*'] = star
527 data['*'] = star
528 else:
528 else:
529 data[k] = vals[k]
529 data[k] = vals[k]
530 result = func(repo, proto, *[data[k] for k in keys])
530 result = func(repo, proto, *[data[k] for k in keys])
531 else:
531 else:
532 result = func(repo, proto)
532 result = func(repo, proto)
533 if isinstance(result, ooberror):
533 if isinstance(result, ooberror):
534 return result
534 return result
535 res.append(escapearg(result))
535 res.append(escapearg(result))
536 return ';'.join(res)
536 return ';'.join(res)
537
537
538 @wireprotocommand('between', 'pairs')
538 @wireprotocommand('between', 'pairs')
539 def between(repo, proto, pairs):
539 def between(repo, proto, pairs):
540 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
540 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
541 r = []
541 r = []
542 for b in repo.between(pairs):
542 for b in repo.between(pairs):
543 r.append(encodelist(b) + "\n")
543 r.append(encodelist(b) + "\n")
544 return "".join(r)
544 return "".join(r)
545
545
546 @wireprotocommand('branchmap')
546 @wireprotocommand('branchmap')
547 def branchmap(repo, proto):
547 def branchmap(repo, proto):
548 branchmap = repo.branchmap()
548 branchmap = repo.branchmap()
549 heads = []
549 heads = []
550 for branch, nodes in branchmap.iteritems():
550 for branch, nodes in branchmap.iteritems():
551 branchname = urllib.quote(encoding.fromlocal(branch))
551 branchname = urllib.quote(encoding.fromlocal(branch))
552 branchnodes = encodelist(nodes)
552 branchnodes = encodelist(nodes)
553 heads.append('%s %s' % (branchname, branchnodes))
553 heads.append('%s %s' % (branchname, branchnodes))
554 return '\n'.join(heads)
554 return '\n'.join(heads)
555
555
556 @wireprotocommand('branches', 'nodes')
556 @wireprotocommand('branches', 'nodes')
557 def branches(repo, proto, nodes):
557 def branches(repo, proto, nodes):
558 nodes = decodelist(nodes)
558 nodes = decodelist(nodes)
559 r = []
559 r = []
560 for b in repo.branches(nodes):
560 for b in repo.branches(nodes):
561 r.append(encodelist(b) + "\n")
561 r.append(encodelist(b) + "\n")
562 return "".join(r)
562 return "".join(r)
563
563
564
564
565 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
565 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
566 'known', 'getbundle', 'unbundlehash', 'batch']
566 'known', 'getbundle', 'unbundlehash', 'batch']
567
567
568 def _capabilities(repo, proto):
568 def _capabilities(repo, proto):
569 """return a list of capabilities for a repo
569 """return a list of capabilities for a repo
570
570
571 This function exists to allow extensions to easily wrap capabilities
571 This function exists to allow extensions to easily wrap capabilities
572 computation
572 computation
573
573
574 - returns a lists: easy to alter
574 - returns a lists: easy to alter
575 - change done here will be propagated to both `capabilities` and `hello`
575 - change done here will be propagated to both `capabilities` and `hello`
576 command without any other action needed.
576 command without any other action needed.
577 """
577 """
578 # copy to prevent modification of the global list
578 # copy to prevent modification of the global list
579 caps = list(wireprotocaps)
579 caps = list(wireprotocaps)
580 if _allowstream(repo.ui):
580 if _allowstream(repo.ui):
581 if repo.ui.configbool('server', 'preferuncompressed', False):
581 if repo.ui.configbool('server', 'preferuncompressed', False):
582 caps.append('stream-preferred')
582 caps.append('stream-preferred')
583 requiredformats = repo.requirements & repo.supportedformats
583 requiredformats = repo.requirements & repo.supportedformats
584 # if our local revlogs are just revlogv1, add 'stream' cap
584 # if our local revlogs are just revlogv1, add 'stream' cap
585 if not requiredformats - set(('revlogv1',)):
585 if not requiredformats - set(('revlogv1',)):
586 caps.append('stream')
586 caps.append('stream')
587 # otherwise, add 'streamreqs' detailing our local revlog format
587 # otherwise, add 'streamreqs' detailing our local revlog format
588 else:
588 else:
589 caps.append('streamreqs=%s' % ','.join(requiredformats))
589 caps.append('streamreqs=%s' % ','.join(requiredformats))
590 if repo.ui.configbool('experimental', 'bundle2-exp', False):
590 if repo.ui.configbool('experimental', 'bundle2-exp', False):
591 capsblob = bundle2.encodecaps(repo.bundle2caps)
591 capsblob = bundle2.encodecaps(repo.bundle2caps)
592 caps.append('bundle2-exp=' + urllib.quote(capsblob))
592 caps.append('bundle2-exp=' + urllib.quote(capsblob))
593 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
593 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
594 caps.append('httpheader=1024')
594 caps.append('httpheader=1024')
595 return caps
595 return caps
596
596
597 # If you are writing an extension and consider wrapping this function. Wrap
597 # If you are writing an extension and consider wrapping this function. Wrap
598 # `_capabilities` instead.
598 # `_capabilities` instead.
599 @wireprotocommand('capabilities')
599 @wireprotocommand('capabilities')
600 def capabilities(repo, proto):
600 def capabilities(repo, proto):
601 return ' '.join(_capabilities(repo, proto))
601 return ' '.join(_capabilities(repo, proto))
602
602
603 @wireprotocommand('changegroup', 'roots')
603 @wireprotocommand('changegroup', 'roots')
604 def changegroup(repo, proto, roots):
604 def changegroup(repo, proto, roots):
605 nodes = decodelist(roots)
605 nodes = decodelist(roots)
606 cg = changegroupmod.changegroup(repo, nodes, 'serve')
606 cg = changegroupmod.changegroup(repo, nodes, 'serve')
607 return streamres(proto.groupchunks(cg))
607 return streamres(proto.groupchunks(cg))
608
608
609 @wireprotocommand('changegroupsubset', 'bases heads')
609 @wireprotocommand('changegroupsubset', 'bases heads')
610 def changegroupsubset(repo, proto, bases, heads):
610 def changegroupsubset(repo, proto, bases, heads):
611 bases = decodelist(bases)
611 bases = decodelist(bases)
612 heads = decodelist(heads)
612 heads = decodelist(heads)
613 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
613 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
614 return streamres(proto.groupchunks(cg))
614 return streamres(proto.groupchunks(cg))
615
615
616 @wireprotocommand('debugwireargs', 'one two *')
616 @wireprotocommand('debugwireargs', 'one two *')
617 def debugwireargs(repo, proto, one, two, others):
617 def debugwireargs(repo, proto, one, two, others):
618 # only accept optional args from the known set
618 # only accept optional args from the known set
619 opts = options('debugwireargs', ['three', 'four'], others)
619 opts = options('debugwireargs', ['three', 'four'], others)
620 return repo.debugwireargs(one, two, **opts)
620 return repo.debugwireargs(one, two, **opts)
621
621
622 @wireprotocommand('getbundle', '*')
622 @wireprotocommand('getbundle', '*')
623 def getbundle(repo, proto, others):
623 def getbundle(repo, proto, others):
624 opts = options('getbundle', ['heads', 'common', 'bundlecaps'], others)
624 opts = options('getbundle', ['heads', 'common', 'bundlecaps'], others)
625 for k, v in opts.iteritems():
625 for k, v in opts.iteritems():
626 if k in ('heads', 'common'):
626 if k in ('heads', 'common'):
627 opts[k] = decodelist(v)
627 opts[k] = decodelist(v)
628 elif k == 'bundlecaps':
628 elif k == 'bundlecaps':
629 opts[k] = set(v.split(','))
629 opts[k] = set(v.split(','))
630 cg = exchange.getbundle(repo, 'serve', **opts)
630 cg = exchange.getbundle(repo, 'serve', **opts)
631 return streamres(proto.groupchunks(cg))
631 return streamres(proto.groupchunks(cg))
632
632
633 @wireprotocommand('heads')
633 @wireprotocommand('heads')
634 def heads(repo, proto):
634 def heads(repo, proto):
635 h = repo.heads()
635 h = repo.heads()
636 return encodelist(h) + "\n"
636 return encodelist(h) + "\n"
637
637
638 @wireprotocommand('hello')
638 @wireprotocommand('hello')
639 def hello(repo, proto):
639 def hello(repo, proto):
640 '''the hello command returns a set of lines describing various
640 '''the hello command returns a set of lines describing various
641 interesting things about the server, in an RFC822-like format.
641 interesting things about the server, in an RFC822-like format.
642 Currently the only one defined is "capabilities", which
642 Currently the only one defined is "capabilities", which
643 consists of a line in the form:
643 consists of a line in the form:
644
644
645 capabilities: space separated list of tokens
645 capabilities: space separated list of tokens
646 '''
646 '''
647 return "capabilities: %s\n" % (capabilities(repo, proto))
647 return "capabilities: %s\n" % (capabilities(repo, proto))
648
648
649 @wireprotocommand('listkeys', 'namespace')
649 @wireprotocommand('listkeys', 'namespace')
650 def listkeys(repo, proto, namespace):
650 def listkeys(repo, proto, namespace):
651 d = repo.listkeys(encoding.tolocal(namespace)).items()
651 d = repo.listkeys(encoding.tolocal(namespace)).items()
652 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
652 t = '\n'.join(['%s\t%s' % (encoding.fromlocal(k), encoding.fromlocal(v))
653 for k, v in d])
653 for k, v in d])
654 return t
654 return t
655
655
656 @wireprotocommand('lookup', 'key')
656 @wireprotocommand('lookup', 'key')
657 def lookup(repo, proto, key):
657 def lookup(repo, proto, key):
658 try:
658 try:
659 k = encoding.tolocal(key)
659 k = encoding.tolocal(key)
660 c = repo[k]
660 c = repo[k]
661 r = c.hex()
661 r = c.hex()
662 success = 1
662 success = 1
663 except Exception, inst:
663 except Exception, inst:
664 r = str(inst)
664 r = str(inst)
665 success = 0
665 success = 0
666 return "%s %s\n" % (success, r)
666 return "%s %s\n" % (success, r)
667
667
668 @wireprotocommand('known', 'nodes *')
668 @wireprotocommand('known', 'nodes *')
669 def known(repo, proto, nodes, others):
669 def known(repo, proto, nodes, others):
670 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
670 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
671
671
672 @wireprotocommand('pushkey', 'namespace key old new')
672 @wireprotocommand('pushkey', 'namespace key old new')
673 def pushkey(repo, proto, namespace, key, old, new):
673 def pushkey(repo, proto, namespace, key, old, new):
674 # compatibility with pre-1.8 clients which were accidentally
674 # compatibility with pre-1.8 clients which were accidentally
675 # sending raw binary nodes rather than utf-8-encoded hex
675 # sending raw binary nodes rather than utf-8-encoded hex
676 if len(new) == 20 and new.encode('string-escape') != new:
676 if len(new) == 20 and new.encode('string-escape') != new:
677 # looks like it could be a binary node
677 # looks like it could be a binary node
678 try:
678 try:
679 new.decode('utf-8')
679 new.decode('utf-8')
680 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
680 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
681 except UnicodeDecodeError:
681 except UnicodeDecodeError:
682 pass # binary, leave unmodified
682 pass # binary, leave unmodified
683 else:
683 else:
684 new = encoding.tolocal(new) # normal path
684 new = encoding.tolocal(new) # normal path
685
685
686 if util.safehasattr(proto, 'restore'):
686 if util.safehasattr(proto, 'restore'):
687
687
688 proto.redirect()
688 proto.redirect()
689
689
690 try:
690 try:
691 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
691 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
692 encoding.tolocal(old), new) or False
692 encoding.tolocal(old), new) or False
693 except util.Abort:
693 except util.Abort:
694 r = False
694 r = False
695
695
696 output = proto.restore()
696 output = proto.restore()
697
697
698 return '%s\n%s' % (int(r), output)
698 return '%s\n%s' % (int(r), output)
699
699
700 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
700 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
701 encoding.tolocal(old), new)
701 encoding.tolocal(old), new)
702 return '%s\n' % int(r)
702 return '%s\n' % int(r)
703
703
704 def _allowstream(ui):
704 def _allowstream(ui):
705 return ui.configbool('server', 'uncompressed', True, untrusted=True)
705 return ui.configbool('server', 'uncompressed', True, untrusted=True)
706
706
707 def _walkstreamfiles(repo):
707 def _walkstreamfiles(repo):
708 # this is it's own function so extensions can override it
708 # this is it's own function so extensions can override it
709 return repo.store.walk()
709 return repo.store.walk()
710
710
711 @wireprotocommand('stream_out')
711 @wireprotocommand('stream_out')
712 def stream(repo, proto):
712 def stream(repo, proto):
713 '''If the server supports streaming clone, it advertises the "stream"
713 '''If the server supports streaming clone, it advertises the "stream"
714 capability with a value representing the version and flags of the repo
714 capability with a value representing the version and flags of the repo
715 it is serving. Client checks to see if it understands the format.
715 it is serving. Client checks to see if it understands the format.
716
716
717 The format is simple: the server writes out a line with the amount
717 The format is simple: the server writes out a line with the amount
718 of files, then the total amount of bytes to be transferred (separated
718 of files, then the total amount of bytes to be transferred (separated
719 by a space). Then, for each file, the server first writes the filename
719 by a space). Then, for each file, the server first writes the filename
720 and file size (separated by the null character), then the file contents.
720 and file size (separated by the null character), then the file contents.
721 '''
721 '''
722
722
723 if not _allowstream(repo.ui):
723 if not _allowstream(repo.ui):
724 return '1\n'
724 return '1\n'
725
725
726 entries = []
726 entries = []
727 total_bytes = 0
727 total_bytes = 0
728 try:
728 try:
729 # get consistent snapshot of repo, lock during scan
729 # get consistent snapshot of repo, lock during scan
730 lock = repo.lock()
730 lock = repo.lock()
731 try:
731 try:
732 repo.ui.debug('scanning\n')
732 repo.ui.debug('scanning\n')
733 for name, ename, size in _walkstreamfiles(repo):
733 for name, ename, size in _walkstreamfiles(repo):
734 if size:
734 if size:
735 entries.append((name, size))
735 entries.append((name, size))
736 total_bytes += size
736 total_bytes += size
737 finally:
737 finally:
738 lock.release()
738 lock.release()
739 except error.LockError:
739 except error.LockError:
740 return '2\n' # error: 2
740 return '2\n' # error: 2
741
741
742 def streamer(repo, entries, total):
742 def streamer(repo, entries, total):
743 '''stream out all metadata files in repository.'''
743 '''stream out all metadata files in repository.'''
744 yield '0\n' # success
744 yield '0\n' # success
745 repo.ui.debug('%d files, %d bytes to transfer\n' %
745 repo.ui.debug('%d files, %d bytes to transfer\n' %
746 (len(entries), total_bytes))
746 (len(entries), total_bytes))
747 yield '%d %d\n' % (len(entries), total_bytes)
747 yield '%d %d\n' % (len(entries), total_bytes)
748
748
749 sopener = repo.sopener
749 sopener = repo.sopener
750 oldaudit = sopener.mustaudit
750 oldaudit = sopener.mustaudit
751 debugflag = repo.ui.debugflag
751 debugflag = repo.ui.debugflag
752 sopener.mustaudit = False
752 sopener.mustaudit = False
753
753
754 try:
754 try:
755 for name, size in entries:
755 for name, size in entries:
756 if debugflag:
756 if debugflag:
757 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
757 repo.ui.debug('sending %s (%d bytes)\n' % (name, size))
758 # partially encode name over the wire for backwards compat
758 # partially encode name over the wire for backwards compat
759 yield '%s\0%d\n' % (store.encodedir(name), size)
759 yield '%s\0%d\n' % (store.encodedir(name), size)
760 if size <= 65536:
760 if size <= 65536:
761 fp = sopener(name)
761 fp = sopener(name)
762 try:
762 try:
763 data = fp.read(size)
763 data = fp.read(size)
764 finally:
764 finally:
765 fp.close()
765 fp.close()
766 yield data
766 yield data
767 else:
767 else:
768 for chunk in util.filechunkiter(sopener(name), limit=size):
768 for chunk in util.filechunkiter(sopener(name), limit=size):
769 yield chunk
769 yield chunk
770 # replace with "finally:" when support for python 2.4 has been dropped
770 # replace with "finally:" when support for python 2.4 has been dropped
771 except Exception:
771 except Exception:
772 sopener.mustaudit = oldaudit
772 sopener.mustaudit = oldaudit
773 raise
773 raise
774 sopener.mustaudit = oldaudit
774 sopener.mustaudit = oldaudit
775
775
776 return streamres(streamer(repo, entries, total_bytes))
776 return streamres(streamer(repo, entries, total_bytes))
777
777
778 @wireprotocommand('unbundle', 'heads')
778 @wireprotocommand('unbundle', 'heads')
779 def unbundle(repo, proto, heads):
779 def unbundle(repo, proto, heads):
780 their_heads = decodelist(heads)
780 their_heads = decodelist(heads)
781
781
782 try:
782 try:
783 proto.redirect()
783 proto.redirect()
784
784
785 exchange.check_heads(repo, their_heads, 'preparing changes')
785 exchange.check_heads(repo, their_heads, 'preparing changes')
786
786
787 # write bundle data to temporary file because it can be big
787 # write bundle data to temporary file because it can be big
788 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
788 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
789 fp = os.fdopen(fd, 'wb+')
789 fp = os.fdopen(fd, 'wb+')
790 r = 0
790 r = 0
791 try:
791 try:
792 proto.getfile(fp)
792 proto.getfile(fp)
793 fp.seek(0)
793 fp.seek(0)
794 gen = exchange.readbundle(repo.ui, fp, None)
794 gen = exchange.readbundle(repo.ui, fp, None)
795 r = exchange.unbundle(repo, gen, their_heads, 'serve',
795 r = exchange.unbundle(repo, gen, their_heads, 'serve',
796 proto._client())
796 proto._client())
797 if util.safehasattr(r, 'addpart'):
797 if util.safehasattr(r, 'addpart'):
798 # The return looks streameable, we are in the bundle2 case and
798 # The return looks streameable, we are in the bundle2 case and
799 # should return a stream.
799 # should return a stream.
800 return streamres(r.getchunks())
800 return streamres(r.getchunks())
801 return pushres(r)
801 return pushres(r)
802
802
803 finally:
803 finally:
804 fp.close()
804 fp.close()
805 os.unlink(tempname)
805 os.unlink(tempname)
806 except bundle2.UnknownPartError, exc:
807 bundler = bundle2.bundle20(repo.ui)
808 part = bundle2.bundlepart('B2X:ERROR:UNKNOWNPART',
809 [('parttype', str(exc))])
810 bundler.addpart(part)
811 return streamres(bundler.getchunks())
806 except util.Abort, inst:
812 except util.Abort, inst:
807 # The old code we moved used sys.stderr directly.
813 # The old code we moved used sys.stderr directly.
808 # We did not change it to minimise code change.
814 # We did not change it to minimise code change.
809 # This need to be moved to something proper.
815 # This need to be moved to something proper.
810 # Feel free to do it.
816 # Feel free to do it.
811 if getattr(inst, 'duringunbundle2', False):
817 if getattr(inst, 'duringunbundle2', False):
812 bundler = bundle2.bundle20(repo.ui)
818 bundler = bundle2.bundle20(repo.ui)
813 manargs = [('message', str(inst))]
819 manargs = [('message', str(inst))]
814 advargs = []
820 advargs = []
815 if inst.hint is not None:
821 if inst.hint is not None:
816 advargs.append(('hint', inst.hint))
822 advargs.append(('hint', inst.hint))
817 bundler.addpart(bundle2.bundlepart('B2X:ERROR:ABORT',
823 bundler.addpart(bundle2.bundlepart('B2X:ERROR:ABORT',
818 manargs, advargs))
824 manargs, advargs))
819 return streamres(bundler.getchunks())
825 return streamres(bundler.getchunks())
820 else:
826 else:
821 sys.stderr.write("abort: %s\n" % inst)
827 sys.stderr.write("abort: %s\n" % inst)
822 return pushres(0)
828 return pushres(0)
823 except exchange.PushRaced, exc:
829 except exchange.PushRaced, exc:
824 return pusherr(str(exc))
830 return pusherr(str(exc))
@@ -1,971 +1,997 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%3D%21=celeste%2Cville'
75 > capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
76 > bundler.addpart(bundle2.bundlepart('b2x:replycaps', data=capsstring))
76 > bundler.addpart(bundle2.bundlepart('b2x: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('b2x:changegroup', data=cg.getchunks())
88 > part = bundle2.bundlepart('b2x: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 > [experimental]
177 > [experimental]
178 > bundle2-exp=True
178 > bundle2-exp=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 HG2X\x00\x00\x00\x00 (no-eol) (esc)
204 HG2X\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 HG2X\x00\x07caution\x00\x00 (no-eol) (esc)
234 HG2X\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 HG2X\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
246 HG2X\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 HG2X\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
262 HG2X\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 HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
280 HG2X\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 HG2X stream
304 start emission of HG2X 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 HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
312 HG2X\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 HG2X stream
317 start processing of HG2X 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 HG2X stream
351 start emission of HG2X 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 HG2X\x00\x00\x00\x11 (esc)
363 HG2X\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 HG2X stream
399 start processing of HG2X 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 HG2X stream
469 start processing of HG2X 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 HG2X\x00\x00\x00\x1f (esc)
546 HG2X\x00\x00\x00\x1f (esc)
547 b2x:output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
547 b2x:output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
548 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
548 Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
549 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
549 Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
550 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
550 Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
551 \x00\x00\x00\x00\x00\x1f (esc)
551 \x00\x00\x00\x00\x00\x1f (esc)
552 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
552 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
553 debugreply: 'city=!'
553 debugreply: 'city=!'
554 debugreply: 'celeste,ville'
554 debugreply: 'celeste,ville'
555 debugreply: 'elephants'
555 debugreply: 'elephants'
556 debugreply: 'babar'
556 debugreply: 'babar'
557 debugreply: 'celeste'
557 debugreply: 'celeste'
558 debugreply: 'ping-pong'
558 debugreply: 'ping-pong'
559 \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to6\x00\x00\x00\x00\x00\x1f (esc)
559 \x00\x00\x00\x00\x00\x1e test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to6\x00\x00\x00\x00\x00\x1f (esc)
560 b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to6\x00\x00\x00=received ping request (id 6) (esc)
560 b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to6\x00\x00\x00=received ping request (id 6) (esc)
561 replying to ping request (id 6)
561 replying to ping request (id 6)
562 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
562 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
563
563
564 The reply is valid
564 The reply is valid
565
565
566 $ hg statbundle2 < ../reply.hg2
566 $ hg statbundle2 < ../reply.hg2
567 options count: 0
567 options count: 0
568 :b2x:output:
568 :b2x:output:
569 mandatory: 0
569 mandatory: 0
570 advisory: 1
570 advisory: 1
571 payload: 217 bytes
571 payload: 217 bytes
572 :b2x:output:
572 :b2x:output:
573 mandatory: 0
573 mandatory: 0
574 advisory: 1
574 advisory: 1
575 payload: 201 bytes
575 payload: 201 bytes
576 :test:pong:
576 :test:pong:
577 mandatory: 1
577 mandatory: 1
578 advisory: 0
578 advisory: 0
579 payload: 0 bytes
579 payload: 0 bytes
580 :b2x:output:
580 :b2x:output:
581 mandatory: 0
581 mandatory: 0
582 advisory: 1
582 advisory: 1
583 payload: 61 bytes
583 payload: 61 bytes
584 parts count: 4
584 parts count: 4
585
585
586 Unbundle the reply to get the output:
586 Unbundle the reply to get the output:
587
587
588 $ hg unbundle2 < ../reply.hg2
588 $ hg unbundle2 < ../reply.hg2
589 remote: The choir starts singing:
589 remote: The choir starts singing:
590 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
590 remote: Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
591 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
591 remote: Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
592 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
592 remote: Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
593 remote: debugreply: capabilities:
593 remote: debugreply: capabilities:
594 remote: debugreply: 'city=!'
594 remote: debugreply: 'city=!'
595 remote: debugreply: 'celeste,ville'
595 remote: debugreply: 'celeste,ville'
596 remote: debugreply: 'elephants'
596 remote: debugreply: 'elephants'
597 remote: debugreply: 'babar'
597 remote: debugreply: 'babar'
598 remote: debugreply: 'celeste'
598 remote: debugreply: 'celeste'
599 remote: debugreply: 'ping-pong'
599 remote: debugreply: 'ping-pong'
600 remote: received ping request (id 6)
600 remote: received ping request (id 6)
601 remote: replying to ping request (id 6)
601 remote: replying to ping request (id 6)
602 0 unread bytes
602 0 unread bytes
603
603
604 Support for changegroup
604 Support for changegroup
605 ===================================
605 ===================================
606
606
607 $ hg unbundle $TESTDIR/bundles/rebase.hg
607 $ hg unbundle $TESTDIR/bundles/rebase.hg
608 adding changesets
608 adding changesets
609 adding manifests
609 adding manifests
610 adding file changes
610 adding file changes
611 added 8 changesets with 7 changes to 7 files (+3 heads)
611 added 8 changesets with 7 changes to 7 files (+3 heads)
612 (run 'hg heads' to see heads, 'hg merge' to merge)
612 (run 'hg heads' to see heads, 'hg merge' to merge)
613
613
614 $ hg log -G
614 $ hg log -G
615 o changeset: 8:02de42196ebe
615 o changeset: 8:02de42196ebe
616 | tag: tip
616 | tag: tip
617 | parent: 6:24b6387c8c8c
617 | parent: 6:24b6387c8c8c
618 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
618 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
619 | date: Sat Apr 30 15:24:48 2011 +0200
619 | date: Sat Apr 30 15:24:48 2011 +0200
620 | summary: H
620 | summary: H
621 |
621 |
622 | o changeset: 7:eea13746799a
622 | o changeset: 7:eea13746799a
623 |/| parent: 6:24b6387c8c8c
623 |/| parent: 6:24b6387c8c8c
624 | | parent: 5:9520eea781bc
624 | | parent: 5:9520eea781bc
625 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
625 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
626 | | date: Sat Apr 30 15:24:48 2011 +0200
626 | | date: Sat Apr 30 15:24:48 2011 +0200
627 | | summary: G
627 | | summary: G
628 | |
628 | |
629 o | changeset: 6:24b6387c8c8c
629 o | changeset: 6:24b6387c8c8c
630 | | parent: 1:cd010b8cd998
630 | | parent: 1:cd010b8cd998
631 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
631 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
632 | | date: Sat Apr 30 15:24:48 2011 +0200
632 | | date: Sat Apr 30 15:24:48 2011 +0200
633 | | summary: F
633 | | summary: F
634 | |
634 | |
635 | o changeset: 5:9520eea781bc
635 | o changeset: 5:9520eea781bc
636 |/ parent: 1:cd010b8cd998
636 |/ parent: 1:cd010b8cd998
637 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
637 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
638 | date: Sat Apr 30 15:24:48 2011 +0200
638 | date: Sat Apr 30 15:24:48 2011 +0200
639 | summary: E
639 | summary: E
640 |
640 |
641 | o changeset: 4:32af7686d403
641 | o changeset: 4:32af7686d403
642 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
642 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
643 | | date: Sat Apr 30 15:24:48 2011 +0200
643 | | date: Sat Apr 30 15:24:48 2011 +0200
644 | | summary: D
644 | | summary: D
645 | |
645 | |
646 | o changeset: 3:5fddd98957c8
646 | o changeset: 3:5fddd98957c8
647 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
647 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
648 | | date: Sat Apr 30 15:24:48 2011 +0200
648 | | date: Sat Apr 30 15:24:48 2011 +0200
649 | | summary: C
649 | | summary: C
650 | |
650 | |
651 | o changeset: 2:42ccdea3bb16
651 | o changeset: 2:42ccdea3bb16
652 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
652 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
653 | date: Sat Apr 30 15:24:48 2011 +0200
653 | date: Sat Apr 30 15:24:48 2011 +0200
654 | summary: B
654 | summary: B
655 |
655 |
656 o changeset: 1:cd010b8cd998
656 o changeset: 1:cd010b8cd998
657 parent: -1:000000000000
657 parent: -1:000000000000
658 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
658 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
659 date: Sat Apr 30 15:24:48 2011 +0200
659 date: Sat Apr 30 15:24:48 2011 +0200
660 summary: A
660 summary: A
661
661
662 @ changeset: 0:3903775176ed
662 @ changeset: 0:3903775176ed
663 user: test
663 user: test
664 date: Thu Jan 01 00:00:00 1970 +0000
664 date: Thu Jan 01 00:00:00 1970 +0000
665 summary: a
665 summary: a
666
666
667
667
668 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
668 $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
669 4 changesets found
669 4 changesets found
670 list of changesets:
670 list of changesets:
671 32af7686d403cf45b5d95f2d70cebea587ac806a
671 32af7686d403cf45b5d95f2d70cebea587ac806a
672 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
672 9520eea781bcca16c1e15acc0ba14335a0e8e5ba
673 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
673 eea13746799a9e0bfd88f29d3c2e9dc9389f524f
674 02de42196ebee42ef284b6780a87cdc96e8eaab6
674 02de42196ebee42ef284b6780a87cdc96e8eaab6
675 start emission of HG2X stream
675 start emission of HG2X stream
676 bundle parameter:
676 bundle parameter:
677 start of parts
677 start of parts
678 bundle part: "b2x:changegroup"
678 bundle part: "b2x:changegroup"
679 bundling: 1/4 changesets (25.00%)
679 bundling: 1/4 changesets (25.00%)
680 bundling: 2/4 changesets (50.00%)
680 bundling: 2/4 changesets (50.00%)
681 bundling: 3/4 changesets (75.00%)
681 bundling: 3/4 changesets (75.00%)
682 bundling: 4/4 changesets (100.00%)
682 bundling: 4/4 changesets (100.00%)
683 bundling: 1/4 manifests (25.00%)
683 bundling: 1/4 manifests (25.00%)
684 bundling: 2/4 manifests (50.00%)
684 bundling: 2/4 manifests (50.00%)
685 bundling: 3/4 manifests (75.00%)
685 bundling: 3/4 manifests (75.00%)
686 bundling: 4/4 manifests (100.00%)
686 bundling: 4/4 manifests (100.00%)
687 bundling: D 1/3 files (33.33%)
687 bundling: D 1/3 files (33.33%)
688 bundling: E 2/3 files (66.67%)
688 bundling: E 2/3 files (66.67%)
689 bundling: H 3/3 files (100.00%)
689 bundling: H 3/3 files (100.00%)
690 end of bundle
690 end of bundle
691
691
692 $ cat ../rev.hg2
692 $ cat ../rev.hg2
693 HG2X\x00\x00\x00\x16\x0fb2x:changegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
693 HG2X\x00\x00\x00\x16\x0fb2x:changegroup\x00\x00\x00\x00\x00\x00\x00\x00\x06\x13\x00\x00\x00\xa42\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j_\xdd\xd9\x89W\xc8\xa5JMCm\xfe\x1d\xa9\xd8\x7f!\xa1\xb9{\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x002\xafv\x86\xd4\x03\xcfE\xb5\xd9_-p\xce\xbe\xa5\x87\xac\x80j\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c (esc)
694 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
694 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
695 \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)
695 \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)
696 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
696 \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
697 \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)
697 \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)
698 \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)
698 \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)
699 \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)
699 \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)
700 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
700 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
701 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
701 \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
702 \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)
702 \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)
703 \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)
703 \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)
704 \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)
704 \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)
705 \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)
705 \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)
706 \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)
706 \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)
707 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
707 \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
708 \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)
708 \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)
709 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
709 \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
710 l\r (no-eol) (esc)
710 l\r (no-eol) (esc)
711 \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)
711 \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)
712 \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)
712 \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)
713 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
713 \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
714 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
714 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
715
715
716 $ hg unbundle2 < ../rev.hg2
716 $ hg unbundle2 < ../rev.hg2
717 adding changesets
717 adding changesets
718 adding manifests
718 adding manifests
719 adding file changes
719 adding file changes
720 added 0 changesets with 0 changes to 3 files
720 added 0 changesets with 0 changes to 3 files
721 0 unread bytes
721 0 unread bytes
722 addchangegroup return: 1
722 addchangegroup return: 1
723
723
724 with reply
724 with reply
725
725
726 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
726 $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
727 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
727 $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
728 0 unread bytes
728 0 unread bytes
729 addchangegroup return: 1
729 addchangegroup return: 1
730
730
731 $ cat ../rev-reply.hg2
731 $ cat ../rev-reply.hg2
732 HG2X\x00\x00\x003\x15b2x:reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1f (esc)
732 HG2X\x00\x00\x003\x15b2x:reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1f (esc)
733 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
733 b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
734 adding manifests
734 adding manifests
735 adding file changes
735 adding file changes
736 added 0 changesets with 0 changes to 3 files
736 added 0 changesets with 0 changes to 3 files
737 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
737 \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
738
738
739 Real world exchange
739 Real world exchange
740 =====================
740 =====================
741
741
742
742
743 clone --pull
743 clone --pull
744
744
745 $ cd ..
745 $ cd ..
746 $ hg clone main other --pull --rev 9520eea781bc
746 $ hg clone main other --pull --rev 9520eea781bc
747 adding changesets
747 adding changesets
748 adding manifests
748 adding manifests
749 adding file changes
749 adding file changes
750 added 2 changesets with 2 changes to 2 files
750 added 2 changesets with 2 changes to 2 files
751 updating to branch default
751 updating to branch default
752 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
752 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
753 $ hg -R other log -G
753 $ hg -R other log -G
754 @ changeset: 1:9520eea781bc
754 @ changeset: 1:9520eea781bc
755 | tag: tip
755 | tag: tip
756 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
756 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
757 | date: Sat Apr 30 15:24:48 2011 +0200
757 | date: Sat Apr 30 15:24:48 2011 +0200
758 | summary: E
758 | summary: E
759 |
759 |
760 o changeset: 0:cd010b8cd998
760 o changeset: 0:cd010b8cd998
761 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
761 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
762 date: Sat Apr 30 15:24:48 2011 +0200
762 date: Sat Apr 30 15:24:48 2011 +0200
763 summary: A
763 summary: A
764
764
765
765
766 pull
766 pull
767
767
768 $ hg -R other pull -r 24b6387c8c8c
768 $ hg -R other pull -r 24b6387c8c8c
769 pulling from $TESTTMP/main (glob)
769 pulling from $TESTTMP/main (glob)
770 searching for changes
770 searching for changes
771 adding changesets
771 adding changesets
772 adding manifests
772 adding manifests
773 adding file changes
773 adding file changes
774 added 1 changesets with 1 changes to 1 files (+1 heads)
774 added 1 changesets with 1 changes to 1 files (+1 heads)
775 (run 'hg heads' to see heads, 'hg merge' to merge)
775 (run 'hg heads' to see heads, 'hg merge' to merge)
776
776
777 push
777 push
778
778
779 $ hg -R main push other --rev eea13746799a
779 $ hg -R main push other --rev eea13746799a
780 pushing to other
780 pushing to other
781 searching for changes
781 searching for changes
782 remote: adding changesets
782 remote: adding changesets
783 remote: adding manifests
783 remote: adding manifests
784 remote: adding file changes
784 remote: adding file changes
785 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
785 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
786
786
787 pull over ssh
787 pull over ssh
788
788
789 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
789 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
790 pulling from ssh://user@dummy/main
790 pulling from ssh://user@dummy/main
791 searching for changes
791 searching for changes
792 adding changesets
792 adding changesets
793 adding manifests
793 adding manifests
794 adding file changes
794 adding file changes
795 added 1 changesets with 1 changes to 1 files (+1 heads)
795 added 1 changesets with 1 changes to 1 files (+1 heads)
796 (run 'hg heads' to see heads, 'hg merge' to merge)
796 (run 'hg heads' to see heads, 'hg merge' to merge)
797
797
798 pull over http
798 pull over http
799
799
800 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
800 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
801 $ cat main.pid >> $DAEMON_PIDS
801 $ cat main.pid >> $DAEMON_PIDS
802
802
803 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
803 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
804 pulling from http://localhost:$HGPORT/
804 pulling from http://localhost:$HGPORT/
805 searching for changes
805 searching for changes
806 adding changesets
806 adding changesets
807 adding manifests
807 adding manifests
808 adding file changes
808 adding file changes
809 added 1 changesets with 1 changes to 1 files (+1 heads)
809 added 1 changesets with 1 changes to 1 files (+1 heads)
810 (run 'hg heads .' to see heads, 'hg merge' to merge)
810 (run 'hg heads .' to see heads, 'hg merge' to merge)
811 $ cat main-error.log
811 $ cat main-error.log
812
812
813 push over ssh
813 push over ssh
814
814
815 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
815 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
816 pushing to ssh://user@dummy/other
816 pushing to ssh://user@dummy/other
817 searching for changes
817 searching for changes
818 remote: adding changesets
818 remote: adding changesets
819 remote: adding manifests
819 remote: adding manifests
820 remote: adding file changes
820 remote: adding file changes
821 remote: added 1 changesets with 1 changes to 1 files
821 remote: added 1 changesets with 1 changes to 1 files
822
822
823 push over http
823 push over http
824
824
825 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
825 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
826 $ cat other.pid >> $DAEMON_PIDS
826 $ cat other.pid >> $DAEMON_PIDS
827
827
828 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
828 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
829 pushing to http://localhost:$HGPORT2/
829 pushing to http://localhost:$HGPORT2/
830 searching for changes
830 searching for changes
831 remote: adding changesets
831 remote: adding changesets
832 remote: adding manifests
832 remote: adding manifests
833 remote: adding file changes
833 remote: adding file changes
834 remote: added 1 changesets with 1 changes to 1 files
834 remote: added 1 changesets with 1 changes to 1 files
835 $ cat other-error.log
835 $ cat other-error.log
836
836
837 Check final content.
837 Check final content.
838
838
839 $ hg -R other log -G
839 $ hg -R other log -G
840 o changeset: 7:32af7686d403
840 o changeset: 7:32af7686d403
841 | tag: tip
841 | tag: tip
842 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
842 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
843 | date: Sat Apr 30 15:24:48 2011 +0200
843 | date: Sat Apr 30 15:24:48 2011 +0200
844 | summary: D
844 | summary: D
845 |
845 |
846 o changeset: 6:5fddd98957c8
846 o changeset: 6:5fddd98957c8
847 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
847 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
848 | date: Sat Apr 30 15:24:48 2011 +0200
848 | date: Sat Apr 30 15:24:48 2011 +0200
849 | summary: C
849 | summary: C
850 |
850 |
851 o changeset: 5:42ccdea3bb16
851 o changeset: 5:42ccdea3bb16
852 | parent: 0:cd010b8cd998
852 | parent: 0:cd010b8cd998
853 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
853 | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
854 | date: Sat Apr 30 15:24:48 2011 +0200
854 | date: Sat Apr 30 15:24:48 2011 +0200
855 | summary: B
855 | summary: B
856 |
856 |
857 | o changeset: 4:02de42196ebe
857 | o changeset: 4:02de42196ebe
858 | | parent: 2:24b6387c8c8c
858 | | parent: 2:24b6387c8c8c
859 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
859 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
860 | | date: Sat Apr 30 15:24:48 2011 +0200
860 | | date: Sat Apr 30 15:24:48 2011 +0200
861 | | summary: H
861 | | summary: H
862 | |
862 | |
863 | | o changeset: 3:eea13746799a
863 | | o changeset: 3:eea13746799a
864 | |/| parent: 2:24b6387c8c8c
864 | |/| parent: 2:24b6387c8c8c
865 | | | parent: 1:9520eea781bc
865 | | | parent: 1:9520eea781bc
866 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
866 | | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
867 | | | date: Sat Apr 30 15:24:48 2011 +0200
867 | | | date: Sat Apr 30 15:24:48 2011 +0200
868 | | | summary: G
868 | | | summary: G
869 | | |
869 | | |
870 | o | changeset: 2:24b6387c8c8c
870 | o | changeset: 2:24b6387c8c8c
871 |/ / parent: 0:cd010b8cd998
871 |/ / parent: 0:cd010b8cd998
872 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
872 | | user: Nicolas Dumazet <nicdumz.commits@gmail.com>
873 | | date: Sat Apr 30 15:24:48 2011 +0200
873 | | date: Sat Apr 30 15:24:48 2011 +0200
874 | | summary: F
874 | | summary: F
875 | |
875 | |
876 | @ changeset: 1:9520eea781bc
876 | @ changeset: 1:9520eea781bc
877 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
877 |/ user: Nicolas Dumazet <nicdumz.commits@gmail.com>
878 | date: Sat Apr 30 15:24:48 2011 +0200
878 | date: Sat Apr 30 15:24:48 2011 +0200
879 | summary: E
879 | summary: E
880 |
880 |
881 o changeset: 0:cd010b8cd998
881 o changeset: 0:cd010b8cd998
882 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
882 user: Nicolas Dumazet <nicdumz.commits@gmail.com>
883 date: Sat Apr 30 15:24:48 2011 +0200
883 date: Sat Apr 30 15:24:48 2011 +0200
884 summary: A
884 summary: A
885
885
886
886
887 Error Handling
887 Error Handling
888 ==============
888 ==============
889
889
890 Check that errors are properly returned to the client during push.
890 Check that errors are properly returned to the client during push.
891
891
892 Setting up
892 Setting up
893
893
894 $ cat > failpush.py << EOF
894 $ cat > failpush.py << EOF
895 > """A small extension that makes push fails when using bundle2
895 > """A small extension that makes push fails when using bundle2
896 >
896 >
897 > used to test error handling in bundle2
897 > used to test error handling in bundle2
898 > """
898 > """
899 >
899 >
900 > from mercurial import util
900 > from mercurial import util
901 > from mercurial import bundle2
901 > from mercurial import bundle2
902 > from mercurial import exchange
902 > from mercurial import exchange
903 > from mercurial import extensions
903 > from mercurial import extensions
904 >
904 >
905 > def _pushbundle2failpart(orig, pushop, bundler):
905 > def _pushbundle2failpart(orig, pushop, bundler):
906 > extradata = orig(pushop, bundler)
906 > extradata = orig(pushop, bundler)
907 > reason = pushop.ui.config('failpush', 'reason', None)
907 > reason = pushop.ui.config('failpush', 'reason', None)
908 > part = None
908 > part = None
909 > if reason == 'abort':
909 > if reason == 'abort':
910 > part = bundle2.bundlepart('test:abort')
910 > part = bundle2.bundlepart('test:abort')
911 > if reason == 'unknown':
912 > part = bundle2.bundlepart('TEST:UNKNOWN')
911 > if part is not None:
913 > if part is not None:
912 > bundler.addpart(part)
914 > bundler.addpart(part)
913 > return extradata
915 > return extradata
914 >
916 >
915 > @bundle2.parthandler("test:abort")
917 > @bundle2.parthandler("test:abort")
916 > def handleabort(op, part):
918 > def handleabort(op, part):
917 > raise util.Abort('Abandon ship!', hint="don't panic")
919 > raise util.Abort('Abandon ship!', hint="don't panic")
918 >
920 >
919 > def uisetup(ui):
921 > def uisetup(ui):
920 > extensions.wrapfunction(exchange, '_pushbundle2extraparts', _pushbundle2failpart)
922 > extensions.wrapfunction(exchange, '_pushbundle2extraparts', _pushbundle2failpart)
921 >
923 >
922 > EOF
924 > EOF
923
925
924 $ cd main
926 $ cd main
925 $ hg up tip
927 $ hg up tip
926 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
928 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
927 $ echo 'I' > I
929 $ echo 'I' > I
928 $ hg add I
930 $ hg add I
929 $ hg ci -m 'I'
931 $ hg ci -m 'I'
930 $ hg id
932 $ hg id
931 e7ec4e813ba6 tip
933 e7ec4e813ba6 tip
932 $ cd ..
934 $ cd ..
933
935
934 $ cat << EOF >> $HGRCPATH
936 $ cat << EOF >> $HGRCPATH
935 > [extensions]
937 > [extensions]
936 > failpush=$TESTTMP/failpush.py
938 > failpush=$TESTTMP/failpush.py
937 > EOF
939 > EOF
938
940
939 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
941 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
940 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
942 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
941 $ cat other.pid >> $DAEMON_PIDS
943 $ cat other.pid >> $DAEMON_PIDS
942
944
943 Doing the actual push: Abort error
945 Doing the actual push: Abort error
944
946
945 $ cat << EOF >> $HGRCPATH
947 $ cat << EOF >> $HGRCPATH
946 > [failpush]
948 > [failpush]
947 > reason = abort
949 > reason = abort
948 > EOF
950 > EOF
949
951
950 $ hg -R main push other -r e7ec4e813ba6
952 $ hg -R main push other -r e7ec4e813ba6
951 pushing to other
953 pushing to other
952 searching for changes
954 searching for changes
953 abort: Abandon ship!
955 abort: Abandon ship!
954 (don't panic)
956 (don't panic)
955 [255]
957 [255]
956
958
957 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
959 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
958 pushing to ssh://user@dummy/other
960 pushing to ssh://user@dummy/other
959 searching for changes
961 searching for changes
960 abort: Abandon ship!
962 abort: Abandon ship!
961 (don't panic)
963 (don't panic)
962 [255]
964 [255]
963
965
964 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
966 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
965 pushing to http://localhost:$HGPORT2/
967 pushing to http://localhost:$HGPORT2/
966 searching for changes
968 searching for changes
967 abort: Abandon ship!
969 abort: Abandon ship!
968 (don't panic)
970 (don't panic)
969 [255]
971 [255]
970
972
971
973
974 Doing the actual push: unknown mandatory parts
975
976 $ cat << EOF >> $HGRCPATH
977 > [failpush]
978 > reason = unknown
979 > EOF
980
981 $ hg -R main push other -r e7ec4e813ba6
982 pushing to other
983 searching for changes
984 abort: missing support for 'test:unknown'
985 [255]
986
987 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
988 pushing to ssh://user@dummy/other
989 searching for changes
990 abort: missing support for "'test:unknown'"
991 [255]
992
993 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
994 pushing to http://localhost:$HGPORT2/
995 searching for changes
996 abort: missing support for "'test:unknown'"
997 [255]
General Comments 0
You need to be logged in to leave comments. Login now