hgext3rd/evolve/evolvecmd.py
changeset 3470 ece5cd58147d
parent 3469 e97bfd529e72
child 3477 713e26a647f5
equal deleted inserted replaced
3469:e97bfd529e72 3470:ece5cd58147d
    30 from mercurial.i18n import _
    30 from mercurial.i18n import _
    31 
    31 
    32 from . import (
    32 from . import (
    33     cmdrewrite,
    33     cmdrewrite,
    34     compat,
    34     compat,
       
    35     exthelper,
    35     rewriteutil,
    36     rewriteutil,
    36     state,
    37     state,
    37     utility,
    38     utility,
    38 )
    39 )
    39 
    40 
    40 TROUBLES = compat.TROUBLES
    41 TROUBLES = compat.TROUBLES
    41 shorttemplate = utility.shorttemplate
    42 shorttemplate = utility.shorttemplate
    42 _bookmarksupdater = rewriteutil.bookmarksupdater
    43 _bookmarksupdater = rewriteutil.bookmarksupdater
    43 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
    44 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
    44 
    45 
       
    46 eh = exthelper.exthelper()
    45 _bookmarksupdater = rewriteutil.bookmarksupdater
    47 _bookmarksupdater = rewriteutil.bookmarksupdater
       
    48 mergetoolopts = cmdutil.mergetoolopts
    46 
    49 
    47 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
    50 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
    48     """Resolve the troubles affecting one revision
    51     """Resolve the troubles affecting one revision
    49 
    52 
    50     returns a tuple (bool, newnode) where,
    53     returns a tuple (bool, newnode) where,
   947             'divergentnodes': divset,
   950             'divergentnodes': divset,
   948             'commonprecursor': b
   951             'commonprecursor': b
   949         })
   952         })
   950 
   953 
   951     return divergence
   954     return divergence
       
   955 
       
   956 @eh.command(
       
   957     '^evolve|stabilize|solve',
       
   958     [('n', 'dry-run', False,
       
   959       _('do not perform actions, just print what would be done')),
       
   960      ('', 'confirm', False,
       
   961       _('ask for confirmation before performing the action')),
       
   962      ('A', 'any', False,
       
   963       _('also consider troubled changesets unrelated to current working '
       
   964         'directory')),
       
   965      ('r', 'rev', [], _('solves troubles of these revisions')),
       
   966      ('', 'bumped', False, _('solves only bumped changesets')),
       
   967      ('', 'phase-divergent', False, _('solves only phase-divergent changesets')),
       
   968      ('', 'divergent', False, _('solves only divergent changesets')),
       
   969      ('', 'content-divergent', False, _('solves only content-divergent changesets')),
       
   970      ('', 'unstable', False, _('solves only unstable changesets')),
       
   971      ('', 'orphan', False, _('solves only orphan changesets (default)')),
       
   972      ('a', 'all', False, _('evolve all troubled changesets related to the '
       
   973                            'current  working directory and its descendants')),
       
   974      ('c', 'continue', False, _('continue an interrupted evolution')),
       
   975      ('l', 'list', False, 'provide details on troubled changesets in the repo'),
       
   976     ] + mergetoolopts,
       
   977     _('[OPTIONS]...')
       
   978 )
       
   979 def evolve(ui, repo, **opts):
       
   980     """solve troubled changesets in your repository
       
   981 
       
   982     Modifying history can lead to various types of troubled changesets:
       
   983     unstable, bumped, or divergent. The evolve command resolves your troubles
       
   984     by executing one of the following actions:
       
   985 
       
   986     - update working copy to a successor
       
   987     - rebase an unstable changeset
       
   988     - extract the desired changes from a bumped changeset
       
   989     - fuse divergent changesets back together
       
   990 
       
   991     If you pass no arguments, evolve works in automatic mode: it will execute a
       
   992     single action to reduce instability related to your working copy. There are
       
   993     two cases for this action. First, if the parent of your working copy is
       
   994     obsolete, evolve updates to the parent's successor. Second, if the working
       
   995     copy parent is not obsolete but has obsolete predecessors, then evolve
       
   996     determines if there is an unstable changeset that can be rebased onto the
       
   997     working copy parent in order to reduce instability.
       
   998     If so, evolve rebases that changeset. If not, evolve refuses to guess your
       
   999     intention, and gives a hint about what you might want to do next.
       
  1000 
       
  1001     Any time evolve creates a changeset, it updates the working copy to the new
       
  1002     changeset. (Currently, every successful evolve operation involves an update
       
  1003     as well; this may change in future.)
       
  1004 
       
  1005     Automatic mode only handles common use cases. For example, it avoids taking
       
  1006     action in the case of ambiguity, and it ignores unstable changesets that
       
  1007     are not related to your working copy.
       
  1008     It also refuses to solve bumped or divergent changesets unless you
       
  1009     explicitly request such behavior (see below).
       
  1010 
       
  1011     Eliminating all instability around your working copy may require multiple
       
  1012     invocations of :hg:`evolve`. Alternately, use ``--all`` to recursively
       
  1013     select and evolve all unstable changesets that can be rebased onto the
       
  1014     working copy parent.
       
  1015     This is more powerful than successive invocations, since ``--all`` handles
       
  1016     ambiguous cases (e.g. unstable changesets with multiple children) by
       
  1017     evolving all branches.
       
  1018 
       
  1019     When your repository cannot be handled by automatic mode, you might need to
       
  1020     use ``--rev`` to specify a changeset to evolve. For example, if you have
       
  1021     an unstable changeset that is not related to the working copy parent,
       
  1022     you could use ``--rev`` to evolve it. Or, if some changeset has multiple
       
  1023     unstable children, evolve in automatic mode refuses to guess which one to
       
  1024     evolve; you have to use ``--rev`` in that case.
       
  1025 
       
  1026     Alternately, ``--any`` makes evolve search for the next evolvable changeset
       
  1027     regardless of whether it is related to the working copy parent.
       
  1028 
       
  1029     You can supply multiple revisions to evolve multiple troubled changesets
       
  1030     in a single invocation. In revset terms, ``--any`` is equivalent to ``--rev
       
  1031     first(unstable())``. ``--rev`` and ``--all`` are mutually exclusive, as are
       
  1032     ``--rev`` and ``--any``.
       
  1033 
       
  1034     ``hg evolve --any --all`` is useful for cleaning up instability across all
       
  1035     branches, letting evolve figure out the appropriate order and destination.
       
  1036 
       
  1037     When you have troubled changesets that are not unstable, :hg:`evolve`
       
  1038     refuses to consider them unless you specify the category of trouble you
       
  1039     wish to resolve, with ``--bumped`` or ``--divergent``. These options are
       
  1040     currently mutually exclusive with each other and with ``--unstable``
       
  1041     (the default). You can combine ``--bumped`` or ``--divergent`` with
       
  1042     ``--rev``, ``--all``, or ``--any``.
       
  1043 
       
  1044     You can also use the evolve command to list the troubles affecting your
       
  1045     repository by using the --list flag. You can choose to display only some
       
  1046     categories of troubles with the --unstable, --divergent or --bumped flags.
       
  1047     """
       
  1048 
       
  1049     opts = _checkevolveopts(repo, opts)
       
  1050     # Options
       
  1051     contopt = opts['continue']
       
  1052     anyopt = opts['any']
       
  1053     allopt = opts['all']
       
  1054     startnode = repo['.']
       
  1055     dryrunopt = opts['dry_run']
       
  1056     confirmopt = opts['confirm']
       
  1057     revopt = opts['rev']
       
  1058 
       
  1059     troublecategories = ['phase_divergent', 'content_divergent', 'orphan']
       
  1060     specifiedcategories = [t.replace('_', '')
       
  1061                            for t in troublecategories
       
  1062                            if opts[t]]
       
  1063     if opts['list']:
       
  1064         compat.startpager(ui, 'evolve')
       
  1065         listtroubles(ui, repo, specifiedcategories, **opts)
       
  1066         return
       
  1067 
       
  1068     targetcat = 'orphan'
       
  1069     if 1 < len(specifiedcategories):
       
  1070         msg = _('cannot specify more than one trouble category to solve (yet)')
       
  1071         raise error.Abort(msg)
       
  1072     elif len(specifiedcategories) == 1:
       
  1073         targetcat = specifiedcategories[0]
       
  1074     elif repo['.'].obsolete():
       
  1075         displayer = cmdutil.show_changeset(ui, repo,
       
  1076                                            {'template': shorttemplate})
       
  1077         # no args and parent is obsolete, update to successors
       
  1078         try:
       
  1079             ctx = repo[utility._singlesuccessor(repo, repo['.'])]
       
  1080         except utility.MultipleSuccessorsError as exc:
       
  1081             repo.ui.write_err('parent is obsolete with multiple successors:\n')
       
  1082             for ln in exc.successorssets:
       
  1083                 for n in ln:
       
  1084                     displayer.show(repo[n])
       
  1085             return 2
       
  1086 
       
  1087         ui.status(_('update:'))
       
  1088         if not ui.quiet:
       
  1089             displayer.show(ctx)
       
  1090 
       
  1091         if dryrunopt:
       
  1092             return 0
       
  1093         res = hg.update(repo, ctx.rev())
       
  1094         if ctx != startnode:
       
  1095             ui.status(_('working directory is now at %s\n') % ctx)
       
  1096         return res
       
  1097 
       
  1098     ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'evolve')
       
  1099     troubled = set(repo.revs('troubled()'))
       
  1100 
       
  1101     # Progress handling
       
  1102     seen = 1
       
  1103     count = allopt and len(troubled) or 1
       
  1104     showprogress = allopt
       
  1105 
       
  1106     def progresscb():
       
  1107         if revopt or allopt:
       
  1108             ui.progress(_('evolve'), seen, unit=_('changesets'), total=count)
       
  1109 
       
  1110     # Continuation handling
       
  1111     if contopt:
       
  1112         evolvestate = state.cmdstate(repo)
       
  1113         if not evolvestate:
       
  1114             raise error.Abort('no evolve to continue')
       
  1115         evolvestate.load()
       
  1116         orig = repo[evolvestate['current']]
       
  1117         with repo.wlock(), repo.lock():
       
  1118             ctx = orig
       
  1119             source = ctx.extra().get('source')
       
  1120             extra = {}
       
  1121             if source:
       
  1122                 extra['source'] = source
       
  1123                 extra['intermediate-source'] = ctx.hex()
       
  1124             else:
       
  1125                 extra['source'] = ctx.hex()
       
  1126             user = ctx.user()
       
  1127             date = ctx.date()
       
  1128             message = ctx.description()
       
  1129             ui.status(_('evolving %d:%s "%s"\n') % (ctx.rev(), ctx,
       
  1130                                                     message.split('\n', 1)[0]))
       
  1131             targetphase = max(ctx.phase(), phases.draft)
       
  1132             overrides = {('phases', 'new-commit'): targetphase}
       
  1133 
       
  1134             with repo.ui.configoverride(overrides, 'evolve-continue'):
       
  1135                 node = repo.commit(text=message, user=user,
       
  1136                                    date=date, extra=extra)
       
  1137 
       
  1138             obsolete.createmarkers(repo, [(ctx, (repo[node],))])
       
  1139             evolvestate.delete()
       
  1140             return
       
  1141 
       
  1142     cmdutil.bailifchanged(repo)
       
  1143 
       
  1144     revs = _selectrevs(repo, allopt, revopt, anyopt, targetcat)
       
  1145 
       
  1146     if not revs:
       
  1147         return _handlenotrouble(ui, repo, allopt, revopt, anyopt, targetcat)
       
  1148 
       
  1149     # For the progress bar to show
       
  1150     count = len(revs)
       
  1151     replacements = {}
       
  1152     # Order the revisions
       
  1153     if targetcat == 'orphan':
       
  1154         revs = _orderrevs(repo, revs)
       
  1155     for rev in revs:
       
  1156         curctx = repo[rev]
       
  1157         progresscb()
       
  1158         ret = _solveone(ui, repo, curctx, dryrunopt, confirmopt,
       
  1159                         progresscb, targetcat)
       
  1160         seen += 1
       
  1161         if ret[0]:
       
  1162             replacements[curctx.node()] = [ret[1]]
       
  1163     progresscb()
       
  1164     _cleanup(ui, repo, startnode, showprogress)