changeset 4335:b2e2f9fe7a45

branching: merge with stable
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Sat, 05 Jan 2019 05:21:37 +0100
parents 594495e1e47e (diff) 24f90069b772 (current diff)
children ac39cfb5bddd
files tests/test-discovery-obshashrange.t
diffstat 45 files changed, 1258 insertions(+), 911 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGELOG	Fri Jan 04 19:17:38 2019 -0500
+++ b/CHANGELOG	Sat Jan 05 05:21:37 2019 +0100
@@ -1,6 +1,22 @@
 Changelog
 =========
 
+8.4.0 - in progress
+-------------------
+
+  * split: improve and update the user prompt (BC)
+  * split: make it possible to drop change during a split
+  * split: no longer accept revision with --rev (BC)
+  * split: accept file patterns
+  * split: support for non interactive splits
+  * push: have `--publish` overrule the `auto-publish` config
+  * next: evolve aspiring children by default (use --no-evolve to skip)
+  * next: pick lower part of a split as destination
+  * compat: drop compatibility with Mercurial 4.3
+  * topics: improve the message around topic changing
+  * stack: introduce a --children flag (see help for details)
+  * topic: make --age compatible with the usual other display for `hg topic`
+
 8.3.3 -- 2017-12-24
 -------------------
 
--- a/hgext3rd/evolve/__init__.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/__init__.py	Sat Jan 05 05:21:37 2019 +0100
@@ -2,6 +2,7 @@
 #                Logilab SA        <contact@logilab.fr>
 #                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
 #                Patrick Mezard <patrick@mezard.eu>
+#                Octobus <contact@octobus.net>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
@@ -32,7 +33,7 @@
 backported to older version of Mercurial by this extension. Some older
 experimental protocol are also supported for a longer time in the extensions to
 help people transitioning. (The extensions is currently compatible down to
-Mercurial version 4.3).
+Mercurial version 4.4).
 
 New Config::
 
@@ -285,7 +286,6 @@
     context,
     dirstate,
     error,
-    extensions,
     help,
     hg,
     lock as lockmod,
@@ -364,20 +364,18 @@
 eh.merge(compat.eh)
 eh.merge(cmdrewrite.eh)
 eh.merge(rewind.eh)
-uisetup = eh.final_uisetup
-extsetup = eh.final_extsetup
-reposetup = eh.final_reposetup
+uisetup = eh.finaluisetup
+extsetup = eh.finalextsetup
+reposetup = eh.finalreposetup
 cmdtable = eh.cmdtable
 configtable = eh.configtable
+revsetpredicate = eh.revsetpredicate
+templatekeyword = eh.templatekeyword
 
 # Configuration
-eh.configitem('experimental', 'evolutioncommands')
-eh.configitem('experimental', 'evolution.allnewcommands')
-eh.configitem('experimental', 'prunestrip')
-
-# hack around because we need an actual default there
-if configtable:
-    configtable['experimental']['evolution.allnewcommands'].default = None
+eh.configitem('experimental', 'evolutioncommands', [])
+eh.configitem('experimental', 'evolution.allnewcommands', None)
+eh.configitem('experimental', 'prunestrip', False)
 
 # pre hg 4.0 compat
 
@@ -435,7 +433,7 @@
     # This must be in the same function as the option configuration above to
     # guarantee it happens after the above configuration, but before the
     # extsetup functions.
-    evolvecommands = ui.configlist('experimental', 'evolutioncommands', [])
+    evolvecommands = ui.configlist('experimental', 'evolutioncommands')
     evolveopts = ui.configlist('experimental', 'evolution')
     if evolveopts and (commandopt not in evolveopts
                        and 'all' not in evolveopts):
@@ -516,7 +514,7 @@
 
 ### Troubled revset symbol
 
-@eh.revset('troubled()')
+@eh.revsetpredicate('troubled()')
 def revsettroubled(repo, subset, x):
     """Changesets with troubles.
     """
@@ -624,7 +622,7 @@
 
 
 ### XXX I'm not sure this revset is useful
-@eh.revset('suspended()')
+@eh.revsetpredicate('suspended()')
 def revsetsuspended(repo, subset, x):
     """Obsolete changesets with non-obsolete descendants.
     """
@@ -634,7 +632,7 @@
     return subset & suspended
 
 
-@eh.revset('precursors(set)')
+@eh.revsetpredicate('precursors(set)')
 def revsetprecursors(repo, subset, x):
     """Immediate precursors of changesets in set.
     """
@@ -644,7 +642,7 @@
     return subset & s
 
 
-@eh.revset('allprecursors(set)')
+@eh.revsetpredicate('allprecursors(set)')
 def revsetallprecursors(repo, subset, x):
     """Transitive precursors of changesets in set.
     """
@@ -654,7 +652,7 @@
     return subset & s
 
 
-@eh.revset('successors(set)')
+@eh.revsetpredicate('successors(set)')
 def revsetsuccessors(repo, subset, x):
     """Immediate successors of changesets in set.
     """
@@ -663,7 +661,7 @@
     s.sort()
     return subset & s
 
-@eh.revset('allsuccessors(set)')
+@eh.revsetpredicate('allsuccessors(set)')
 def revsetallsuccessors(repo, subset, x):
     """Transitive successors of changesets in set.
     """
@@ -781,45 +779,6 @@
     _warnobsoletewc(ui, repo)
     return res
 
-# XXX this could wrap transaction code
-# XXX (but this is a bit a layer violation)
-@eh.wrapcommand("commit")
-@eh.wrapcommand("import")
-@eh.wrapcommand("push")
-@eh.wrapcommand("pull")
-@eh.wrapcommand("graft")
-@eh.wrapcommand("phase")
-@eh.wrapcommand("unbundle")
-def warnobserrors(orig, ui, repo, *args, **kwargs):
-    """display warning is the command resulted in more instable changeset"""
-    # hg < 4.4 does not have the feature built in. bail out otherwise.
-    if util.safehasattr(scmutil, '_reportstroubledchangesets'):
-        return orig(ui, repo, *args, **kwargs)
-
-    # part of the troubled stuff may be filtered (stash ?)
-    # This needs a better implementation but will probably wait for core.
-    filtered = repo.changelog.filteredrevs
-    priorunstables = len(set(getrevs(repo, 'orphan')) - filtered)
-    priorbumpeds = len(set(getrevs(repo, 'phasedivergent')) - filtered)
-    priordivergents = len(set(getrevs(repo, 'contentdivergent')) - filtered)
-    ret = orig(ui, repo, *args, **kwargs)
-    filtered = repo.changelog.filteredrevs
-    newunstables = \
-        len(set(getrevs(repo, 'orphan')) - filtered) - priorunstables
-    newbumpeds = \
-        len(set(getrevs(repo, 'phasedivergent')) - filtered) - priorbumpeds
-    newdivergents = \
-        len(set(getrevs(repo, 'contentdivergent')) - filtered) - priordivergents
-
-    base_msg = _('%i new %s changesets\n')
-    if newunstables > 0:
-        ui.warn(base_msg % (newunstables, compat.TROUBLES['ORPHAN']))
-    if newbumpeds > 0:
-        ui.warn(base_msg % (newbumpeds, compat.TROUBLES['PHASEDIVERGENT']))
-    if newdivergents > 0:
-        ui.warn(base_msg % (newdivergents, compat.TROUBLES['CONTENTDIVERGENT']))
-    return ret
-
 @eh.wrapfunction(mercurial.exchange, 'push')
 def push(orig, repo, *args, **opts):
     """Add a hint for "hg evolve" when troubles make push fails
@@ -845,28 +804,6 @@
 def obssummarysetup(ui):
     cmdutil.summaryhooks.add('evolve', summaryhook)
 
-
-#####################################################################
-### Core Other extension compat                                   ###
-#####################################################################
-
-
-@eh.extsetup
-def _rebasewrapping(ui):
-    # warning about more obsolete
-    try:
-        rebase = extensions.find('rebase')
-        if rebase:
-            extensions.wrapcommand(rebase.cmdtable, 'rebase', warnobserrors)
-    except KeyError:
-        pass # rebase not found
-    try:
-        histedit = extensions.find('histedit')
-        if histedit:
-            extensions.wrapcommand(histedit.cmdtable, 'histedit', warnobserrors)
-    except KeyError:
-        pass # histedit not found
-
 #####################################################################
 ### Old Evolve extension content                                  ###
 #####################################################################
@@ -1071,11 +1008,7 @@
             if ui.config('commands', 'update.check') == 'noconflict':
                 pass
             else:
-                try:
-                    cmdutil.bailifchanged(repo)
-                except error.Abort as exc:
-                    exc.hint = _('do you want --merge?')
-                    raise
+                cmdutil.bailifchanged(repo, hint=_('do you want --merge?'))
 
         topic = not opts.get("no_topic", False)
         hastopic = bool(_getcurrenttopic(repo))
@@ -1109,7 +1042,7 @@
     [('B', 'move-bookmark', False,
         _('move active bookmark after update')),
      ('m', 'merge', False, _('bring uncommitted change along')),
-     ('', 'evolve', False, _('evolve the next changeset if necessary')),
+     ('', 'evolve', True, _('evolve the next changeset if necessary')),
      ('', 'no-topic', False, _('ignore topic and move topologically')),
      ('n', 'dry-run', False,
       _('do not perform actions, just print what would be done'))],
@@ -1118,7 +1051,8 @@
 def cmdnext(ui, repo, **opts):
     """update to next child revision
 
-    Use the ``--evolve`` flag to evolve unstable children on demand.
+    If necessary, evolve the next changeset. Use --no-evolve to disable this
+    behavior.
 
     Displays the summary line of the destination for clarity.
     """
@@ -1132,21 +1066,6 @@
         if len(wparents) != 1:
             raise error.Abort(_('merge in progress'))
 
-        # check for dirty wdir if --evolve is passed
-        if opts['evolve']:
-            cmdutil.bailifchanged(repo)
-
-        if not opts['merge']:
-            # we only skip the check if noconflict is set
-            if ui.config('commands', 'update.check') == 'noconflict':
-                pass
-            else:
-                try:
-                    cmdutil.bailifchanged(repo)
-                except error.Abort as exc:
-                    exc.hint = _('do you want --merge?')
-                    raise
-
         children = [ctx for ctx in wparents[0].children() if not ctx.obsolete()]
         topic = _getcurrenttopic(repo)
         filtered = set()
@@ -1156,6 +1075,39 @@
             children = [ctx for ctx in children if ctx not in filtered]
             template = utility.stacktemplate
         displayer = compat.changesetdisplayer(ui, repo, {'template': template})
+
+        # check if we need to evolve while updating to the next child revision
+        needevolve = False
+        aspchildren = evolvecmd._aspiringchildren(repo, [repo['.'].rev()])
+        if topic:
+            filtered.update(repo[c] for c in aspchildren
+                            if repo[c].topic() != topic)
+            aspchildren = [ctx for ctx in aspchildren if ctx not in filtered]
+
+        # To catch and prevent the case when `next` would get confused by split,
+        # lets filter those aspiring children which can be stablized on one of
+        # the aspiring children itself.
+        aspirants = set(aspchildren)
+        for aspchild in aspchildren:
+            possdests = evolvecmd._possibledestination(repo, aspchild)
+            if possdests & aspirants:
+                filtered.add(aspchild)
+        aspchildren = [ctx for ctx in aspchildren if ctx not in filtered]
+        if aspchildren:
+            needevolve = True
+
+        # check if working directory is clean before we evolve the next cset
+        if needevolve and opts['evolve']:
+            hint = _('use `hg amend`, `hg revert` or `hg shelve`')
+            cmdutil.bailifchanged(repo, hint=hint)
+
+        if not (opts['merge'] or (needevolve and opts['evolve'])):
+            # we only skip the check if noconflict is set
+            if ui.config('commands', 'update.check') == 'noconflict':
+                pass
+            else:
+                cmdutil.bailifchanged(repo, hint=_('do you want --merge?'))
+
         if len(children) == 1:
             c = children[0]
             return _updatetonext(ui, repo, c, displayer, opts)
@@ -1172,11 +1124,6 @@
             else:
                 return _updatetonext(ui, repo, repo[choosedrev], displayer, opts)
         else:
-            aspchildren = evolvecmd._aspiringchildren(repo, [repo['.'].rev()])
-            if topic:
-                filtered.update(repo[c] for c in aspchildren
-                                if repo[c].topic() != topic)
-                aspchildren = [ctx for ctx in aspchildren if ctx not in filtered]
             if not opts['evolve'] or not aspchildren:
                 if filtered:
                     ui.warn(_('no children on topic "%s"\n') % topic)
@@ -1188,7 +1135,7 @@
                             'do you want --evolve?)\n')
                     ui.warn(msg % len(aspchildren))
                 return 1
-            elif 1 < len(aspchildren):
+            elif len(aspchildren) > 1:
                 cheader = _("ambiguous next (unstable) changeset, choose one to"
                             " evolve and update:")
                 choosedrev = utility.revselectionprompt(ui, repo,
@@ -1303,7 +1250,7 @@
                            "backup bundle")),
     ])
 def stripwrapper(orig, ui, repo, *revs, **kwargs):
-    if (not ui.configbool('experimental', 'prunestrip', False)
+    if (not ui.configbool('experimental', 'prunestrip')
         or kwargs.get('bundle', False)):
         return orig(ui, repo, *revs, **kwargs)
 
@@ -1325,14 +1272,6 @@
 
 @eh.extsetup
 def oldevolveextsetup(ui):
-    for cmd in ['prune', 'uncommit', 'touch', 'fold']:
-        try:
-            entry = extensions.wrapcommand(cmdtable, cmd,
-                                           warnobserrors)
-        except error.UnknownCommand:
-            # Commands may be disabled
-            continue
-
     entry = cmdutil.findcmd('commit', commands.table)[1]
     entry[1].append(('o', 'obsolete', [],
                      _("make commit obsolete this revision (DEPRECATED)")))
--- a/hgext3rd/evolve/cmdrewrite.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/cmdrewrite.py	Sat Jan 05 05:21:37 2019 +0100
@@ -105,7 +105,7 @@
      ('', 'close-branch', None,
       _('mark a branch as closed, hiding it from the branch list')),
      ('s', 'secret', None, _('use the secret phase for committing')),
-     ('n', 'note', '', _('store a note on amend')),
+     ('n', 'note', '', _('store a note on amend'), _('TEXT')),
     ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt,
     _('[OPTION]... [FILE]...'),
     helpbasic=True)
@@ -462,9 +462,9 @@
     'uncommit',
     [('a', 'all', None, _('uncommit all changes when no arguments given')),
      ('i', 'interactive', False, _('interactive mode to uncommit (EXPERIMENTAL)')),
-     ('r', 'rev', '', _('revert commit content to REV instead')),
+     ('r', 'rev', '', _('revert commit content to REV instead'), _('REV')),
      ('', 'revert', False, _('discard working directory changes after uncommit')),
-     ('n', 'note', '', _('store a note on uncommit')),
+     ('n', 'note', '', _('store a note on uncommit'), _('TEXT')),
      ] + commands.walkopts + commitopts + commitopts2 + commitopts3,
     _('[OPTION]... [NAME]'))
 def uncommit(ui, repo, *pats, **opts):
@@ -662,10 +662,10 @@
 
 @eh.command(
     'fold|squash',
-    [('r', 'rev', [], _("revision to fold")),
+    [('r', 'rev', [], _("revision to fold"), _('REV')),
      ('', 'exact', None, _("only fold specified revisions")),
      ('', 'from', None, _("fold revisions linearly to working copy parent")),
-     ('n', 'note', '', _('store a note on fold')),
+     ('n', 'note', '', _('store a note on fold'), _('TEXT')),
     ] + commitopts + commitopts2 + commitopts3,
     _('hg fold [OPTION]... [-r] REV'),
     helpbasic=True)
@@ -791,9 +791,9 @@
 
 @eh.command(
     'metaedit',
-    [('r', 'rev', [], _("revision to edit")),
+    [('r', 'rev', [], _("revision to edit"), _('REV')),
      ('', 'fold', None, _("also fold specified revisions into one")),
-     ('n', 'note', '', _('store a note on metaedit')),
+     ('n', 'note', '', _('store a note on metaedit'), _('TEXT')),
     ] + commitopts + commitopts2 + commitopts3,
     _('hg metaedit [OPTION]... [-r] [REV]'))
 def metaedit(ui, repo, *revs, **opts):
@@ -941,10 +941,10 @@
 @eh.command(
     'prune|obsolete',
     [('n', 'new', [], _("successor changeset (DEPRECATED)")),
-     ('s', 'succ', [], _("successor changeset")),
-     ('r', 'rev', [], _("revisions to prune")),
+     ('s', 'succ', [], _("successor changeset"), _('REV')),
+     ('r', 'rev', [], _("revisions to prune"), _('REV')),
      ('k', 'keep', None, _("does not modify working copy during prune")),
-     ('n', 'note', '', _('store a note on prune')),
+     ('n', 'note', '', _('store a note on prune'), _('TEXT')),
      ('', 'pair', False, _("record a pairing, such as a rebase or divergence resolution "
                            "(pairing multiple precursors to multiple successors)")),
      ('', 'biject', False, _("alias to --pair (DEPRECATED)")),
@@ -953,7 +953,7 @@
      ('', 'split', False,
       _("record a split (on precursor, multiple successors)")),
      ('B', 'bookmark', [], _("remove revs only reachable from given"
-                             " bookmark"))] + metadataopts,
+                             " bookmark"), _('BOOKMARK'))] + metadataopts,
     _('[OPTION] [-r] REV...'),
     helpbasic=True)
 # XXX -U  --noupdate option to prevent wc update and or bookmarks update ?
@@ -1132,29 +1132,39 @@
 
 @eh.command(
     'split',
-    [('r', 'rev', [], _("revision to split")),
-     ('n', 'note', '', _("store a note on split")),
+    [('i', 'interactive', True, _('use interactive mode')),
+     ('r', 'rev', [], _("revision to split"), _('REV')),
+     ('n', 'note', '', _("store a note on split"), _('TEXT')),
     ] + commitopts + commitopts2 + commitopts3,
-    _('hg split [OPTION]... [-r] REV'),
+    _('hg split [OPTION] [-r REV] [FILES]'),
     helpbasic=True)
-def cmdsplit(ui, repo, *revs, **opts):
+def cmdsplit(ui, repo, *pats, **opts):
     """split a changeset into smaller changesets
 
     By default, split the current revision by prompting for all its hunks to be
     redistributed into new changesets.
 
     Use --rev to split a given changeset instead.
