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