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