+
+    If file patterns are specified only files matching these patterns will be
+    considered to be split in earlier changesets. The files that doesn't match
+    will be gathered in the last changeset.
     """
     _checknotesize(ui, opts)
     _resolveoptions(ui, opts)
     tr = wlock = lock = None
     newcommits = []
+    iselect = opts.pop('interactive')
 
-    revarg = (list(revs) + opts.get('rev')) or ['.']
-    if len(revarg) != 1:
-        msg = _("more than one revset is given")
-        hnt = _("use either `hg split <rs>` or `hg split --rev <rs>`, not both")
-        raise error.Abort(msg, hint=hnt)
+    revs = opts.get('rev') or '.'
+    if not revs:
+        revarg = '.'
+    elif len(revs) == 1:
+        revarg = revs[0]
+    else:
+        # XXX --rev often accept multiple value, it seems safer to explicitly
+        # complains here instead of just taking the last value.
+        raise error.Abort(_('more than one revset is given'))
 
     # Save the current branch to restore it in the end
     savedbranch = repo.dirstate.branch()
@@ -1162,7 +1172,7 @@
     try:
         wlock = repo.wlock()
         lock = repo.lock()
-        ctx = scmutil.revsingle(repo, revarg[0])
+        ctx = scmutil.revsingle(repo, revarg)
         rev = ctx.rev()
         cmdutil.bailifchanged(repo)
         rewriteutil.precheck(repo, [rev], action='split')
@@ -1180,8 +1190,8 @@
         # Prepare the working directory
         rewriteutil.presplitupdate(repo, ui, prev, ctx)
 
-        def haschanges():
-            modified, added, removed, deleted = repo.status()[:4]
+        def haschanges(matcher=None):
+            modified, added, removed, deleted = repo.status(match=matcher)[:4]
             return modified or added or removed or deleted
         msg = ("HG: This is the original pre-split commit message. "
                "Edit it as appropriate.\n\n")
@@ -1195,21 +1205,79 @@
         # XXX-TODO: Find a way to set the branch without altering the dirstate
         repo.dirstate.setbranch(ctx.branch())
 
+        if pats:
+            # refresh the wctx used for the matcher
+            matcher = scmutil.match(repo[None], pats)
+        else:
+            matcher = scmutil.matchall(repo)
+
         while haschanges():
-            pats = ()
-            cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
-                             cmdutil.recordfilter, *pats, **opts)
-            # TODO: Does no seem like the best way to do this
-            # We should make dorecord return the newly created commit
-            newcommits.append(repo['.'])
-            if haschanges():
-                if ui.prompt('Done splitting? [yN]', default='n') == 'y':
+
+            if haschanges(matcher):
+                if iselect:
+                    cmdutil.dorecord(ui, repo, commands.commit, 'commit', False,
+                                     cmdutil.recordfilter, *pats, **opts)
+                    # TODO: Does no seem like the best way to do this
+                    # We should make dorecord return the newly created commit
+                    newcommits.append(repo['.'])
+                elif not pats:
+                    msg = _("no files of directories specified")
+                    hint = _("do you want --interactive")
+                    raise error.Abort(msg, hint=hint)
+                else:
+                    commands.commit(ui, repo, *pats, **opts)
+                    newcommits.append(repo['.'])
+            if pats:
+                # refresh the wctx used for the matcher
+                matcher = scmutil.match(repo[None], pats)
+            else:
+                matcher = scmutil.matchall(repo)
+
+            if haschanges(matcher):
+                nextaction = None
+                while nextaction is None:
+                    nextaction = ui.prompt('continue splitting? [Ycdq?]', default='y')
+                    if nextaction == 'c':
+                        commands.commit(ui, repo, **opts)
+                        newcommits.append(repo['.'])
+                        break
+                    elif nextaction == 'q':
+                        raise error.Abort(_('user quit'))
+                    elif nextaction == 'd':
+                        # TODO: We should offer a way for the user to confirm
+                        # what is the remaining changes, either via a separate
+                        # diff action or by showing the remaining and
+                        # prompting for confirmation
+                        ui.status(_('discarding remaining changes\n'))
+                        target = newcommits[0]
+                        if pats:
+                            status = repo.status(match=matcher)[:4]
+                            dirty = set()
+                            for i in status:
+                                dirty.update(i)
+                            dirty = sorted(dirty)
+                            cmdutil.revert(ui, repo, repo[target],
+                                           (target, node.nullid), *dirty)
+                        else:
+                            cmdutil.revert(ui, repo, repo[target],
+                                           (target, node.nullid), all=True)
+                    elif nextaction == '?':
+                        nextaction = None
+                        ui.write(_("y - yes, continue selection\n"))
+                        ui.write(_("c - commit, select all remaining changes\n"))
+                        ui.write(_("d - discard, discard remaining changes\n"))
+                        ui.write(_("q - quit, abort the split\n"))
+                        ui.write(_("? - ?, display help\n"))
+                else:
+                    continue
+                break # propagate the previous break
+            else:
+                ui.status(_("no more change to split\n"))
+                if haschanges():
+                    # XXX: Should we show a message for informing the user
+                    # that we create another commit with remaining changes?
                     commands.commit(ui, repo, **opts)
                     newcommits.append(repo['.'])
-                    break
-            else:
-                ui.status(_("no more change to split\n"))
-
         if newcommits:
             tip = repo[newcommits[-1]]
             bmupdate(tip.node())
@@ -1229,8 +1297,8 @@
 
 @eh.command(
     'touch',
-    [('r', 'rev', [], 'revision to update'),
-     ('n', 'note', '', _('store a note on touch')),
+    [('r', 'rev', [], _('revision to update'), _('REV')),
+     ('n', 'note', '', _('store a note on touch'), _('TEXT')),
      ('D', 'duplicate', False,
       'do not mark the new revision as successor of the old one'),
      ('A', 'allowdivergence', False,
@@ -1322,7 +1390,7 @@
 
 @eh.command(
     'pick|grab',
-    [('r', 'rev', '', 'revision to pick'),
+    [('r', 'rev', '', _('revision to pick'), _('REV')),
      ('c', 'continue', False, 'continue interrupted pick'),
      ('a', 'abort', False, 'abort interrupted pick'),
     ],
--- a/hgext3rd/evolve/compat.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/compat.py	Sat Jan 05 05:21:37 2019 +0100
@@ -77,17 +77,17 @@
     context.basectx.isunstable = context.basectx.troubled
 
 if not util.safehasattr(revset, 'orphan'):
-    @eh.revset('orphan')
+    @eh.revsetpredicate('orphan')
     def oprhanrevset(*args, **kwargs):
         return revset.unstable(*args, **kwargs)
 
 if not util.safehasattr(revset, 'contentdivergent'):
-    @eh.revset('contentdivergent')
+    @eh.revsetpredicate('contentdivergent')
     def contentdivergentrevset(*args, **kwargs):
         return revset.divergent(*args, **kwargs)
 
 if not util.safehasattr(revset, 'phasedivergent'):
-    @eh.revset('phasedivergent')
+    @eh.revsetpredicate('phasedivergent')
     def phasedivergentrevset(*args, **kwargs):
         return revset.bumped(*args, **kwargs)
 
@@ -466,256 +466,8 @@
 
     return copy, movewithdir, diverge, renamedelete, dirmove
 
-# code imported from Mercurial core at 4.3 + patch
-def fixoldmergecopies(repo, c1, c2, base):
-
-    from mercurial import pathutil
-
-    # avoid silly behavior for update from empty dir
-    if not c1 or not c2 or c1 == c2:
-        return {}, {}, {}, {}, {}
-
-    # avoid silly behavior for parent -> working dir
-    if c2.node() is None and c1.node() == repo.dirstate.p1():
-        return repo.dirstate.copies(), {}, {}, {}, {}
-
-    # Copy trace disabling is explicitly below the node == p1 logic above
-    # because the logic above is required for a simple copy to be kept across a
-    # rebase.
-    if repo.ui.configbool('experimental', 'disablecopytrace'):
-        return {}, {}, {}, {}, {}
-
-    # In certain scenarios (e.g. graft, update or rebase), base can be
-    # overridden We still need to know a real common ancestor in this case We
-    # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
-    # can be multiple common ancestors, e.g. in case of bidmerge.  Because our
-    # caller may not know if the revision passed in lieu of the CA is a genuine
-    # common ancestor or not without explicitly checking it, it's better to
-    # determine that here.
-    #
-    # base.descendant(wc) and base.descendant(base) are False, work around that
-    _c1 = c1.p1() if c1.rev() is None else c1
-    _c2 = c2.p1() if c2.rev() is None else c2
-    # an endpoint is "dirty" if it isn't a descendant of the merge base
-    # if we have a dirty endpoint, we need to trigger graft logic, and also
-    # keep track of which endpoint is dirty
-    dirtyc1 = not (base == _c1 or base.descendant(_c1))
-    dirtyc2 = not (base == _c2 or base.descendant(_c2))
-    graft = dirtyc1 or dirtyc2
-    tca = base
-    if graft:
-        tca = _c1.ancestor(_c2)
-
-    limit = copies._findlimit(repo, c1.rev(), c2.rev())
-    if limit is None:
-        # no common ancestor, no copies
-        return {}, {}, {}, {}, {}
-    repo.ui.debug("  searching for copies back to rev %d\n" % limit)
-
-    m1 = c1.manifest()
-    m2 = c2.manifest()
-    mb = base.manifest()
-
-    # gather data from _checkcopies:
-    # - diverge = record all diverges in this dict
-    # - copy = record all non-divergent copies in this dict
-    # - fullcopy = record all copies in this dict
-    # - incomplete = record non-divergent partial copies here
-    # - incompletediverge = record divergent partial copies here
-    diverge = {} # divergence data is shared
-    incompletediverge = {}
-    data1 = {'copy': {},
-             'fullcopy': {},
-             'incomplete': {},
-             'diverge': diverge,
-             'incompletediverge': incompletediverge,
-            }
-    data2 = {'copy': {},
-             'fullcopy': {},
-             'incomplete': {},
-             'diverge': diverge,
-             'incompletediverge': incompletediverge,
-            }
-
-    # find interesting file sets from manifests
-    addedinm1 = m1.filesnotin(mb)
-    addedinm2 = m2.filesnotin(mb)
-    bothnew = sorted(addedinm1 & addedinm2)
-    if tca == base:
-        # unmatched file from base
-        u1r, u2r = copies._computenonoverlap(repo, c1, c2, addedinm1, addedinm2)
-        u1u, u2u = u1r, u2r
-    else:
-        # unmatched file from base (DAG rotation in the graft case)
-        u1r, u2r = copies._computenonoverlap(repo, c1, c2, addedinm1, addedinm2,
-                                             baselabel='base')
-        # unmatched file from topological common ancestors (no DAG rotation)
-        # need to recompute this for directory move handling when grafting
-        mta = tca.manifest()
-        u1u, u2u = copies._computenonoverlap(repo, c1, c2, m1.filesnotin(mta),
-                                             m2.filesnotin(mta),
-                                             baselabel='topological common ancestor')
-
-    for f in u1u:
-        copies._checkcopies(c1, c2, f, base, tca, dirtyc1, limit, data1)
-
-    for f in u2u:
-        copies._checkcopies(c2, c1, f, base, tca, dirtyc2, limit, data2)
-
-    copy = dict(data1['copy'])
-    copy.update(data2['copy'])
-    fullcopy = dict(data1['fullcopy'])
-    fullcopy.update(data2['fullcopy'])
-
-    if dirtyc1:
-        copies._combinecopies(data2['incomplete'], data1['incomplete'], copy, diverge,
-                              incompletediverge)
-    else:
-        copies._combinecopies(data1['incomplete'], data2['incomplete'], copy, diverge,
-                              incompletediverge)
-
-    renamedelete = {}
-    renamedeleteset = set()
-    divergeset = set()
-    for of, fl in diverge.items():
-        if len(fl) == 1 or of in c1 or of in c2:
-            del diverge[of] # not actually divergent, or not a rename
-            if of not in c1 and of not in c2:
-                # renamed on one side, deleted on the other side, but filter
-                # out files that have been renamed and then deleted
-                renamedelete[of] = [f for f in fl if f in c1 or f in c2]
-                renamedeleteset.update(fl) # reverse map for below
-        else:
-            divergeset.update(fl) # reverse map for below
-
-    if bothnew:
-        repo.ui.debug("  unmatched files new in both:\n   %s\n"
-                      % "\n   ".join(bothnew))
-    bothdiverge = {}
-    bothincompletediverge = {}
-    remainder = {}
-    both1 = {'copy': {},
-             'fullcopy': {},
-             'incomplete': {},
-             'diverge': bothdiverge,
-             'incompletediverge': bothincompletediverge
-            }
-    both2 = {'copy': {},
-             'fullcopy': {},
-             'incomplete': {},
-             'diverge': bothdiverge,
-             'incompletediverge': bothincompletediverge
-            }
-    for f in bothnew:
-        copies._checkcopies(c1, c2, f, base, tca, dirtyc1, limit, both1)
-        copies._checkcopies(c2, c1, f, base, tca, dirtyc2, limit, both2)
-    if dirtyc1 and dirtyc2:
-        pass
-    elif dirtyc1:
-        # incomplete copies may only be found on the "dirty" side for bothnew
-        assert not both2['incomplete']
-        remainder = copies._combinecopies({}, both1['incomplete'], copy, bothdiverge,
-                                          bothincompletediverge)
-    elif dirtyc2:
-        assert not both1['incomplete']
-        remainder = copies._combinecopies({}, both2['incomplete'], copy, bothdiverge,
-                                          bothincompletediverge)
-    else:
-        # incomplete copies and divergences can't happen outside grafts
-        assert not both1['incomplete']
-        assert not both2['incomplete']
-        assert not bothincompletediverge
-    for f in remainder:
-        assert f not in bothdiverge
-        ic = remainder[f]
-        if ic[0] in (m1 if dirtyc1 else m2):
-            # backed-out rename on one side, but watch out for deleted files
-            bothdiverge[f] = ic
-    for of, fl in bothdiverge.items():
-        if len(fl) == 2 and fl[0] == fl[1]:
-            copy[fl[0]] = of # not actually divergent, just matching renames
-
-    if fullcopy and repo.ui.debugflag:
-        repo.ui.debug("  all copies found (* = to merge, ! = divergent, "
-                      "% = renamed and deleted):\n")
-        for f in sorted(fullcopy):
-            note = ""
-            if f in copy:
-                note += "*"
-            if f in divergeset:
-                note += "!"
-            if f in renamedeleteset:
-                note += "%"
-            repo.ui.debug("   src: '%s' -> dst: '%s' %s\n" % (fullcopy[f], f,
-                                                              note))
-    del divergeset
-
-    if not fullcopy:
-        return copy, {}, diverge, renamedelete, {}
-
-    repo.ui.debug("  checking for directory renames\n")
-
-    # generate a directory move map
-    d1, d2 = c1.dirs(), c2.dirs()
-    # Hack for adding '', which is not otherwise added, to d1 and d2
-    d1.addpath('/')
-    d2.addpath('/')
-    invalid = set()
-    dirmove = {}
-
-    # examine each file copy for a potential directory move, which is
-    # when all the files in a directory are moved to a new directory
-    for dst, src in fullcopy.iteritems():
-        dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
-        if dsrc in invalid:
-            # already seen to be uninteresting
-            continue
-        elif dsrc in d1 and ddst in d1:
-            # directory wasn't entirely moved locally
-            invalid.add(dsrc + "/")
-        elif dsrc in d2 and ddst in d2:
-            # directory wasn't entirely moved remotely
-            invalid.add(dsrc + "/")
-        elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
-            # files from the same directory moved to two different places
-            invalid.add(dsrc + "/")
-        else:
-            # looks good so far
-            dirmove[dsrc + "/"] = ddst + "/"
-
-    for i in invalid:
-        if i in dirmove:
-            del dirmove[i]
-    del d1, d2, invalid
-
-    if not dirmove:
-        return copy, {}, diverge, renamedelete, {}
-
-    for d in dirmove:
-        repo.ui.debug("   discovered dir src: '%s' -> dst: '%s'\n" %
-                      (d, dirmove[d]))
-
-    movewithdir = {}
-    # check unaccounted nonoverlapping files against directory moves
-    for f in u1r + u2r:
-        if f not in fullcopy:
-            for d in dirmove:
-                if f.startswith(d):
-                    # new file added in a directory that was moved, move it
-                    df = dirmove[d] + f[len(d):]
-                    if df not in copy:
-                        movewithdir[f] = df
-                        repo.ui.debug(("   pending file src: '%s' -> "
-                                       "dst: '%s'\n") % (f, df))
-                    break
-
-    return copy, movewithdir, diverge, renamedelete, dirmove
-
 if util.safehasattr(copies, '_fullcopytracing'):
     copies._fullcopytracing = fixedcopytracing
-elif util.safehasattr(copies, 'mergecopies'):
-    # compat fix for hg <= 4.3
-    copies.mergecopies = fixoldmergecopies
 
 if not util.safehasattr(obsutil, "_succs"):
     class _succs(list):
--- a/hgext3rd/evolve/evolvecmd.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/evolvecmd.py	Sat Jan 05 05:21:37 2019 +0100
@@ -716,10 +716,7 @@
                     " content-divergent changesets.\nHG: Resolve conflicts"
                     " in commit messages to continue.\n\n")
 
-        if 5 <= len(ui.edit.im_func.func_defaults): # <= hg-4.3
-            resolveddesc = ui.edit(prefixes + desc, ui.username(), action='desc')
-        else:
-            resolveddesc = ui.edit(prefixes + desc, ui.username())
+        resolveddesc = ui.edit(prefixes + desc, ui.username(), action='desc')
         # make sure we remove the prefixes part from final commit message
         if prefixes in resolveddesc:
             # hack, we should find something better
@@ -1310,10 +1307,7 @@
     """Compute sets of commits divergent with a given one"""
     cache = {}
     base = {}
-    allpredecessors = getattr(obsutil, 'allpredecessors', None)
-    if allpredecessors is None: # <= Mercurial 4.3
-        allpredecessors = obsutil.allprecursors
-    for n in allpredecessors(repo.obsstore, [ctx.node()]):
+    for n in obsutil.allpredecessors(repo.obsstore, [ctx.node()]):
         if n == ctx.node():
             # a node can't be a base for divergence with itself
             continue
@@ -1344,7 +1338,7 @@
      ('A', 'any', False,
       _('also consider troubled changesets unrelated to current working '
         'directory')),
-     ('r', 'rev', [], _('solves troubles of these revisions')),
+     ('r', 'rev', [], _('solves troubles of these revisions'), _('REV')),
      ('', 'bumped', False, _('solves only bumped changesets (DEPRECATED)')),
      ('', 'phase-divergent', False, _('solves only phase-divergent changesets')),
      ('', 'divergent', False, _('solves only divergent changesets (DEPRECATED)')),
@@ -1584,14 +1578,14 @@
         # to confirm that if atop msg should be suppressed to remove redundancy
         lastsolved = None
 
-        # check if revs to be evolved are in active topic to make sure that we
-        # can use stack aliases s# in evolve msgs.
         activetopic = getattr(repo, 'currenttopic', '')
         for rev in revs:
             curctx = repo[rev]
             revtopic = getattr(curctx, 'topic', lambda: '')()
             topicidx = getattr(curctx, 'topicidx', lambda: None)()
             stacktmplt = False
+            # check if revision being evolved is in active topic to make sure
+            # that we can use stack aliases s# in evolve msgs.
             if activetopic and (activetopic == revtopic) and topicidx is not None:
                 stacktmplt = True
             progresscb()
@@ -1753,14 +1747,25 @@
             # evolved to confirm that if atop msg should be suppressed to remove
             # redundancy
             lastsolved = None
+            activetopic = getattr(repo, 'currenttopic', '')
             for rev in evolvestate['revs']:
                 # XXX: prevent this lookup by storing nodes instead of revnums
                 curctx = unfi[rev]
+
+                # check if we can use stack template
+                revtopic = getattr(curctx, 'topic', lambda: '')()
+                topicidx = getattr(curctx, 'topicidx', lambda: None)()
+                stacktmplt = False
+                if (activetopic and (activetopic == revtopic)
+                    and topicidx is not None):
+                    stacktmplt = True
+
                 if (curctx.node() not in evolvestate['replacements']
                     and curctx.node() not in evolvestate['skippedrevs']):
                     newnode = _solveone(ui, repo, curctx, evolvestate, False,
                                         confirm, progresscb, category,
-                                        lastsolved=lastsolved)
+                                        lastsolved=lastsolved,
+                                        stacktmplt=stacktmplt)
                     if newnode[0]:
                         evolvestate['replacements'][curctx.node()] = newnode[1]
                         lastsolved = newnode[1]
--- a/hgext3rd/evolve/exthelper.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/exthelper.py	Sat Jan 05 05:21:37 2019 +0100
@@ -1,38 +1,82 @@
+# Copyright 2012 Logilab SA        <contact@logilab.fr>
+#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#                Octobus <contact@octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
 #####################################################################
 ### Extension helper                                              ###
 #####################################################################
 
 from mercurial import (
     commands,
+    error,
     extensions,
     registrar,
-    revset,
-    templatekw,
-    util,
 )
 
-configitem = None
-dynamicdefault = None
-if util.safehasattr(registrar, 'configitem'):
-    configitem = registrar.configitem
-    from mercurial import configitems
-    dynamicdefault = configitems.dynamicdefault
-
 class exthelper(object):
     """Helper for modular extension setup
 
