##// END OF EJS Templates
actions: introduce function to calculate downgrades...
Pulkit Goyal -
r46829:c97d8e04 default
parent child Browse files
Show More
@@ -1,830 +1,845 b''
1 # upgrade.py - functions for in place upgrade of Mercurial repository
1 # upgrade.py - functions for in place upgrade of Mercurial repository
2 #
2 #
3 # Copyright (c) 2016-present, Gregory Szorc
3 # Copyright (c) 2016-present, Gregory Szorc
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from ..i18n import _
10 from ..i18n import _
11 from .. import (
11 from .. import (
12 error,
12 error,
13 localrepo,
13 localrepo,
14 requirements,
14 requirements,
15 util,
15 util,
16 )
16 )
17
17
18 from ..utils import compression
18 from ..utils import compression
19
19
20 # list of requirements that request a clone of all revlog if added/removed
20 # list of requirements that request a clone of all revlog if added/removed
21 RECLONES_REQUIREMENTS = {
21 RECLONES_REQUIREMENTS = {
22 b'generaldelta',
22 b'generaldelta',
23 requirements.SPARSEREVLOG_REQUIREMENT,
23 requirements.SPARSEREVLOG_REQUIREMENT,
24 }
24 }
25
25
26
26
27 def preservedrequirements(repo):
27 def preservedrequirements(repo):
28 return set()
28 return set()
29
29
30
30
31 FORMAT_VARIANT = b'deficiency'
31 FORMAT_VARIANT = b'deficiency'
32 OPTIMISATION = b'optimization'
32 OPTIMISATION = b'optimization'
33
33
34
34
35 class improvement(object):
35 class improvement(object):
36 """Represents an improvement that can be made as part of an upgrade.
36 """Represents an improvement that can be made as part of an upgrade.
37
37
38 The following attributes are defined on each instance:
38 The following attributes are defined on each instance:
39
39
40 name
40 name
41 Machine-readable string uniquely identifying this improvement. It
41 Machine-readable string uniquely identifying this improvement. It
42 will be mapped to an action later in the upgrade process.
42 will be mapped to an action later in the upgrade process.
43
43
44 type
44 type
45 Either ``FORMAT_VARIANT`` or ``OPTIMISATION``.
45 Either ``FORMAT_VARIANT`` or ``OPTIMISATION``.
46 A format variant is where we change the storage format. Not all format
46 A format variant is where we change the storage format. Not all format
47 variant changes are an obvious problem.
47 variant changes are an obvious problem.
48 An optimization is an action (sometimes optional) that
48 An optimization is an action (sometimes optional) that
49 can be taken to further improve the state of the repository.
49 can be taken to further improve the state of the repository.
50
50
51 description
51 description
52 Message intended for humans explaining the improvement in more detail,
52 Message intended for humans explaining the improvement in more detail,
53 including the implications of it. For ``FORMAT_VARIANT`` types, should be
53 including the implications of it. For ``FORMAT_VARIANT`` types, should be
54 worded in the present tense. For ``OPTIMISATION`` types, should be
54 worded in the present tense. For ``OPTIMISATION`` types, should be
55 worded in the future tense.
55 worded in the future tense.
56
56
57 upgrademessage
57 upgrademessage
58 Message intended for humans explaining what an upgrade addressing this
58 Message intended for humans explaining what an upgrade addressing this
59 issue will do. Should be worded in the future tense.
59 issue will do. Should be worded in the future tense.
60 """
60 """
61
61
62 def __init__(self, name, type, description, upgrademessage):
62 def __init__(self, name, type, description, upgrademessage):
63 self.name = name
63 self.name = name
64 self.type = type
64 self.type = type
65 self.description = description
65 self.description = description
66 self.upgrademessage = upgrademessage
66 self.upgrademessage = upgrademessage
67
67
68 def __eq__(self, other):
68 def __eq__(self, other):
69 if not isinstance(other, improvement):
69 if not isinstance(other, improvement):
70 # This is what python tell use to do
70 # This is what python tell use to do
71 return NotImplemented
71 return NotImplemented
72 return self.name == other.name
72 return self.name == other.name
73
73
74 def __ne__(self, other):
74 def __ne__(self, other):
75 return not (self == other)
75 return not (self == other)
76
76
77 def __hash__(self):
77 def __hash__(self):
78 return hash(self.name)
78 return hash(self.name)
79
79
80
80
81 allformatvariant = []
81 allformatvariant = []
82
82
83
83
84 def registerformatvariant(cls):
84 def registerformatvariant(cls):
85 allformatvariant.append(cls)
85 allformatvariant.append(cls)
86 return cls
86 return cls
87
87
88
88
89 class formatvariant(improvement):
89 class formatvariant(improvement):
90 """an improvement subclass dedicated to repository format"""
90 """an improvement subclass dedicated to repository format"""
91
91
92 type = FORMAT_VARIANT
92 type = FORMAT_VARIANT
93 ### The following attributes should be defined for each class:
93 ### The following attributes should be defined for each class:
94
94
95 # machine-readable string uniquely identifying this improvement. it will be
95 # machine-readable string uniquely identifying this improvement. it will be
96 # mapped to an action later in the upgrade process.
96 # mapped to an action later in the upgrade process.
97 name = None
97 name = None
98
98
99 # message intended for humans explaining the improvement in more detail,
99 # message intended for humans explaining the improvement in more detail,
100 # including the implications of it ``FORMAT_VARIANT`` types, should be
100 # including the implications of it ``FORMAT_VARIANT`` types, should be
101 # worded
101 # worded
102 # in the present tense.
102 # in the present tense.
103 description = None
103 description = None
104
104
105 # message intended for humans explaining what an upgrade addressing this
105 # message intended for humans explaining what an upgrade addressing this
106 # issue will do. should be worded in the future tense.
106 # issue will do. should be worded in the future tense.
107 upgrademessage = None
107 upgrademessage = None
108
108
109 # value of current Mercurial default for new repository
109 # value of current Mercurial default for new repository
110 default = None
110 default = None
111
111
112 def __init__(self):
112 def __init__(self):
113 raise NotImplementedError()
113 raise NotImplementedError()
114
114
115 @staticmethod
115 @staticmethod
116 def fromrepo(repo):
116 def fromrepo(repo):
117 """current value of the variant in the repository"""
117 """current value of the variant in the repository"""
118 raise NotImplementedError()
118 raise NotImplementedError()
119
119
120 @staticmethod
120 @staticmethod
121 def fromconfig(repo):
121 def fromconfig(repo):
122 """current value of the variant in the configuration"""
122 """current value of the variant in the configuration"""
123 raise NotImplementedError()
123 raise NotImplementedError()
124
124
125
125
126 class requirementformatvariant(formatvariant):
126 class requirementformatvariant(formatvariant):
127 """formatvariant based on a 'requirement' name.
127 """formatvariant based on a 'requirement' name.
128
128
129 Many format variant are controlled by a 'requirement'. We define a small
129 Many format variant are controlled by a 'requirement'. We define a small
130 subclass to factor the code.
130 subclass to factor the code.
131 """
131 """
132
132
133 # the requirement that control this format variant
133 # the requirement that control this format variant
134 _requirement = None
134 _requirement = None
135
135
136 @staticmethod
136 @staticmethod
137 def _newreporequirements(ui):
137 def _newreporequirements(ui):
138 return localrepo.newreporequirements(
138 return localrepo.newreporequirements(
139 ui, localrepo.defaultcreateopts(ui)
139 ui, localrepo.defaultcreateopts(ui)
140 )
140 )
141
141
142 @classmethod
142 @classmethod
143 def fromrepo(cls, repo):
143 def fromrepo(cls, repo):
144 assert cls._requirement is not None
144 assert cls._requirement is not None
145 return cls._requirement in repo.requirements
145 return cls._requirement in repo.requirements
146
146
147 @classmethod
147 @classmethod
148 def fromconfig(cls, repo):
148 def fromconfig(cls, repo):
149 assert cls._requirement is not None
149 assert cls._requirement is not None
150 return cls._requirement in cls._newreporequirements(repo.ui)
150 return cls._requirement in cls._newreporequirements(repo.ui)
151
151
152
152
153 @registerformatvariant
153 @registerformatvariant
154 class fncache(requirementformatvariant):
154 class fncache(requirementformatvariant):
155 name = b'fncache'
155 name = b'fncache'
156
156
157 _requirement = b'fncache'
157 _requirement = b'fncache'
158
158
159 default = True
159 default = True
160
160
161 description = _(
161 description = _(
162 b'long and reserved filenames may not work correctly; '
162 b'long and reserved filenames may not work correctly; '
163 b'repository performance is sub-optimal'
163 b'repository performance is sub-optimal'
164 )
164 )
165
165
166 upgrademessage = _(
166 upgrademessage = _(
167 b'repository will be more resilient to storing '
167 b'repository will be more resilient to storing '
168 b'certain paths and performance of certain '
168 b'certain paths and performance of certain '
169 b'operations should be improved'
169 b'operations should be improved'
170 )
170 )
171
171
172
172
173 @registerformatvariant
173 @registerformatvariant
174 class dotencode(requirementformatvariant):
174 class dotencode(requirementformatvariant):
175 name = b'dotencode'
175 name = b'dotencode'
176
176
177 _requirement = b'dotencode'
177 _requirement = b'dotencode'
178
178
179 default = True
179 default = True
180
180
181 description = _(
181 description = _(
182 b'storage of filenames beginning with a period or '
182 b'storage of filenames beginning with a period or '
183 b'space may not work correctly'
183 b'space may not work correctly'
184 )
184 )
185
185
186 upgrademessage = _(
186 upgrademessage = _(
187 b'repository will be better able to store files '
187 b'repository will be better able to store files '
188 b'beginning with a space or period'
188 b'beginning with a space or period'
189 )
189 )
190
190
191
191
192 @registerformatvariant
192 @registerformatvariant
193 class generaldelta(requirementformatvariant):
193 class generaldelta(requirementformatvariant):
194 name = b'generaldelta'
194 name = b'generaldelta'
195
195
196 _requirement = b'generaldelta'
196 _requirement = b'generaldelta'
197
197
198 default = True
198 default = True
199
199
200 description = _(
200 description = _(
201 b'deltas within internal storage are unable to '
201 b'deltas within internal storage are unable to '
202 b'choose optimal revisions; repository is larger and '
202 b'choose optimal revisions; repository is larger and '
203 b'slower than it could be; interaction with other '
203 b'slower than it could be; interaction with other '
204 b'repositories may require extra network and CPU '
204 b'repositories may require extra network and CPU '
205 b'resources, making "hg push" and "hg pull" slower'
205 b'resources, making "hg push" and "hg pull" slower'
206 )
206 )
207
207
208 upgrademessage = _(
208 upgrademessage = _(
209 b'repository storage will be able to create '
209 b'repository storage will be able to create '
210 b'optimal deltas; new repository data will be '
210 b'optimal deltas; new repository data will be '
211 b'smaller and read times should decrease; '
211 b'smaller and read times should decrease; '
212 b'interacting with other repositories using this '
212 b'interacting with other repositories using this '
213 b'storage model should require less network and '
213 b'storage model should require less network and '
214 b'CPU resources, making "hg push" and "hg pull" '
214 b'CPU resources, making "hg push" and "hg pull" '
215 b'faster'
215 b'faster'
216 )
216 )
217
217
218
218
219 @registerformatvariant
219 @registerformatvariant
220 class sharesafe(requirementformatvariant):
220 class sharesafe(requirementformatvariant):
221 name = b'exp-sharesafe'
221 name = b'exp-sharesafe'
222 _requirement = requirements.SHARESAFE_REQUIREMENT
222 _requirement = requirements.SHARESAFE_REQUIREMENT
223
223
224 default = False
224 default = False
225
225
226 description = _(
226 description = _(
227 b'old shared repositories do not share source repository '
227 b'old shared repositories do not share source repository '
228 b'requirements and config. This leads to various problems '
228 b'requirements and config. This leads to various problems '
229 b'when the source repository format is upgraded or some new '
229 b'when the source repository format is upgraded or some new '
230 b'extensions are enabled.'
230 b'extensions are enabled.'
231 )
231 )
232
232
233 upgrademessage = _(
233 upgrademessage = _(
234 b'Upgrades a repository to share-safe format so that future '
234 b'Upgrades a repository to share-safe format so that future '
235 b'shares of this repository share its requirements and configs.'
235 b'shares of this repository share its requirements and configs.'
236 )
236 )
237
237
238
238
239 @registerformatvariant
239 @registerformatvariant
240 class sparserevlog(requirementformatvariant):
240 class sparserevlog(requirementformatvariant):
241 name = b'sparserevlog'
241 name = b'sparserevlog'
242
242
243 _requirement = requirements.SPARSEREVLOG_REQUIREMENT
243 _requirement = requirements.SPARSEREVLOG_REQUIREMENT
244
244
245 default = True
245 default = True
246
246
247 description = _(
247 description = _(
248 b'in order to limit disk reading and memory usage on older '
248 b'in order to limit disk reading and memory usage on older '
249 b'version, the span of a delta chain from its root to its '
249 b'version, the span of a delta chain from its root to its '
250 b'end is limited, whatever the relevant data in this span. '
250 b'end is limited, whatever the relevant data in this span. '
251 b'This can severly limit Mercurial ability to build good '
251 b'This can severly limit Mercurial ability to build good '
252 b'chain of delta resulting is much more storage space being '
252 b'chain of delta resulting is much more storage space being '
253 b'taken and limit reusability of on disk delta during '
253 b'taken and limit reusability of on disk delta during '
254 b'exchange.'
254 b'exchange.'
255 )
255 )
256
256
257 upgrademessage = _(
257 upgrademessage = _(
258 b'Revlog supports delta chain with more unused data '
258 b'Revlog supports delta chain with more unused data '
259 b'between payload. These gaps will be skipped at read '
259 b'between payload. These gaps will be skipped at read '
260 b'time. This allows for better delta chains, making a '
260 b'time. This allows for better delta chains, making a '
261 b'better compression and faster exchange with server.'
261 b'better compression and faster exchange with server.'
262 )
262 )
263
263
264
264
265 @registerformatvariant
265 @registerformatvariant
266 class sidedata(requirementformatvariant):
266 class sidedata(requirementformatvariant):
267 name = b'sidedata'
267 name = b'sidedata'
268
268
269 _requirement = requirements.SIDEDATA_REQUIREMENT
269 _requirement = requirements.SIDEDATA_REQUIREMENT
270
270
271 default = False
271 default = False
272
272
273 description = _(
273 description = _(
274 b'Allows storage of extra data alongside a revision, '
274 b'Allows storage of extra data alongside a revision, '
275 b'unlocking various caching options.'
275 b'unlocking various caching options.'
276 )
276 )
277
277
278 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
278 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
279
279
280
280
281 @registerformatvariant
281 @registerformatvariant
282 class persistentnodemap(requirementformatvariant):
282 class persistentnodemap(requirementformatvariant):
283 name = b'persistent-nodemap'
283 name = b'persistent-nodemap'
284
284
285 _requirement = requirements.NODEMAP_REQUIREMENT
285 _requirement = requirements.NODEMAP_REQUIREMENT
286
286
287 default = False
287 default = False
288
288
289 description = _(
289 description = _(
290 b'persist the node -> rev mapping on disk to speedup lookup'
290 b'persist the node -> rev mapping on disk to speedup lookup'
291 )
291 )
292
292
293 upgrademessage = _(b'Speedup revision lookup by node id.')
293 upgrademessage = _(b'Speedup revision lookup by node id.')
294
294
295
295
296 @registerformatvariant
296 @registerformatvariant
297 class copiessdc(requirementformatvariant):
297 class copiessdc(requirementformatvariant):
298 name = b'copies-sdc'
298 name = b'copies-sdc'
299
299
300 _requirement = requirements.COPIESSDC_REQUIREMENT
300 _requirement = requirements.COPIESSDC_REQUIREMENT
301
301
302 default = False
302 default = False
303
303
304 description = _(b'Stores copies information alongside changesets.')
304 description = _(b'Stores copies information alongside changesets.')
305
305
306 upgrademessage = _(
306 upgrademessage = _(
307 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
307 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
308 )
308 )
309
309
310
310
311 @registerformatvariant
311 @registerformatvariant
312 class removecldeltachain(formatvariant):
312 class removecldeltachain(formatvariant):
313 name = b'plain-cl-delta'
313 name = b'plain-cl-delta'
314
314
315 default = True
315 default = True
316
316
317 description = _(
317 description = _(
318 b'changelog storage is using deltas instead of '
318 b'changelog storage is using deltas instead of '
319 b'raw entries; changelog reading and any '
319 b'raw entries; changelog reading and any '
320 b'operation relying on changelog data are slower '
320 b'operation relying on changelog data are slower '
321 b'than they could be'
321 b'than they could be'
322 )
322 )
323
323
324 upgrademessage = _(
324 upgrademessage = _(
325 b'changelog storage will be reformated to '
325 b'changelog storage will be reformated to '
326 b'store raw entries; changelog reading will be '
326 b'store raw entries; changelog reading will be '
327 b'faster; changelog size may be reduced'
327 b'faster; changelog size may be reduced'
328 )
328 )
329
329
330 @staticmethod
330 @staticmethod
331 def fromrepo(repo):
331 def fromrepo(repo):
332 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
332 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
333 # changelogs with deltas.
333 # changelogs with deltas.
334 cl = repo.changelog
334 cl = repo.changelog
335 chainbase = cl.chainbase
335 chainbase = cl.chainbase
336 return all(rev == chainbase(rev) for rev in cl)
336 return all(rev == chainbase(rev) for rev in cl)
337
337
338 @staticmethod
338 @staticmethod
339 def fromconfig(repo):
339 def fromconfig(repo):
340 return True
340 return True
341
341
342
342
343 @registerformatvariant
343 @registerformatvariant
344 class compressionengine(formatvariant):
344 class compressionengine(formatvariant):
345 name = b'compression'
345 name = b'compression'
346 default = b'zlib'
346 default = b'zlib'
347
347
348 description = _(
348 description = _(
349 b'Compresion algorithm used to compress data. '
349 b'Compresion algorithm used to compress data. '
350 b'Some engine are faster than other'
350 b'Some engine are faster than other'
351 )
351 )
352
352
353 upgrademessage = _(
353 upgrademessage = _(
354 b'revlog content will be recompressed with the new algorithm.'
354 b'revlog content will be recompressed with the new algorithm.'
355 )
355 )
356
356
357 @classmethod
357 @classmethod
358 def fromrepo(cls, repo):
358 def fromrepo(cls, repo):
359 # we allow multiple compression engine requirement to co-exist because
359 # we allow multiple compression engine requirement to co-exist because
360 # strickly speaking, revlog seems to support mixed compression style.
360 # strickly speaking, revlog seems to support mixed compression style.
361 #
361 #
362 # The compression used for new entries will be "the last one"
362 # The compression used for new entries will be "the last one"
363 compression = b'zlib'
363 compression = b'zlib'
364 for req in repo.requirements:
364 for req in repo.requirements:
365 prefix = req.startswith
365 prefix = req.startswith
366 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
366 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
367 compression = req.split(b'-', 2)[2]
367 compression = req.split(b'-', 2)[2]
368 return compression
368 return compression
369
369
370 @classmethod
370 @classmethod
371 def fromconfig(cls, repo):
371 def fromconfig(cls, repo):
372 compengines = repo.ui.configlist(b'format', b'revlog-compression')
372 compengines = repo.ui.configlist(b'format', b'revlog-compression')
373 # return the first valid value as the selection code would do
373 # return the first valid value as the selection code would do
374 for comp in compengines:
374 for comp in compengines:
375 if comp in util.compengines:
375 if comp in util.compengines:
376 return comp
376 return comp
377
377
378 # no valide compression found lets display it all for clarity
378 # no valide compression found lets display it all for clarity
379 return b','.join(compengines)
379 return b','.join(compengines)
380
380
381
381
382 @registerformatvariant
382 @registerformatvariant
383 class compressionlevel(formatvariant):
383 class compressionlevel(formatvariant):
384 name = b'compression-level'
384 name = b'compression-level'
385 default = b'default'
385 default = b'default'
386
386
387 description = _(b'compression level')
387 description = _(b'compression level')
388
388
389 upgrademessage = _(b'revlog content will be recompressed')
389 upgrademessage = _(b'revlog content will be recompressed')
390
390
391 @classmethod
391 @classmethod
392 def fromrepo(cls, repo):
392 def fromrepo(cls, repo):
393 comp = compressionengine.fromrepo(repo)
393 comp = compressionengine.fromrepo(repo)
394 level = None
394 level = None
395 if comp == b'zlib':
395 if comp == b'zlib':
396 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
396 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
397 elif comp == b'zstd':
397 elif comp == b'zstd':
398 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
398 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
399 if level is None:
399 if level is None:
400 return b'default'
400 return b'default'
401 return bytes(level)
401 return bytes(level)
402
402
403 @classmethod
403 @classmethod
404 def fromconfig(cls, repo):
404 def fromconfig(cls, repo):
405 comp = compressionengine.fromconfig(repo)
405 comp = compressionengine.fromconfig(repo)
406 level = None
406 level = None
407 if comp == b'zlib':
407 if comp == b'zlib':
408 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
408 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
409 elif comp == b'zstd':
409 elif comp == b'zstd':
410 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
410 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
411 if level is None:
411 if level is None:
412 return b'default'
412 return b'default'
413 return bytes(level)
413 return bytes(level)
414
414
415
415
416 def find_format_upgrades(repo):
416 def find_format_upgrades(repo):
417 """returns a list of format upgrades which can be perform on the repo"""
417 """returns a list of format upgrades which can be perform on the repo"""
418 upgrades = []
418 upgrades = []
419
419
420 # We could detect lack of revlogv1 and store here, but they were added
420 # We could detect lack of revlogv1 and store here, but they were added
421 # in 0.9.2 and we don't support upgrading repos without these
421 # in 0.9.2 and we don't support upgrading repos without these
422 # requirements, so let's not bother.
422 # requirements, so let's not bother.
423
423
424 for fv in allformatvariant:
424 for fv in allformatvariant:
425 if not fv.fromrepo(repo):
425 if not fv.fromrepo(repo):
426 upgrades.append(fv)
426 upgrades.append(fv)
427
427
428 return upgrades
428 return upgrades
429
429
430
430
431 def find_format_downgrades(repo):
432 """returns a list of format downgrades which will be performed on the repo
433 because of disabled config option for them"""
434
435 downgrades = []
436
437 for fv in allformatvariant:
438 # format variant exist in repo but does not exist in new repository
439 # config
440 if fv.fromrepo(repo) and not fv.fromconfig(repo):
441 downgrades.append(fv)
442
443 return downgrades
444
445
431 ALL_OPTIMISATIONS = []
446 ALL_OPTIMISATIONS = []
432
447
433
448
434 def register_optimization(obj):
449 def register_optimization(obj):
435 ALL_OPTIMISATIONS.append(obj)
450 ALL_OPTIMISATIONS.append(obj)
436 return obj
451 return obj
437
452
438
453
439 register_optimization(
454 register_optimization(
440 improvement(
455 improvement(
441 name=b're-delta-parent',
456 name=b're-delta-parent',
442 type=OPTIMISATION,
457 type=OPTIMISATION,
443 description=_(
458 description=_(
444 b'deltas within internal storage will be recalculated to '
459 b'deltas within internal storage will be recalculated to '
445 b'choose an optimal base revision where this was not '
460 b'choose an optimal base revision where this was not '
446 b'already done; the size of the repository may shrink and '
461 b'already done; the size of the repository may shrink and '
447 b'various operations may become faster; the first time '
462 b'various operations may become faster; the first time '
448 b'this optimization is performed could slow down upgrade '
463 b'this optimization is performed could slow down upgrade '
449 b'execution considerably; subsequent invocations should '
464 b'execution considerably; subsequent invocations should '
450 b'not run noticeably slower'
465 b'not run noticeably slower'
451 ),
466 ),
452 upgrademessage=_(
467 upgrademessage=_(
453 b'deltas within internal storage will choose a new '
468 b'deltas within internal storage will choose a new '
454 b'base revision if needed'
469 b'base revision if needed'
455 ),
470 ),
456 )
471 )
457 )
472 )
458
473
459 register_optimization(
474 register_optimization(
460 improvement(
475 improvement(
461 name=b're-delta-multibase',
476 name=b're-delta-multibase',
462 type=OPTIMISATION,
477 type=OPTIMISATION,
463 description=_(
478 description=_(
464 b'deltas within internal storage will be recalculated '
479 b'deltas within internal storage will be recalculated '
465 b'against multiple base revision and the smallest '
480 b'against multiple base revision and the smallest '
466 b'difference will be used; the size of the repository may '
481 b'difference will be used; the size of the repository may '
467 b'shrink significantly when there are many merges; this '
482 b'shrink significantly when there are many merges; this '
468 b'optimization will slow down execution in proportion to '
483 b'optimization will slow down execution in proportion to '
469 b'the number of merges in the repository and the amount '
484 b'the number of merges in the repository and the amount '
470 b'of files in the repository; this slow down should not '
485 b'of files in the repository; this slow down should not '
471 b'be significant unless there are tens of thousands of '
486 b'be significant unless there are tens of thousands of '
472 b'files and thousands of merges'
487 b'files and thousands of merges'
473 ),
488 ),
474 upgrademessage=_(
489 upgrademessage=_(
475 b'deltas within internal storage will choose an '
490 b'deltas within internal storage will choose an '
476 b'optimal delta by computing deltas against multiple '
491 b'optimal delta by computing deltas against multiple '
477 b'parents; may slow down execution time '
492 b'parents; may slow down execution time '
478 b'significantly'
493 b'significantly'
479 ),
494 ),
480 )
495 )
481 )
496 )
482
497
483 register_optimization(
498 register_optimization(
484 improvement(
499 improvement(
485 name=b're-delta-all',
500 name=b're-delta-all',
486 type=OPTIMISATION,
501 type=OPTIMISATION,
487 description=_(
502 description=_(
488 b'deltas within internal storage will always be '
503 b'deltas within internal storage will always be '
489 b'recalculated without reusing prior deltas; this will '
504 b'recalculated without reusing prior deltas; this will '
490 b'likely make execution run several times slower; this '
505 b'likely make execution run several times slower; this '
491 b'optimization is typically not needed'
506 b'optimization is typically not needed'
492 ),
507 ),
493 upgrademessage=_(
508 upgrademessage=_(
494 b'deltas within internal storage will be fully '
509 b'deltas within internal storage will be fully '
495 b'recomputed; this will likely drastically slow down '
510 b'recomputed; this will likely drastically slow down '
496 b'execution time'
511 b'execution time'
497 ),
512 ),
498 )
513 )
499 )
514 )
500
515
501 register_optimization(
516 register_optimization(
502 improvement(
517 improvement(
503 name=b're-delta-fulladd',
518 name=b're-delta-fulladd',
504 type=OPTIMISATION,
519 type=OPTIMISATION,
505 description=_(
520 description=_(
506 b'every revision will be re-added as if it was new '
521 b'every revision will be re-added as if it was new '
507 b'content. It will go through the full storage '
522 b'content. It will go through the full storage '
508 b'mechanism giving extensions a chance to process it '
523 b'mechanism giving extensions a chance to process it '
509 b'(eg. lfs). This is similar to "re-delta-all" but even '
524 b'(eg. lfs). This is similar to "re-delta-all" but even '
510 b'slower since more logic is involved.'
525 b'slower since more logic is involved.'
511 ),
526 ),
512 upgrademessage=_(
527 upgrademessage=_(
513 b'each revision will be added as new content to the '
528 b'each revision will be added as new content to the '
514 b'internal storage; this will likely drastically slow '
529 b'internal storage; this will likely drastically slow '
515 b'down execution time, but some extensions might need '
530 b'down execution time, but some extensions might need '
516 b'it'
531 b'it'
517 ),
532 ),
518 )
533 )
519 )
534 )
520
535
521
536
522 def findoptimizations(repo):
537 def findoptimizations(repo):
523 """Determine optimisation that could be used during upgrade"""
538 """Determine optimisation that could be used during upgrade"""
524 # These are unconditionally added. There is logic later that figures out
539 # These are unconditionally added. There is logic later that figures out
525 # which ones to apply.
540 # which ones to apply.
526 return list(ALL_OPTIMISATIONS)
541 return list(ALL_OPTIMISATIONS)
527
542
528
543
529 def determine_upgrade_actions(
544 def determine_upgrade_actions(
530 repo, format_upgrades, optimizations, sourcereqs, destreqs
545 repo, format_upgrades, optimizations, sourcereqs, destreqs
531 ):
546 ):
532 """Determine upgrade actions that will be performed.
547 """Determine upgrade actions that will be performed.
533
548
534 Given a list of improvements as returned by ``find_format_upgrades`` and
549 Given a list of improvements as returned by ``find_format_upgrades`` and
535 ``findoptimizations``, determine the list of upgrade actions that
550 ``findoptimizations``, determine the list of upgrade actions that
536 will be performed.
551 will be performed.
537
552
538 The role of this function is to filter improvements if needed, apply
553 The role of this function is to filter improvements if needed, apply
539 recommended optimizations from the improvements list that make sense,
554 recommended optimizations from the improvements list that make sense,
540 etc.
555 etc.
541
556
542 Returns a list of action names.
557 Returns a list of action names.
543 """
558 """
544 newactions = []
559 newactions = []
545
560
546 for d in format_upgrades:
561 for d in format_upgrades:
547 name = d._requirement
562 name = d._requirement
548
563
549 # If the action is a requirement that doesn't show up in the
564 # If the action is a requirement that doesn't show up in the
550 # destination requirements, prune the action.
565 # destination requirements, prune the action.
551 if name is not None and name not in destreqs:
566 if name is not None and name not in destreqs:
552 continue
567 continue
553
568
554 newactions.append(d)
569 newactions.append(d)
555
570
556 newactions.extend(o for o in sorted(optimizations) if o not in newactions)
571 newactions.extend(o for o in sorted(optimizations) if o not in newactions)
557
572
558 # FUTURE consider adding some optimizations here for certain transitions.
573 # FUTURE consider adding some optimizations here for certain transitions.
559 # e.g. adding generaldelta could schedule parent redeltas.
574 # e.g. adding generaldelta could schedule parent redeltas.
560
575
561 return newactions
576 return newactions
562
577
563
578
564 class UpgradeOperation(object):
579 class UpgradeOperation(object):
565 """represent the work to be done during an upgrade"""
580 """represent the work to be done during an upgrade"""
566
581
567 def __init__(
582 def __init__(
568 self,
583 self,
569 ui,
584 ui,
570 new_requirements,
585 new_requirements,
571 current_requirements,
586 current_requirements,
572 upgrade_actions,
587 upgrade_actions,
573 revlogs_to_process,
588 revlogs_to_process,
574 ):
589 ):
575 self.ui = ui
590 self.ui = ui
576 self.new_requirements = new_requirements
591 self.new_requirements = new_requirements
577 self.current_requirements = current_requirements
592 self.current_requirements = current_requirements
578 # list of upgrade actions the operation will perform
593 # list of upgrade actions the operation will perform
579 self.upgrade_actions = upgrade_actions
594 self.upgrade_actions = upgrade_actions
580 self._upgrade_actions_names = set([a.name for a in upgrade_actions])
595 self._upgrade_actions_names = set([a.name for a in upgrade_actions])
581 self.revlogs_to_process = revlogs_to_process
596 self.revlogs_to_process = revlogs_to_process
582 # requirements which will be added by the operation
597 # requirements which will be added by the operation
583 self._added_requirements = (
598 self._added_requirements = (
584 self.new_requirements - self.current_requirements
599 self.new_requirements - self.current_requirements
585 )
600 )
586 # requirements which will be removed by the operation
601 # requirements which will be removed by the operation
587 self._removed_requirements = (
602 self._removed_requirements = (
588 self.current_requirements - self.new_requirements
603 self.current_requirements - self.new_requirements
589 )
604 )
590 # requirements which will be preserved by the operation
605 # requirements which will be preserved by the operation
591 self._preserved_requirements = (
606 self._preserved_requirements = (
592 self.current_requirements & self.new_requirements
607 self.current_requirements & self.new_requirements
593 )
608 )
594 # optimizations which are not used and it's recommended that they
609 # optimizations which are not used and it's recommended that they
595 # should use them
610 # should use them
596 all_optimizations = findoptimizations(None)
611 all_optimizations = findoptimizations(None)
597 self.unused_optimizations = [
612 self.unused_optimizations = [
598 i for i in all_optimizations if i not in self.upgrade_actions
613 i for i in all_optimizations if i not in self.upgrade_actions
599 ]
614 ]
600
615
601 def _write_labeled(self, l, label):
616 def _write_labeled(self, l, label):
602 """
617 """
603 Utility function to aid writing of a list under one label
618 Utility function to aid writing of a list under one label
604 """
619 """
605 first = True
620 first = True
606 for r in sorted(l):
621 for r in sorted(l):
607 if not first:
622 if not first:
608 self.ui.write(b', ')
623 self.ui.write(b', ')
609 self.ui.write(r, label=label)
624 self.ui.write(r, label=label)
610 first = False
625 first = False
611
626
612 def print_requirements(self):
627 def print_requirements(self):
613 self.ui.write(_(b'requirements\n'))
628 self.ui.write(_(b'requirements\n'))
614 self.ui.write(_(b' preserved: '))
629 self.ui.write(_(b' preserved: '))
615 self._write_labeled(
630 self._write_labeled(
616 self._preserved_requirements, "upgrade-repo.requirement.preserved"
631 self._preserved_requirements, "upgrade-repo.requirement.preserved"
617 )
632 )
618 self.ui.write((b'\n'))
633 self.ui.write((b'\n'))
619 if self._removed_requirements:
634 if self._removed_requirements:
620 self.ui.write(_(b' removed: '))
635 self.ui.write(_(b' removed: '))
621 self._write_labeled(
636 self._write_labeled(
622 self._removed_requirements, "upgrade-repo.requirement.removed"
637 self._removed_requirements, "upgrade-repo.requirement.removed"
623 )
638 )
624 self.ui.write((b'\n'))
639 self.ui.write((b'\n'))
625 if self._added_requirements:
640 if self._added_requirements:
626 self.ui.write(_(b' added: '))
641 self.ui.write(_(b' added: '))
627 self._write_labeled(
642 self._write_labeled(
628 self._added_requirements, "upgrade-repo.requirement.added"
643 self._added_requirements, "upgrade-repo.requirement.added"
629 )
644 )
630 self.ui.write((b'\n'))
645 self.ui.write((b'\n'))
631 self.ui.write(b'\n')
646 self.ui.write(b'\n')
632
647
633 def print_optimisations(self):
648 def print_optimisations(self):
634 optimisations = [
649 optimisations = [
635 a for a in self.upgrade_actions if a.type == OPTIMISATION
650 a for a in self.upgrade_actions if a.type == OPTIMISATION
636 ]
651 ]
637 optimisations.sort(key=lambda a: a.name)
652 optimisations.sort(key=lambda a: a.name)
638 if optimisations:
653 if optimisations:
639 self.ui.write(_(b'optimisations: '))
654 self.ui.write(_(b'optimisations: '))
640 self._write_labeled(
655 self._write_labeled(
641 [a.name for a in optimisations],
656 [a.name for a in optimisations],
642 "upgrade-repo.optimisation.performed",
657 "upgrade-repo.optimisation.performed",
643 )
658 )
644 self.ui.write(b'\n\n')
659 self.ui.write(b'\n\n')
645
660
646 def print_upgrade_actions(self):
661 def print_upgrade_actions(self):
647 for a in self.upgrade_actions:
662 for a in self.upgrade_actions:
648 self.ui.status(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
663 self.ui.status(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
649
664
650 def print_affected_revlogs(self):
665 def print_affected_revlogs(self):
651 if not self.revlogs_to_process:
666 if not self.revlogs_to_process:
652 self.ui.write((b'no revlogs to process\n'))
667 self.ui.write((b'no revlogs to process\n'))
653 else:
668 else:
654 self.ui.write((b'processed revlogs:\n'))
669 self.ui.write((b'processed revlogs:\n'))
655 for r in sorted(self.revlogs_to_process):
670 for r in sorted(self.revlogs_to_process):
656 self.ui.write((b' - %s\n' % r))
671 self.ui.write((b' - %s\n' % r))
657 self.ui.write((b'\n'))
672 self.ui.write((b'\n'))
658
673
659 def print_unused_optimizations(self):
674 def print_unused_optimizations(self):
660 for i in self.unused_optimizations:
675 for i in self.unused_optimizations:
661 self.ui.status(_(b'%s\n %s\n\n') % (i.name, i.description))
676 self.ui.status(_(b'%s\n %s\n\n') % (i.name, i.description))
662
677
663 def has_upgrade_action(self, name):
678 def has_upgrade_action(self, name):
664 """ Check whether the upgrade operation will perform this action """
679 """ Check whether the upgrade operation will perform this action """
665 return name in self._upgrade_actions_names
680 return name in self._upgrade_actions_names
666
681
667
682
668 ### Code checking if a repository can got through the upgrade process at all. #
683 ### Code checking if a repository can got through the upgrade process at all. #
669
684
670
685
671 def requiredsourcerequirements(repo):
686 def requiredsourcerequirements(repo):
672 """Obtain requirements required to be present to upgrade a repo.
687 """Obtain requirements required to be present to upgrade a repo.
673
688
674 An upgrade will not be allowed if the repository doesn't have the
689 An upgrade will not be allowed if the repository doesn't have the
675 requirements returned by this function.
690 requirements returned by this function.
676 """
691 """
677 return {
692 return {
678 # Introduced in Mercurial 0.9.2.
693 # Introduced in Mercurial 0.9.2.
679 b'revlogv1',
694 b'revlogv1',
680 # Introduced in Mercurial 0.9.2.
695 # Introduced in Mercurial 0.9.2.
681 b'store',
696 b'store',
682 }
697 }
683
698
684
699
685 def blocksourcerequirements(repo):
700 def blocksourcerequirements(repo):
686 """Obtain requirements that will prevent an upgrade from occurring.
701 """Obtain requirements that will prevent an upgrade from occurring.
687
702
688 An upgrade cannot be performed if the source repository contains a
703 An upgrade cannot be performed if the source repository contains a
689 requirements in the returned set.
704 requirements in the returned set.
690 """
705 """
691 return {
706 return {
692 # The upgrade code does not yet support these experimental features.
707 # The upgrade code does not yet support these experimental features.
693 # This is an artificial limitation.
708 # This is an artificial limitation.
694 requirements.TREEMANIFEST_REQUIREMENT,
709 requirements.TREEMANIFEST_REQUIREMENT,
695 # This was a precursor to generaldelta and was never enabled by default.
710 # This was a precursor to generaldelta and was never enabled by default.
696 # It should (hopefully) not exist in the wild.
711 # It should (hopefully) not exist in the wild.
697 b'parentdelta',
712 b'parentdelta',
698 # Upgrade should operate on the actual store, not the shared link.
713 # Upgrade should operate on the actual store, not the shared link.
699 requirements.SHARED_REQUIREMENT,
714 requirements.SHARED_REQUIREMENT,
700 }
715 }
701
716
702
717
703 def check_source_requirements(repo):
718 def check_source_requirements(repo):
704 """Ensure that no existing requirements prevent the repository upgrade"""
719 """Ensure that no existing requirements prevent the repository upgrade"""
705
720
706 required = requiredsourcerequirements(repo)
721 required = requiredsourcerequirements(repo)
707 missingreqs = required - repo.requirements
722 missingreqs = required - repo.requirements
708 if missingreqs:
723 if missingreqs:
709 msg = _(b'cannot upgrade repository; requirement missing: %s')
724 msg = _(b'cannot upgrade repository; requirement missing: %s')
710 missingreqs = b', '.join(sorted(missingreqs))
725 missingreqs = b', '.join(sorted(missingreqs))
711 raise error.Abort(msg % missingreqs)
726 raise error.Abort(msg % missingreqs)
712
727
713 blocking = blocksourcerequirements(repo)
728 blocking = blocksourcerequirements(repo)
714 blockingreqs = blocking & repo.requirements
729 blockingreqs = blocking & repo.requirements
715 if blockingreqs:
730 if blockingreqs:
716 m = _(b'cannot upgrade repository; unsupported source requirement: %s')
731 m = _(b'cannot upgrade repository; unsupported source requirement: %s')
717 blockingreqs = b', '.join(sorted(blockingreqs))
732 blockingreqs = b', '.join(sorted(blockingreqs))
718 raise error.Abort(m % blockingreqs)
733 raise error.Abort(m % blockingreqs)
719
734
720
735
721 ### Verify the validity of the planned requirement changes ####################
736 ### Verify the validity of the planned requirement changes ####################
722
737
723
738
724 def supportremovedrequirements(repo):
739 def supportremovedrequirements(repo):
725 """Obtain requirements that can be removed during an upgrade.
740 """Obtain requirements that can be removed during an upgrade.
726
741
727 If an upgrade were to create a repository that dropped a requirement,
742 If an upgrade were to create a repository that dropped a requirement,
728 the dropped requirement must appear in the returned set for the upgrade
743 the dropped requirement must appear in the returned set for the upgrade
729 to be allowed.
744 to be allowed.
730 """
745 """
731 supported = {
746 supported = {
732 requirements.SPARSEREVLOG_REQUIREMENT,
747 requirements.SPARSEREVLOG_REQUIREMENT,
733 requirements.SIDEDATA_REQUIREMENT,
748 requirements.SIDEDATA_REQUIREMENT,
734 requirements.COPIESSDC_REQUIREMENT,
749 requirements.COPIESSDC_REQUIREMENT,
735 requirements.NODEMAP_REQUIREMENT,
750 requirements.NODEMAP_REQUIREMENT,
736 requirements.SHARESAFE_REQUIREMENT,
751 requirements.SHARESAFE_REQUIREMENT,
737 }
752 }
738 for name in compression.compengines:
753 for name in compression.compengines:
739 engine = compression.compengines[name]
754 engine = compression.compengines[name]
740 if engine.available() and engine.revlogheader():
755 if engine.available() and engine.revlogheader():
741 supported.add(b'exp-compression-%s' % name)
756 supported.add(b'exp-compression-%s' % name)
742 if engine.name() == b'zstd':
757 if engine.name() == b'zstd':
743 supported.add(b'revlog-compression-zstd')
758 supported.add(b'revlog-compression-zstd')
744 return supported
759 return supported
745
760
746
761
747 def supporteddestrequirements(repo):
762 def supporteddestrequirements(repo):
748 """Obtain requirements that upgrade supports in the destination.
763 """Obtain requirements that upgrade supports in the destination.
749
764
750 If the result of the upgrade would create requirements not in this set,
765 If the result of the upgrade would create requirements not in this set,
751 the upgrade is disallowed.
766 the upgrade is disallowed.
752
767
753 Extensions should monkeypatch this to add their custom requirements.
768 Extensions should monkeypatch this to add their custom requirements.
754 """
769 """
755 supported = {
770 supported = {
756 b'dotencode',
771 b'dotencode',
757 b'fncache',
772 b'fncache',
758 b'generaldelta',
773 b'generaldelta',
759 b'revlogv1',
774 b'revlogv1',
760 b'store',
775 b'store',
761 requirements.SPARSEREVLOG_REQUIREMENT,
776 requirements.SPARSEREVLOG_REQUIREMENT,
762 requirements.SIDEDATA_REQUIREMENT,
777 requirements.SIDEDATA_REQUIREMENT,
763 requirements.COPIESSDC_REQUIREMENT,
778 requirements.COPIESSDC_REQUIREMENT,
764 requirements.NODEMAP_REQUIREMENT,
779 requirements.NODEMAP_REQUIREMENT,
765 requirements.SHARESAFE_REQUIREMENT,
780 requirements.SHARESAFE_REQUIREMENT,
766 }
781 }
767 for name in compression.compengines:
782 for name in compression.compengines:
768 engine = compression.compengines[name]
783 engine = compression.compengines[name]
769 if engine.available() and engine.revlogheader():
784 if engine.available() and engine.revlogheader():
770 supported.add(b'exp-compression-%s' % name)
785 supported.add(b'exp-compression-%s' % name)
771 if engine.name() == b'zstd':
786 if engine.name() == b'zstd':
772 supported.add(b'revlog-compression-zstd')
787 supported.add(b'revlog-compression-zstd')
773 return supported
788 return supported
774
789
775
790
776 def allowednewrequirements(repo):
791 def allowednewrequirements(repo):
777 """Obtain requirements that can be added to a repository during upgrade.
792 """Obtain requirements that can be added to a repository during upgrade.
778
793
779 This is used to disallow proposed requirements from being added when
794 This is used to disallow proposed requirements from being added when
780 they weren't present before.
795 they weren't present before.
781
796
782 We use a list of allowed requirement additions instead of a list of known
797 We use a list of allowed requirement additions instead of a list of known
783 bad additions because the whitelist approach is safer and will prevent
798 bad additions because the whitelist approach is safer and will prevent
784 future, unknown requirements from accidentally being added.
799 future, unknown requirements from accidentally being added.
785 """
800 """
786 supported = {
801 supported = {
787 b'dotencode',
802 b'dotencode',
788 b'fncache',
803 b'fncache',
789 b'generaldelta',
804 b'generaldelta',
790 requirements.SPARSEREVLOG_REQUIREMENT,
805 requirements.SPARSEREVLOG_REQUIREMENT,
791 requirements.SIDEDATA_REQUIREMENT,
806 requirements.SIDEDATA_REQUIREMENT,
792 requirements.COPIESSDC_REQUIREMENT,
807 requirements.COPIESSDC_REQUIREMENT,
793 requirements.NODEMAP_REQUIREMENT,
808 requirements.NODEMAP_REQUIREMENT,
794 requirements.SHARESAFE_REQUIREMENT,
809 requirements.SHARESAFE_REQUIREMENT,
795 }
810 }
796 for name in compression.compengines:
811 for name in compression.compengines:
797 engine = compression.compengines[name]
812 engine = compression.compengines[name]
798 if engine.available() and engine.revlogheader():
813 if engine.available() and engine.revlogheader():
799 supported.add(b'exp-compression-%s' % name)
814 supported.add(b'exp-compression-%s' % name)
800 if engine.name() == b'zstd':
815 if engine.name() == b'zstd':
801 supported.add(b'revlog-compression-zstd')
816 supported.add(b'revlog-compression-zstd')
802 return supported
817 return supported
803
818
804
819
805 def check_requirements_changes(repo, new_reqs):
820 def check_requirements_changes(repo, new_reqs):
806 old_reqs = repo.requirements
821 old_reqs = repo.requirements
807
822
808 support_removal = supportremovedrequirements(repo)
823 support_removal = supportremovedrequirements(repo)
809 no_remove_reqs = old_reqs - new_reqs - support_removal
824 no_remove_reqs = old_reqs - new_reqs - support_removal
810 if no_remove_reqs:
825 if no_remove_reqs:
811 msg = _(b'cannot upgrade repository; requirement would be removed: %s')
826 msg = _(b'cannot upgrade repository; requirement would be removed: %s')
812 no_remove_reqs = b', '.join(sorted(no_remove_reqs))
827 no_remove_reqs = b', '.join(sorted(no_remove_reqs))
813 raise error.Abort(msg % no_remove_reqs)
828 raise error.Abort(msg % no_remove_reqs)
814
829
815 support_addition = allowednewrequirements(repo)
830 support_addition = allowednewrequirements(repo)
816 no_add_reqs = new_reqs - old_reqs - support_addition
831 no_add_reqs = new_reqs - old_reqs - support_addition
817 if no_add_reqs:
832 if no_add_reqs:
818 m = _(b'cannot upgrade repository; do not support adding requirement: ')
833 m = _(b'cannot upgrade repository; do not support adding requirement: ')
819 no_add_reqs = b', '.join(sorted(no_add_reqs))
834 no_add_reqs = b', '.join(sorted(no_add_reqs))
820 raise error.Abort(m + no_add_reqs)
835 raise error.Abort(m + no_add_reqs)
821
836
822 supported = supporteddestrequirements(repo)
837 supported = supporteddestrequirements(repo)
823 unsupported_reqs = new_reqs - supported
838 unsupported_reqs = new_reqs - supported
824 if unsupported_reqs:
839 if unsupported_reqs:
825 msg = _(
840 msg = _(
826 b'cannot upgrade repository; do not support destination '
841 b'cannot upgrade repository; do not support destination '
827 b'requirement: %s'
842 b'requirement: %s'
828 )
843 )
829 unsupported_reqs = b', '.join(sorted(unsupported_reqs))
844 unsupported_reqs = b', '.join(sorted(unsupported_reqs))
830 raise error.Abort(msg % unsupported_reqs)
845 raise error.Abort(msg % unsupported_reqs)
General Comments 0
You need to be logged in to leave comments. Login now