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