##// END OF EJS Templates
bundle-spec: add an official method to format a spec into a string...
marmoute -
r51605:0913a49e default
parent child Browse files
Show More
@@ -1,556 +1,562 b''
1 # bundlecaches.py - utility to deal with pre-computed bundle for servers
1 # bundlecaches.py - utility to deal with pre-computed bundle for servers
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 import collections
6 import collections
7
7
8 from typing import (
8 from typing import (
9 cast,
9 cast,
10 )
10 )
11
11
12 from .i18n import _
12 from .i18n import _
13
13
14 from .thirdparty import attr
14 from .thirdparty import attr
15
15
16 from . import (
16 from . import (
17 error,
17 error,
18 requirements as requirementsmod,
18 requirements as requirementsmod,
19 sslutil,
19 sslutil,
20 util,
20 util,
21 )
21 )
22 from .utils import stringutil
22 from .utils import stringutil
23
23
24 urlreq = util.urlreq
24 urlreq = util.urlreq
25
25
26 BUNDLE_CACHE_DIR = b'bundle-cache'
26 BUNDLE_CACHE_DIR = b'bundle-cache'
27 CB_MANIFEST_FILE = b'clonebundles.manifest'
27 CB_MANIFEST_FILE = b'clonebundles.manifest'
28 CLONEBUNDLESCHEME = b"peer-bundle-cache://"
28 CLONEBUNDLESCHEME = b"peer-bundle-cache://"
29
29
30
30
31 def get_manifest(repo):
31 def get_manifest(repo):
32 """get the bundle manifest to be served to a client from a server"""
32 """get the bundle manifest to be served to a client from a server"""
33 raw_text = repo.vfs.tryread(CB_MANIFEST_FILE)
33 raw_text = repo.vfs.tryread(CB_MANIFEST_FILE)
34 entries = [e.split(b' ', 1) for e in raw_text.splitlines()]
34 entries = [e.split(b' ', 1) for e in raw_text.splitlines()]
35
35
36 new_lines = []
36 new_lines = []
37 for e in entries:
37 for e in entries:
38 url = alter_bundle_url(repo, e[0])
38 url = alter_bundle_url(repo, e[0])
39 if len(e) == 1:
39 if len(e) == 1:
40 line = url + b'\n'
40 line = url + b'\n'
41 else:
41 else:
42 line = b"%s %s\n" % (url, e[1])
42 line = b"%s %s\n" % (url, e[1])
43 new_lines.append(line)
43 new_lines.append(line)
44 return b''.join(new_lines)
44 return b''.join(new_lines)
45
45
46
46
47 def alter_bundle_url(repo, url):
47 def alter_bundle_url(repo, url):
48 """a function that exist to help extension and hosting to alter the url
48 """a function that exist to help extension and hosting to alter the url
49
49
50 This will typically be used to inject authentication information in the url
50 This will typically be used to inject authentication information in the url
51 of cached bundles."""
51 of cached bundles."""
52 return url
52 return url
53
53
54
54
55 SUPPORTED_CLONEBUNDLE_SCHEMES = [
55 SUPPORTED_CLONEBUNDLE_SCHEMES = [
56 b"http://",
56 b"http://",
57 b"https://",
57 b"https://",
58 b"largefile://",
58 b"largefile://",
59 CLONEBUNDLESCHEME,
59 CLONEBUNDLESCHEME,
60 ]
60 ]
61
61
62
62
63 @attr.s
63 @attr.s
64 class bundlespec:
64 class bundlespec:
65 compression = attr.ib()
65 compression = attr.ib()
66 wirecompression = attr.ib()
66 wirecompression = attr.ib()
67 version = attr.ib()
67 version = attr.ib()
68 wireversion = attr.ib()
68 wireversion = attr.ib()
69 # parameters explicitly overwritten by the config or the specification
69 # parameters explicitly overwritten by the config or the specification
70 _explicit_params = attr.ib()
70 _explicit_params = attr.ib()
71 # default parameter for the version
71 # default parameter for the version
72 #
72 #
73 # Keeping it separated is useful to check what was actually overwritten.
73 # Keeping it separated is useful to check what was actually overwritten.
74 _default_opts = attr.ib()
74 _default_opts = attr.ib()
75
75
76 @property
76 @property
77 def params(self):
77 def params(self):
78 return collections.ChainMap(self._explicit_params, self._default_opts)
78 return collections.ChainMap(self._explicit_params, self._default_opts)
79
79
80 @property
80 @property
81 def contentopts(self):
81 def contentopts(self):
82 # kept for Backward Compatibility concerns.
82 # kept for Backward Compatibility concerns.
83 return self.params
83 return self.params
84
84
85 def set_param(self, key, value, overwrite=True):
85 def set_param(self, key, value, overwrite=True):
86 """Set a bundle parameter value.
86 """Set a bundle parameter value.
87
87
88 Will only overwrite if overwrite is true"""
88 Will only overwrite if overwrite is true"""
89 if overwrite or key not in self._explicit_params:
89 if overwrite or key not in self._explicit_params:
90 self._explicit_params[key] = value
90 self._explicit_params[key] = value
91
91
92 def as_spec(self):
93 parts = [b"%s-%s" % (self.compression, self.version)]
94 for param in sorted(self._explicit_params.items()):
95 parts.append(b'%s=%s' % param)
96 return b';'.join(parts)
97
92
98
93 # Maps bundle version human names to changegroup versions.
99 # Maps bundle version human names to changegroup versions.
94 _bundlespeccgversions = {
100 _bundlespeccgversions = {
95 b'v1': b'01',
101 b'v1': b'01',
96 b'v2': b'02',
102 b'v2': b'02',
97 b'v3': b'03',
103 b'v3': b'03',
98 b'packed1': b's1',
104 b'packed1': b's1',
99 b'bundle2': b'02', # legacy
105 b'bundle2': b'02', # legacy
100 }
106 }
101
107
102 # Maps bundle version with content opts to choose which part to bundle
108 # Maps bundle version with content opts to choose which part to bundle
103 _bundlespeccontentopts = {
109 _bundlespeccontentopts = {
104 b'v1': {
110 b'v1': {
105 b'changegroup': True,
111 b'changegroup': True,
106 b'cg.version': b'01',
112 b'cg.version': b'01',
107 b'obsolescence': False,
113 b'obsolescence': False,
108 b'phases': False,
114 b'phases': False,
109 b'tagsfnodescache': False,
115 b'tagsfnodescache': False,
110 b'revbranchcache': False,
116 b'revbranchcache': False,
111 },
117 },
112 b'v2': {
118 b'v2': {
113 b'changegroup': True,
119 b'changegroup': True,
114 b'cg.version': b'02',
120 b'cg.version': b'02',
115 b'obsolescence': False,
121 b'obsolescence': False,
116 b'phases': False,
122 b'phases': False,
117 b'tagsfnodescache': True,
123 b'tagsfnodescache': True,
118 b'revbranchcache': True,
124 b'revbranchcache': True,
119 },
125 },
120 b'v3': {
126 b'v3': {
121 b'changegroup': True,
127 b'changegroup': True,
122 b'cg.version': b'03',
128 b'cg.version': b'03',
123 b'obsolescence': False,
129 b'obsolescence': False,
124 b'phases': True,
130 b'phases': True,
125 b'tagsfnodescache': True,
131 b'tagsfnodescache': True,
126 b'revbranchcache': True,
132 b'revbranchcache': True,
127 },
133 },
128 b'streamv2': {
134 b'streamv2': {
129 b'changegroup': False,
135 b'changegroup': False,
130 b'cg.version': b'02',
136 b'cg.version': b'02',
131 b'obsolescence': False,
137 b'obsolescence': False,
132 b'phases': False,
138 b'phases': False,
133 b"streamv2": True,
139 b"streamv2": True,
134 b'tagsfnodescache': False,
140 b'tagsfnodescache': False,
135 b'revbranchcache': False,
141 b'revbranchcache': False,
136 },
142 },
137 b'streamv3-exp': {
143 b'streamv3-exp': {
138 b'changegroup': False,
144 b'changegroup': False,
139 b'cg.version': b'03',
145 b'cg.version': b'03',
140 b'obsolescence': False,
146 b'obsolescence': False,
141 b'phases': False,
147 b'phases': False,
142 b"streamv3-exp": True,
148 b"streamv3-exp": True,
143 b'tagsfnodescache': False,
149 b'tagsfnodescache': False,
144 b'revbranchcache': False,
150 b'revbranchcache': False,
145 },
151 },
146 b'packed1': {
152 b'packed1': {
147 b'cg.version': b's1',
153 b'cg.version': b's1',
148 },
154 },
149 b'bundle2': { # legacy
155 b'bundle2': { # legacy
150 b'cg.version': b'02',
156 b'cg.version': b'02',
151 },
157 },
152 }
158 }
153 _bundlespeccontentopts[b'bundle2'] = _bundlespeccontentopts[b'v2']
159 _bundlespeccontentopts[b'bundle2'] = _bundlespeccontentopts[b'v2']
154
160
155 _bundlespecvariants = {b"streamv2": {}}
161 _bundlespecvariants = {b"streamv2": {}}
156
162
157 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
163 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
158 _bundlespecv1compengines = {b'gzip', b'bzip2', b'none'}
164 _bundlespecv1compengines = {b'gzip', b'bzip2', b'none'}
159
165
160
166
161 def param_bool(key, value):
167 def param_bool(key, value):
162 """make a boolean out of a parameter value"""
168 """make a boolean out of a parameter value"""
163 b = stringutil.parsebool(value)
169 b = stringutil.parsebool(value)
164 if b is None:
170 if b is None:
165 msg = _(b"parameter %s should be a boolean ('%s')")
171 msg = _(b"parameter %s should be a boolean ('%s')")
166 msg %= (key, value)
172 msg %= (key, value)
167 raise error.InvalidBundleSpecification(msg)
173 raise error.InvalidBundleSpecification(msg)
168 return b
174 return b
169
175
170
176
171 # mapping of known parameter name need their value processed
177 # mapping of known parameter name need their value processed
172 bundle_spec_param_processing = {
178 bundle_spec_param_processing = {
173 b"obsolescence": param_bool,
179 b"obsolescence": param_bool,
174 b"obsolescence-mandatory": param_bool,
180 b"obsolescence-mandatory": param_bool,
175 b"phases": param_bool,
181 b"phases": param_bool,
176 }
182 }
177
183
178
184
179 def _parseparams(s):
185 def _parseparams(s):
180 """parse bundlespec parameter section
186 """parse bundlespec parameter section
181
187
182 input: "comp-version;params" string
188 input: "comp-version;params" string
183
189
184 return: (spec; {param_key: param_value})
190 return: (spec; {param_key: param_value})
185 """
191 """
186 if b';' not in s:
192 if b';' not in s:
187 return s, {}
193 return s, {}
188
194
189 params = {}
195 params = {}
190 version, paramstr = s.split(b';', 1)
196 version, paramstr = s.split(b';', 1)
191
197
192 err = _(b'invalid bundle specification: missing "=" in parameter: %s')
198 err = _(b'invalid bundle specification: missing "=" in parameter: %s')
193 for p in paramstr.split(b';'):
199 for p in paramstr.split(b';'):
194 if b'=' not in p:
200 if b'=' not in p:
195 msg = err % p
201 msg = err % p
196 raise error.InvalidBundleSpecification(msg)
202 raise error.InvalidBundleSpecification(msg)
197
203
198 key, value = p.split(b'=', 1)
204 key, value = p.split(b'=', 1)
199 key = urlreq.unquote(key)
205 key = urlreq.unquote(key)
200 value = urlreq.unquote(value)
206 value = urlreq.unquote(value)
201 process = bundle_spec_param_processing.get(key)
207 process = bundle_spec_param_processing.get(key)
202 if process is not None:
208 if process is not None:
203 value = process(key, value)
209 value = process(key, value)
204 params[key] = value
210 params[key] = value
205
211
206 return version, params
212 return version, params
207
213
208
214
209 def parsebundlespec(repo, spec, strict=True):
215 def parsebundlespec(repo, spec, strict=True):
210 """Parse a bundle string specification into parts.
216 """Parse a bundle string specification into parts.
211
217
212 Bundle specifications denote a well-defined bundle/exchange format.
218 Bundle specifications denote a well-defined bundle/exchange format.
213 The content of a given specification should not change over time in
219 The content of a given specification should not change over time in
214 order to ensure that bundles produced by a newer version of Mercurial are
220 order to ensure that bundles produced by a newer version of Mercurial are
215 readable from an older version.
221 readable from an older version.
216
222
217 The string currently has the form:
223 The string currently has the form:
218
224
219 <compression>-<type>[;<parameter0>[;<parameter1>]]
225 <compression>-<type>[;<parameter0>[;<parameter1>]]
220
226
221 Where <compression> is one of the supported compression formats
227 Where <compression> is one of the supported compression formats
222 and <type> is (currently) a version string. A ";" can follow the type and
228 and <type> is (currently) a version string. A ";" can follow the type and
223 all text afterwards is interpreted as URI encoded, ";" delimited key=value
229 all text afterwards is interpreted as URI encoded, ";" delimited key=value
224 pairs.
230 pairs.
225
231
226 If ``strict`` is True (the default) <compression> is required. Otherwise,
232 If ``strict`` is True (the default) <compression> is required. Otherwise,
227 it is optional.
233 it is optional.
228
234
229 Returns a bundlespec object of (compression, version, parameters).
235 Returns a bundlespec object of (compression, version, parameters).
230 Compression will be ``None`` if not in strict mode and a compression isn't
236 Compression will be ``None`` if not in strict mode and a compression isn't
231 defined.
237 defined.
232
238
233 An ``InvalidBundleSpecification`` is raised when the specification is
239 An ``InvalidBundleSpecification`` is raised when the specification is
234 not syntactically well formed.
240 not syntactically well formed.
235
241
236 An ``UnsupportedBundleSpecification`` is raised when the compression or
242 An ``UnsupportedBundleSpecification`` is raised when the compression or
237 bundle type/version is not recognized.
243 bundle type/version is not recognized.
238
244
239 Note: this function will likely eventually return a more complex data
245 Note: this function will likely eventually return a more complex data
240 structure, including bundle2 part information.
246 structure, including bundle2 part information.
241 """
247 """
242 if strict and b'-' not in spec:
248 if strict and b'-' not in spec:
243 raise error.InvalidBundleSpecification(
249 raise error.InvalidBundleSpecification(
244 _(
250 _(
245 b'invalid bundle specification; '
251 b'invalid bundle specification; '
246 b'must be prefixed with compression: %s'
252 b'must be prefixed with compression: %s'
247 )
253 )
248 % spec
254 % spec
249 )
255 )
250
256
251 pre_args = spec.split(b';', 1)[0]
257 pre_args = spec.split(b';', 1)[0]
252 if b'-' in pre_args:
258 if b'-' in pre_args:
253 compression, version = spec.split(b'-', 1)
259 compression, version = spec.split(b'-', 1)
254
260
255 if compression not in util.compengines.supportedbundlenames:
261 if compression not in util.compengines.supportedbundlenames:
256 raise error.UnsupportedBundleSpecification(
262 raise error.UnsupportedBundleSpecification(
257 _(b'%s compression is not supported') % compression
263 _(b'%s compression is not supported') % compression
258 )
264 )
259
265
260 version, params = _parseparams(version)
266 version, params = _parseparams(version)
261
267
262 if version not in _bundlespeccontentopts:
268 if version not in _bundlespeccontentopts:
263 raise error.UnsupportedBundleSpecification(
269 raise error.UnsupportedBundleSpecification(
264 _(b'%s is not a recognized bundle version') % version
270 _(b'%s is not a recognized bundle version') % version
265 )
271 )
266 else:
272 else:
267 # Value could be just the compression or just the version, in which
273 # Value could be just the compression or just the version, in which
268 # case some defaults are assumed (but only when not in strict mode).
274 # case some defaults are assumed (but only when not in strict mode).
269 assert not strict
275 assert not strict
270
276
271 spec, params = _parseparams(spec)
277 spec, params = _parseparams(spec)
272
278
273 if spec in util.compengines.supportedbundlenames:
279 if spec in util.compengines.supportedbundlenames:
274 compression = spec
280 compression = spec
275 version = b'v1'
281 version = b'v1'
276 # Generaldelta repos require v2.
282 # Generaldelta repos require v2.
277 if requirementsmod.GENERALDELTA_REQUIREMENT in repo.requirements:
283 if requirementsmod.GENERALDELTA_REQUIREMENT in repo.requirements:
278 version = b'v2'
284 version = b'v2'
279 elif requirementsmod.REVLOGV2_REQUIREMENT in repo.requirements:
285 elif requirementsmod.REVLOGV2_REQUIREMENT in repo.requirements:
280 version = b'v2'
286 version = b'v2'
281 # Modern compression engines require v2.
287 # Modern compression engines require v2.
282 if compression not in _bundlespecv1compengines:
288 if compression not in _bundlespecv1compengines:
283 version = b'v2'
289 version = b'v2'
284 elif spec in _bundlespeccontentopts:
290 elif spec in _bundlespeccontentopts:
285 if spec == b'packed1':
291 if spec == b'packed1':
286 compression = b'none'
292 compression = b'none'
287 else:
293 else:
288 compression = b'bzip2'
294 compression = b'bzip2'
289 version = spec
295 version = spec
290 else:
296 else:
291 raise error.UnsupportedBundleSpecification(
297 raise error.UnsupportedBundleSpecification(
292 _(b'%s is not a recognized bundle specification') % spec
298 _(b'%s is not a recognized bundle specification') % spec
293 )
299 )
294
300
295 # Bundle version 1 only supports a known set of compression engines.
301 # Bundle version 1 only supports a known set of compression engines.
296 if version == b'v1' and compression not in _bundlespecv1compengines:
302 if version == b'v1' and compression not in _bundlespecv1compengines:
297 raise error.UnsupportedBundleSpecification(
303 raise error.UnsupportedBundleSpecification(
298 _(b'compression engine %s is not supported on v1 bundles')
304 _(b'compression engine %s is not supported on v1 bundles')
299 % compression
305 % compression
300 )
306 )
301
307
302 # The specification for packed1 can optionally declare the data formats
308 # The specification for packed1 can optionally declare the data formats
303 # required to apply it. If we see this metadata, compare against what the
309 # required to apply it. If we see this metadata, compare against what the
304 # repo supports and error if the bundle isn't compatible.
310 # repo supports and error if the bundle isn't compatible.
305 if version == b'packed1' and b'requirements' in params:
311 if version == b'packed1' and b'requirements' in params:
306 requirements = set(cast(bytes, params[b'requirements']).split(b','))
312 requirements = set(cast(bytes, params[b'requirements']).split(b','))
307 missingreqs = requirements - requirementsmod.STREAM_FIXED_REQUIREMENTS
313 missingreqs = requirements - requirementsmod.STREAM_FIXED_REQUIREMENTS
308 if missingreqs:
314 if missingreqs:
309 raise error.UnsupportedBundleSpecification(
315 raise error.UnsupportedBundleSpecification(
310 _(b'missing support for repository features: %s')
316 _(b'missing support for repository features: %s')
311 % b', '.join(sorted(missingreqs))
317 % b', '.join(sorted(missingreqs))
312 )
318 )
313
319
314 # Compute contentopts based on the version
320 # Compute contentopts based on the version
315 if b"stream" in params:
321 if b"stream" in params:
316 # This case is fishy as this mostly derails the version selection
322 # This case is fishy as this mostly derails the version selection
317 # mechanism. `stream` bundles are quite specific and used differently
323 # mechanism. `stream` bundles are quite specific and used differently
318 # as "normal" bundles.
324 # as "normal" bundles.
319 #
325 #
320 # (we should probably define a cleaner way to do this and raise a
326 # (we should probably define a cleaner way to do this and raise a
321 # warning when the old way is encountered)
327 # warning when the old way is encountered)
322 if params[b"stream"] == b"v2":
328 if params[b"stream"] == b"v2":
323 version = b"streamv2"
329 version = b"streamv2"
324 if params[b"stream"] == b"v3-exp":
330 if params[b"stream"] == b"v3-exp":
325 version = b"streamv3-exp"
331 version = b"streamv3-exp"
326 contentopts = _bundlespeccontentopts.get(version, {}).copy()
332 contentopts = _bundlespeccontentopts.get(version, {}).copy()
327 if version == b"streamv2" or version == b"streamv3-exp":
333 if version == b"streamv2" or version == b"streamv3-exp":
328 # streamv2 have been reported as "v2" for a while.
334 # streamv2 have been reported as "v2" for a while.
329 version = b"v2"
335 version = b"v2"
330
336
331 engine = util.compengines.forbundlename(compression)
337 engine = util.compengines.forbundlename(compression)
332 compression, wirecompression = engine.bundletype()
338 compression, wirecompression = engine.bundletype()
333 wireversion = _bundlespeccontentopts[version][b'cg.version']
339 wireversion = _bundlespeccontentopts[version][b'cg.version']
334
340
335 return bundlespec(
341 return bundlespec(
336 compression, wirecompression, version, wireversion, params, contentopts
342 compression, wirecompression, version, wireversion, params, contentopts
337 )
343 )
338
344
339
345
340 def parseclonebundlesmanifest(repo, s):
346 def parseclonebundlesmanifest(repo, s):
341 """Parses the raw text of a clone bundles manifest.
347 """Parses the raw text of a clone bundles manifest.
342
348
343 Returns a list of dicts. The dicts have a ``URL`` key corresponding
349 Returns a list of dicts. The dicts have a ``URL`` key corresponding
344 to the URL and other keys are the attributes for the entry.
350 to the URL and other keys are the attributes for the entry.
345 """
351 """
346 m = []
352 m = []
347 for line in s.splitlines():
353 for line in s.splitlines():
348 fields = line.split()
354 fields = line.split()
349 if not fields:
355 if not fields:
350 continue
356 continue
351 attrs = {b'URL': fields[0]}
357 attrs = {b'URL': fields[0]}
352 for rawattr in fields[1:]:
358 for rawattr in fields[1:]:
353 key, value = rawattr.split(b'=', 1)
359 key, value = rawattr.split(b'=', 1)
354 key = util.urlreq.unquote(key)
360 key = util.urlreq.unquote(key)
355 value = util.urlreq.unquote(value)
361 value = util.urlreq.unquote(value)
356 attrs[key] = value
362 attrs[key] = value
357
363
358 # Parse BUNDLESPEC into components. This makes client-side
364 # Parse BUNDLESPEC into components. This makes client-side
359 # preferences easier to specify since you can prefer a single
365 # preferences easier to specify since you can prefer a single
360 # component of the BUNDLESPEC.
366 # component of the BUNDLESPEC.
361 if key == b'BUNDLESPEC':
367 if key == b'BUNDLESPEC':
362 try:
368 try:
363 bundlespec = parsebundlespec(repo, value)
369 bundlespec = parsebundlespec(repo, value)
364 attrs[b'COMPRESSION'] = bundlespec.compression
370 attrs[b'COMPRESSION'] = bundlespec.compression
365 attrs[b'VERSION'] = bundlespec.version
371 attrs[b'VERSION'] = bundlespec.version
366 except error.InvalidBundleSpecification:
372 except error.InvalidBundleSpecification:
367 pass
373 pass
368 except error.UnsupportedBundleSpecification:
374 except error.UnsupportedBundleSpecification:
369 pass
375 pass
370
376
371 m.append(attrs)
377 m.append(attrs)
372
378
373 return m
379 return m
374
380
375
381
376 def isstreamclonespec(bundlespec):
382 def isstreamclonespec(bundlespec):
377 # Stream clone v1
383 # Stream clone v1
378 if bundlespec.wirecompression == b'UN' and bundlespec.wireversion == b's1':
384 if bundlespec.wirecompression == b'UN' and bundlespec.wireversion == b's1':
379 return True
385 return True
380
386
381 # Stream clone v2
387 # Stream clone v2
382 if (
388 if (
383 bundlespec.wirecompression == b'UN'
389 bundlespec.wirecompression == b'UN'
384 and bundlespec.wireversion == b'02'
390 and bundlespec.wireversion == b'02'
385 and (
391 and (
386 bundlespec.contentopts.get(b'streamv2')
392 bundlespec.contentopts.get(b'streamv2')
387 or bundlespec.contentopts.get(b'streamv3-exp')
393 or bundlespec.contentopts.get(b'streamv3-exp')
388 )
394 )
389 ):
395 ):
390 return True
396 return True
391
397
392 return False
398 return False
393
399
394
400
395 def filterclonebundleentries(
401 def filterclonebundleentries(
396 repo, entries, streamclonerequested=False, pullbundles=False
402 repo, entries, streamclonerequested=False, pullbundles=False
397 ):
403 ):
398 """Remove incompatible clone bundle manifest entries.
404 """Remove incompatible clone bundle manifest entries.
399
405
400 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
406 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
401 and returns a new list consisting of only the entries that this client
407 and returns a new list consisting of only the entries that this client
402 should be able to apply.
408 should be able to apply.
403
409
404 There is no guarantee we'll be able to apply all returned entries because
410 There is no guarantee we'll be able to apply all returned entries because
405 the metadata we use to filter on may be missing or wrong.
411 the metadata we use to filter on may be missing or wrong.
406 """
412 """
407 newentries = []
413 newentries = []
408 for entry in entries:
414 for entry in entries:
409 url = entry.get(b'URL')
415 url = entry.get(b'URL')
410 if not pullbundles and not any(
416 if not pullbundles and not any(
411 [url.startswith(scheme) for scheme in SUPPORTED_CLONEBUNDLE_SCHEMES]
417 [url.startswith(scheme) for scheme in SUPPORTED_CLONEBUNDLE_SCHEMES]
412 ):
418 ):
413 repo.ui.debug(
419 repo.ui.debug(
414 b'filtering %s because not a supported clonebundle scheme\n'
420 b'filtering %s because not a supported clonebundle scheme\n'
415 % url
421 % url
416 )
422 )
417 continue
423 continue
418
424
419 spec = entry.get(b'BUNDLESPEC')
425 spec = entry.get(b'BUNDLESPEC')
420 if spec:
426 if spec:
421 try:
427 try:
422 bundlespec = parsebundlespec(repo, spec, strict=True)
428 bundlespec = parsebundlespec(repo, spec, strict=True)
423
429
424 # If a stream clone was requested, filter out non-streamclone
430 # If a stream clone was requested, filter out non-streamclone
425 # entries.
431 # entries.
426 if streamclonerequested and not isstreamclonespec(bundlespec):
432 if streamclonerequested and not isstreamclonespec(bundlespec):
427 repo.ui.debug(
433 repo.ui.debug(
428 b'filtering %s because not a stream clone\n' % url
434 b'filtering %s because not a stream clone\n' % url
429 )
435 )
430 continue
436 continue
431
437
432 except error.InvalidBundleSpecification as e:
438 except error.InvalidBundleSpecification as e:
433 repo.ui.debug(stringutil.forcebytestr(e) + b'\n')
439 repo.ui.debug(stringutil.forcebytestr(e) + b'\n')
434 continue
440 continue
435 except error.UnsupportedBundleSpecification as e:
441 except error.UnsupportedBundleSpecification as e:
436 repo.ui.debug(
442 repo.ui.debug(
437 b'filtering %s because unsupported bundle '
443 b'filtering %s because unsupported bundle '
438 b'spec: %s\n' % (url, stringutil.forcebytestr(e))
444 b'spec: %s\n' % (url, stringutil.forcebytestr(e))
439 )
445 )
440 continue
446 continue
441 # If we don't have a spec and requested a stream clone, we don't know
447 # If we don't have a spec and requested a stream clone, we don't know
442 # what the entry is so don't attempt to apply it.
448 # what the entry is so don't attempt to apply it.
443 elif streamclonerequested:
449 elif streamclonerequested:
444 repo.ui.debug(
450 repo.ui.debug(
445 b'filtering %s because cannot determine if a stream '
451 b'filtering %s because cannot determine if a stream '
446 b'clone bundle\n' % url
452 b'clone bundle\n' % url
447 )
453 )
448 continue
454 continue
449
455
450 if b'REQUIRESNI' in entry and not sslutil.hassni:
456 if b'REQUIRESNI' in entry and not sslutil.hassni:
451 repo.ui.debug(b'filtering %s because SNI not supported\n' % url)
457 repo.ui.debug(b'filtering %s because SNI not supported\n' % url)
452 continue
458 continue
453
459
454 if b'REQUIREDRAM' in entry:
460 if b'REQUIREDRAM' in entry:
455 try:
461 try:
456 requiredram = util.sizetoint(entry[b'REQUIREDRAM'])
462 requiredram = util.sizetoint(entry[b'REQUIREDRAM'])
457 except error.ParseError:
463 except error.ParseError:
458 repo.ui.debug(
464 repo.ui.debug(
459 b'filtering %s due to a bad REQUIREDRAM attribute\n' % url
465 b'filtering %s due to a bad REQUIREDRAM attribute\n' % url
460 )
466 )
461 continue
467 continue
462 actualram = repo.ui.estimatememory()
468 actualram = repo.ui.estimatememory()
463 if actualram is not None and actualram * 0.66 < requiredram:
469 if actualram is not None and actualram * 0.66 < requiredram:
464 repo.ui.debug(
470 repo.ui.debug(
465 b'filtering %s as it needs more than 2/3 of system memory\n'
471 b'filtering %s as it needs more than 2/3 of system memory\n'
466 % url
472 % url
467 )
473 )
468 continue
474 continue
469
475
470 newentries.append(entry)
476 newentries.append(entry)
471
477
472 return newentries
478 return newentries
473
479
474
480
475 class clonebundleentry:
481 class clonebundleentry:
476 """Represents an item in a clone bundles manifest.
482 """Represents an item in a clone bundles manifest.
477
483
478 This rich class is needed to support sorting since sorted() in Python 3
484 This rich class is needed to support sorting since sorted() in Python 3
479 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
485 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
480 won't work.
486 won't work.
481 """
487 """
482
488
483 def __init__(self, value, prefers):
489 def __init__(self, value, prefers):
484 self.value = value
490 self.value = value
485 self.prefers = prefers
491 self.prefers = prefers
486
492
487 def _cmp(self, other):
493 def _cmp(self, other):
488 for prefkey, prefvalue in self.prefers:
494 for prefkey, prefvalue in self.prefers:
489 avalue = self.value.get(prefkey)
495 avalue = self.value.get(prefkey)
490 bvalue = other.value.get(prefkey)
496 bvalue = other.value.get(prefkey)
491
497
492 # Special case for b missing attribute and a matches exactly.
498 # Special case for b missing attribute and a matches exactly.
493 if avalue is not None and bvalue is None and avalue == prefvalue:
499 if avalue is not None and bvalue is None and avalue == prefvalue:
494 return -1
500 return -1
495
501
496 # Special case for a missing attribute and b matches exactly.
502 # Special case for a missing attribute and b matches exactly.
497 if bvalue is not None and avalue is None and bvalue == prefvalue:
503 if bvalue is not None and avalue is None and bvalue == prefvalue:
498 return 1
504 return 1
499
505
500 # We can't compare unless attribute present on both.
506 # We can't compare unless attribute present on both.
501 if avalue is None or bvalue is None:
507 if avalue is None or bvalue is None:
502 continue
508 continue
503
509
504 # Same values should fall back to next attribute.
510 # Same values should fall back to next attribute.
505 if avalue == bvalue:
511 if avalue == bvalue:
506 continue
512 continue
507
513
508 # Exact matches come first.
514 # Exact matches come first.
509 if avalue == prefvalue:
515 if avalue == prefvalue:
510 return -1
516 return -1
511 if bvalue == prefvalue:
517 if bvalue == prefvalue:
512 return 1
518 return 1
513
519
514 # Fall back to next attribute.
520 # Fall back to next attribute.
515 continue
521 continue
516
522
517 # If we got here we couldn't sort by attributes and prefers. Fall
523 # If we got here we couldn't sort by attributes and prefers. Fall
518 # back to index order.
524 # back to index order.
519 return 0
525 return 0
520
526
521 def __lt__(self, other):
527 def __lt__(self, other):
522 return self._cmp(other) < 0
528 return self._cmp(other) < 0
523
529
524 def __gt__(self, other):
530 def __gt__(self, other):
525 return self._cmp(other) > 0
531 return self._cmp(other) > 0
526
532
527 def __eq__(self, other):
533 def __eq__(self, other):
528 return self._cmp(other) == 0
534 return self._cmp(other) == 0
529
535
530 def __le__(self, other):
536 def __le__(self, other):
531 return self._cmp(other) <= 0
537 return self._cmp(other) <= 0
532
538
533 def __ge__(self, other):
539 def __ge__(self, other):
534 return self._cmp(other) >= 0
540 return self._cmp(other) >= 0
535
541
536 def __ne__(self, other):
542 def __ne__(self, other):
537 return self._cmp(other) != 0
543 return self._cmp(other) != 0
538
544
539
545
540 def sortclonebundleentries(ui, entries):
546 def sortclonebundleentries(ui, entries):
541 prefers = ui.configlist(b'ui', b'clonebundleprefers')
547 prefers = ui.configlist(b'ui', b'clonebundleprefers')
542 if not prefers:
548 if not prefers:
543 return list(entries)
549 return list(entries)
544
550
545 def _split(p):
551 def _split(p):
546 if b'=' not in p:
552 if b'=' not in p:
547 hint = _(b"each comma separated item should be key=value pairs")
553 hint = _(b"each comma separated item should be key=value pairs")
548 raise error.Abort(
554 raise error.Abort(
549 _(b"invalid ui.clonebundleprefers item: %s") % p, hint=hint
555 _(b"invalid ui.clonebundleprefers item: %s") % p, hint=hint
550 )
556 )
551 return p.split(b'=', 1)
557 return p.split(b'=', 1)
552
558
553 prefers = [_split(p) for p in prefers]
559 prefers = [_split(p) for p in prefers]
554
560
555 items = sorted(clonebundleentry(v, prefers) for v in entries)
561 items = sorted(clonebundleentry(v, prefers) for v in entries)
556 return [i.value for i in items]
562 return [i.value for i in items]
General Comments 0
You need to be logged in to leave comments. Login now