##// END OF EJS Templates
bundle2: add a `obsmarkersversion` function to extract supported version...
Pierre-Yves David -
r22344:9829b794 default
parent child Browse files
Show More
@@ -1,939 +1,946 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 Each parameter's key MUST be unique within the part.
116 Each parameter's key MUST be unique within the part.
117
117
118 :payload:
118 :payload:
119
119
120 payload is a series of `<chunksize><chunkdata>`.
120 payload is a series of `<chunksize><chunkdata>`.
121
121
122 `chunksize` is a 32 bits integer, `chunkdata` are plain bytes (as much as
122 `chunksize` is a 32 bits integer, `chunkdata` are plain bytes (as much as
123 `chunksize` says)` The payload part is concluded by a zero size chunk.
123 `chunksize` says)` The payload part is concluded by a zero size chunk.
124
124
125 The current implementation always produces either zero or one chunk.
125 The current implementation always produces either zero or one chunk.
126 This is an implementation limitation that will ultimately be lifted.
126 This is an implementation limitation that will ultimately be lifted.
127
127
128 Bundle processing
128 Bundle processing
129 ============================
129 ============================
130
130
131 Each part is processed in order using a "part handler". Handler are registered
131 Each part is processed in order using a "part handler". Handler are registered
132 for a certain part type.
132 for a certain part type.
133
133
134 The matching of a part to its handler is case insensitive. The case of the
134 The matching of a part to its handler is case insensitive. The case of the
135 part type is used to know if a part is mandatory or advisory. If the Part type
135 part type is used to know if a part is mandatory or advisory. If the Part type
136 contains any uppercase char it is considered mandatory. When no handler is
136 contains any uppercase char it is considered mandatory. When no handler is
137 known for a Mandatory part, the process is aborted and an exception is raised.
137 known for a Mandatory part, the process is aborted and an exception is raised.
138 If the part is advisory and no handler is known, the part is ignored. When the
138 If the part is advisory and no handler is known, the part is ignored. When the
139 process is aborted, the full bundle is still read from the stream to keep the
139 process is aborted, the full bundle is still read from the stream to keep the
140 channel usable. But none of the part read from an abort are processed. In the
140 channel usable. But none of the part read from an abort are processed. In the
141 future, dropping the stream may become an option for channel we do not care to
141 future, dropping the stream may become an option for channel we do not care to
142 preserve.
142 preserve.
143 """
143 """
144
144
145 import util
145 import util
146 import struct
146 import struct
147 import urllib
147 import urllib
148 import string
148 import string
149 import obsolete
149 import pushkey
150 import pushkey
150
151
151 import changegroup, error
152 import changegroup, error
152 from i18n import _
153 from i18n import _
153
154
154 _pack = struct.pack
155 _pack = struct.pack
155 _unpack = struct.unpack
156 _unpack = struct.unpack
156
157
157 _magicstring = 'HG2X'
158 _magicstring = 'HG2X'
158
159
159 _fstreamparamsize = '>H'
160 _fstreamparamsize = '>H'
160 _fpartheadersize = '>H'
161 _fpartheadersize = '>H'
161 _fparttypesize = '>B'
162 _fparttypesize = '>B'
162 _fpartid = '>I'
163 _fpartid = '>I'
163 _fpayloadsize = '>I'
164 _fpayloadsize = '>I'
164 _fpartparamcount = '>BB'
165 _fpartparamcount = '>BB'
165
166
166 preferedchunksize = 4096
167 preferedchunksize = 4096
167
168
168 def _makefpartparamsizes(nbparams):
169 def _makefpartparamsizes(nbparams):
169 """return a struct format to read part parameter sizes
170 """return a struct format to read part parameter sizes
170
171
171 The number parameters is variable so we need to build that format
172 The number parameters is variable so we need to build that format
172 dynamically.
173 dynamically.
173 """
174 """
174 return '>'+('BB'*nbparams)
175 return '>'+('BB'*nbparams)
175
176
176 parthandlermapping = {}
177 parthandlermapping = {}
177
178
178 def parthandler(parttype, params=()):
179 def parthandler(parttype, params=()):
179 """decorator that register a function as a bundle2 part handler
180 """decorator that register a function as a bundle2 part handler
180
181
181 eg::
182 eg::
182
183
183 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
184 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
184 def myparttypehandler(...):
185 def myparttypehandler(...):
185 '''process a part of type "my part".'''
186 '''process a part of type "my part".'''
186 ...
187 ...
187 """
188 """
188 def _decorator(func):
189 def _decorator(func):
189 lparttype = parttype.lower() # enforce lower case matching.
190 lparttype = parttype.lower() # enforce lower case matching.
190 assert lparttype not in parthandlermapping
191 assert lparttype not in parthandlermapping
191 parthandlermapping[lparttype] = func
192 parthandlermapping[lparttype] = func
192 func.params = frozenset(params)
193 func.params = frozenset(params)
193 return func
194 return func
194 return _decorator
195 return _decorator
195
196
196 class unbundlerecords(object):
197 class unbundlerecords(object):
197 """keep record of what happens during and unbundle
198 """keep record of what happens during and unbundle
198
199
199 New records are added using `records.add('cat', obj)`. Where 'cat' is a
200 New records are added using `records.add('cat', obj)`. Where 'cat' is a
200 category of record and obj is an arbitrary object.
201 category of record and obj is an arbitrary object.
201
202
202 `records['cat']` will return all entries of this category 'cat'.
203 `records['cat']` will return all entries of this category 'cat'.
203
204
204 Iterating on the object itself will yield `('category', obj)` tuples
205 Iterating on the object itself will yield `('category', obj)` tuples
205 for all entries.
206 for all entries.
206
207
207 All iterations happens in chronological order.
208 All iterations happens in chronological order.
208 """
209 """
209
210
210 def __init__(self):
211 def __init__(self):
211 self._categories = {}
212 self._categories = {}
212 self._sequences = []
213 self._sequences = []
213 self._replies = {}
214 self._replies = {}
214
215
215 def add(self, category, entry, inreplyto=None):
216 def add(self, category, entry, inreplyto=None):
216 """add a new record of a given category.
217 """add a new record of a given category.
217
218
218 The entry can then be retrieved in the list returned by
219 The entry can then be retrieved in the list returned by
219 self['category']."""
220 self['category']."""
220 self._categories.setdefault(category, []).append(entry)
221 self._categories.setdefault(category, []).append(entry)
221 self._sequences.append((category, entry))
222 self._sequences.append((category, entry))
222 if inreplyto is not None:
223 if inreplyto is not None:
223 self.getreplies(inreplyto).add(category, entry)
224 self.getreplies(inreplyto).add(category, entry)
224
225
225 def getreplies(self, partid):
226 def getreplies(self, partid):
226 """get the subrecords that replies to a specific part"""
227 """get the subrecords that replies to a specific part"""
227 return self._replies.setdefault(partid, unbundlerecords())
228 return self._replies.setdefault(partid, unbundlerecords())
228
229
229 def __getitem__(self, cat):
230 def __getitem__(self, cat):
230 return tuple(self._categories.get(cat, ()))
231 return tuple(self._categories.get(cat, ()))
231
232
232 def __iter__(self):
233 def __iter__(self):
233 return iter(self._sequences)
234 return iter(self._sequences)
234
235
235 def __len__(self):
236 def __len__(self):
236 return len(self._sequences)
237 return len(self._sequences)
237
238
238 def __nonzero__(self):
239 def __nonzero__(self):
239 return bool(self._sequences)
240 return bool(self._sequences)
240
241
241 class bundleoperation(object):
242 class bundleoperation(object):
242 """an object that represents a single bundling process
243 """an object that represents a single bundling process
243
244
244 Its purpose is to carry unbundle-related objects and states.
245 Its purpose is to carry unbundle-related objects and states.
245
246
246 A new object should be created at the beginning of each bundle processing.
247 A new object should be created at the beginning of each bundle processing.
247 The object is to be returned by the processing function.
248 The object is to be returned by the processing function.
248
249
249 The object has very little content now it will ultimately contain:
250 The object has very little content now it will ultimately contain:
250 * an access to the repo the bundle is applied to,
251 * an access to the repo the bundle is applied to,
251 * a ui object,
252 * a ui object,
252 * a way to retrieve a transaction to add changes to the repo,
253 * a way to retrieve a transaction to add changes to the repo,
253 * a way to record the result of processing each part,
254 * a way to record the result of processing each part,
254 * a way to construct a bundle response when applicable.
255 * a way to construct a bundle response when applicable.
255 """
256 """
256
257
257 def __init__(self, repo, transactiongetter):
258 def __init__(self, repo, transactiongetter):
258 self.repo = repo
259 self.repo = repo
259 self.ui = repo.ui
260 self.ui = repo.ui
260 self.records = unbundlerecords()
261 self.records = unbundlerecords()
261 self.gettransaction = transactiongetter
262 self.gettransaction = transactiongetter
262 self.reply = None
263 self.reply = None
263
264
264 class TransactionUnavailable(RuntimeError):
265 class TransactionUnavailable(RuntimeError):
265 pass
266 pass
266
267
267 def _notransaction():
268 def _notransaction():
268 """default method to get a transaction while processing a bundle
269 """default method to get a transaction while processing a bundle
269
270
270 Raise an exception to highlight the fact that no transaction was expected
271 Raise an exception to highlight the fact that no transaction was expected
271 to be created"""
272 to be created"""
272 raise TransactionUnavailable()
273 raise TransactionUnavailable()
273
274
274 def processbundle(repo, unbundler, transactiongetter=_notransaction):
275 def processbundle(repo, unbundler, transactiongetter=_notransaction):
275 """This function process a bundle, apply effect to/from a repo
276 """This function process a bundle, apply effect to/from a repo
276
277
277 It iterates over each part then searches for and uses the proper handling
278 It iterates over each part then searches for and uses the proper handling
278 code to process the part. Parts are processed in order.
279 code to process the part. Parts are processed in order.
279
280
280 This is very early version of this function that will be strongly reworked
281 This is very early version of this function that will be strongly reworked
281 before final usage.
282 before final usage.
282
283
283 Unknown Mandatory part will abort the process.
284 Unknown Mandatory part will abort the process.
284 """
285 """
285 op = bundleoperation(repo, transactiongetter)
286 op = bundleoperation(repo, transactiongetter)
286 # todo:
287 # todo:
287 # - replace this is a init function soon.
288 # - replace this is a init function soon.
288 # - exception catching
289 # - exception catching
289 unbundler.params
290 unbundler.params
290 iterparts = unbundler.iterparts()
291 iterparts = unbundler.iterparts()
291 part = None
292 part = None
292 try:
293 try:
293 for part in iterparts:
294 for part in iterparts:
294 parttype = part.type
295 parttype = part.type
295 # part key are matched lower case
296 # part key are matched lower case
296 key = parttype.lower()
297 key = parttype.lower()
297 try:
298 try:
298 handler = parthandlermapping.get(key)
299 handler = parthandlermapping.get(key)
299 if handler is None:
300 if handler is None:
300 raise error.BundleValueError(parttype=key)
301 raise error.BundleValueError(parttype=key)
301 op.ui.debug('found a handler for part %r\n' % parttype)
302 op.ui.debug('found a handler for part %r\n' % parttype)
302 unknownparams = part.mandatorykeys - handler.params
303 unknownparams = part.mandatorykeys - handler.params
303 if unknownparams:
304 if unknownparams:
304 unknownparams = list(unknownparams)
305 unknownparams = list(unknownparams)
305 unknownparams.sort()
306 unknownparams.sort()
306 raise error.BundleValueError(parttype=key,
307 raise error.BundleValueError(parttype=key,
307 params=unknownparams)
308 params=unknownparams)
308 except error.BundleValueError, exc:
309 except error.BundleValueError, exc:
309 if key != parttype: # mandatory parts
310 if key != parttype: # mandatory parts
310 raise
311 raise
311 op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
312 op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
312 # consuming the part
313 # consuming the part
313 part.read()
314 part.read()
314 continue
315 continue
315
316
316
317
317 # handler is called outside the above try block so that we don't
318 # handler is called outside the above try block so that we don't
318 # risk catching KeyErrors from anything other than the
319 # risk catching KeyErrors from anything other than the
319 # parthandlermapping lookup (any KeyError raised by handler()
320 # parthandlermapping lookup (any KeyError raised by handler()
320 # itself represents a defect of a different variety).
321 # itself represents a defect of a different variety).
321 output = None
322 output = None
322 if op.reply is not None:
323 if op.reply is not None:
323 op.ui.pushbuffer(error=True)
324 op.ui.pushbuffer(error=True)
324 output = ''
325 output = ''
325 try:
326 try:
326 handler(op, part)
327 handler(op, part)
327 finally:
328 finally:
328 if output is not None:
329 if output is not None:
329 output = op.ui.popbuffer()
330 output = op.ui.popbuffer()
330 if output:
331 if output:
331 outpart = op.reply.newpart('b2x:output', data=output)
332 outpart = op.reply.newpart('b2x:output', data=output)
332 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
333 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
333 part.read()
334 part.read()
334 except Exception, exc:
335 except Exception, exc:
335 if part is not None:
336 if part is not None:
336 # consume the bundle content
337 # consume the bundle content
337 part.read()
338 part.read()
338 for part in iterparts:
339 for part in iterparts:
339 # consume the bundle content
340 # consume the bundle content
340 part.read()
341 part.read()
341 # Small hack to let caller code distinguish exceptions from bundle2
342 # Small hack to let caller code distinguish exceptions from bundle2
342 # processing fron the ones from bundle1 processing. This is mostly
343 # processing fron the ones from bundle1 processing. This is mostly
343 # needed to handle different return codes to unbundle according to the
344 # needed to handle different return codes to unbundle according to the
344 # type of bundle. We should probably clean up or drop this return code
345 # type of bundle. We should probably clean up or drop this return code
345 # craziness in a future version.
346 # craziness in a future version.
346 exc.duringunbundle2 = True
347 exc.duringunbundle2 = True
347 raise
348 raise
348 return op
349 return op
349
350
350 def decodecaps(blob):
351 def decodecaps(blob):
351 """decode a bundle2 caps bytes blob into a dictionnary
352 """decode a bundle2 caps bytes blob into a dictionnary
352
353
353 The blob is a list of capabilities (one per line)
354 The blob is a list of capabilities (one per line)
354 Capabilities may have values using a line of the form::
355 Capabilities may have values using a line of the form::
355
356
356 capability=value1,value2,value3
357 capability=value1,value2,value3
357
358
358 The values are always a list."""
359 The values are always a list."""
359 caps = {}
360 caps = {}
360 for line in blob.splitlines():
361 for line in blob.splitlines():
361 if not line:
362 if not line:
362 continue
363 continue
363 if '=' not in line:
364 if '=' not in line:
364 key, vals = line, ()
365 key, vals = line, ()
365 else:
366 else:
366 key, vals = line.split('=', 1)
367 key, vals = line.split('=', 1)
367 vals = vals.split(',')
368 vals = vals.split(',')
368 key = urllib.unquote(key)
369 key = urllib.unquote(key)
369 vals = [urllib.unquote(v) for v in vals]
370 vals = [urllib.unquote(v) for v in vals]
370 caps[key] = vals
371 caps[key] = vals
371 return caps
372 return caps
372
373
373 def encodecaps(caps):
374 def encodecaps(caps):
374 """encode a bundle2 caps dictionary into a bytes blob"""
375 """encode a bundle2 caps dictionary into a bytes blob"""
375 chunks = []
376 chunks = []
376 for ca in sorted(caps):
377 for ca in sorted(caps):
377 vals = caps[ca]
378 vals = caps[ca]
378 ca = urllib.quote(ca)
379 ca = urllib.quote(ca)
379 vals = [urllib.quote(v) for v in vals]
380 vals = [urllib.quote(v) for v in vals]
380 if vals:
381 if vals:
381 ca = "%s=%s" % (ca, ','.join(vals))
382 ca = "%s=%s" % (ca, ','.join(vals))
382 chunks.append(ca)
383 chunks.append(ca)
383 return '\n'.join(chunks)
384 return '\n'.join(chunks)
384
385
385 class bundle20(object):
386 class bundle20(object):
386 """represent an outgoing bundle2 container
387 """represent an outgoing bundle2 container
387
388
388 Use the `addparam` method to add stream level parameter. and `newpart` to
389 Use the `addparam` method to add stream level parameter. and `newpart` to
389 populate it. Then call `getchunks` to retrieve all the binary chunks of
390 populate it. Then call `getchunks` to retrieve all the binary chunks of
390 data that compose the bundle2 container."""
391 data that compose the bundle2 container."""
391
392
392 def __init__(self, ui, capabilities=()):
393 def __init__(self, ui, capabilities=()):
393 self.ui = ui
394 self.ui = ui
394 self._params = []
395 self._params = []
395 self._parts = []
396 self._parts = []
396 self.capabilities = dict(capabilities)
397 self.capabilities = dict(capabilities)
397
398
398 @property
399 @property
399 def nbparts(self):
400 def nbparts(self):
400 """total number of parts added to the bundler"""
401 """total number of parts added to the bundler"""
401 return len(self._parts)
402 return len(self._parts)
402
403
403 # methods used to defines the bundle2 content
404 # methods used to defines the bundle2 content
404 def addparam(self, name, value=None):
405 def addparam(self, name, value=None):
405 """add a stream level parameter"""
406 """add a stream level parameter"""
406 if not name:
407 if not name:
407 raise ValueError('empty parameter name')
408 raise ValueError('empty parameter name')
408 if name[0] not in string.letters:
409 if name[0] not in string.letters:
409 raise ValueError('non letter first character: %r' % name)
410 raise ValueError('non letter first character: %r' % name)
410 self._params.append((name, value))
411 self._params.append((name, value))
411
412
412 def addpart(self, part):
413 def addpart(self, part):
413 """add a new part to the bundle2 container
414 """add a new part to the bundle2 container
414
415
415 Parts contains the actual applicative payload."""
416 Parts contains the actual applicative payload."""
416 assert part.id is None
417 assert part.id is None
417 part.id = len(self._parts) # very cheap counter
418 part.id = len(self._parts) # very cheap counter
418 self._parts.append(part)
419 self._parts.append(part)
419
420
420 def newpart(self, typeid, *args, **kwargs):
421 def newpart(self, typeid, *args, **kwargs):
421 """create a new part and add it to the containers
422 """create a new part and add it to the containers
422
423
423 As the part is directly added to the containers. For now, this means
424 As the part is directly added to the containers. For now, this means
424 that any failure to properly initialize the part after calling
425 that any failure to properly initialize the part after calling
425 ``newpart`` should result in a failure of the whole bundling process.
426 ``newpart`` should result in a failure of the whole bundling process.
426
427
427 You can still fall back to manually create and add if you need better
428 You can still fall back to manually create and add if you need better
428 control."""
429 control."""
429 part = bundlepart(typeid, *args, **kwargs)
430 part = bundlepart(typeid, *args, **kwargs)
430 self.addpart(part)
431 self.addpart(part)
431 return part
432 return part
432
433
433 # methods used to generate the bundle2 stream
434 # methods used to generate the bundle2 stream
434 def getchunks(self):
435 def getchunks(self):
435 self.ui.debug('start emission of %s stream\n' % _magicstring)
436 self.ui.debug('start emission of %s stream\n' % _magicstring)
436 yield _magicstring
437 yield _magicstring
437 param = self._paramchunk()
438 param = self._paramchunk()
438 self.ui.debug('bundle parameter: %s\n' % param)
439 self.ui.debug('bundle parameter: %s\n' % param)
439 yield _pack(_fstreamparamsize, len(param))
440 yield _pack(_fstreamparamsize, len(param))
440 if param:
441 if param:
441 yield param
442 yield param
442
443
443 self.ui.debug('start of parts\n')
444 self.ui.debug('start of parts\n')
444 for part in self._parts:
445 for part in self._parts:
445 self.ui.debug('bundle part: "%s"\n' % part.type)
446 self.ui.debug('bundle part: "%s"\n' % part.type)
446 for chunk in part.getchunks():
447 for chunk in part.getchunks():
447 yield chunk
448 yield chunk
448 self.ui.debug('end of bundle\n')
449 self.ui.debug('end of bundle\n')
449 yield '\0\0'
450 yield '\0\0'
450
451
451 def _paramchunk(self):
452 def _paramchunk(self):
452 """return a encoded version of all stream parameters"""
453 """return a encoded version of all stream parameters"""
453 blocks = []
454 blocks = []
454 for par, value in self._params:
455 for par, value in self._params:
455 par = urllib.quote(par)
456 par = urllib.quote(par)
456 if value is not None:
457 if value is not None:
457 value = urllib.quote(value)
458 value = urllib.quote(value)
458 par = '%s=%s' % (par, value)
459 par = '%s=%s' % (par, value)
459 blocks.append(par)
460 blocks.append(par)
460 return ' '.join(blocks)
461 return ' '.join(blocks)
461
462
462 class unpackermixin(object):
463 class unpackermixin(object):
463 """A mixin to extract bytes and struct data from a stream"""
464 """A mixin to extract bytes and struct data from a stream"""
464
465
465 def __init__(self, fp):
466 def __init__(self, fp):
466 self._fp = fp
467 self._fp = fp
467
468
468 def _unpack(self, format):
469 def _unpack(self, format):
469 """unpack this struct format from the stream"""
470 """unpack this struct format from the stream"""
470 data = self._readexact(struct.calcsize(format))
471 data = self._readexact(struct.calcsize(format))
471 return _unpack(format, data)
472 return _unpack(format, data)
472
473
473 def _readexact(self, size):
474 def _readexact(self, size):
474 """read exactly <size> bytes from the stream"""
475 """read exactly <size> bytes from the stream"""
475 return changegroup.readexactly(self._fp, size)
476 return changegroup.readexactly(self._fp, size)
476
477
477
478
478 class unbundle20(unpackermixin):
479 class unbundle20(unpackermixin):
479 """interpret a bundle2 stream
480 """interpret a bundle2 stream
480
481
481 This class is fed with a binary stream and yields parts through its
482 This class is fed with a binary stream and yields parts through its
482 `iterparts` methods."""
483 `iterparts` methods."""
483
484
484 def __init__(self, ui, fp, header=None):
485 def __init__(self, ui, fp, header=None):
485 """If header is specified, we do not read it out of the stream."""
486 """If header is specified, we do not read it out of the stream."""
486 self.ui = ui
487 self.ui = ui
487 super(unbundle20, self).__init__(fp)
488 super(unbundle20, self).__init__(fp)
488 if header is None:
489 if header is None:
489 header = self._readexact(4)
490 header = self._readexact(4)
490 magic, version = header[0:2], header[2:4]
491 magic, version = header[0:2], header[2:4]
491 if magic != 'HG':
492 if magic != 'HG':
492 raise util.Abort(_('not a Mercurial bundle'))
493 raise util.Abort(_('not a Mercurial bundle'))
493 if version != '2X':
494 if version != '2X':
494 raise util.Abort(_('unknown bundle version %s') % version)
495 raise util.Abort(_('unknown bundle version %s') % version)
495 self.ui.debug('start processing of %s stream\n' % header)
496 self.ui.debug('start processing of %s stream\n' % header)
496
497
497 @util.propertycache
498 @util.propertycache
498 def params(self):
499 def params(self):
499 """dictionary of stream level parameters"""
500 """dictionary of stream level parameters"""
500 self.ui.debug('reading bundle2 stream parameters\n')
501 self.ui.debug('reading bundle2 stream parameters\n')
501 params = {}
502 params = {}
502 paramssize = self._unpack(_fstreamparamsize)[0]
503 paramssize = self._unpack(_fstreamparamsize)[0]
503 if paramssize:
504 if paramssize:
504 for p in self._readexact(paramssize).split(' '):
505 for p in self._readexact(paramssize).split(' '):
505 p = p.split('=', 1)
506 p = p.split('=', 1)
506 p = [urllib.unquote(i) for i in p]
507 p = [urllib.unquote(i) for i in p]
507 if len(p) < 2:
508 if len(p) < 2:
508 p.append(None)
509 p.append(None)
509 self._processparam(*p)
510 self._processparam(*p)
510 params[p[0]] = p[1]
511 params[p[0]] = p[1]
511 return params
512 return params
512
513
513 def _processparam(self, name, value):
514 def _processparam(self, name, value):
514 """process a parameter, applying its effect if needed
515 """process a parameter, applying its effect if needed
515
516
516 Parameter starting with a lower case letter are advisory and will be
517 Parameter starting with a lower case letter are advisory and will be
517 ignored when unknown. Those starting with an upper case letter are
518 ignored when unknown. Those starting with an upper case letter are
518 mandatory and will this function will raise a KeyError when unknown.
519 mandatory and will this function will raise a KeyError when unknown.
519
520
520 Note: no option are currently supported. Any input will be either
521 Note: no option are currently supported. Any input will be either
521 ignored or failing.
522 ignored or failing.
522 """
523 """
523 if not name:
524 if not name:
524 raise ValueError('empty parameter name')
525 raise ValueError('empty parameter name')
525 if name[0] not in string.letters:
526 if name[0] not in string.letters:
526 raise ValueError('non letter first character: %r' % name)
527 raise ValueError('non letter first character: %r' % name)
527 # Some logic will be later added here to try to process the option for
528 # Some logic will be later added here to try to process the option for
528 # a dict of known parameter.
529 # a dict of known parameter.
529 if name[0].islower():
530 if name[0].islower():
530 self.ui.debug("ignoring unknown parameter %r\n" % name)
531 self.ui.debug("ignoring unknown parameter %r\n" % name)
531 else:
532 else:
532 raise error.BundleValueError(params=(name,))
533 raise error.BundleValueError(params=(name,))
533
534
534
535
535 def iterparts(self):
536 def iterparts(self):
536 """yield all parts contained in the stream"""
537 """yield all parts contained in the stream"""
537 # make sure param have been loaded
538 # make sure param have been loaded
538 self.params
539 self.params
539 self.ui.debug('start extraction of bundle2 parts\n')
540 self.ui.debug('start extraction of bundle2 parts\n')
540 headerblock = self._readpartheader()
541 headerblock = self._readpartheader()
541 while headerblock is not None:
542 while headerblock is not None:
542 part = unbundlepart(self.ui, headerblock, self._fp)
543 part = unbundlepart(self.ui, headerblock, self._fp)
543 yield part
544 yield part
544 headerblock = self._readpartheader()
545 headerblock = self._readpartheader()
545 self.ui.debug('end of bundle2 stream\n')
546 self.ui.debug('end of bundle2 stream\n')
546
547
547 def _readpartheader(self):
548 def _readpartheader(self):
548 """reads a part header size and return the bytes blob
549 """reads a part header size and return the bytes blob
549
550
550 returns None if empty"""
551 returns None if empty"""
551 headersize = self._unpack(_fpartheadersize)[0]
552 headersize = self._unpack(_fpartheadersize)[0]
552 self.ui.debug('part header size: %i\n' % headersize)
553 self.ui.debug('part header size: %i\n' % headersize)
553 if headersize:
554 if headersize:
554 return self._readexact(headersize)
555 return self._readexact(headersize)
555 return None
556 return None
556
557
557
558
558 class bundlepart(object):
559 class bundlepart(object):
559 """A bundle2 part contains application level payload
560 """A bundle2 part contains application level payload
560
561
561 The part `type` is used to route the part to the application level
562 The part `type` is used to route the part to the application level
562 handler.
563 handler.
563
564
564 The part payload is contained in ``part.data``. It could be raw bytes or a
565 The part payload is contained in ``part.data``. It could be raw bytes or a
565 generator of byte chunks.
566 generator of byte chunks.
566
567
567 You can add parameters to the part using the ``addparam`` method.
568 You can add parameters to the part using the ``addparam`` method.
568 Parameters can be either mandatory (default) or advisory. Remote side
569 Parameters can be either mandatory (default) or advisory. Remote side
569 should be able to safely ignore the advisory ones.
570 should be able to safely ignore the advisory ones.
570
571
571 Both data and parameters cannot be modified after the generation has begun.
572 Both data and parameters cannot be modified after the generation has begun.
572 """
573 """
573
574
574 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
575 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
575 data=''):
576 data=''):
576 self.id = None
577 self.id = None
577 self.type = parttype
578 self.type = parttype
578 self._data = data
579 self._data = data
579 self._mandatoryparams = list(mandatoryparams)
580 self._mandatoryparams = list(mandatoryparams)
580 self._advisoryparams = list(advisoryparams)
581 self._advisoryparams = list(advisoryparams)
581 # checking for duplicated entries
582 # checking for duplicated entries
582 self._seenparams = set()
583 self._seenparams = set()
583 for pname, __ in self._mandatoryparams + self._advisoryparams:
584 for pname, __ in self._mandatoryparams + self._advisoryparams:
584 if pname in self._seenparams:
585 if pname in self._seenparams:
585 raise RuntimeError('duplicated params: %s' % pname)
586 raise RuntimeError('duplicated params: %s' % pname)
586 self._seenparams.add(pname)
587 self._seenparams.add(pname)
587 # status of the part's generation:
588 # status of the part's generation:
588 # - None: not started,
589 # - None: not started,
589 # - False: currently generated,
590 # - False: currently generated,
590 # - True: generation done.
591 # - True: generation done.
591 self._generated = None
592 self._generated = None
592
593
593 # methods used to defines the part content
594 # methods used to defines the part content
594 def __setdata(self, data):
595 def __setdata(self, data):
595 if self._generated is not None:
596 if self._generated is not None:
596 raise error.ReadOnlyPartError('part is being generated')
597 raise error.ReadOnlyPartError('part is being generated')
597 self._data = data
598 self._data = data
598 def __getdata(self):
599 def __getdata(self):
599 return self._data
600 return self._data
600 data = property(__getdata, __setdata)
601 data = property(__getdata, __setdata)
601
602
602 @property
603 @property
603 def mandatoryparams(self):
604 def mandatoryparams(self):
604 # make it an immutable tuple to force people through ``addparam``
605 # make it an immutable tuple to force people through ``addparam``
605 return tuple(self._mandatoryparams)
606 return tuple(self._mandatoryparams)
606
607
607 @property
608 @property
608 def advisoryparams(self):
609 def advisoryparams(self):
609 # make it an immutable tuple to force people through ``addparam``
610 # make it an immutable tuple to force people through ``addparam``
610 return tuple(self._advisoryparams)
611 return tuple(self._advisoryparams)
611
612
612 def addparam(self, name, value='', mandatory=True):
613 def addparam(self, name, value='', mandatory=True):
613 if self._generated is not None:
614 if self._generated is not None:
614 raise error.ReadOnlyPartError('part is being generated')
615 raise error.ReadOnlyPartError('part is being generated')
615 if name in self._seenparams:
616 if name in self._seenparams:
616 raise ValueError('duplicated params: %s' % name)
617 raise ValueError('duplicated params: %s' % name)
617 self._seenparams.add(name)
618 self._seenparams.add(name)
618 params = self._advisoryparams
619 params = self._advisoryparams
619 if mandatory:
620 if mandatory:
620 params = self._mandatoryparams
621 params = self._mandatoryparams
621 params.append((name, value))
622 params.append((name, value))
622
623
623 # methods used to generates the bundle2 stream
624 # methods used to generates the bundle2 stream
624 def getchunks(self):
625 def getchunks(self):
625 if self._generated is not None:
626 if self._generated is not None:
626 raise RuntimeError('part can only be consumed once')
627 raise RuntimeError('part can only be consumed once')
627 self._generated = False
628 self._generated = False
628 #### header
629 #### header
629 ## parttype
630 ## parttype
630 header = [_pack(_fparttypesize, len(self.type)),
631 header = [_pack(_fparttypesize, len(self.type)),
631 self.type, _pack(_fpartid, self.id),
632 self.type, _pack(_fpartid, self.id),
632 ]
633 ]
633 ## parameters
634 ## parameters
634 # count
635 # count
635 manpar = self.mandatoryparams
636 manpar = self.mandatoryparams
636 advpar = self.advisoryparams
637 advpar = self.advisoryparams
637 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
638 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
638 # size
639 # size
639 parsizes = []
640 parsizes = []
640 for key, value in manpar:
641 for key, value in manpar:
641 parsizes.append(len(key))
642 parsizes.append(len(key))
642 parsizes.append(len(value))
643 parsizes.append(len(value))
643 for key, value in advpar:
644 for key, value in advpar:
644 parsizes.append(len(key))
645 parsizes.append(len(key))
645 parsizes.append(len(value))
646 parsizes.append(len(value))
646 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
647 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
647 header.append(paramsizes)
648 header.append(paramsizes)
648 # key, value
649 # key, value
649 for key, value in manpar:
650 for key, value in manpar:
650 header.append(key)
651 header.append(key)
651 header.append(value)
652 header.append(value)
652 for key, value in advpar:
653 for key, value in advpar:
653 header.append(key)
654 header.append(key)
654 header.append(value)
655 header.append(value)
655 ## finalize header
656 ## finalize header
656 headerchunk = ''.join(header)
657 headerchunk = ''.join(header)
657 yield _pack(_fpartheadersize, len(headerchunk))
658 yield _pack(_fpartheadersize, len(headerchunk))
658 yield headerchunk
659 yield headerchunk
659 ## payload
660 ## payload
660 for chunk in self._payloadchunks():
661 for chunk in self._payloadchunks():
661 yield _pack(_fpayloadsize, len(chunk))
662 yield _pack(_fpayloadsize, len(chunk))
662 yield chunk
663 yield chunk
663 # end of payload
664 # end of payload
664 yield _pack(_fpayloadsize, 0)
665 yield _pack(_fpayloadsize, 0)
665 self._generated = True
666 self._generated = True
666
667
667 def _payloadchunks(self):
668 def _payloadchunks(self):
668 """yield chunks of a the part payload
669 """yield chunks of a the part payload
669
670
670 Exists to handle the different methods to provide data to a part."""
671 Exists to handle the different methods to provide data to a part."""
671 # we only support fixed size data now.
672 # we only support fixed size data now.
672 # This will be improved in the future.
673 # This will be improved in the future.
673 if util.safehasattr(self.data, 'next'):
674 if util.safehasattr(self.data, 'next'):
674 buff = util.chunkbuffer(self.data)
675 buff = util.chunkbuffer(self.data)
675 chunk = buff.read(preferedchunksize)
676 chunk = buff.read(preferedchunksize)
676 while chunk:
677 while chunk:
677 yield chunk
678 yield chunk
678 chunk = buff.read(preferedchunksize)
679 chunk = buff.read(preferedchunksize)
679 elif len(self.data):
680 elif len(self.data):
680 yield self.data
681 yield self.data
681
682
682 class unbundlepart(unpackermixin):
683 class unbundlepart(unpackermixin):
683 """a bundle part read from a bundle"""
684 """a bundle part read from a bundle"""
684
685
685 def __init__(self, ui, header, fp):
686 def __init__(self, ui, header, fp):
686 super(unbundlepart, self).__init__(fp)
687 super(unbundlepart, self).__init__(fp)
687 self.ui = ui
688 self.ui = ui
688 # unbundle state attr
689 # unbundle state attr
689 self._headerdata = header
690 self._headerdata = header
690 self._headeroffset = 0
691 self._headeroffset = 0
691 self._initialized = False
692 self._initialized = False
692 self.consumed = False
693 self.consumed = False
693 # part data
694 # part data
694 self.id = None
695 self.id = None
695 self.type = None
696 self.type = None
696 self.mandatoryparams = None
697 self.mandatoryparams = None
697 self.advisoryparams = None
698 self.advisoryparams = None
698 self.params = None
699 self.params = None
699 self.mandatorykeys = ()
700 self.mandatorykeys = ()
700 self._payloadstream = None
701 self._payloadstream = None
701 self._readheader()
702 self._readheader()
702
703
703 def _fromheader(self, size):
704 def _fromheader(self, size):
704 """return the next <size> byte from the header"""
705 """return the next <size> byte from the header"""
705 offset = self._headeroffset
706 offset = self._headeroffset
706 data = self._headerdata[offset:(offset + size)]
707 data = self._headerdata[offset:(offset + size)]
707 self._headeroffset = offset + size
708 self._headeroffset = offset + size
708 return data
709 return data
709
710
710 def _unpackheader(self, format):
711 def _unpackheader(self, format):
711 """read given format from header
712 """read given format from header
712
713
713 This automatically compute the size of the format to read."""
714 This automatically compute the size of the format to read."""
714 data = self._fromheader(struct.calcsize(format))
715 data = self._fromheader(struct.calcsize(format))
715 return _unpack(format, data)
716 return _unpack(format, data)
716
717
717 def _initparams(self, mandatoryparams, advisoryparams):
718 def _initparams(self, mandatoryparams, advisoryparams):
718 """internal function to setup all logic related parameters"""
719 """internal function to setup all logic related parameters"""
719 # make it read only to prevent people touching it by mistake.
720 # make it read only to prevent people touching it by mistake.
720 self.mandatoryparams = tuple(mandatoryparams)
721 self.mandatoryparams = tuple(mandatoryparams)
721 self.advisoryparams = tuple(advisoryparams)
722 self.advisoryparams = tuple(advisoryparams)
722 # user friendly UI
723 # user friendly UI
723 self.params = dict(self.mandatoryparams)
724 self.params = dict(self.mandatoryparams)
724 self.params.update(dict(self.advisoryparams))
725 self.params.update(dict(self.advisoryparams))
725 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
726 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
726
727
727 def _readheader(self):
728 def _readheader(self):
728 """read the header and setup the object"""
729 """read the header and setup the object"""
729 typesize = self._unpackheader(_fparttypesize)[0]
730 typesize = self._unpackheader(_fparttypesize)[0]
730 self.type = self._fromheader(typesize)
731 self.type = self._fromheader(typesize)
731 self.ui.debug('part type: "%s"\n' % self.type)
732 self.ui.debug('part type: "%s"\n' % self.type)
732 self.id = self._unpackheader(_fpartid)[0]
733 self.id = self._unpackheader(_fpartid)[0]
733 self.ui.debug('part id: "%s"\n' % self.id)
734 self.ui.debug('part id: "%s"\n' % self.id)
734 ## reading parameters
735 ## reading parameters
735 # param count
736 # param count
736 mancount, advcount = self._unpackheader(_fpartparamcount)
737 mancount, advcount = self._unpackheader(_fpartparamcount)
737 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
738 self.ui.debug('part parameters: %i\n' % (mancount + advcount))
738 # param size
739 # param size
739 fparamsizes = _makefpartparamsizes(mancount + advcount)
740 fparamsizes = _makefpartparamsizes(mancount + advcount)
740 paramsizes = self._unpackheader(fparamsizes)
741 paramsizes = self._unpackheader(fparamsizes)
741 # make it a list of couple again
742 # make it a list of couple again
742 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
743 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
743 # split mandatory from advisory
744 # split mandatory from advisory
744 mansizes = paramsizes[:mancount]
745 mansizes = paramsizes[:mancount]
745 advsizes = paramsizes[mancount:]
746 advsizes = paramsizes[mancount:]
746 # retrive param value
747 # retrive param value
747 manparams = []
748 manparams = []
748 for key, value in mansizes:
749 for key, value in mansizes:
749 manparams.append((self._fromheader(key), self._fromheader(value)))
750 manparams.append((self._fromheader(key), self._fromheader(value)))
750 advparams = []
751 advparams = []
751 for key, value in advsizes:
752 for key, value in advsizes:
752 advparams.append((self._fromheader(key), self._fromheader(value)))
753 advparams.append((self._fromheader(key), self._fromheader(value)))
753 self._initparams(manparams, advparams)
754 self._initparams(manparams, advparams)
754 ## part payload
755 ## part payload
755 def payloadchunks():
756 def payloadchunks():
756 payloadsize = self._unpack(_fpayloadsize)[0]
757 payloadsize = self._unpack(_fpayloadsize)[0]
757 self.ui.debug('payload chunk size: %i\n' % payloadsize)
758 self.ui.debug('payload chunk size: %i\n' % payloadsize)
758 while payloadsize:
759 while payloadsize:
759 yield self._readexact(payloadsize)
760 yield self._readexact(payloadsize)
760 payloadsize = self._unpack(_fpayloadsize)[0]
761 payloadsize = self._unpack(_fpayloadsize)[0]
761 self.ui.debug('payload chunk size: %i\n' % payloadsize)
762 self.ui.debug('payload chunk size: %i\n' % payloadsize)
762 self._payloadstream = util.chunkbuffer(payloadchunks())
763 self._payloadstream = util.chunkbuffer(payloadchunks())
763 # we read the data, tell it
764 # we read the data, tell it
764 self._initialized = True
765 self._initialized = True
765
766
766 def read(self, size=None):
767 def read(self, size=None):
767 """read payload data"""
768 """read payload data"""
768 if not self._initialized:
769 if not self._initialized:
769 self._readheader()
770 self._readheader()
770 if size is None:
771 if size is None:
771 data = self._payloadstream.read()
772 data = self._payloadstream.read()
772 else:
773 else:
773 data = self._payloadstream.read(size)
774 data = self._payloadstream.read(size)
774 if size is None or len(data) < size:
775 if size is None or len(data) < size:
775 self.consumed = True
776 self.consumed = True
776 return data
777 return data
777
778
778 capabilities = {'HG2X': (),
779 capabilities = {'HG2X': (),
779 'b2x:listkeys': (),
780 'b2x:listkeys': (),
780 'b2x:pushkey': (),
781 'b2x:pushkey': (),
781 'b2x:changegroup': (),
782 'b2x:changegroup': (),
782 }
783 }
783
784
784 def getrepocaps(repo):
785 def getrepocaps(repo):
785 """return the bundle2 capabilities for a given repo
786 """return the bundle2 capabilities for a given repo
786
787
787 Exists to allow extensions (like evolution) to mutate the capabilities.
788 Exists to allow extensions (like evolution) to mutate the capabilities.
788 """
789 """
789 caps = capabilities.copy()
790 caps = capabilities.copy()
790 if obsolete._enabled:
791 if obsolete._enabled:
791 supportedformat = tuple('V%i' % v for v in obsolete.formats)
792 supportedformat = tuple('V%i' % v for v in obsolete.formats)
792 caps['b2x:obsmarkers'] = supportedformat
793 caps['b2x:obsmarkers'] = supportedformat
793 return caps
794 return caps
794
795
795 def bundle2caps(remote):
796 def bundle2caps(remote):
796 """return the bundlecapabilities of a peer as dict"""
797 """return the bundlecapabilities of a peer as dict"""
797 raw = remote.capable('bundle2-exp')
798 raw = remote.capable('bundle2-exp')
798 if not raw and raw != '':
799 if not raw and raw != '':
799 return {}
800 return {}
800 capsblob = urllib.unquote(remote.capable('bundle2-exp'))
801 capsblob = urllib.unquote(remote.capable('bundle2-exp'))
801 return decodecaps(capsblob)
802 return decodecaps(capsblob)
802
803
804 def obsmarkersversion(caps):
805 """extract the list of supported obsmarkers versions from a bundle2caps dict
806 """
807 obscaps = caps.get('b2x:obsmarkers', ())
808 return [int(c[1:]) for c in obscaps if c.startswith('V')]
809
803 @parthandler('b2x:changegroup')
810 @parthandler('b2x:changegroup')
804 def handlechangegroup(op, inpart):
811 def handlechangegroup(op, inpart):
805 """apply a changegroup part on the repo
812 """apply a changegroup part on the repo
806
813
807 This is a very early implementation that will massive rework before being
814 This is a very early implementation that will massive rework before being
808 inflicted to any end-user.
815 inflicted to any end-user.
809 """
816 """
810 # Make sure we trigger a transaction creation
817 # Make sure we trigger a transaction creation
811 #
818 #
812 # The addchangegroup function will get a transaction object by itself, but
819 # The addchangegroup function will get a transaction object by itself, but
813 # we need to make sure we trigger the creation of a transaction object used
820 # we need to make sure we trigger the creation of a transaction object used
814 # for the whole processing scope.
821 # for the whole processing scope.
815 op.gettransaction()
822 op.gettransaction()
816 cg = changegroup.unbundle10(inpart, 'UN')
823 cg = changegroup.unbundle10(inpart, 'UN')
817 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
824 ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
818 op.records.add('changegroup', {'return': ret})
825 op.records.add('changegroup', {'return': ret})
819 if op.reply is not None:
826 if op.reply is not None:
820 # This is definitly not the final form of this
827 # This is definitly not the final form of this
821 # return. But one need to start somewhere.
828 # return. But one need to start somewhere.
822 part = op.reply.newpart('b2x:reply:changegroup')
829 part = op.reply.newpart('b2x:reply:changegroup')
823 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
830 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
824 part.addparam('return', '%i' % ret, mandatory=False)
831 part.addparam('return', '%i' % ret, mandatory=False)
825 assert not inpart.read()
832 assert not inpart.read()
826
833
827 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to'))
834 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to'))
828 def handlechangegroup(op, inpart):
835 def handlechangegroup(op, inpart):
829 ret = int(inpart.params['return'])
836 ret = int(inpart.params['return'])
830 replyto = int(inpart.params['in-reply-to'])
837 replyto = int(inpart.params['in-reply-to'])
831 op.records.add('changegroup', {'return': ret}, replyto)
838 op.records.add('changegroup', {'return': ret}, replyto)
832
839
833 @parthandler('b2x:check:heads')
840 @parthandler('b2x:check:heads')
834 def handlechangegroup(op, inpart):
841 def handlechangegroup(op, inpart):
835 """check that head of the repo did not change
842 """check that head of the repo did not change
836
843
837 This is used to detect a push race when using unbundle.
844 This is used to detect a push race when using unbundle.
838 This replaces the "heads" argument of unbundle."""
845 This replaces the "heads" argument of unbundle."""
839 h = inpart.read(20)
846 h = inpart.read(20)
840 heads = []
847 heads = []
841 while len(h) == 20:
848 while len(h) == 20:
842 heads.append(h)
849 heads.append(h)
843 h = inpart.read(20)
850 h = inpart.read(20)
844 assert not h
851 assert not h
845 if heads != op.repo.heads():
852 if heads != op.repo.heads():
846 raise error.PushRaced('repository changed while pushing - '
853 raise error.PushRaced('repository changed while pushing - '
847 'please try again')
854 'please try again')
848
855
849 @parthandler('b2x:output')
856 @parthandler('b2x:output')
850 def handleoutput(op, inpart):
857 def handleoutput(op, inpart):
851 """forward output captured on the server to the client"""
858 """forward output captured on the server to the client"""
852 for line in inpart.read().splitlines():
859 for line in inpart.read().splitlines():
853 op.ui.write(('remote: %s\n' % line))
860 op.ui.write(('remote: %s\n' % line))
854
861
855 @parthandler('b2x:replycaps')
862 @parthandler('b2x:replycaps')
856 def handlereplycaps(op, inpart):
863 def handlereplycaps(op, inpart):
857 """Notify that a reply bundle should be created
864 """Notify that a reply bundle should be created
858
865
859 The payload contains the capabilities information for the reply"""
866 The payload contains the capabilities information for the reply"""
860 caps = decodecaps(inpart.read())
867 caps = decodecaps(inpart.read())
861 if op.reply is None:
868 if op.reply is None:
862 op.reply = bundle20(op.ui, caps)
869 op.reply = bundle20(op.ui, caps)
863
870
864 @parthandler('b2x:error:abort', ('message', 'hint'))
871 @parthandler('b2x:error:abort', ('message', 'hint'))
865 def handlereplycaps(op, inpart):
872 def handlereplycaps(op, inpart):
866 """Used to transmit abort error over the wire"""
873 """Used to transmit abort error over the wire"""
867 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
874 raise util.Abort(inpart.params['message'], hint=inpart.params.get('hint'))
868
875
869 @parthandler('b2x:error:unsupportedcontent', ('parttype', 'params'))
876 @parthandler('b2x:error:unsupportedcontent', ('parttype', 'params'))
870 def handlereplycaps(op, inpart):
877 def handlereplycaps(op, inpart):
871 """Used to transmit unknown content error over the wire"""
878 """Used to transmit unknown content error over the wire"""
872 kwargs = {}
879 kwargs = {}
873 parttype = inpart.params.get('parttype')
880 parttype = inpart.params.get('parttype')
874 if parttype is not None:
881 if parttype is not None:
875 kwargs['parttype'] = parttype
882 kwargs['parttype'] = parttype
876 params = inpart.params.get('params')
883 params = inpart.params.get('params')
877 if params is not None:
884 if params is not None:
878 kwargs['params'] = params.split('\0')
885 kwargs['params'] = params.split('\0')
879
886
880 raise error.BundleValueError(**kwargs)
887 raise error.BundleValueError(**kwargs)
881
888
882 @parthandler('b2x:error:pushraced', ('message',))
889 @parthandler('b2x:error:pushraced', ('message',))
883 def handlereplycaps(op, inpart):
890 def handlereplycaps(op, inpart):
884 """Used to transmit push race error over the wire"""
891 """Used to transmit push race error over the wire"""
885 raise error.ResponseError(_('push failed:'), inpart.params['message'])
892 raise error.ResponseError(_('push failed:'), inpart.params['message'])
886
893
887 @parthandler('b2x:listkeys', ('namespace',))
894 @parthandler('b2x:listkeys', ('namespace',))
888 def handlelistkeys(op, inpart):
895 def handlelistkeys(op, inpart):
889 """retrieve pushkey namespace content stored in a bundle2"""
896 """retrieve pushkey namespace content stored in a bundle2"""
890 namespace = inpart.params['namespace']
897 namespace = inpart.params['namespace']
891 r = pushkey.decodekeys(inpart.read())
898 r = pushkey.decodekeys(inpart.read())
892 op.records.add('listkeys', (namespace, r))
899 op.records.add('listkeys', (namespace, r))
893
900
894 @parthandler('b2x:pushkey', ('namespace', 'key', 'old', 'new'))
901 @parthandler('b2x:pushkey', ('namespace', 'key', 'old', 'new'))
895 def handlepushkey(op, inpart):
902 def handlepushkey(op, inpart):
896 """process a pushkey request"""
903 """process a pushkey request"""
897 dec = pushkey.decode
904 dec = pushkey.decode
898 namespace = dec(inpart.params['namespace'])
905 namespace = dec(inpart.params['namespace'])
899 key = dec(inpart.params['key'])
906 key = dec(inpart.params['key'])
900 old = dec(inpart.params['old'])
907 old = dec(inpart.params['old'])
901 new = dec(inpart.params['new'])
908 new = dec(inpart.params['new'])
902 ret = op.repo.pushkey(namespace, key, old, new)
909 ret = op.repo.pushkey(namespace, key, old, new)
903 record = {'namespace': namespace,
910 record = {'namespace': namespace,
904 'key': key,
911 'key': key,
905 'old': old,
912 'old': old,
906 'new': new}
913 'new': new}
907 op.records.add('pushkey', record)
914 op.records.add('pushkey', record)
908 if op.reply is not None:
915 if op.reply is not None:
909 rpart = op.reply.newpart('b2x:reply:pushkey')
916 rpart = op.reply.newpart('b2x:reply:pushkey')
910 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
917 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
911 rpart.addparam('return', '%i' % ret, mandatory=False)
918 rpart.addparam('return', '%i' % ret, mandatory=False)
912
919
913 @parthandler('b2x:reply:pushkey', ('return', 'in-reply-to'))
920 @parthandler('b2x:reply:pushkey', ('return', 'in-reply-to'))
914 def handlepushkeyreply(op, inpart):
921 def handlepushkeyreply(op, inpart):
915 """retrieve the result of a pushkey request"""
922 """retrieve the result of a pushkey request"""
916 ret = int(inpart.params['return'])
923 ret = int(inpart.params['return'])
917 partid = int(inpart.params['in-reply-to'])
924 partid = int(inpart.params['in-reply-to'])
918 op.records.add('pushkey', {'return': ret}, partid)
925 op.records.add('pushkey', {'return': ret}, partid)
919
926
920 @parthandler('b2x:obsmarkers')
927 @parthandler('b2x:obsmarkers')
921 def handleobsmarker(op, inpart):
928 def handleobsmarker(op, inpart):
922 """add a stream of obsmarkers to the repo"""
929 """add a stream of obsmarkers to the repo"""
923 tr = op.gettransaction()
930 tr = op.gettransaction()
924 new = op.repo.obsstore.mergemarkers(tr, inpart.read())
931 new = op.repo.obsstore.mergemarkers(tr, inpart.read())
925 if new:
932 if new:
926 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
933 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
927 op.records.add('obsmarkers', {'new': new})
934 op.records.add('obsmarkers', {'new': new})
928 if op.reply is not None:
935 if op.reply is not None:
929 rpart = op.reply.newpart('b2x:reply:obsmarkers')
936 rpart = op.reply.newpart('b2x:reply:obsmarkers')
930 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
937 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
931 rpart.addparam('new', '%i' % new, mandatory=False)
938 rpart.addparam('new', '%i' % new, mandatory=False)
932
939
933
940
934 @parthandler('b2x:reply:obsmarkers', ('new', 'in-reply-to'))
941 @parthandler('b2x:reply:obsmarkers', ('new', 'in-reply-to'))
935 def handlepushkeyreply(op, inpart):
942 def handlepushkeyreply(op, inpart):
936 """retrieve the result of a pushkey request"""
943 """retrieve the result of a pushkey request"""
937 ret = int(inpart.params['new'])
944 ret = int(inpart.params['new'])
938 partid = int(inpart.params['in-reply-to'])
945 partid = int(inpart.params['in-reply-to'])
939 op.records.add('obsmarkers', {'new': ret}, partid)
946 op.records.add('obsmarkers', {'new': ret}, partid)
General Comments 0
You need to be logged in to leave comments. Login now