-    A single helper should be instantiated for each extension. Helper
-    methods are then used as decorators for various purpose.
+    A single helper should be instantiated for each module of an
+    extension, where a command or function needs to be wrapped, or a
+    command, extension hook, fileset, revset or template needs to be
+    registered.  Helper methods are then used as decorators for
+    these various purposes.  If an extension spans multiple modules,
+    all helper instances should be merged in the main module.
 
     All decorators return the original function and may be chained.
+
+    Aside from the helper functions with examples below, several
+    registrar method aliases are available for adding commands,
+    configitems, filesets, revsets, and templates.  Simply decorate
+    the appropriate methods, and assign the corresponding exthelper
+    variable to a module level variable of the extension.  The
+    extension loading mechanism will handle the rest.
+
+    example::
+
+        # ext.py
+        eh = exthelper.exthelper()
+
+        # As needed:
+        cmdtable = eh.cmdtable
+        configtable = eh.configtable
+        filesetpredicate = eh.filesetpredicate
+        revsetpredicate = eh.revsetpredicate
+        templatekeyword = eh.templatekeyword
+
+        @eh.command('mynewcommand',
+            [('r', 'rev', [], _('operate on these revisions'))],
+            _('-r REV...'),
+            helpcategory=command.CATEGORY_XXX)
+        def newcommand(ui, repo, *revs, **opts):
+            # implementation goes here
+
+        eh.configitem('experimental', 'foo',
+            default=False,
+        )
+
+        @eh.filesetpredicate('lfs()')
+        def filesetbabar(mctx, x):
+            return mctx.predicate(...)
+
+        @eh.revsetpredicate('hidden')
+        def revsetbabar(repo, subset, x):
+            args = revset.getargs(x, 0, 0, 'babar accept no argument')
+            return [r for r in subset if 'babar' in repo[r].description()]
+
+        @eh.templatekeyword('babar')
+        def kwbabar(ctx):
+            return 'babar'
     """
 
     def __init__(self):
+        self._uipopulatecallables = []
         self._uicallables = []
         self._extcallables = []
         self._repocallables = []
-        self._revsetsymbols = []
-        self._templatekws = []
         self._commandwrappers = []
         self._extcommandwrappers = []
         self._functionwrappers = []
@@ -49,27 +93,19 @@
             self.command._doregister = _newdoregister
 
         self.configtable = {}
-        self._configitem = None
-        if configitem is not None:
-            self._configitem = configitem(self.configtable)
-
-    def configitem(self, section, config):
-        """For Mercurial 4.4 and above, register a config item
-
-        For now constraint to 'dynamicdefault' until we only support version with the feature.
-        Older version would otherwise not use the declare default.
-
-        For older version no-op fallback for old Mercurial versions
-        """
-        if self._configitem is not None:
-            self._configitem(section, config, default=dynamicdefault)
+        self.configitem = registrar.configitem(self.configtable)
+        self.filesetpredicate = registrar.filesetpredicate()
+        self.revsetpredicate = registrar.revsetpredicate()
+        self.templatekeyword = registrar.templatekeyword()
 
     def merge(self, other):
         self._uicallables.extend(other._uicallables)
+        self._uipopulatecallables.extend(other._uipopulatecallables)
         self._extcallables.extend(other._extcallables)
         self._repocallables.extend(other._repocallables)
-        self._revsetsymbols.extend(other._revsetsymbols)
-        self._templatekws.extend(other._templatekws)
+        self.filesetpredicate._table.update(other.filesetpredicate._table)
+        self.revsetpredicate._table.update(other.revsetpredicate._table)
+        self.templatekeyword._table.update(other.templatekeyword._table)
         self._commandwrappers.extend(other._commandwrappers)
         self._extcommandwrappers.extend(other._extcommandwrappers)
         self._functionwrappers.extend(other._functionwrappers)
@@ -81,7 +117,7 @@
             else:
                 self.configtable[section] = items
 
-    def final_uisetup(self, ui):
+    def finaluisetup(self, ui):
         """Method to be used as the extension uisetup
 
         The following operations belong here:
@@ -105,14 +141,26 @@
         for command, wrapper, opts in self._commandwrappers:
             entry = extensions.wrapcommand(commands.table, command, wrapper)
             if opts:
-                for short, long, val, msg in opts:
-                    entry[1].append((short, long, val, msg))
+                for opt in opts:
+                    entry[1].append(opt)
         for cont, funcname, wrapper in self._functionwrappers:
             extensions.wrapfunction(cont, funcname, wrapper)
         for c in self._uicallables:
             c(ui)
 
-    def final_extsetup(self, ui):
+    def finaluipopulate(self, ui):
+        """Method to be used as the extension uipopulate
+
+        This is called once per ui instance to:
+
+        - Set up additional ui members
+        - Update configuration by ``ui.setconfig()``
+        - Extend the class dynamically
+        """
+        for c in self._uipopulatecallables:
+            c(ui)
+
+    def finalextsetup(self, ui):
         """Method to be used as a the extension extsetup
 
         The following operations belong here:
@@ -120,23 +168,9 @@
         - Changes depending on the status of other extensions. (if
           extensions.find('mq'))
         - Add a global option to all commands
-        - Register revset functions
         """
         knownexts = {}
 
