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