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