-        revsetpredicate = registrar.revsetpredicate()
-        for name, symbol in self._revsetsymbols:
-            revsetpredicate(name)(symbol)
-        revset.loadpredicate(ui, 'evolve', revsetpredicate)
-
-        templatekeyword = registrar.templatekeyword()
-        for name, kw, requires in self._templatekws:
-            if requires is not None:
-                templatekeyword(name, requires=requires)(kw)
-            else:
-                templatekeyword(name)(kw)
-        templatekw.loadkeyword(ui, 'evolve', templatekeyword)
-
         for ext, command, wrapper, opts in self._extcommandwrappers:
             if ext not in knownexts:
                 try:
@@ -148,13 +182,13 @@
                 knownexts[ext] = e.cmdtable
             entry = extensions.wrapcommand(knownexts[ext], command, wrapper)
             if opts:
-                for short, long, val, msg in opts:
-                    entry[1].append((short, long, val, msg))
+                for opt in opts:
+                    entry[1].append(opt)
 
         for c in self._extcallables:
             c(ui)
 
-    def final_reposetup(self, ui, repo):
+    def finalreposetup(self, ui, repo):
         """Method to be used as the extension reposetup
 
         The following operations belong here:
@@ -178,6 +212,18 @@
         self._uicallables.append(call)
         return call
 
+    def uipopulate(self, call):
+        """Decorated function will be executed during uipopulate
+
+        example::
+
+            @eh.uipopulate
+            def setupfoo(ui):
+                print 'this is uipopulate!'
+        """
+        self._uipopulatecallables.append(call)
+        return call
+
     def extsetup(self, call):
         """Decorated function will be executed during extsetup
 
@@ -202,42 +248,7 @@
         self._repocallables.append(call)
         return call
 
-    def revset(self, symbolname):
-        """Decorated function is a revset symbol
-
-        The name of the symbol must be given as the decorator argument.
-        The symbol is added during `extsetup`.
-
-        example::
-
-            @eh.revset('hidden')
-            def revsetbabar(repo, subset, x):
-                args = revset.getargs(x, 0, 0, 'babar accept no argument')
-                return [r for r in subset if 'babar' in repo[r].description()]
-        """
-        def dec(symbol):
-            self._revsetsymbols.append((symbolname, symbol))
-            return symbol
-        return dec
-
-    def templatekw(self, keywordname, requires=None):
-        """Decorated function is a template keyword
-
-        The name of the keyword must be given as the decorator argument.
-        The symbol is added during `extsetup`.
-
-        example::
-
-            @eh.templatekw('babar')
-            def kwbabar(ctx):
-                return 'babar'
-        """
-        def dec(keyword):
-            self._templatekws.append((keywordname, keyword, requires))
-            return keyword
-        return dec
-
-    def wrapcommand(self, command, extension=None, opts=[]):
+    def wrapcommand(self, command, extension=None, opts=None):
         """Decorated function is a command wrapper
 
         The name of the command must be given as the decorator argument.
@@ -256,10 +267,21 @@
                 ui.note('Barry!')
                 return orig(ui, repo, *args, **kwargs)
 
-        The `opts` argument allows specifying additional arguments for the
-        command.
+        The `opts` argument allows specifying a list of tuples for additional
+        arguments for the command.  See ``mercurial.fancyopts.fancyopts()`` for
+        the format of the tuple.
 
         """
+        if opts is None:
+            opts = []
+        else:
+            for opt in opts:
+                if not isinstance(opt, tuple):
+                    raise error.ProgrammingError('opts must be list of tuples')
+                if len(opt) not in (4, 5):
+                    msg = 'each opt tuple must contain 4 or 5 values'
+                    raise error.ProgrammingError(msg)
+
         def dec(wrapper):
             if extension is None:
                 self._commandwrappers.append((command, wrapper, opts))
@@ -294,9 +316,18 @@
         This function takes two arguments, the container and the name of the
         function to wrap. The wrapping is performed during `uisetup`.
 
+        Adding attributes to a container like this is discouraged, because the
+        container modification is visible even in repositories that do not
+        have the extension loaded.  Therefore, care must be taken that the
+        function doesn't make assumptions that the extension was loaded for the
+        current repository.  For `ui` and `repo` instances, a better option is
+        to subclass the instance in `uipopulate` and `reposetup` respectively.
+
+        https://www.mercurial-scm.org/wiki/WritingExtensions
+
         example::
 
-            @eh.function(context.changectx, 'babar')
+            @eh.addattr(context.changectx, 'babar')
             def babar(ctx):
                 return 'babar' in ctx.description
         """
--- a/hgext3rd/evolve/metadata.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/metadata.py	Sat Jan 05 05:21:37 2019 +0100
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-__version__ = '8.3.4.dev'
-testedwith = '4.3.2 4.4.2 4.5.2 4.6.2 4.7 4.8'
-minimumhgversion = '4.3'
+__version__ = '8.4.0.dev'
+testedwith = '4.4.2 4.5.2 4.6.2 4.7 4.8'
+minimumhgversion = '4.4'
 buglink = 'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obsdiscovery.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/obsdiscovery.py	Sat Jan 05 05:21:37 2019 +0100
@@ -74,11 +74,11 @@
 obsexcmsg = utility.obsexcmsg
 
 # Config
-eh.configitem('experimental', 'evolution.obsdiscovery')
-eh.configitem('experimental', 'obshashrange')
-eh.configitem('experimental', 'obshashrange.warm-cache')
-eh.configitem('experimental', 'obshashrange.max-revs')
-eh.configitem('experimental', 'obshashrange.lru-size')
+eh.configitem('experimental', 'evolution.obsdiscovery', True)
+eh.configitem('experimental', 'obshashrange', True)
+eh.configitem('experimental', 'obshashrange.warm-cache', 'auto')
+eh.configitem('experimental', 'obshashrange.max-revs', None)
+eh.configitem('experimental', 'obshashrange.lru-size', 2000)
 
 ##################################
 ###  Code performing discovery ###
@@ -775,9 +775,9 @@
     return encodelist(hashes)
 
 def _useobshashrange(repo):
-    base = repo.ui.configbool('experimental', 'obshashrange', True)
+    base = repo.ui.configbool('experimental', 'obshashrange')
     if base:
-        maxrevs = repo.ui.configint('experimental', 'obshashrange.max-revs', None)
+        maxrevs = repo.ui.configint('experimental', 'obshashrange.max-revs')
         if maxrevs is not None and maxrevs < len(repo.unfiltered()):
             base = False
     return base
@@ -945,7 +945,7 @@
     """wrapper to advertise new capability"""
     caps = orig(repo, proto)
     if (obsolete.isenabled(repo, obsolete.exchangeopt)
-        and repo.ui.configbool('experimental', 'evolution.obsdiscovery', True)):
+        and repo.ui.configbool('experimental', 'evolution.obsdiscovery')):
 
         # Compat hg 4.6+ (2f7290555c96)
         bytesresponse = False
@@ -1015,7 +1015,7 @@
 """
 
 def usediscovery(repo):
-    return repo.ui.configbool('experimental', 'evolution.obsdiscovery', True)
+    return repo.ui.configbool('experimental', 'evolution.obsdiscovery')
 
 @eh.wrapfunction(exchange, '_pushdiscoveryobsmarkers')
 def _pushdiscoveryobsmarkers(orig, pushop):
--- a/hgext3rd/evolve/obsexchange.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/obsexchange.py	Sat Jan 05 05:21:37 2019 +0100
@@ -39,7 +39,7 @@
 obsexcmsg = utility.obsexcmsg
 obsexcprg = utility.obsexcprg
 
-eh.configitem('experimental', 'verbose-obsolescence-exchange')
+eh.configitem('experimental', 'verbose-obsolescence-exchange', False)
 
 _bestformat = max(obsolete.formats.keys())
 
--- a/hgext3rd/evolve/obshistory.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/obshistory.py	Sat Jan 05 05:21:37 2019 +0100
@@ -14,7 +14,6 @@
     error,
     graphmod,
     patch,
-    obsolete,
     obsutil,
     node as nodemod,
     scmutil,
@@ -863,49 +862,6 @@
             return False
     return True
 
