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