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