-# Wrap pre Mercurial 4.4 createmarkers that didn't included effect-flag
-if not util.safehasattr(obsutil, 'geteffectflag'):
-    @eh.wrapfunction(obsolete, 'createmarkers')
-    def createmarkerswithbits(orig, repo, relations, flag=0, date=None,
-                              metadata=None, **kwargs):
-        """compute 'effect-flag' and augment the created markers
-
-        Wrap obsolete.createmarker in order to compute the effect of each
-        relationship and store them as flag in the metadata.
-
-        While we experiment, we store flag in a metadata field. This field is
-        "versionned" to easilly allow moving to other meaning for flags.
-
-        The comparison of description or other infos just before creating the obs
-        marker might induce overhead in some cases. However it is a good place to
-        start since it automatically makes all markers creation recording more
-        meaningful data. In the future, we can introduce way for commands to
-        provide precomputed effect to avoid the overhead.
-        """
-        if not repo.ui.configbool('experimental', 'evolution.effect-flags', **efd):
-            return orig(repo, relations, flag, date, metadata, **kwargs)
-        if metadata is None:
-            metadata = {}
-        tr = repo.transaction('add-obsolescence-marker')
-        try:
-            for r in relations:
-                # Compute the effect flag for each obsmarker
-                effect = geteffectflag(r)
-
-                # Copy the metadata in order to add them, we copy because the
-                # effect flag might be different per relation
-                m = metadata.copy()
-                # we store the effect even if "0". This disctinct markers created
-                # without the feature with markers recording a no-op.
-                m['ef1'] = "%d" % effect
-
-                # And call obsolete.createmarkers for creating the obsmarker for real
-                orig(repo, [r], flag, date, m, **kwargs)
-
-            tr.close()
-        finally:
-            tr.release()
-
 def _getobsfate(successorssets):
     """ Compute a changeset obsolescence fate based on his successorssets.
     Successors can be the tipmost ones or the immediate ones.
--- a/hgext3rd/evolve/rewind.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/rewind.py	Sat Jan 05 05:21:37 2019 +0100
@@ -27,10 +27,11 @@
 
 @eh.command(
     'rewind|undo',
-    [('', 'to', [], _("rewind to these revisions")),
+    [('', 'to', [], _("rewind to these revisions"), _('REV')),
      ('', 'as-divergence', None, _("preserve current latest successors")),
      ('', 'exact', None, _("only rewind explicitly selected revisions")),
-     ('', 'from', [], _("rewind these revisions to their predecessors")),
+     ('', 'from', [],
+      _("rewind these revisions to their predecessors"), _('REV')),
     ],
     _(''),
     helpbasic=True)
--- a/hgext3rd/evolve/safeguard.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/safeguard.py	Sat Jan 05 05:21:37 2019 +0100
@@ -8,37 +8,48 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from mercurial import error
+from mercurial.i18n import _
 
-from mercurial.i18n import _
+from mercurial import (
+    configitems,
+    error,
+)
 
 from . import exthelper
 
 eh = exthelper.exthelper()
 
-eh.configitem('experimental', 'auto-publish')
+# hg <= 4.8
+if 'auto-publish' not in configitems.coreitems.get('experimental', {}):
+
+    eh.configitem('experimental', 'auto-publish', 'publish')
 
-@eh.reposetup
-def setuppublishprevention(ui, repo):
+    @eh.reposetup
+    def setuppublishprevention(ui, repo):
 
-    class noautopublishrepo(repo.__class__):
+        class noautopublishrepo(repo.__class__):
 
-        def checkpush(self, pushop):
-            super(noautopublishrepo, self).checkpush(pushop)
-            behavior = self.ui.config('experimental', 'auto-publish', 'default')
-            remotephases = pushop.remote.listkeys('phases')
-            publishing = remotephases.get('publishing', False)
-            if behavior in ('warn', 'abort') and publishing:
-                if pushop.revs is None:
-                    published = self.filtered('served').revs("not public()")
-                else:
-                    published = self.revs("::%ln - public()", pushop.revs)
-                if published:
-                    if behavior == 'warn':
-                        self.ui.warn(_('%i changesets about to be published\n') % len(published))
-                    elif behavior == 'abort':
-                        msg = _('push would publish 1 changesets')
-                        hint = _("behavior controlled by 'experimental.auto-publish' config")
-                        raise error.Abort(msg, hint=hint)
+            def checkpush(self, pushop):
+                super(noautopublishrepo, self).checkpush(pushop)
+                behavior = self.ui.config('experimental', 'auto-publish')
+                nocheck = behavior not in ('warn', 'abort')
+                if nocheck or getattr(pushop, 'publish', False):
+                    return
+                remotephases = pushop.remote.listkeys('phases')
+                publishing = remotephases.get('publishing', False)
+                if publishing:
+                    if pushop.revs is None:
+                        published = self.filtered('served').revs("not public()")
+                    else:
+                        published = self.revs("::%ln - public()", pushop.revs)
+                    if published:
+                        if behavior == 'warn':
+                            self.ui.warn(_('%i changesets about to be published\n')
+                                         % len(published))
+                        elif behavior == 'abort':
+                            msg = _('push would publish 1 changesets')
+                            hint = _("behavior controlled by "
+                                     "'experimental.auto-publish' config")
+                            raise error.Abort(msg, hint=hint)
 
-    repo.__class__ = noautopublishrepo
+        repo.__class__ = noautopublishrepo
--- a/hgext3rd/evolve/serveronly.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/serveronly.py	Sat Jan 05 05:21:37 2019 +0100
@@ -45,9 +45,9 @@
 eh.merge(compat.eh)
 eh.merge(obscache.eh)
 eh.merge(obsexchange.eh)
-uisetup = eh.final_uisetup
-extsetup = eh.final_extsetup
-reposetup = eh.final_reposetup
+uisetup = eh.finaluisetup
+extsetup = eh.finalextsetup
+reposetup = eh.finalreposetup
 cmdtable = eh.cmdtable
 configtable = eh.configtable
 
--- a/hgext3rd/evolve/templatekw.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/templatekw.py	Sat Jan 05 05:21:37 2019 +0100
@@ -9,7 +9,6 @@
 """
 
 from . import (
-    compat,
     error,
     exthelper,
     obshistory
@@ -17,7 +16,6 @@
 
 from mercurial import (
     templatekw,
-    node,
     util
 )
 
@@ -26,14 +24,14 @@
 ### template keywords
 
 if util.safehasattr(templatekw, 'compatlist'):
-    @eh.templatekw('troubles', requires=set(['ctx', 'templ']))
+    @eh.templatekeyword('troubles', requires=set(['ctx', 'templ']))
     def showtroubles(context, mapping):
         ctx = context.resource(mapping, 'ctx')
         return templatekw.compatlist(context, mapping, 'trouble',
                                      ctx.instabilities(), plural='troubles')
 else:
     # older template API in hg < 4.6
-    @eh.templatekw('troubles')
+    @eh.templatekeyword('troubles')
     def showtroubles(**args):
         """List of strings. Evolution troubles affecting the changeset
         (zero or more of "unstable", "divergent" or "bumped")."""
@@ -41,79 +39,14 @@
         return templatekw.showlist('trouble', ctx.instabilities(), args,
                                    plural='troubles')
 
-if util.safehasattr(templatekw, 'showpredecessors'):
-    templatekw.keywords["precursors"] = templatekw.showpredecessors
-else:
-    # for version <= hg4.3
-    def closestprecursors(repo, nodeid):
-        """ Yield the list of next precursors pointing on visible changectx nodes
-        """
-
-        precursors = repo.obsstore.predecessors
-        stack = [nodeid]
-        seen = set(stack)
-
-        while stack:
-            current = stack.pop()
-            currentpreccs = precursors.get(current, ())
-
-            for prec in currentpreccs:
-                precnodeid = prec[0]
-
-                # Basic cycle protection
-                if precnodeid in seen:
-                    continue
-                seen.add(precnodeid)
-
-                if precnodeid in repo:
-                    yield precnodeid
-                else:
-                    stack.append(precnodeid)
-
-    @eh.templatekw("precursors")
-    def shownextvisibleprecursors(repo, ctx, **args):
-        """Returns a string containing the list of the closest precursors
-        """
-        precursors = sorted(closestprecursors(repo, ctx.node()))
-        precursors = [node.hex(p) for p in precursors]
-
-        return templatekw._hybrid(None, precursors, lambda x: {'precursor': x},
-                                  lambda d: d['precursor'][:12])
+templatekw.keywords["precursors"] = templatekw.showpredecessors
 
 def closestsuccessors(repo, nodeid):
     """ returns the closest visible successors sets instead.
     """
     return directsuccessorssets(repo, nodeid)
 
-if util.safehasattr(templatekw, 'showsuccessorssets'):
-    templatekw.keywords["successors"] = templatekw.showsuccessorssets
-else:
-    # for version <= hg4.3
-
-    @eh.templatekw("successors")
-    def shownextvisiblesuccessors(repo, ctx, templ, **args):
-        """Returns a string of sets of successors for a changectx
-
-        Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
-        ctx2 while also diverged into ctx3"""
-        if not ctx.obsolete():
-            return ''
-
-        ssets, _ = closestsuccessors(repo, ctx.node())
-        ssets = [[node.hex(n) for n in ss] for ss in ssets]
-
-        data = []
-        gen = []
-        for ss in ssets:
-            subgen = '[%s]' % ', '.join(n[:12] for n in ss)
-            gen.append(subgen)
-            h = templatekw._hybrid(iter(subgen), ss, lambda x: {'successor': x},
-                                   lambda d: "%s" % d["successor"])
-            data.append(h)
-
-        gen = ', '.join(gen)
-        return templatekw._hybrid(iter(gen), data, lambda x: {'successorset': x},
-                                  lambda d: d["successorset"])
+templatekw.keywords["successors"] = templatekw.showsuccessorssets
 
 def _getusername(ui):
     """the default username in the config or None"""
@@ -241,25 +174,8 @@
 
     return "\n".join(lines)
 
-if util.safehasattr(templatekw, 'obsfateverb'):
-    # Individuals fragments are available in core
-    pass
-elif util.safehasattr(templatekw, 'compatlist'):
-    @eh.templatekw('obsfatedata', requires=set(['ctx', 'templ']))
-    def showobsfatedata(context, mapping):
-        ctx = context.resource(mapping, 'ctx')
-        repo = ctx.repo()
-        values = obsfatedata(repo, ctx)
-
-        if values is None:
-            return templatekw.compatlist(context, mapping, "obsfatedata", [])
-        args = mapping.copy()
-        args.pop('ctx')
-        args['templ'] = context
-        return _showobsfatedata(repo, ctx, values, **args)
-else:
-    # pre hg-4.6
-    @eh.templatekw("obsfatedata")
+if not util.safehasattr(templatekw, 'obsfateverb'): # <= hg-4.5
+    @eh.templatekeyword("obsfatedata")
     def showobsfatedata(repo, ctx, **args):
         # Get the needed obsfate data
         values = obsfatedata(repo, ctx)
@@ -325,30 +241,6 @@
 
     return templatekw._hybrid(gen, values, lambda x: {name: x}, fmt)
 
-# rely on core mercurial starting from 4.4 for the obsfate template
-if not util.safehasattr(templatekw, 'showobsfate'):
-
-    @eh.templatekw("obsfate")
-    def showobsfate(*args, **kwargs):
-        return showobsfatedata(*args, **kwargs)
-
-if util.safehasattr(compat.changesetprinter, '_showobsfate'):
-    pass # already included by default
-elif util.safehasattr(compat.changesetprinter, '_exthook'):
-    @eh.wrapfunction(compat.changesetprinter, '_exthook')
-    def exthook(original, self, ctx):
-        # Call potential other extensions
-        original(self, ctx)
-
-        obsfate = obsfatedata(self.repo, ctx)
-        if obsfate is None:
-            return ""
-
-        output = obsfateprinter(obsfate, self.ui, prefix="obsolete:    ")
-
-        self.ui.write(output, label='log.obsfate')
-        self.ui.write("\n")
-
 # copy from mercurial.obsolete with a small change to stop at first known changeset.
 
 def directsuccessorssets(repo, initialnode, cache=None):
--- a/hgext3rd/evolve/utility.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/evolve/utility.py	Sat Jan 05 05:21:37 2019 +0100
@@ -17,8 +17,7 @@
 stacktemplate = """[{label('evolve.rev', if(topicidx, "s{topicidx}", rev))}] {desc|firstline}\n"""
 
 def obsexcmsg(ui, message, important=False):
-    verbose = ui.configbool('experimental', 'verbose-obsolescence-exchange',
-                            False)
+    verbose = ui.configbool('experimental', 'verbose-obsolescence-exchange')
     if verbose:
         message = 'OBSEXC: ' + message
     if important or verbose:
@@ -26,7 +25,7 @@
 
 def obsexcprg(ui, *args, **kwargs):
     topic = 'obsmarkers exchange'
-    if ui.configbool('experimental', 'verbose-obsolescence-exchange', False):
+    if ui.configbool('experimental', 'verbose-obsolescence-exchange'):
         topic = 'OBSEXC'
     ui.progress(topic, *args, **kwargs)
 
@@ -63,12 +62,12 @@
         warm = autocase
     else:
         # note: we should not get to the default case
-        warm = configbool('experimental', 'obshashrange.warm-cache', True)
-    if not configbool('experimental', 'obshashrange', True):
+        warm = configbool('experimental', 'obshashrange.warm-cache')
+    if not configbool('experimental', 'obshashrange'):
         return False
     if not warm:
         return False
-    maxrevs = repo.ui.configint('experimental', 'obshashrange.max-revs', None)
+    maxrevs = repo.ui.configint('experimental', 'obshashrange.max-revs')
     if maxrevs is not None and maxrevs < len(repo.unfiltered()):
         return False
     return True
--- a/hgext3rd/topic/__init__.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/topic/__init__.py	Sat Jan 05 05:21:37 2019 +0100
@@ -177,10 +177,10 @@
               'topic.active': 'green',
              }
 
-__version__ = '0.12.4.dev'
+__version__ = '0.13.0.dev'
 
-testedwith = '4.3.3 4.4.2 4.5.2 4.6.2 4.7 4.8'
-minimumhgversion = '4.3'
+testedwith = '4.4.2 4.5.2 4.6.2 4.7 4.8'
+minimumhgversion = '4.4'
 buglink = 'https://bz.mercurial-scm.org/'
 
 if util.safehasattr(registrar, 'configitem'):
@@ -680,7 +680,11 @@
             txn = repo.transaction('rewrite-topics')
             rewrote = _changetopics(ui, repo, touchedrevs, topic)
             txn.close()
-            ui.status('changed topic on %d changes\n' % rewrote)
+            if topic is None:
+                ui.status('cleared topic on %d changesets\n' % rewrote)
+            else:
+                ui.status('changed topic on %d changesets to "%s"\n' % (rewrote,
+                                                                        topic))
         finally:
             lockmod.release(txn, lock, wl)
             repo.invalidate()
@@ -717,6 +721,8 @@
     return ret
 
 @command('stack', [
+        ('c', 'children', None,
+            _('display data about children outside of the stack'))
     ] + commands.formatteropts,
     _('hg stack [TOPIC]'))
 def cmdstack(ui, repo, topic='', **opts):
@@ -950,17 +956,21 @@
 
 def _listtopics(ui, repo, opts):
     fm = ui.formatter('topics', opts)
-    showlast = opts.get('age')
-    if showlast:
-        # we have a new function as plugging logic into existing function is
-        # pretty much difficult
-        return _showlasttouched(repo, fm, opts)
     activetopic = repo.currenttopic
     namemask = '%s'
     if repo.topics:
         maxwidth = max(len(t) for t in repo.topics)
         namemask = '%%-%is' % maxwidth
-    for topic in sorted(repo.topics):
+    if opts.get('age'):
+        # here we sort by age and topic name
+        topicsdata = sorted(_getlasttouched(repo, repo.topics))
+    else:
+        # here we sort by topic name only
+        topicsdata = (
+            (None, topic, None, None)
+            for topic in sorted(repo.topics)
+        )
+    for age, topic, date, user in topicsdata:
         fm.startitem()
         marker = ' '
         label = 'topic'
@@ -977,8 +987,18 @@
         if ui.quiet:
             fm.plain('\n')
             continue
+        fm.plain(' (')
+        if date:
+            if age == -1:
+                timestr = 'empty and active'
+            else:
+                timestr = templatefilters.age(date)
+            fm.write('lasttouched', '%s', timestr, label='topic.list.time')
+        if user:
+            fm.write('usertouched', ' by %s', user, label='topic.list.user')
+        if date:
+            fm.plain(', ')
         data = stack.stack(repo, topic=topic)
-        fm.plain(' (')
         if ui.verbose:
             fm.write('branches+', 'on branch: %s',
                      '+'.join(data.branches), # XXX use list directly after 4.0 is released
@@ -1018,52 +1038,17 @@
         fm.plain(')\n')
     fm.end()
 
-def _showlasttouched(repo, fm, opts):
-    topics = repo.topics
-    timedict = _getlasttouched(repo, topics)
-    times = timedict.keys()
-    times.sort()
-    if topics:
-        maxwidth = max(len(t) for t in topics)
-        namemask = '%%-%is' % maxwidth
-    activetopic = repo.currenttopic
-    for timevalue in times:
-        curtopics = sorted(timedict[timevalue][1])
-        for topic, user in curtopics:
-            fm.startitem()
-            marker = ' '
-            label = 'topic'
-            active = (topic == activetopic)
-            if active:
-                marker = '*'
-                label = 'topic.active'
-            fm.plain(' %s ' % marker, label=label)
-            fm.write('topic', namemask, topic, label=label)
-            fm.data(active=active)
-            fm.plain(' (')
-            if timevalue == -1:
-                timestr = 'empty and active'
-            else:
-                timestr = templatefilters.age(timedict[timevalue][0])
-            fm.write('lasttouched', '%s', timestr, label='topic.list.time')
-            if user:
-                fm.write('usertouched', ' by %s', user, label='topic.list.user')
-            fm.plain(')')
-            fm.plain('\n')
-    fm.end()
-
 def _getlasttouched(repo, topics):
     """
-    Calculates the last time a topic was used. Returns a dictionary of seconds
-    passed from current time for a topic as keys and topic name as values.
+    Calculates the last time a topic was used. Returns a generator of 4-tuples:
+    (age in seconds, topic name, date, and user who last touched the topic).
     """
-    topicstime = {}
     curtime = time.time()
-    for t in topics:
-        secspassed = -1
+    for topic in topics:
+        age = -1
         user = None
         maxtime = (0, 0)
-        trevs = repo.revs("topic(%s)", t)
+        trevs = repo.revs("topic(%s)", topic)
         # Need to check for the time of all changesets in the topic, whether
         # they are obsolete of non-heads
         # XXX: can we just rely on the max rev number for this
@@ -1084,16 +1069,10 @@
                     maxtime = rt
 
         username = stack.parseusername(user)
-        topicuser = (t, username)
+        if trevs:
+            age = curtime - maxtime[0]
 
-        if trevs:
-            secspassed = (curtime - maxtime[0])
-        try:
-            topicstime[secspassed][1].append(topicuser)
-        except KeyError:
-            topicstime[secspassed] = (maxtime, [topicuser])
-
-    return topicstime
+        yield (age, topic, maxtime, username)
 
 def summaryhook(ui, repo):
     t = getattr(repo, 'currenttopic', '')
--- a/hgext3rd/topic/discovery.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/topic/discovery.py	Sat Jan 05 05:21:37 2019 +0100
@@ -33,32 +33,30 @@
     publishedset = ()
     remotebranchmap = None
     origremotebranchmap = remote.branchmap
-    # < hg-4.4 do not have a --publish flag anyway
-    if util.safehasattr(pushop, 'remotephases'):
-        publishednode = [c.node() for c in pushop.outdatedphases]
-        publishedset = repo.revs('ancestors(%ln + %ln)',
-                                 publishednode,
-                                 pushop.remotephases.publicheads)
+    publishednode = [c.node() for c in pushop.outdatedphases]
+    publishedset = repo.revs('ancestors(%ln + %ln)',
+                             publishednode,
+                             pushop.remotephases.publicheads)
 
-        rev = repo.unfiltered().changelog.nodemap.get
+    rev = repo.unfiltered().changelog.nodemap.get
 
-        def remotebranchmap():
-            # drop topic information from changeset about to be published
-            result = collections.defaultdict(list)
-            for branch, heads in origremotebranchmap().iteritems():
-                if ':' not in branch:
-                    result[branch].extend(heads)
-                else:
-                    namedbranch = branch.split(':', 1)[0]
-                    for h in heads:
-                        r = rev(h)
-                        if r is not None and r in publishedset:
-                            result[namedbranch].append(h)
-                        else:
-                            result[branch].append(h)
-            for heads in result.itervalues():
-                heads.sort()
-            return result
+    def remotebranchmap():
+        # drop topic information from changeset about to be published
+        result = collections.defaultdict(list)
+        for branch, heads in origremotebranchmap().iteritems():
+            if ':' not in branch:
+                result[branch].extend(heads)
+            else:
+                namedbranch = branch.split(':', 1)[0]
+                for h in heads:
+                    r = rev(h)
+                    if r is not None and r in publishedset:
+                        result[namedbranch].append(h)
+                    else:
+                        result[branch].append(h)
+        for heads in result.itervalues():
+            heads.sort()
+        return result
 
     class repocls(repo.__class__):
         # awful hack to see branch as "branch:topic"
--- a/hgext3rd/topic/flow.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/topic/flow.py	Sat Jan 05 05:21:37 2019 +0100
@@ -7,7 +7,6 @@
     extensions,
     node,
     phases,
-    util,
 )
 
 from mercurial.i18n import _
