Mercurial > hg
comparison mercurial/repair.py @ 30776:3997edc4a86d
repair: determine what upgrade will do
This commit introduces code for determining what actions/improvements
an upgrade should perform.
The "upgradefindimprovements" function introduces a mechanism to
return a list of improvements that can be made to a repository.
Each improvement is effectively an action that an upgrade will
perform. Associated with each of these improvements is metadata
that will be used to inform users what's wrong and what an
upgrade will do.
Each "improvement" is categorized as a "deficiency" or an
"optimization." TBH, I'm not thrilled about the terminology and
am receptive to constructive bikeshedding. The main difference
between a "deficiency" and an "optimization" is a deficiency
is always corrected (if it deviates from the current config) and
an "optimization" is an optional action that goes above and beyond
to improve the state of the repository (usually by requiring more
CPU during upgrade).
Our initial set of improvements identifies missing repository
requirements, a single, easily correctable problem with
changelog storage, and a set of "optimizations" related to delta
recalculation.
The main "upgraderepo" function has been expanded to handle
improvements. It queries for the list of improvements and determines
which of them will run based on the current repository state and user
I went through numerous iterations of the output format before
settling on a ReST-inspired definition list format. (I used
bulleted lists in the first submission of this commit and could
not get it to format just right.) Even with the various iterations,
I'm still not super thrilled with the format. But, this is a debug*
command, so that should mean we can refine the output without BC
concerns.
author | Gregory Szorc <gregory.szorc@gmail.com> |
---|---|
date | Sun, 18 Dec 2016 16:51:09 -0800 |
parents | 513d68a90398 |
children | 7de7afd8bdd9 |
comparison
equal
deleted
inserted
replaced
30775:513d68a90398 | 30776:3997edc4a86d |
---|---|
429 'dotencode', | 429 'dotencode', |
430 'fncache', | 430 'fncache', |
431 'generaldelta', | 431 'generaldelta', |
432 ]) | 432 ]) |
433 | 433 |
434 deficiency = 'deficiency' | |
435 optimisation = 'optimization' | |
436 | |
437 class upgradeimprovement(object): | |
438 """Represents an improvement that can be made as part of an upgrade. | |
439 | |
440 The following attributes are defined on each instance: | |
441 | |
442 name | |
443 Machine-readable string uniquely identifying this improvement. It | |
444 will be mapped to an action later in the upgrade process. | |
445 | |
446 type | |
447 Either ``deficiency`` or ``optimisation``. A deficiency is an obvious | |
448 problem. An optimization is an action (sometimes optional) that | |
449 can be taken to further improve the state of the repository. | |
450 | |
451 description | |
452 Message intended for humans explaining the improvement in more detail, | |
453 including the implications of it. For ``deficiency`` types, should be | |
454 worded in the present tense. For ``optimisation`` types, should be | |
455 worded in the future tense. | |
456 | |
457 upgrademessage | |
458 Message intended for humans explaining what an upgrade addressing this | |
459 issue will do. Should be worded in the future tense. | |
460 | |
461 fromdefault (``deficiency`` types only) | |
462 Boolean indicating whether the current (deficient) state deviates | |
463 from Mercurial's default configuration. | |
464 | |
465 fromconfig (``deficiency`` types only) | |
466 Boolean indicating whether the current (deficient) state deviates | |
467 from the current Mercurial configuration. | |
468 """ | |
469 def __init__(self, name, type, description, upgrademessage, **kwargs): | |
470 self.name = name | |
471 self.type = type | |
472 self.description = description | |
473 self.upgrademessage = upgrademessage | |
474 | |
475 for k, v in kwargs.items(): | |
476 setattr(self, k, v) | |
477 | |
478 def upgradefindimprovements(repo): | |
479 """Determine improvements that can be made to the repo during upgrade. | |
480 | |
481 Returns a list of ``upgradeimprovement`` describing repository deficiencies | |
482 and optimizations. | |
483 """ | |
484 # Avoid cycle: cmdutil -> repair -> localrepo -> cmdutil | |
485 from . import localrepo | |
486 | |
487 newreporeqs = localrepo.newreporequirements(repo) | |
488 | |
489 improvements = [] | |
490 | |
491 # We could detect lack of revlogv1 and store here, but they were added | |
492 # in 0.9.2 and we don't support upgrading repos without these | |
493 # requirements, so let's not bother. | |
494 | |
495 if 'fncache' not in repo.requirements: | |
496 improvements.append(upgradeimprovement( | |
497 name='fncache', | |
498 type=deficiency, | |
499 description=_('long and reserved filenames may not work correctly; ' | |
500 'repository performance is sub-optimal'), | |
501 upgrademessage=_('repository will be more resilient to storing ' | |
502 'certain paths and performance of certain ' | |
503 'operations should be improved'), | |
504 fromdefault=True, | |
505 fromconfig='fncache' in newreporeqs)) | |
506 | |
507 if 'dotencode' not in repo.requirements: | |
508 improvements.append(upgradeimprovement( | |
509 name='dotencode', | |
510 type=deficiency, | |
511 description=_('storage of filenames beginning with a period or ' | |
512 'space may not work correctly'), | |
513 upgrademessage=_('repository will be better able to store files ' | |
514 'beginning with a space or period'), | |
515 fromdefault=True, | |
516 fromconfig='dotencode' in newreporeqs)) | |
517 | |
518 if 'generaldelta' not in repo.requirements: | |
519 improvements.append(upgradeimprovement( | |
520 name='generaldelta', | |
521 type=deficiency, | |
522 description=_('deltas within internal storage are unable to ' | |
523 'choose optimal revisions; repository is larger and ' | |
524 'slower than it could be; interaction with other ' | |
525 'repositories may require extra network and CPU ' | |
526 'resources, making "hg push" and "hg pull" slower'), | |
527 upgrademessage=_('repository storage will be able to create ' | |
528 'optimal deltas; new repository data will be ' | |
529 'smaller and read times should decrease; ' | |
530 'interacting with other repositories using this ' | |
531 'storage model should require less network and ' | |
532 'CPU resources, making "hg push" and "hg pull" ' | |
533 'faster'), | |
534 fromdefault=True, | |
535 fromconfig='generaldelta' in newreporeqs)) | |
536 | |
537 # Mercurial 4.0 changed changelogs to not use delta chains. Search for | |
538 # changelogs with deltas. | |
539 cl = repo.changelog | |
540 for rev in cl: | |
541 chainbase = cl.chainbase(rev) | |
542 if chainbase != rev: | |
543 improvements.append(upgradeimprovement( | |
544 name='removecldeltachain', | |
545 type=deficiency, | |
546 description=_('changelog storage is using deltas instead of ' | |
547 'raw entries; changelog reading and any ' | |
548 'operation relying on changelog data are slower ' | |
549 'than they could be'), | |
550 upgrademessage=_('changelog storage will be reformated to ' | |
551 'store raw entries; changelog reading will be ' | |
552 'faster; changelog size may be reduced'), | |
553 fromdefault=True, | |
554 fromconfig=True)) | |
555 break | |
556 | |
557 # Now for the optimizations. | |
558 | |
559 # These are unconditionally added. There is logic later that figures out | |
560 # which ones to apply. | |
561 | |
562 improvements.append(upgradeimprovement( | |
563 name='redeltaparent', | |
564 type=optimisation, | |
565 description=_('deltas within internal storage will be recalculated to ' | |
566 'choose an optimal base revision where this was not ' | |
567 'already done; the size of the repository may shrink and ' | |
568 'various operations may become faster; the first time ' | |
569 'this optimization is performed could slow down upgrade ' | |
570 'execution considerably; subsequent invocations should ' | |
571 'not run noticeably slower'), | |
572 upgrademessage=_('deltas within internal storage will choose a new ' | |
573 'base revision if needed'))) | |
574 | |
575 improvements.append(upgradeimprovement( | |
576 name='redeltamultibase', | |
577 type=optimisation, | |
578 description=_('deltas within internal storage will be recalculated ' | |
579 'against multiple base revision and the smallest ' | |
580 'difference will be used; the size of the repository may ' | |
581 'shrink significantly when there are many merges; this ' | |
582 'optimization will slow down execution in proportion to ' | |
583 'the number of merges in the repository and the amount ' | |
584 'of files in the repository; this slow down should not ' | |
585 'be significant unless there are tens of thousands of ' | |
586 'files and thousands of merges'), | |
587 upgrademessage=_('deltas within internal storage will choose an ' | |
588 'optimal delta by computing deltas against multiple ' | |
589 'parents; may slow down execution time ' | |
590 'significantly'))) | |
591 | |
592 improvements.append(upgradeimprovement( | |
593 name='redeltaall', | |
594 type=optimisation, | |
595 description=_('deltas within internal storage will always be ' | |
596 'recalculated without reusing prior deltas; this will ' | |
597 'likely make execution run several times slower; this ' | |
598 'optimization is typically not needed'), | |
599 upgrademessage=_('deltas within internal storage will be fully ' | |
600 'recomputed; this will likely drastically slow down ' | |
601 'execution time'))) | |
602 | |
603 return improvements | |
604 | |
605 def upgradedetermineactions(repo, improvements, sourcereqs, destreqs, | |
606 optimize): | |
607 """Determine upgrade actions that will be performed. | |
608 | |
609 Given a list of improvements as returned by ``upgradefindimprovements``, | |
610 determine the list of upgrade actions that will be performed. | |
611 | |
612 The role of this function is to filter improvements if needed, apply | |
613 recommended optimizations from the improvements list that make sense, | |
614 etc. | |
615 | |
616 Returns a list of action names. | |
617 """ | |
618 newactions = [] | |
619 | |
620 knownreqs = upgradesupporteddestrequirements(repo) | |
621 | |
622 for i in improvements: | |
623 name = i.name | |
624 | |
625 # If the action is a requirement that doesn't show up in the | |
626 # destination requirements, prune the action. | |
627 if name in knownreqs and name not in destreqs: | |
628 continue | |
629 | |
630 if i.type == deficiency: | |
631 newactions.append(name) | |
632 | |
633 newactions.extend(o for o in sorted(optimize) if o not in newactions) | |
634 | |
635 # FUTURE consider adding some optimizations here for certain transitions. | |
636 # e.g. adding generaldelta could schedule parent redeltas. | |
637 | |
638 return newactions | |
639 | |
434 def upgraderepo(ui, repo, run=False, optimize=None): | 640 def upgraderepo(ui, repo, run=False, optimize=None): |
435 """Upgrade a repository in place.""" | 641 """Upgrade a repository in place.""" |
436 # Avoid cycle: cmdutil -> repair -> localrepo -> cmdutil | 642 # Avoid cycle: cmdutil -> repair -> localrepo -> cmdutil |
437 from . import localrepo | 643 from . import localrepo |
438 | 644 |
645 optimize = set(optimize or []) | |
439 repo = repo.unfiltered() | 646 repo = repo.unfiltered() |
440 | 647 |
441 # Ensure the repository can be upgraded. | 648 # Ensure the repository can be upgraded. |
442 missingreqs = upgraderequiredsourcerequirements(repo) - repo.requirements | 649 missingreqs = upgraderequiredsourcerequirements(repo) - repo.requirements |
443 if missingreqs: | 650 if missingreqs: |
471 if unsupportedreqs: | 678 if unsupportedreqs: |
472 raise error.Abort(_('cannot upgrade repository; do not support ' | 679 raise error.Abort(_('cannot upgrade repository; do not support ' |
473 'destination requirement: %s') % | 680 'destination requirement: %s') % |
474 _(', ').join(sorted(unsupportedreqs))) | 681 _(', ').join(sorted(unsupportedreqs))) |
475 | 682 |
683 # Find and validate all improvements that can be made. | |
684 improvements = upgradefindimprovements(repo) | |
685 for i in improvements: | |
686 if i.type not in (deficiency, optimisation): | |
687 raise error.Abort(_('unexpected improvement type %s for %s') % ( | |
688 i.type, i.name)) | |
689 | |
690 # Validate arguments. | |
691 unknownoptimize = optimize - set(i.name for i in improvements | |
692 if i.type == optimisation) | |
693 if unknownoptimize: | |
694 raise error.Abort(_('unknown optimization action requested: %s') % | |
695 ', '.join(sorted(unknownoptimize)), | |
696 hint=_('run without arguments to see valid ' | |
697 'optimizations')) | |
698 | |
699 actions = upgradedetermineactions(repo, improvements, repo.requirements, | |
700 newreqs, optimize) | |
701 | |
476 def printrequirements(): | 702 def printrequirements(): |
477 ui.write(_('requirements\n')) | 703 ui.write(_('requirements\n')) |
478 ui.write(_(' preserved: %s\n') % | 704 ui.write(_(' preserved: %s\n') % |
479 _(', ').join(sorted(newreqs & repo.requirements))) | 705 _(', ').join(sorted(newreqs & repo.requirements))) |
480 | 706 |
486 ui.write(_(' added: %s\n') % | 712 ui.write(_(' added: %s\n') % |
487 _(', ').join(sorted(newreqs - repo.requirements))) | 713 _(', ').join(sorted(newreqs - repo.requirements))) |
488 | 714 |
489 ui.write('\n') | 715 ui.write('\n') |
490 | 716 |
717 def printupgradeactions(): | |
718 for action in actions: | |
719 for i in improvements: | |
720 if i.name == action: | |
721 ui.write('%s\n %s\n\n' % | |
722 (i.name, i.upgrademessage)) | |
723 | |
491 if not run: | 724 if not run: |
725 fromdefault = [] | |
726 fromconfig = [] | |
727 optimizations = [] | |
728 | |
729 for i in improvements: | |
730 assert i.type in (deficiency, optimisation) | |
731 if i.type == deficiency: | |
732 if i.fromdefault: | |
733 fromdefault.append(i) | |
734 if i.fromconfig: | |
735 fromconfig.append(i) | |
736 else: | |
737 optimizations.append(i) | |
738 | |
739 if fromdefault or fromconfig: | |
740 fromconfignames = set(x.name for x in fromconfig) | |
741 onlydefault = [i for i in fromdefault | |
742 if i.name not in fromconfignames] | |
743 | |
744 if fromconfig: | |
745 ui.write(_('repository lacks features recommended by ' | |
746 'current config options:\n\n')) | |
747 for i in fromconfig: | |
748 ui.write('%s\n %s\n\n' % (i.name, i.description)) | |
749 | |
750 if onlydefault: | |
751 ui.write(_('repository lacks features used by the default ' | |
752 'config options:\n\n')) | |
753 for i in onlydefault: | |
754 ui.write('%s\n %s\n\n' % (i.name, i.description)) | |
755 | |
756 ui.write('\n') | |
757 else: | |
758 ui.write(_('(no feature deficiencies found in existing ' | |
759 'repository)\n')) | |
760 | |
492 ui.write(_('performing an upgrade with "--run" will make the following ' | 761 ui.write(_('performing an upgrade with "--run" will make the following ' |
493 'changes:\n\n')) | 762 'changes:\n\n')) |
494 | 763 |
495 printrequirements() | 764 printrequirements() |
765 printupgradeactions() | |
766 | |
767 unusedoptimize = [i for i in improvements | |
768 if i.name not in actions and i.type == optimisation] | |
769 if unusedoptimize: | |
770 ui.write(_('additional optimizations are available by specifying ' | |
771 '"--optimize <name>":\n\n')) | |
772 for i in unusedoptimize: | |
773 ui.write(_('%s\n %s\n\n') % (i.name, i.description)) |