@@ -75,9 +74,6 @@
 def wrapphasediscovery(orig, pushop):
     orig(pushop)
     if getattr(pushop, 'publish', False):
-        if not util.safehasattr(pushop, 'remotephases'):
-            msg = _('--publish flag only supported from Mercurial 4.4 and higher')
-            raise error.Abort(msg)
         if not pushop.remotephases.publishing:
             unfi = pushop.repo.unfiltered()
             droots = pushop.remotephases.draftroots
@@ -87,8 +83,9 @@
 
 def installpushflag(ui):
     entry = extensions.wrapcommand(commands.table, 'push', wrappush)
-    entry[1].append(('', 'publish', False,
-                    _('push the changeset as public')))
+    if not any(opt for opt in entry[1] if opt[1] == 'publish'): # hg <= 4.9
+        entry[1].append(('', 'publish', False,
+                         _('push the changeset as public')))
     extensions.wrapfunction(exchange.pushoperation, '__init__',
                             extendpushoperation)
     extensions.wrapfunction(exchange, '_pushdiscoveryphase', wrapphasediscovery)
--- a/hgext3rd/topic/randomname.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/topic/randomname.py	Sat Jan 05 05:21:37 2019 +0100
@@ -189,7 +189,6 @@
     'pony',
     'porcupine',
     'porpoise',
-    'prairie',
     'puffin',
     'pug',
     'quagga',
--- a/hgext3rd/topic/revset.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/topic/revset.py	Sat Jan 05 05:21:37 2019 +0100
@@ -106,3 +106,39 @@
     else:
         branch = repo[None].branch()
     return revset.baseset(stack.stack(repo, branch=branch, topic=topic)[1:]) & subset
+
+if util.safehasattr(revset, 'subscriptrelations'):
+    def stackrel(repo, subset, x, rel, n, order):
+        """This is a revset-flavored implementation of stack aliases.
+
+        The syntax is: rev#stack[n] or rev#s[n]. Plenty of logic is borrowed
+        from topic._namemap, but unlike that function, which prefers to abort
+        (e.g. when stack index is too high), this returns empty set to be more
+        revset-friendly.
+        """
+        s = revset.getset(repo, revset.fullreposet(repo), x)
+        if not s:
+            return revset.baseset()
+        revs = []
+        for r in s:
+            topic = repo[r].topic()
+            if topic:
+                st = stack.stack(repo, topic=topic)
+            else:
+                st = stack.stack(repo, branch=repo[r].branch())
+            if n < 0:
+                st = list(st)[1:]
+            else:
+                st = list(st)
+            try:
+                rev = st[n]
+            except IndexError:
+                continue
+            if rev == -1 and n == 0:
+                continue
+            if rev not in revs:
+                revs.append(rev)
+        return subset & revset.baseset(revs)
+
+    revset.subscriptrelations['stack'] = stackrel
+    revset.subscriptrelations['s'] = stackrel
--- a/hgext3rd/topic/stack.py	Fri Jan 04 19:17:38 2019 -0500
+++ b/hgext3rd/topic/stack.py	Sat Jan 05 05:21:37 2019 +0100
@@ -329,15 +329,32 @@
 
         symbol = None
         states = []
+        msg = ''
         iscurrentrevision = repo.revs('%d and parents()', ctx.rev())
+        if opts.get('children'):
+            if branch:
+                t_msg = '-branch("%s")' % branch
+            if topic:
+                t_msg = '-topic("%s")' % topic
+            rev_msg = 'children(%s) and merge() %s'
+            revisions = repo.revs(rev_msg % (ctx.rev(), t_msg))
+            len_rev = len(revisions)
+            if len_rev > 0:
+                msg = 'external-children'
 
         if iscurrentrevision:
-            states.append('current')
             symbol = '@'
+            if msg:
+                states.append('current - ' + msg)
+            else:
+                states.append('current')
 
         if ctx.orphan():
             symbol = '$'
-            states.append('unstable')
+            if msg:
+                states.append('unstable - ' + msg)
+            else:
+                states.append('unstable')
 
         if not isentry:
             symbol = '^'
@@ -347,7 +364,10 @@
         # none of the above if statments get executed
         if not symbol:
             symbol = ':'
-            states.append('clean')
+            if msg:
+                states.append(msg)
+            else:
+                states.append('clean')
 
         states.sort()
 
--- a/tests/test-amend.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-amend.t	Sat Jan 05 05:21:37 2019 +0100
@@ -153,7 +153,7 @@
       --close-branch        mark a branch as closed, hiding it from the branch
                             list
    -s --secret              use the secret phase for committing
-   -n --note VALUE          store a note on amend
+   -n --note TEXT           store a note on amend
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -m --message TEXT        use text as commit message
--- a/tests/test-discovery-obshashrange-cache.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-discovery-obshashrange-cache.t	Sat Jan 05 05:21:37 2019 +0100
@@ -28,7 +28,7 @@
   $ hg -R main debugbuilddag '.+7'
 
   $ for node in `hg -R main log -T '{node}\n'`; do
-  >     echo -n $node | grep -o . | sort |tr -d "\n" > ancfile
+  >     printf $node | grep -o . | sort |tr -d "\n" > ancfile
   >     anc=`cat ancfile`
   >     rm ancfile
   >     echo "marking $anc as predecessors of $node"
--- a/tests/test-discovery-obshashrange.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-discovery-obshashrange.t	Sat Jan 05 05:21:37 2019 +0100
@@ -9,6 +9,10 @@
   > blackbox =
   > [defaults]
   > blackbox = -l 100
+  > [blackbox]
+  > track = backupbundle, branchcache, cache, command, commandalias,
+  >         commandfinish, debug, discovery, evoext-cache, evoext-obsdiscovery,
+  >         incoming, tagscache
   > [experimental]
   > obshashrange=1
   > verbose-obsolescence-exchange=1
@@ -190,9 +194,6 @@
   remote: capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch * (glob)
   remote: 1
   sending protocaps command
-  preparing listkeys for "phases"
-  sending listkeys command
-  received listkey for "phases": 58 bytes
   query 1; heads
   sending batch command
   searching for changes
@@ -319,12 +320,9 @@
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending hello command (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending between command (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> remote: * (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> remote: capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache%0Astream%3Dv2 changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> remote: capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache%0Astream%3Dv2 changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> remote: 1 (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending protocaps command (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> preparing listkeys for "phases" (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending listkeys command (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> received listkey for "phases": 58 bytes (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> query 1; heads (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending batch command (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> taking quick initial sample (glob)
--- a/tests/test-evolve-abort-orphan.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-evolve-abort-orphan.t	Sat Jan 05 05:21:37 2019 +0100
@@ -554,3 +554,4 @@
   undo.branch
   undo.desc
   undo.dirstate
+  wcache
--- a/tests/test-evolve-obshistory-complex.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-evolve-obshistory-complex.t	Sat Jan 05 05:21:37 2019 +0100
@@ -140,11 +140,11 @@
 Then split
 ----------
 
-  $ hg split "desc(fold0)" -d "0 0" << EOF
+  $ hg split --rev "desc(fold0)" -d "0 0" << EOF
   > Y
   > Y
   > N
-  > N
+  > Y
   > Y
   > Y
   > EOF
@@ -164,7 +164,7 @@
   examine changes to 'B'? [Ynesfdaq?] N
   
   created new head
-  Done splitting? [yN] N
+  continue splitting? [Ycdq?] Y
   diff --git a/B b/B
   new file mode 100644
   examine changes to 'B'? [Ynesfdaq?] Y
@@ -174,11 +174,11 @@
   record this change to 'B'? [Ynesfdaq?] Y
   
   no more change to split
-  $ hg split "desc(fold1)" -d "0 0" << EOF
+  $ hg split --rev "desc(fold1)" -d "0 0" << EOF
   > Y
   > Y
   > N
-  > N
+  > Y
   > Y
   > Y
   > EOF
@@ -198,7 +198,7 @@
   examine changes to 'D'? [Ynesfdaq?] N
   
   created new head
-  Done splitting? [yN] N
+  continue splitting? [Ycdq?] Y
   diff --git a/D b/D
   new file mode 100644
   examine changes to 'D'? [Ynesfdaq?] Y
@@ -209,11 +209,11 @@
   
   no more change to split
   1 new orphan changesets
-  $ hg split "desc(fold2)" -d "0 0" << EOF
+  $ hg split --rev "desc(fold2)" -d "0 0" << EOF
   > Y
   > Y
   > N
-  > N
+  > Y
   > Y
   > Y
   > EOF
@@ -233,7 +233,7 @@
   examine changes to 'F'? [Ynesfdaq?] N
   
   created new head
-  Done splitting? [yN] N
+  continue splitting? [Ycdq?] Y
   diff --git a/F b/F
   new file mode 100644
   examine changes to 'F'? [Ynesfdaq?] Y
--- a/tests/test-evolve-obshistory-lots-of-splits.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-evolve-obshistory-lots-of-splits.t	Sat Jan 05 05:21:37 2019 +0100
@@ -43,16 +43,16 @@
   > n
   > n
   > n
-  > n
+  > y
   > y
   > y
   > n
   > n
-  > n
+  > y
   > y
   > y
   > n
-  > n
+  > y
   > y
   > y
   > EOF
@@ -82,7 +82,7 @@
   examine changes to 'd'? [Ynesfdaq?] n
   
   created new head
-  Done splitting? [yN] n
+  continue splitting? [Ycdq?] y
   diff --git a/b b/b
   new file mode 100644
   examine changes to 'b'? [Ynesfdaq?] y
@@ -99,7 +99,7 @@
   new file mode 100644
   examine changes to 'd'? [Ynesfdaq?] n
   
-  Done splitting? [yN] n
+  continue splitting? [Ycdq?] y
   diff --git a/c b/c
   new file mode 100644
   examine changes to 'c'? [Ynesfdaq?] y
@@ -112,7 +112,7 @@
   new file mode 100644
   examine changes to 'd'? [Ynesfdaq?] n
   
-  Done splitting? [yN] n
+  continue splitting? [Ycdq?] y
   diff --git a/d b/d
   new file mode 100644
   examine changes to 'd'? [Ynesfdaq?] y
--- a/tests/test-evolve-obshistory-split.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-evolve-obshistory-split.t	Sat Jan 05 05:21:37 2019 +0100
@@ -38,7 +38,7 @@
   > y
   > y
   > n
-  > n
+  > y
   > y
   > y
   > EOF
@@ -58,7 +58,7 @@
   examine changes to 'b'? [Ynesfdaq?] n
   
   created new head
-  Done splitting? [yN] n
+  continue splitting? [Ycdq?] y
   diff --git a/b b/b
   new file mode 100644
   examine changes to 'b'? [Ynesfdaq?] y
--- a/tests/test-evolve-orphan-split.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-evolve-orphan-split.t	Sat Jan 05 05:21:37 2019 +0100
@@ -41,7 +41,7 @@
   > y
   > y
   > n
-  > y
+  > c
   > EOF
   0 files updated, 0 files merged, 3 files removed, 0 files unresolved
   adding a
@@ -59,7 +59,7 @@
   examine changes to 'b'? [Ynesfdaq?] n
   
   created new head
-  Done splitting? [yN] y
+  continue splitting? [Ycdq?] c
   1 new orphan changesets
 
   $ hg glog
@@ -121,7 +121,7 @@
   > y
   > y
   > y
-  > y
+  > c
   > EOF
   0 files updated, 0 files merged, 3 files removed, 0 files unresolved
   adding a
@@ -152,7 +152,7 @@
   record change 3/3 to 'c'? [Ynesfdaq?] y
   
   created new head
-  Done splitting? [yN] y
+  continue splitting? [Ycdq?] c
   1 new orphan changesets
 
   $ hg glog
--- a/tests/test-evolve-stop-orphan.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-evolve-stop-orphan.t	Sat Jan 05 05:21:37 2019 +0100
@@ -109,9 +109,8 @@
 Checking working dir
   $ hg status
 Checking for incomplete mergestate
-  $ ls .hg/merge
-  ls: cannot access .?\.hg/merge.?: No such file or directory (re)
-  [2]
+  $ ls .hg/ | grep merge
+  [1]
 
 Checking graph
   $ hg glog
@@ -182,6 +181,7 @@
   undo.branch
   undo.desc
   undo.dirstate
+  wcache
 
 Checking when multiple revs need to be evolved, some revs evolve without
 conflicts
--- a/tests/test-evolve-templates.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-evolve-templates.t	Sat Jan 05 05:21:37 2019 +0100
@@ -272,7 +272,7 @@
   > y
   > y
   > n
-  > n
+  > y
   > y
   > y
   > EOF
@@ -292,7 +292,7 @@
   examine changes to 'b'? [Ynesfdaq?] n
   
   created new head
-  Done splitting? [yN] n
+  continue splitting? [Ycdq?] y
   diff --git a/b b/b
   new file mode 100644
   examine changes to 'b'? [Ynesfdaq?] y
--- a/tests/test-evolve-topic.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-evolve-topic.t	Sat Jan 05 05:21:37 2019 +0100
@@ -257,7 +257,7 @@
   
   $ hg topic -r 070c5573d8f9 bar
   4 new orphan changesets
-  changed topic on 1 changes
+  changed topic on 1 changesets to "bar"
   $ hg up 16d6f664b17c
   switching to topic bar
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -381,3 +381,63 @@
   $ hg prev
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   [s3] add eee
+
+Check stackaliases(s#) works with  --continue case also, while evolving:
+------------------------------------------------------------------------
+  $ hg up 18
+  switching to topic bar
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg evolve --all
+  move:[s2] add ggg
+  atop:[s1] add fff
+  move:[s3] add hhh
+  move:[s4] add iii
+  move:[s5] add jjj
+  working directory is now at 38a82cbb794a
+  $ hg up 18
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ echo "changes in hhh" > hhh
+  $ hg add hhh
+  $ hg ci --amend
+  4 new orphan changesets
+  $ hg log -G
+  @  26 - {bar} 2c295936ac04 add fff (draft)
+  |
+  | *  25 - {bar} 38a82cbb794a add jjj (draft)
+  | |
+  | *  24 - {bar} 4a44eba0fdb3 add iii (draft)
+  | |
+  | *  23 - {bar} 7acd9ea5d677 add hhh (draft)
+  | |
+  | *  22 - {bar} 735c7bd8f133 add ggg (draft)
+  | |
+  | x  18 - {bar} 793eb6370b2d add fff (draft)
+  |/
+  o  12 - {foo} 42b49017ff90 add eee (draft)
+  |
+  o  10 - {foo} d9cacd156ffc add ddd (draft)
+  |
+  o  2 - {foo} cced9bac76e3 add ccc (draft)
+  |
+  o  1 - {} a4dbed0837ea add bbb (draft)
+  |
+  o  0 - {} 199cc73e9a0b add aaa (draft)
+  
+  $ hg evolve --all
+  move:[s2] add ggg
+  atop:[s1] add fff
+  move:[s3] add hhh
+  merging hhh
+  warning: conflicts while merging hhh! (edit, then use 'hg resolve --mark')
+  fix conflicts and see `hg help evolve.interrupted`
+  [1]
+  $ echo "resolved hhh" > hhh
+  $ hg resolve --mark hhh
+  (no more unresolved files)
+  continue: hg evolve --continue
+  $ hg evolve --continue
+  evolving 23:7acd9ea5d677 "add hhh"
+  move:[s4] add iii
+  atop:[s3] add hhh
+  move:[s5] add jjj
+  working directory is now at 119e4c126fb2
--- a/tests/test-grab.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-grab.t	Sat Jan 05 05:21:37 2019 +0100
@@ -24,9 +24,9 @@
   
   options:
   
-   -r --rev VALUE revision to pick
-   -c --continue  continue interrupted pick
-   -a --abort     abort interrupted pick
+   -r --rev REV  revision to pick
+   -c --continue continue interrupted pick
+   -a --abort    abort interrupted pick
   
   (some details hidden, use --verbose to show complete help)
 
--- a/tests/test-obsolete-push.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-obsolete-push.t	Sat Jan 05 05:21:37 2019 +0100
@@ -4,6 +4,7 @@
   > [extensions]
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
+  $ echo "topic=$(echo $(dirname $TESTDIR))/hgext3rd/topic/" >> $HGRCPATH
 
   $ template='{rev}:{node|short}@{branch}({separate("/", obsolete, phase)}) {desc|firstline}\n'
   $ glog() {
@@ -72,12 +73,12 @@
   $ hg push -r .
   pushing to $TESTTMP/source
   abort: push would publish 1 changesets
-  (behavior controlled by 'experimental.auto-publish' config)
+  (* 'experimental.auto-publish' config) (glob)
   [255]
   $ hg push
   pushing to $TESTTMP/source
   abort: push would publish 1 changesets
-  (behavior controlled by 'experimental.auto-publish' config)
+  (* 'experimental.auto-publish' config) (glob)
   [255]
 
 warning behavior
@@ -91,3 +92,15 @@
   adding manifests
   adding file changes
   added 0 changesets with 0 changes to 1 files
+
+--publish overrides auto-publish
+
+  $ echo d > d
+  $ hg ci -qAm D d
+  $ hg push -r . --publish --config experimental.auto-publish=abort
+  pushing to $TESTTMP/source
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
--- a/tests/test-options.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-options.t	Sat Jan 05 05:21:37 2019 +0100
@@ -24,6 +24,7 @@
   >   allowunstable
   >   exchange
   > EOF
-  $ hg prune | head -n 2
+  $ hg prune
   hg: unknown command 'prune'
   (use 'hg help' for a list of commands)
+  [255]
--- a/tests/test-prev-next.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-prev-next.t	Sat Jan 05 05:21:37 2019 +0100
@@ -184,7 +184,7 @@
 
   $ hg amend -m 'added b (2)'
   1 new orphan changesets
-  $ hg next
+  $ hg next --no-evolve
   no children
   (1 unstable changesets to be evolved here, do you want --evolve?)
   [1]
@@ -231,7 +231,7 @@
 
   $ hg am -m 'added b (3)'
   2 new orphan changesets
-  $ hg next
+  $ hg next --no-evolve
   no children
   (2 unstable changesets to be evolved here, do you want --evolve?)
   [1]
@@ -375,6 +375,7 @@
 
   $ hg next --evolve
   abort: uncommitted changes
+  (use `hg amend`, `hg revert` or `hg shelve`)
   [255]
 
   $ cd ..
@@ -482,3 +483,107 @@
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   [2] added bar
+
+Add test which shows that now `next` command does not get confused by split:
+----------------------------------------------------------------------------
+  $ cd ..
+  $ mkdir nextconfused
+  $ cd nextconfused
+  $ hg init
+  $ echo firstline > a
+  $ hg add a
+  $ hg ci -qm A
+  $ echo bbbbb > b
+  $ echo secondline >> a
+  $ hg add b
+  $ hg ci -qm B
+  $ echo ccccc > c
+  $ hg add c
+  $ hg ci -qm C
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  2:fdc998261dcb C
+  |
+  o  1:cc0edb0cc2b1 B
+  |
+  o  0:cae96ff49c84 A
+  
+  $ hg up 1
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg split << EOF
+  > y
+  > y
+  > n
+  > Y
+  > y
+  > y
+  > EOF
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  reverting a
+  adding b
+  diff --git a/a b/a
+  1 hunks, 1 lines changed
+  examine changes to 'a'? [Ynesfdaq?] y
+  
+  @@ -1,1 +1,2 @@
+   firstline
+  +secondline
+  record change 1/2 to 'a'? [Ynesfdaq?] y
+  
+  diff --git a/b b/b
+  new file mode 100644
+  examine changes to 'b'? [Ynesfdaq?] n
+  
+  created new head
+  continue splitting? [Ycdq?] Y
+  diff --git a/b b/b
+  new file mode 100644
+  examine changes to 'b'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +bbbbb
+  record this change to 'b'? [Ynesfdaq?] y
+  
+  no more change to split
+  1 new orphan changesets
+
+  $ hg up 3 -q
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  o  4:279f6cab32b5 B
+  |
+  |
+  |  new desc
+  @  3:a9f74d07e45c B
+  |
+  |
+  |  new desc
+  | *  2:fdc998261dcb C
+  | |
+  | x  1:cc0edb0cc2b1 B
+  |/
+  o  0:cae96ff49c84 A
+  
+  $ hg ci --amend -m "B modified"
+  1 new orphan changesets
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  5:64ab03d3110c B modified
+  |
+  | *  4:279f6cab32b5 B
+  | |
+  | |
+  | |  new desc
+  | x  3:a9f74d07e45c B
+  |/
+  |
+  |    new desc
+  | *  2:fdc998261dcb C
+  | |
+  | x  1:cc0edb0cc2b1 B
+  |/
+  o  0:cae96ff49c84 A
+  
+  $ hg next --evolve << EOF
+  > q
+  > EOF
+  move:[4] B
+  atop:[5] B modified
+  working directory now at 1b434459c7e7
--- a/tests/test-rewind.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-rewind.t	Sat Jan 05 05:21:37 2019 +0100
@@ -460,7 +460,7 @@
   > y
   > f
   > d
-  > y
+  > c
   > EOF
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   adding C
@@ -478,7 +478,7 @@
   examine changes to 'D'? [Ynesfdaq?] d
   
   created new head
-  Done splitting? [yN] y
+  continue splitting? [Ycdq?] c
   $ hg log -G
   @  changeset:   5:9576e80d6851
   |  tag:         tip
--- a/tests/test-split.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-split.t	Sat Jan 05 05:21:37 2019 +0100
@@ -55,7 +55,7 @@
   > y
   > y
   > n
-  > N
+  > Y
   > y
   > y
   > EOF
@@ -79,7 +79,7 @@
   record change 2/2 to '_d'? [Ynesfdaq?] n
   
   created new head
-  Done splitting? [yN] N
+  continue splitting? [Ycdq?] Y
   diff --git a/_d b/_d
   new file mode 100644
   examine changes to '_d'? [Ynesfdaq?] y
@@ -179,7 +179,7 @@
   > y
   > y
   > n
-  > y
+  > c
   > EOF
   2 files updated, 0 files merged, 2 files removed, 0 files unresolved
   reverting _b
@@ -201,7 +201,7 @@
   record change 2/2 to '_c'? [Ynesfdaq?] n
   
   created new head
-  Done splitting? [yN] y
+  continue splitting? [Ycdq?] c
   2 new orphan changesets
 
 Stop before splitting the commit completely creates a commit with all the
@@ -281,7 +281,7 @@
   > y
   > y
   > n
-  > y
+  > c
   > EOF
   (leaving bookmark bookB)
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -302,7 +302,7 @@
   examine changes to '_d'? [Ynesfdaq?] n
   
   created new head
-  Done splitting? [yN] y
+  continue splitting? [Ycdq?] c
   $ hg log -G -r "3f134f739075::"
   @  changeset:   16:452a26648478
   |  bookmark:    bookA
@@ -366,7 +366,7 @@
   [255]
 
 Running split with tip revision, specified as unnamed argument
-  $ hg split . << EOF
+  $ hg split --rev . << EOF
   > q
   > EOF
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -379,11 +379,10 @@
   [255]
 
 Running split with both unnamed and named revision arguments shows an error msg
-  $ hg split . --rev .^ << EOF
+  $ hg split  --rev . --rev .^ << EOF
   > q
   > EOF
   abort: more than one revset is given
-  (use either `hg split <rs>` or `hg split --rev <rs>`, not both)
   [255]
 
 Split empty commit (issue5191)
@@ -435,7 +434,7 @@
   > Y
   > Y
   > N
-  > Y
+  > c
   > Y
   > Y
   > EOF
@@ -454,16 +453,7 @@
   new file mode 100644
   examine changes to 'celeste'? [Ynesfdaq?] N
   
-  Done splitting? [yN] Y
-  diff --git a/celeste b/celeste
-  new file mode 100644
-  examine changes to 'celeste'? [Ynesfdaq?] Y
-  
-  @@ -0,0 +1,1 @@
-  +celeste
-  record this change to 'celeste'? [Ynesfdaq?] Y
-  
-  no more change to split
+  continue splitting? [Ycdq?] c
 
 Check that the topic is still here
 
@@ -537,7 +527,7 @@
   $ hg split -r . << EOF
   > Y
   > N
-  > N
+  > Y
   > Y
   > EOF
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
@@ -551,7 +541,7 @@
   new file mode 100644
   examine changes to 'SPLIT2'? [Ynesfdaq?] N
   
-  Done splitting? [yN] N
+  continue splitting? [Ycdq?] Y
   diff --git a/SPLIT2 b/SPLIT2
   new file mode 100644
   examine changes to 'SPLIT2'? [Ynesfdaq?] Y
@@ -651,3 +641,335 @@
      date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     split10
   
+
+Check prompt options
+--------------------
+
+Look at the help (both record and split helps)
+
+  $ hg split -r tip << EOF
+  > Y
+  > ?
+  > d
+  > ?
+  > q
+  > EOF
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  adding SPLIT3
+  adding SPLIT4
+  diff --git a/SPLIT3 b/SPLIT3
+  new file mode 100644
+  examine changes to 'SPLIT3'? [Ynesfdaq?] Y
+  
+  diff --git a/SPLIT4 b/SPLIT4
+  new file mode 100644
+  examine changes to 'SPLIT4'? [Ynesfdaq?] ?
+  
+  y - yes, record this change
+  n - no, skip this change
+  e - edit this change manually
+  s - skip remaining changes to this file
+  f - record remaining changes to this file
+  d - done, skip remaining changes and files
+  a - record all changes to all remaining files
+  q - quit, recording no changes
+  ? - ? (display help)
+  examine changes to 'SPLIT4'? [Ynesfdaq?] d
+  
+  continue splitting? [Ycdq?] ?
+  y - yes, continue selection
+  c - commit, select all remaining changes
+  d - discard, discard remaining changes
+  q - quit, abort the split
+  ? - ?, display help
+  continue splitting? [Ycdq?] q
+  transaction abort!
+  rollback completed
+  abort: user quit
+  [255]
+
+discard some of changeset during split
+
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution=all
+  > evolutioncommands=
+  > EOF
+
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Branch another-branch
+  # Node ID 56a59faa8af70dc104faa905231731ffece5f18a
+  # Parent  75695e3e2300d316cc515c4c25bab8b825ef1433
+  # EXP-Topic mytopic
+  split10
+  
+  diff --git a/SPLIT2 b/SPLIT2
+  new file mode 100644
+  $ hg add SPLIT3
+  $ hg amend
+  1 new orphan changesets
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Branch another-branch
+  # Node ID 3acb634dc68ddb4dea75a9cee982955bc1f3e8cd
+  # Parent  75695e3e2300d316cc515c4c25bab8b825ef1433
+  # EXP-Topic mytopic
+  split10
+  
+  diff --git a/SPLIT2 b/SPLIT2
+  new file mode 100644
+  diff --git a/SPLIT3 b/SPLIT3
+  new file mode 100644
+  $ hg split << EOF
+  > Y
+  > d
+  > d
+  > EOF
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  diff --git a/SPLIT2 b/SPLIT2
+  new file mode 100644
+  examine changes to 'SPLIT2'? [Ynesfdaq?] Y
+  
+  diff --git a/SPLIT3 b/SPLIT3
+  new file mode 100644
+  examine changes to 'SPLIT3'? [Ynesfdaq?] d
+  
+  continue splitting? [Ycdq?] d
+  discarding remaining changes
+  forgetting SPLIT3
+  $ hg export
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Branch another-branch
+  # Node ID db690d5566962489d65945c90b468b44e0b1507a
+  # Parent  75695e3e2300d316cc515c4c25bab8b825ef1433
+  # EXP-Topic mytopic
+  split12
+  
+  diff --git a/SPLIT2 b/SPLIT2
+  new file mode 100644
+  $ hg status
+  ? SPLIT3
+  ? SPLIT4
+  ? editor.sh
+  ? num
+
+Test restricting the split to a subset of files
+-----------------------------------------------
+
+  $ hg add SPLIT3 SPLIT4
+  $ hg amend
+
+Only run on 2 files
+
+(remaining changes gathered with unmatched one)
+
+  $ hg split SPLIT2 SPLIT3 << EOF
+  > y
+  > n
+  > c
+  > EOF
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  diff --git a/SPLIT2 b/SPLIT2
+  new file mode 100644
+  examine changes to 'SPLIT2'? [Ynesfdaq?] y
+  
+  diff --git a/SPLIT3 b/SPLIT3
+  new file mode 100644
+  examine changes to 'SPLIT3'? [Ynesfdaq?] n
+  
+  continue splitting? [Ycdq?] c
+  $ hg status --change '.~1'
+  A SPLIT2
+  $ hg status --change '.'
+  A SPLIT3
+  A SPLIT4
+  $ hg fold --from '.~1'
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+(no remaining changes)
+
+  $ hg split SPLIT2 SPLIT3 << EOF
+  > y
+  > n
+  > y
+  > y
+  > EOF
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  diff --git a/SPLIT2 b/SPLIT2
+  new file mode 100644
+  examine changes to 'SPLIT2'? [Ynesfdaq?] y
+  
+  diff --git a/SPLIT3 b/SPLIT3
+  new file mode 100644
+  examine changes to 'SPLIT3'? [Ynesfdaq?] n
+  
+  continue splitting? [Ycdq?] y
+  diff --git a/SPLIT3 b/SPLIT3
+  new file mode 100644
+  examine changes to 'SPLIT3'? [Ynesfdaq?] y
+  
+  no more change to split
+  $ hg status --change '.~2'
+  A SPLIT2
+  $ hg status --change '.~1'
+  A SPLIT3
+  $ hg status --change '.'
+  A SPLIT4
+  $ hg fold --from '.~2'
+  3 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+(only all matched selected)
+
+  $ hg split SPLIT2 SPLIT3 << EOF
+  > y
+  > y
+  > EOF
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  diff --git a/SPLIT2 b/SPLIT2
+  new file mode 100644
+  examine changes to 'SPLIT2'? [Ynesfdaq?] y
+  
+  diff --git a/SPLIT3 b/SPLIT3
+  new file mode 100644
+  examine changes to 'SPLIT3'? [Ynesfdaq?] y
+  
+  no more change to split
+  $ hg status --change '.~1'
+  A SPLIT2
+  A SPLIT3
+  $ hg status --change '.'
+  A SPLIT4
+  $ hg fold --from '.~1'
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Check that discard does not alter unmatched files
+
+  $ hg split SPLIT2 SPLIT3 << EOF
+  > y
+  > n
+  > d
+  > EOF
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  diff --git a/SPLIT2 b/SPLIT2
+  new file mode 100644
+  examine changes to 'SPLIT2'? [Ynesfdaq?] y
+  
+  diff --git a/SPLIT3 b/SPLIT3
+  new file mode 100644
+  examine changes to 'SPLIT3'? [Ynesfdaq?] n
+  
+  continue splitting? [Ycdq?] d
+  discarding remaining changes
+  no more change to split
+  $ hg status --change '.~1'
+  A SPLIT2
+  $ hg status --change '.'
+  A SPLIT4
+  $ hg fold --from '.~1'
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg add SPLIT3
+  $ hg amend
+
+Non interractive run
+--------------------
+
+No patterns
+
+  $ hg split --no-interactive
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  abort: no files of directories specified
+  (do you want --interactive)
+  [255]
+
+Selecting unrelated file
+(should we abort?)
+
+  $ hg split --no-interactive SPLIT1
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  no more change to split
+  $ hg status --change '.'
+  A SPLIT2
+  A SPLIT3
+  A SPLIT4
+
+Selecting one file
+
+  $ hg split --no-interactive SPLIT2
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  no more change to split
+  $ hg status --change '.~1'
+  A SPLIT2
+  $ hg status --change '.'
+  A SPLIT3
+  A SPLIT4
+  $ hg fold --from '.~1'
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Selecting two files
+
+  $ hg split --no-interactive SPLIT2 SPLIT3
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  no more change to split
+  $ hg status --change '.~1'
+  A SPLIT2
+  A SPLIT3
+  $ hg status --change '.'
+  A SPLIT4
+  $ hg fold --from '.~1'
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Selecting all files
+(should we abort?)
+
+  $ hg split --no-interactive .
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding SPLIT2
+  adding SPLIT3
+  adding SPLIT4
+  no more change to split
+  $ hg status --change '.'
+  A SPLIT2
+  A SPLIT3
+  A SPLIT4
--- a/tests/test-stack-branch.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-stack-branch.t	Sat Jan 05 05:21:37 2019 +0100
@@ -309,7 +309,7 @@
 ----------------------------------------------------
 
   $ hg topic --rev b4::b5 sometopic
-  changed topic on 2 changes
+  changed topic on 2 changesets to "sometopic"
   $ hg stack
   ### target: foo (branch)
   s3$ c_f (unstable)
--- a/tests/test-topic-change.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-topic-change.t	Sat Jan 05 05:21:37 2019 +0100
@@ -51,7 +51,7 @@
 Clearing topic from revision without topic
 
   $ hg topic -r . --clear
-  changed topic on 0 changes
+  cleared topic on 0 changesets
 
 Clearing current topic when no active topic is not error
 
@@ -62,7 +62,7 @@
 
   $ hg topic -r 0:: foo
   switching to topic foo
-  changed topic on 8 changes
+  changed topic on 8 changesets to "foo"
   $ hg glog
   @  15:05095f607171 {foo}
   |  Added h  ()
@@ -100,7 +100,7 @@
 
   $ hg topic -r abcedffeae90:: bar
   switching to topic bar
-  changed topic on 4 changes
+  changed topic on 4 changesets to "bar"
   $ hg glog
   @  19:d7d36e193ea7 {bar}
   |  Added h  ()
@@ -139,7 +139,7 @@
   $ hg topic -r . --current
   active topic 'foobar' grew its first changeset
   (see 'hg help topics' for more information)
-  changed topic on 1 changes
+  changed topic on 1 changesets to "foobar"
   $ hg glog -r .
   @  20:c2d6b7df5dcf {foobar}
   |  Added h  ()
@@ -149,7 +149,7 @@
 
   $ hg topic -r 9::10 --current
   5 new orphan changesets
-  changed topic on 2 changes
+  changed topic on 2 changesets to "foobar"
   $ hg glog
   o  22:1b88140feefe {foobar}
   |  Added c  ()
@@ -302,7 +302,7 @@
   $ hg topic -r . --clear
   clearing empty topic "watwat"
   active topic 'watwat' is now empty
-  changed topic on 1 changes
+  cleared topic on 1 changesets
 
   $ hg glog
   @  31:c48d6d71b2d9 {}
@@ -335,7 +335,7 @@
   $ hg bookmark bookboo
   $ hg topic -r . movebook
   switching to topic movebook
-  changed topic on 1 changes
+  changed topic on 1 changesets to "movebook"
   $ hg glog
   @  32:1b83d11095b9 {movebook}
   |  Added h  (book bookboo)
@@ -376,7 +376,7 @@
   $ hg topic -r . watwat
   switching to topic watwat
   1 new orphan changesets
-  changed topic on 1 changes
+  changed topic on 1 changesets to "watwat"
 
   $ hg glog
   @  33:894983f69e69 {watwat}
--- a/tests/test-topic-stack-complex.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-topic-stack-complex.t	Sat Jan 05 05:21:37 2019 +0100
@@ -63,7 +63,7 @@
   > y
   > y
   > n
-  > y
+  > c
   > EOF
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   adding c
@@ -80,7 +80,7 @@
   new file mode 100644
   examine changes to 'd'? [Ynesfdaq?] n
   
-  Done splitting? [yN] y
+  continue splitting? [Ycdq?] c
   1 new orphan changesets
 
   $ hg stack
--- a/tests/test-topic-stack.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-topic-stack.t	Sat Jan 05 05:21:37 2019 +0100
@@ -229,8 +229,52 @@
   s1: c_c
   s0^ c_b (base)
 
+merge case (displaying info about external)
+-------------------------------------------
+
+  $ hg up default
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ hg topics zzz
+  marked working directory as topic: zzz
+  $ echo zzz > zzz
+  $ hg add zzz
+  $ hg commit -m zzz_a
+  active topic 'zzz' grew its first changeset
+  (see 'hg help topics' for more information)
+  $ hg merge foo
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -m "merged foo"
+
+stack -m display data about child
+
+  $ hg stack foo
+  ### topic: foo
+  ### target: default (branch)
+  s4: c_f
+  s3: c_e
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
+
+  $ hg stack foo --children
+  ### topic: foo
+  ### target: default (branch)
+  s4: c_f (external-children)
+  s3: c_e
+  s2: c_d
+  s1: c_c
+  s0^ c_b (base)
+
 error case, nothing to list
 
+  $ hg strip --config extensions.strip= t1 --no-backup
+  0 files updated, 0 files merged, 5 files removed, 0 files unresolved
+
+  $ hg up foo
+  switching to topic foo
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
   $ hg topic --clear
   $ hg stack
   ### target: default (branch)
@@ -329,6 +373,17 @@
   hg: parse error: stack takes no arguments, it works on current topic
   [255]
 
+Stack relation subscript:
+
+  $ hg log -r 'foo#stack[0]'
+  1 default {} public c_b
+  $ hg log -r 's0 and foo#stack[0]'
+  1 default {} public c_b
+  $ hg log -r 'foo#stack[4]'
+  5 default {foo} draft c_f
+  $ hg log -r 's4 and foo#stack[4]'
+  5 default {foo} draft c_f
+
 Case with multiple heads on the topic
 -------------------------------------
 
@@ -495,7 +550,7 @@
   $ hg topic foobar -r 'desc(c_e) + desc(c_D)'
   switching to topic foobar
   4 new orphan changesets
-  changed topic on 2 changes
+  changed topic on 2 changesets to "foobar"
   $ hg log -G
   @  17 default {foobar} draft c_D
   |
@@ -850,7 +905,7 @@
   > y
   > y
   > n
-  > y
+  > c
   > EOF
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   adding Z
@@ -867,7 +922,7 @@
   new file mode 100644
   examine changes to 'ggg'? [Ynesfdaq?] n
   
-  Done splitting? [yN] y
+  continue splitting? [Ycdq?] c
 
   $ hg --config extensions.evolve= obslog --all
   o  dde94df880e9 (21) c_G
--- a/tests/test-topic.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-topic.t	Sat Jan 05 05:21:37 2019 +0100
@@ -819,7 +819,95 @@
   s0^ Add file delta (base current)
 
   $ hg topics --age
-   * fran (1970-01-01 by test)
+   * fran (1970-01-01 by test, 1 changesets)
+
+  $ cd ..
+
+Stack relation subscript in revsets
+===================================
+
+  $ hg init more-than-one-commit-per-topic
+  $ cd more-than-one-commit-per-topic
+  $ cat > .hg/hgrc << EOF
+  > [phases]
+  > publish=false
+  > EOF
+
+  $ echo 0 > foo
+  $ hg ci -qAm 0
+  $ hg topic featureA
+  marked working directory as topic: featureA
+  $ echo 1 > foo
+  $ hg ci -qm 1
+  $ echo 2 > foo
+  $ hg ci -qm 2
+  $ echo 3 > foo
+  $ hg ci -qm 3
+  $ hg topic --clear
+  $ echo 4 > foo
+  $ hg ci -qm 4
+
+  $ tlog 'all()'
+  0: 
+  1: featureA
+  2: featureA
+  3: featureA
+  4: 
+
+  $ hg stack
+  ### target: default (branch)
+  s2@ 4 (current)
+    ^ 3
+  s1: 0
+
+  $ tlog 'tip#stack[0]'
+  $ tlog 'tip#stack[1]'
+  0: 
+  $ tlog 'tip#stack[2]'
+  4: 
+  $ tlog 'tip#stack[-1]'
+  4: 
+  $ tlog 'tip#stack[-2]'
+  0: 
+
+  $ hg stack featureA
+  ### topic: featureA
+  ### target: default (branch), 3 behind
+  s3: 3
+  s2: 2
+  s1: 1
+  s0^ 0 (base)
+
+  $ tlog 'featureA#s[0]'
+  0: 
+  $ tlog 'featureA#s[1] and featureA#s[-3]'
+  1: featureA
+  $ tlog 'featureA#s[2] and featureA#s[-2]'
+  2: featureA
+  $ tlog 'featureA#s[3] and featureA#s[-1]'
+  3: featureA
+  $ tlog 'featureA#s[-4]'
+
+  $ tlog 'all()#s[-1]'
+  3: featureA
+  4: 
+  $ tlog 'all()#s[0]'
+  0: 
+  $ tlog 'all()#s[1]'
+  0: 
+  1: featureA
+  $ tlog 'all()#s[9999]'
+  $ tlog 'all()#s[-9999]'
+
+  $ hg topic featureB
+  marked working directory as topic: featureB
+  $ hg stack
+  ### topic: featureB
+  ### target: default (branch)
+  (stack is empty)
+  s0^ 4 (base current)
+  $ tlog 'wdir()#s[0]'
+  4: 
 
   $ cd ..
 
@@ -863,7 +951,7 @@
 
   $ hg topic topic1970 --rev 0
   switching to topic topic1970
-  changed topic on 1 changes
+  changed topic on 1 changesets to "topic1970"
 
   $ hg add b
   $ hg topic topic1990
@@ -903,18 +991,23 @@
    * topic2010 (1 changesets)
 
   $ hg topics --age
-   * topic2010 (2010-01-01 by bar)
-     topic1990 (1990-01-01 by foo)
-     topic1970 (1970-01-01 by test)
+   * topic2010 (2010-01-01 by bar, 1 changesets)
+     topic1990 (1990-01-01 by foo, 1 changesets)
+     topic1970 (1970-01-01 by test, 1 changesets)
+
+  $ hg topics --age --verbose
+   * topic2010 (2010-01-01 by bar, on branch: default, 1 changesets)
+     topic1990 (1990-01-01 by foo, on branch: default, 1 changesets)
+     topic1970 (1970-01-01 by test, on branch: default, 1 changesets)
 
   $ hg up topic1970
   switching to topic topic1970
   0 files updated, 0 files merged, 2 files removed, 0 files unresolved
 
   $ hg topics --age
-     topic2010 (2010-01-01 by bar)
-     topic1990 (1990-01-01 by foo)
-   * topic1970 (1970-01-01 by test)
+     topic2010 (2010-01-01 by bar, 1 changesets)
+     topic1990 (1990-01-01 by foo, 1 changesets)
+   * topic1970 (1970-01-01 by test, 1 changesets)
 
   $ hg topics --age random
   abort: cannot use --age while setting a topic
--- a/tests/test-tutorial.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-tutorial.t	Sat Jan 05 05:21:37 2019 +0100
@@ -934,9 +934,9 @@
   options ([+] can be repeated):
   
    -a --all                 uncommit all changes when no arguments given
-   -r --rev VALUE           revert commit content to REV instead
+   -r --rev REV             revert commit content to REV instead
       --revert              discard working directory changes after uncommit
-   -n --note VALUE          store a note on uncommit
+   -n --note TEXT           store a note on uncommit
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -m --message TEXT        use text as commit message
@@ -973,16 +973,16 @@
   
   options ([+] can be repeated):
   
-   -r --rev VALUE [+] revision to fold
-      --exact         only fold specified revisions
-      --from          fold revisions linearly to working copy parent
-   -n --note VALUE    store a note on fold
-   -m --message TEXT  use text as commit message
-   -l --logfile FILE  read commit message from file
-   -d --date DATE     record the specified date as commit date
-   -u --user USER     record the specified user as committer
-   -D --current-date  record the current date as commit date
-   -U --current-user  record the current user as committer
+   -r --rev REV [+]  revision to fold
+      --exact        only fold specified revisions
+      --from         fold revisions linearly to working copy parent
+   -n --note TEXT    store a note on fold
+   -m --message TEXT use text as commit message
+   -l --logfile FILE read commit message from file
+   -d --date DATE    record the specified date as commit date
+   -u --user USER    record the specified user as committer
+   -D --current-date record the current date as commit date
+   -U --current-user record the current user as committer
   
   (some details hidden, use --verbose to show complete help)
 
--- a/tests/test-wireproto.t	Fri Jan 04 19:17:38 2019 -0500
+++ b/tests/test-wireproto.t	Sat Jan 05 05:21:37 2019 +0100
@@ -195,7 +195,7 @@
   $ cat hg.pid >> $DAEMON_PIDS
 
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache%0Astream%3Dv2 changegroupsubset compression=*zlib getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache%0Astream%3Dv2 changegroupsubset compression=*zlib getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-eol) (glob)
 
 Check we cannot use pushkey for marker exchange anymore