changeset 2698:6d48ad81e7b5 stable

merge: default into stable to prepare next version Let us prepare ourself for 6.5.0
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Sun, 02 Jul 2017 17:24:56 +0200
parents 166ca0aba0ea (current diff) 189fd9d6a405 (diff)
children fddaf14783b1
files tests/test-discovery-obshashrange.t tests/test-drop.t tests/test-evolve-cycles.t tests/test-evolve-obshistory-complex.t tests/test-evolve-obshistory.t tests/test-evolve-templates.t tests/test-inhibit.t tests/test-obsolete.t tests/test-sharing.t tests/test-stabilize-conflict.t tests/test-stabilize-order.t tests/test-wireproto.t
diffstat 28 files changed, 2341 insertions(+), 479 deletions(-) [+]
line wrap: on
line diff
--- a/README	Sun Jun 25 16:37:56 2017 +0200
+++ b/README	Sun Jul 02 17:24:56 2017 +0200
@@ -121,6 +121,20 @@
 Changelog
 =========
 
+6.5.0 - in progress
+-------------------
+
+ - obslog: gain a --patch flag to display changes introduced by the evolution
+  (Currently limited to in simple case only)
+
+ - stack: also show the unstable status for the current changeset (issue5553)
+ - stack: properly abort when and unknown topic is requested,
+ - stack: add basic and raw support for named branches
+ - topic: changing topic on revs no longer adds extra instability (issue5441)
+ - topic: topics: rename '--change' flag to '--rev' flag,
+ - topic: multiple large performance improvements,
+ - topic: various small output improvement,
+
 6.4.1 - in progress
 -------------------
 
--- a/hgext3rd/evolve/__init__.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/evolve/__init__.py	Sun Jul 02 17:24:56 2017 +0200
@@ -20,7 +20,7 @@
 this extensions contains significant additions recommended to any user of
 changeset evolution.
 
-With the extensions various evolution events will display warning (new unstable
+With the extension various evolution events will display warning (new unstable
 changesets, obsolete working copy parent, improved error when accessing hidden
 revision, etc).
 
@@ -77,14 +77,14 @@
   # (recommended 'yes' for server (default))
   obshashrange.warm-cache = no
 
-It is recommended to enable the blackbox extension. It gather useful data about
-the experiment. It is shipped with Mercurial so no extra install are needed.
+It is recommended to enable the blackbox extension. It gathers useful data about
+the experiment. It is shipped with Mercurial so no extra install is needed::
 
     [extensions]
     blackbox =
 
 Finally some extra options are available to help tame the experimental
-implementation of some of the algorithms:
+implementation of some of the algorithms::
 
     [experimental]
     # restrict cache size to reduce memory consumption
@@ -99,19 +99,45 @@
 Effect Flag Experiment
 ======================
 
-We are experimenting with a way to register what changed between a precursor
-and its successors (content, description, parent, etc...). For example, having
-this information is helpful to show what changed between an obsolete changeset
-and its tipmost successors. This experiment is active by default
-
-The following config control the experiment::
+Evolve also records what changed between two evolutions of a changeset. For
+example, having this information is helpful to understand what changed between
+an obsolete changeset and its tipmost successors.
+
+Evolve currently records:
+
+    - Meta changes, user, date
+    - Tree movement, branch and parent, did the changeset moved?
+    - Description, was the commit description edited
+    - Diff, was there apart from potential diff change due to rebase a change in the diff?
+
+These flags are lightweight and can be combined, so it's easy to see if 4
+evolutions of the same changeset has just updated the description or if the
+content changed and you need to review again the diff.
+
+The effect flag recording is enabled by default in Evolve 6.4.0 so you have
+nothing to do to enjoy it. Now every new evolution that you create will have
+the effect flag attached.
+
+The following config control the effect flag recording::
 
   [experimental]
-  # deactivate the registration of effect flags in obs markers
-  evolution.effect-flags = false
-
-The effect flags are shown in the obglog command output without particular
-configuration if you want to inspect them.
+  # uncomment to deactivate the registration of effect flags in obs markers
+  # evolution.effect-flags = false
+
+You can display the effect flags with the command obslog, so if you have a
+changeset and you update only the message, you will see:
+
+    $ hg commit -m "WIP
+    $ hg commit -m "A better commit message!"
+    $ hg obslog .
+   @  8e9045855628 (3133) A better commit message!
+   |
+   x  7863a5bb5763 (3132) WIP
+        rewritten(description) by Boris Feld <boris.feld@octobus.net> (Fri Jun 02 12:00:24 2017 +0200) as 8e9045855628
+
+Servers does not need to activate the effect flag recording. Effect flags that
+you create will not cause interference with other clients or servers without
+the effect flag recording.
 
 Templates
 =========
@@ -121,14 +147,14 @@
 
   - precursors, for each obsolete changeset show the closest visible
     precursors.
-  - successors, for each obsolete changeset show the closets visible
+  - successors, for each obsolete changeset show the closests visible
     successors. It is useful when your working directory is obsolete to see
-    what are its successors. This informations can also be retrieved with the
+    what are its successors. This information can also be retrieved with the
     obslog command and the --all option.
   - obsfate, for each obsolete changeset display a line summarizing what
     changed between the changeset and its successors. Dependending on the
     verbosity level (-q and -v) it display the changeset successors, the users
-    that created the obsmarkers and the date range of theses changes.
+    that created the obsmarkers and the date range of these changes.
 
     The template itself is not complex, the data are basically a list of
     successortset. Each successorset is a dict with these fields:
@@ -902,7 +928,7 @@
         if not ctx.obsolete():
             continue
 
-        successors = obsolete.successorssets(repo, ctx.node(), cache)
+        successors = compat.successorssets(repo, ctx.node(), cache)
 
         # We can't make any assumptions about how to update the hash if the
         # cset in question was split or diverged.
@@ -1175,14 +1201,14 @@
         return p.rev()
     obs = repo[p]
     ui = repo.ui
-    newer = obsolete.successorssets(repo, obs.node())
+    newer = compat.successorssets(repo, obs.node())
     # search of a parent which is not killed
     while not newer:
         ui.debug("stabilize target %s is plain dead,"
                  " trying to stabilize on its parent\n" %
                  obs)
         obs = obs.parents()[0]
-        newer = obsolete.successorssets(repo, obs.node())
+        newer = compat.successorssets(repo, obs.node())
     if len(newer) > 1 or len(newer[0]) > 1:
         raise MultipleSuccessorsError(newer)
 
@@ -1302,11 +1328,11 @@
     """Compute sets of commits divergent with a given one"""
     cache = {}
     base = {}
-    for n in obsolete.allprecursors(repo.obsstore, [ctx.node()]):
+    for n in compat.allprecursors(repo.obsstore, [ctx.node()]):
         if n == ctx.node():
             # a node can't be a base for divergence with itself
             continue
-        nsuccsets = obsolete.successorssets(repo, n, cache)
+        nsuccsets = compat.successorssets(repo, n, cache)
         for nsuccset in nsuccsets:
             if ctx.node() in nsuccset:
                 # we are only interested in *other* successor sets
@@ -1610,7 +1636,7 @@
     tovisit = list(parents(rev))
     while tovisit:
         r = tovisit.pop()
-        succsets = obsolete.successorssets(repo, tonode(r))
+        succsets = compat.successorssets(repo, tonode(r))
         if not succsets:
             tovisit.extend(parents(r))
         else:
@@ -1673,14 +1699,14 @@
         ui.warn(_("cannot solve instability of %s, skipping\n") % orig)
         return False
     obs = pctx
-    newer = obsolete.successorssets(repo, obs.node())
+    newer = compat.successorssets(repo, obs.node())
     # search of a parent which is not killed
     while not newer or newer == [()]:
         ui.debug("stabilize target %s is plain dead,"
                  " trying to stabilize on its parent\n" %
                  obs)
         obs = obs.parents()[0]
-        newer = obsolete.successorssets(repo, obs.node())
+        newer = compat.successorssets(repo, obs.node())
     if len(newer) > 1:
         msg = _("skipping %s: divergent rewriting. can't choose "
                 "destination\n") % obs
@@ -1959,7 +1985,7 @@
     """
     repo = ctx._repo.unfiltered()
     for base in repo.set('reverse(allprecursors(%d))', ctx):
-        newer = obsolete.successorssets(ctx._repo, base.node())
+        newer = compat.successorssets(ctx._repo, base.node())
         # drop filter and solution including the original ctx
         newer = [n for n in newer if n and ctx.node() not in n]
         if newer:
@@ -2637,6 +2663,12 @@
     finally:
         lockmod.release(tr, lock, wlock)
 
+def presplitupdate(repo, ui, prev, ctx):
+    """prepare the working directory for a split (for topic hooking)
+    """
+    hg.update(repo, prev)
+    commands.revert(ui, repo, rev=ctx.rev(), all=True)
+
 @eh.command(
     '^split',
     [('r', 'rev', [], _("revision to split")),
@@ -2682,9 +2714,9 @@
         if bookactive is not None:
             repo.ui.status(_("(leaving bookmark %s)\n") % repo._activebookmark)
         bookmarksmod.deactivate(repo)
-        hg.update(repo, prev)
-
-        commands.revert(ui, repo, rev=r, all=True)
+
+        # Prepare the working directory
+        presplitupdate(repo, ui, prev, ctx)
 
         def haschanges():
             modified, added, removed, deleted = repo.status()[:4]
@@ -2795,7 +2827,7 @@
             if not (duplicate or allowdivergence):
                 # The user hasn't yet decided what to do with the revived
                 # cset, let's ask
-                sset = obsolete.successorssets(repo, ctx.node())
+                sset = compat.successorssets(repo, ctx.node())
                 nodivergencerisk = (len(sset) == 0 or
                                     (len(sset) == 1 and
                                      len(sset[0]) == 1 and
--- a/hgext3rd/evolve/compat.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/evolve/compat.py	Sun Jul 02 17:24:56 2017 +0200
@@ -11,6 +11,12 @@
     obsolete
 )
 
+try:
+    from mercurial import obsutil
+    obsutil.closestpredecessors
+except ImportError:
+    obsutil = None
+
 from . import (
     exthelper,
 )
@@ -55,3 +61,17 @@
             pendingnodes -= seennodes
             seennodes |= pendingnodes
         return seenmarkers
+
+# successors set move from mercurial.obsolete to mercurial.obsutil in 4.3
+def successorssets(*args, **kwargs):
+    func = getattr(obsutil, 'successorssets', None)
+    if func is None:
+        func = obsolete.successorssets
+    return func(*args, **kwargs)
+
+# allprecursors set move from mercurial.obsolete to mercurial.obsutil in 4.3
+def allprecursors(*args, **kwargs):
+    func = getattr(obsutil, 'allprecursors', None)
+    if func is None:
+        func = obsolete.allprecursors
+    return func(*args, **kwargs)
--- a/hgext3rd/evolve/metadata.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/evolve/metadata.py	Sun Jul 02 17:24:56 2017 +0200
@@ -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__ = '6.4.1.dev'
+__version__ = '6.5.0.dev'
 testedwith = '3.8.4 3.9.2 4.0.2 4.1.2 4.2'
 minimumhgversion = '3.8'
 buglink = 'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obshistory.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/evolve/obshistory.py	Sun Jul 02 17:24:56 2017 +0200
@@ -14,6 +14,8 @@
     commands,
     error,
     graphmod,
+    mdiff,
+    patch,
     obsolete,
     node as nodemod,
     scmutil,
@@ -22,6 +24,7 @@
 from mercurial.i18n import _
 
 from . import (
+    compat,
     exthelper,
 )
 
@@ -31,7 +34,8 @@
     'obslog|olog',
     [('G', 'graph', True, _("show the revision DAG")),
      ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
-     ('a', 'all', False, _('show all related changesets, not only precursors'))
+     ('a', 'all', False, _('show all related changesets, not only precursors')),
+     ('p', 'patch', False, _('show the patch between two obs versions'))
     ] + commands.formatteropts,
     _('hg olog [OPTION]... [REV]'))
 def cmdobshistory(ui, repo, *revs, **opts):
@@ -51,8 +55,8 @@
     the obsolescence operation (rewritten or pruned) in addition of the user and
     date of the operation.
 
-    The output is a graph by default but can deactivated with the option '--no-
-    graph'.
+    The output is a graph by default but can deactivated with the option
+    '--no-graph'.
 
     'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
     and '+' represents a fork where the changeset from the lines below is a
@@ -72,7 +76,7 @@
 
     fm = ui.formatter('debugobshistory', opts)
     revs.reverse()
-    _debugobshistorysingle(fm, repo, revs)
+    _debugobshistoryrevs(fm, repo, revs, opts)
 
     fm.end()
 
@@ -91,15 +95,17 @@
             fm = self.ui.formatter('debugobshistory', props)
             _debugobshistorydisplaynode(fm, self.repo, changenode)
 
+            # Succs markers
             succs = self.repo.obsstore.successors.get(changenode, ())
+            succs = sorted(succs)
 
             markerfm = fm.nested("debugobshistory.markers")
-            for successor in sorted(succs):
-                _debugobshistorydisplaymarker(markerfm, self.repo, successor)
+            for successor in succs:
+                _debugobshistorydisplaymarker(markerfm, successor,
+                                              ctx.node(), self.repo, self.diffopts)
             markerfm.end()
 
             markerfm.plain('\n')
-
             self.hunk[ctx.node()] = self.ui.popbuffer()
         else:
             ### graph output is buffered only
@@ -112,6 +118,71 @@
         '''
         pass
 
+def patchavailable(node, repo, marker):
+    if node not in repo:
+        return False, "context is not local"
+
+    successors = marker[1]
+
+    if len(successors) == 0:
+        return False, "no successors"
+    elif len(successors) > 1:
+        return False, "too many successors (%d)" % len(successors)
+
+    succ = successors[0]
+
+    if succ not in repo:
+        return False, "succ is unknown locally"
+
+    # Check that both node and succ have the same parents
+    nodep1, nodep2 = repo[node].p1(), repo[node].p2()
+    succp1, succp2 = repo[succ].p1(), repo[succ].p2()
+
+    if nodep1 != succp1 or nodep2 != succp2:
+        return False, "changesets rebased"
+
+    return True, succ
+
+def _indent(content, indent=4):
+    extra = ' ' * indent
+    return "".join(extra + line for line in content.splitlines(True))
+
+def getmarkercontentpatch(repo, node, succ):
+    # Todo get the ops from the cmd
+    diffopts = patch.diffallopts(repo.ui, {})
+    matchfn = scmutil.matchall(repo)
+
+    repo.ui.pushbuffer()
+    cmdutil.diffordiffstat(repo.ui, repo, diffopts, node, succ,
+                           match=matchfn, stat=False)
+    buffer = repo.ui.popbuffer()
+
+    return _indent(buffer)
+
+def getmarkerdescriptionpatch(repo, base, succ):
+    basectx = repo[base]
+    succctx = repo[succ]
+    # description are stored without final new line,
+    # add one to avoid ugly diff
+    basedesc = basectx.description() + '\n'
+    succdesc = succctx.description() + '\n'
+
+    # fake file name
+    basename = "%s-changeset-description" % basectx
+    succname = "%s-changeset-description" % succctx
+
+    d = mdiff.unidiff(basedesc, '', succdesc, '', basename, succname)
+    # mercurial 4.1 and before return the patch directly
+    if not isinstance(d, tuple):
+        patch = d
+    else:
+        uheaders, hunks = d
+
+        # Copied from patch.diff
+        text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
+        patch = "\n".join(uheaders + [text])
+    return _indent(patch)
+
 class missingchangectx(object):
     ''' a minimal object mimicking changectx for change contexts
     references by obs markers but not available locally '''
@@ -275,17 +346,22 @@
     return sorted(seen), nodesucc, nodeprec
 
 def _debugobshistorygraph(ui, repo, revs, opts):
-    displayer = obsmarker_printer(ui, repo.unfiltered(), None, opts, buffered=True)
+    matchfn = None
+    if opts.get('patch'):
+        matchfn = scmutil.matchall(repo)
+
+    displayer = obsmarker_printer(ui, repo.unfiltered(), matchfn, opts, buffered=True)
     edges = graphmod.asciiedges
     walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False))
     cmdutil.displaygraph(ui, repo, walker, displayer, edges)
 
-def _debugobshistorysingle(fm, repo, revs):
-    """ Display the obsolescence history for a single revision
+def _debugobshistoryrevs(fm, repo, revs, opts):
+    """ Display the obsolescence history for revset
     """
     precursors = repo.obsstore.precursors
     successors = repo.obsstore.successors
     nodec = repo.changelog.node
+    unfi = repo.unfiltered()
     nodes = [nodec(r) for r in revs]
 
     seen = set(nodes)
@@ -293,13 +369,13 @@
     while nodes:
         ctxnode = nodes.pop()
 
-        _debugobshistorydisplaynode(fm, repo, ctxnode)
+        _debugobshistorydisplaynode(fm, unfi, ctxnode)
 
         succs = successors.get(ctxnode, ())
 
         markerfm = fm.nested("debugobshistory.markers")
         for successor in sorted(succs):
-            _debugobshistorydisplaymarker(markerfm, repo, successor)
+            _debugobshistorydisplaymarker(markerfm, successor, ctxnode, repo, opts)
         markerfm.end()
 
         precs = precursors.get(ctxnode, ())
@@ -310,8 +386,8 @@
                 nodes.append(p[0])
 
 def _debugobshistorydisplaynode(fm, repo, node):
-    if node in repo.unfiltered():
-        _debugobshistorydisplayctx(fm, repo.unfiltered()[node])
+    if node in repo:
+        _debugobshistorydisplayctx(fm, repo[node])
     else:
         _debugobshistorydisplaymissingctx(fm, node)
 
@@ -338,7 +414,7 @@
              label="evolve.node evolve.missing_change_ctx")
     fm.plain('\n')
 
-def _debugobshistorydisplaymarker(fm, repo, marker):
+def _debugobshistorydisplaymarker(fm, marker, node, repo, opts):
     succnodes = marker[1]
     date = marker[4]
     metadata = dict(marker[3])
@@ -401,6 +477,30 @@
         fm.write('debugobshistory.succnodes', '%s', nodes,
                  label="evolve.node")
 
+    # Patch display
+    if opts.get('patch'):
+        _patchavailable = patchavailable(node, repo, marker)
+
+        if _patchavailable[0] is True:
+            succ = _patchavailable[1]
+
+            # Description patch
+            descriptionpatch = getmarkerdescriptionpatch(repo, node, succ)
+            if descriptionpatch:
+                fm.plain("\n")
+                fm.plain(descriptionpatch)
+
+            # Content patch
+            contentpatch = getmarkercontentpatch(repo, node, succ)
+            if contentpatch:
+                fm.plain("\n")
+                fm.plain(contentpatch)
+        else:
+            patch = "    (No patch available yet, %s)" % _patchavailable[1]
+            fm.plain("\n")
+            # TODO: should be in json too
+            fm.plain(patch)
+
     fm.plain("\n")
 
 # logic around storing and using effect flags
@@ -586,7 +686,7 @@
     or has diverged
     """
     if successorssets is None:
-        successorssets = obsolete.successorssets(repo, revnode)
+        successorssets = compat.successorssets(repo, revnode)
 
     fate = _getobsfate(successorssets)
 
--- a/hgext3rd/evolve/templatekw.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/evolve/templatekw.py	Sun Jul 02 17:24:56 2017 +0200
@@ -15,8 +15,10 @@
 )
 
 from mercurial import (
+    cmdutil,
     templatekw,
     node,
+    util
 )
 
 eh = exthelper.exthelper()
@@ -140,10 +142,12 @@
         'obsfate_verbose': verbtempl + usertempl + succtempl + datetempl,
     }
 
-@eh.templatekw("obsfate")
-def showobsfate(repo, ctx, **args):
+def obsfatedata(repo, ctx):
+    """compute the raw data needed for computing obsfate
+    Returns a list of dict
+    """
     if not ctx.obsolete():
-        return ''
+        return None
 
     successorssets, pathcache = closestsuccessors(repo, ctx.node())
 
@@ -172,7 +176,74 @@
     values = []
     for sset, rawmarkers in fullsuccessorsets:
         raw = obshistory.preparesuccessorset(sset, rawmarkers)
+        values.append(raw)
 
+    return values
+
+def obsfatelineprinter(obsfateline, ui):
+    quiet = ui.quiet
+    verbose = ui.verbose
+    normal = not verbose and not quiet
+
+    # Build the line step by step
+    line = []
+
+    # Verb
+    line.append(obsfateline['verb'])
+
+    # Users
+    if (verbose or normal) and 'users' in obsfateline:
+        users = obsfateline['users']
+
+        if normal:
+            username = _getusername(ui)
+            users = [user for user in users if user != username]
+
+        if users:
+            line.append(" by %s" % ",".join(users))
+
+    # Successors
+    successors = obsfateline["successors"]
+
+    if successors:
+        fmtsuccessors = map(lambda s: s[:12], successors)
+        line.append(" as %s" % ", ".join(fmtsuccessors))
+
+    # Date
+    if verbose:
+        min_date = obsfateline['min_date']
+        max_date = obsfateline['max_date']
+
+        if min_date == max_date:
+            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+            line.append(" (at %s)" % fmtmin_date)
+        else:
+            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+            fmtmax_date = util.datestr(max_date, '%Y-%m-%d %H:%M %1%2')
+            line.append(" (between %s and %s)" % (fmtmin_date, fmtmax_date))
+
+    return "".join(line)
+
+def obsfateprinter(obsfate, ui, prefix=""):
+    lines = []
+    for raw in obsfate:
+        lines.append(obsfatelineprinter(raw, ui))
+
+    if prefix:
+        lines = [prefix + line for line in lines]
+
+    return "\n".join(lines)
+
+@eh.templatekw("obsfate")
+def showobsfate(repo, ctx, **args):
+    # Get the needed obsfate data
+    values = obsfatedata(repo, ctx)
+
+    if values is None:
+        return ''
+
+    # Format each successorset successors list
+    for raw in values:
         # As we can't do something like
         # "{join(map(nodeshort, successors), ', '}" in template, manually
         # create a correct textual representation
@@ -183,8 +254,7 @@
         raw['successors'] = templatekw._hybrid(gen, raw['successors'], makemap,
                                                joinfmt)
 
-        values.append(raw)
-
+    # And then format them
     # Insert default obsfate templates
     args['templ'].cache.update(obsfatedefaulttempl(repo.ui))
 
@@ -221,6 +291,22 @@
 
     return templatekw._hybrid(gen, values, lambda x: {name: x}, fmt)
 
+# Check if we can hook directly on the changeset_printer
+if util.safehasattr(cmdutil.changeset_printer, '_exthook'):
+    @eh.wrapfunction(cmdutil.changeset_printer, '_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/topic/__init__.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/topic/__init__.py	Sun Jul 02 17:24:56 2017 +0200
@@ -10,7 +10,7 @@
 Compared to bookmark, topic is reference carried by each changesets of the
 series instead of just the single head revision.  Topic are quite similar to
 the way named branch work, except they eventualy fade away when the changeset
-becomes part of the immutable history.  Changeset can below to both a topic and
+becomes part of the immutable history. Changeset can belong to both a topic and
 a named branch, but as long as it is mutable, its topic identity will prevail.
 As a result, default destination for 'update', 'merge', etc...  will take topic
 into account. When a topic is active these operations will only consider other
@@ -21,10 +21,11 @@
 There is currently two commands to be used with that extension: 'topics' and
 'stack'.
 
-The 'hg topics' command is used to set the current topic and list existing one.
-'hg topics --verbose' will list various information related to each topic.
+The 'hg topics' command is used to set the current topic, change and list
+existing one. 'hg topics --verbose' will list various information related to
+each topic.
 
-The 'stack' will show you in formation about the stack of commit belonging to
+The 'stack' will show you information about the stack of commit belonging to
 your current topic.
 
 Topic is offering you aliases reference to changeset in your current topic
@@ -55,12 +56,12 @@
 
 from mercurial.i18n import _
 from mercurial import (
-    branchmap,
     cmdutil,
     commands,
     context,
     error,
     extensions,
+    hg,
     localrepo,
     lock,
     merge,
@@ -114,29 +115,41 @@
 
 testedwith = '4.0.2 4.1.3 4.2'
 
-def _contexttopic(self):
+def _contexttopic(self, force=False):
+    if not (force or self.mutable()):
+        return ''
     return self.extra().get(constants.extrakey, '')
 context.basectx.topic = _contexttopic
 
 topicrev = re.compile(r'^t\d+$')
+branchrev = re.compile(r'^b\d+$')
 
 def _namemap(repo, name):
+    revs = None
     if topicrev.match(name):
         idx = int(name[1:])
-        topic = repo.currenttopic
-        if not topic:
+        ttype = 'topic'
+        tname = topic = repo.currenttopic
+        if not tname:
             raise error.Abort(_('cannot resolve "%s": no active topic') % name)
-        revs = list(stack.getstack(repo, topic))
+        revs = list(stack.getstack(repo, topic=topic))
+    elif branchrev.match(name):
+        ttype = 'branch'
+        idx = int(name[1:])
+        tname = branch = repo[None].branch()
+        revs = list(stack.getstack(repo, branch=branch))
+
+    if revs is not None:
         try:
             r = revs[idx - 1]
         except IndexError:
-            msg = _('cannot resolve "%s": topic "%s" has only %d changesets')
-            raise error.Abort(msg % (name, topic, len(revs)))
+            msg = _('cannot resolve "%s": %s "%s" has only %d changesets')
+            raise error.Abort(msg % (name, ttype, tname, len(revs)))
         return [repo[r].node()]
     if name not in repo.topics:
         return []
-    return [ctx.node() for ctx in
-            repo.set('not public() and extra(topic, %s)', name)]
+    node = repo.changelog.node
+    return [node(rev) for rev in repo.revs('topic(%s)', name)]
 
 def _nodemap(repo, node):
     ctx = repo[node]
@@ -160,6 +173,13 @@
 
     extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap)
     extensions.wrapfunction(merge, 'update', mergeupdatewrap)
+
+    try:
+        evolve = extensions.find('evolve')
+        extensions.wrapfunction(evolve, "presplitupdate", presplitupdatetopic)
+    except (KeyError, AttributeError):
+        pass
+
     cmdutil.summaryhooks.add('topic', summaryhook)
 
 
@@ -167,6 +187,8 @@
     if not isinstance(repo, localrepo.localrepository):
         return # this can be a peer in the ssh case (puzzling)
 
+    repo = repo.unfiltered()
+
     if repo.ui.config('experimental', 'thg.displaynames', None) is None:
         repo.ui.setconfig('experimental', 'thg.displaynames', 'topics',
                           source='topic-extension')
@@ -189,6 +211,11 @@
                 self.ui.restoreconfig(backup)
 
         def commitctx(self, ctx, error=None):
+            topicfilter = topicmap.topicfilter(self.filtername)
+            if topicfilter != self.filtername:
+                other = repo.filtered(topicmap.topicfilter(repo.filtername))
+                other.commitctx(ctx, error=error)
+
             if isinstance(ctx, context.workingcommitctx):
                 current = self.currenttopic
                 if current:
@@ -199,8 +226,7 @@
                 not self.currenttopic):
                 # we are amending and need to remove a topic
                 del ctx.extra()[constants.extrakey]
-            with topicmap.usetopicmap(self):
-                return super(topicrepo, self).commitctx(ctx, error=error)
+            return super(topicrepo, self).commitctx(ctx, error=error)
 
         @property
         def topics(self):
@@ -217,23 +243,21 @@
         def currenttopic(self):
             return self.vfs.tryread('topic')
 
-        def branchmap(self, topic=True):
-            if not topic:
-                super(topicrepo, self).branchmap()
-            with topicmap.usetopicmap(self):
-                branchmap.updatecache(self)
-            return self._topiccaches[self.filtername]
+        # overwritten at the instance level by topicmap.py
+        _autobranchmaptopic = True
 
-        def destroyed(self, *args, **kwargs):
-            with topicmap.usetopicmap(self):
-                return super(topicrepo, self).destroyed(*args, **kwargs)
+        def branchmap(self, topic=None):
+            if topic is None:
+                topic = getattr(repo, '_autobranchmaptopic', False)
+            topicfilter = topicmap.topicfilter(self.filtername)
+            if not topic or topicfilter == self.filtername:
+                return super(topicrepo, self).branchmap()
+            return self.filtered(topicfilter).branchmap()
 
         def invalidatevolatilesets(self):
             # XXX we might be able to move this to something invalidated less often
             super(topicrepo, self).invalidatevolatilesets()
             self._topics = None
-            if '_topiccaches' in vars(self.unfiltered()):
-                self.unfiltered()._topiccaches.clear()
 
         def peer(self):
             peer = super(topicrepo, self).peer()
@@ -254,92 +278,39 @@
 
 @command('topics [TOPIC]', [
         ('', 'clear', False, 'clear active topic if any'),
-        ('', 'change', '', 'revset of existing revisions to change topic'),
+        ('r', 'rev', '', 'revset of existing revisions', _('REV')),
         ('l', 'list', False, 'show the stack of changeset in the topic'),
     ] + commands.formatteropts)
-def topics(ui, repo, topic='', clear=False, change=None, list=False, **opts):
+def topics(ui, repo, topic='', clear=False, rev=None, list=False, **opts):
     """View current topic, set current topic, or see all topics.
 
     The --verbose version of this command display various information on the state of each topic."""
     if list:
-        if clear or change:
-            raise error.Abort(_("cannot use --clear or --change with --list"))
+        if clear or rev:
+            raise error.Abort(_("cannot use --clear or --rev with --list"))
         if not topic:
             topic = repo.currenttopic
         if not topic:
             raise error.Abort(_('no active topic to list'))
-        return stack.showstack(ui, repo, topic, opts)
+        return stack.showstack(ui, repo, topic=topic, opts=opts)
 
-    if change:
+    if rev:
         if not obsolete.isenabled(repo, obsolete.createmarkersopt):
-            raise error.Abort(_('must have obsolete enabled to use --change'))
-        if not topic and not clear:
+            raise error.Abort(_('must have obsolete enabled to change topics'))
+        if clear:
+            topic = None
+        elif not topic:
             raise error.Abort('changing topic requires a topic name or --clear')
-        if any(not c.mutable() for c in repo.set('%r and public()', change)):
+        if any(not c.mutable() for c in repo.set('%r and public()', rev)):
             raise error.Abort("can't change topic of a public change")
-        rewrote = 0
-        needevolve = False
-        l = repo.lock()
-        txn = repo.transaction('rewrite-topics')
-        try:
-            for c in repo.set('%r', change):
-                def filectxfn(repo, ctx, path):
-                    try:
-                        return c[path]
-                    except error.ManifestLookupError:
-                        return None
-                fixedextra = dict(c.extra())
-                ui.debug('old node id is %s\n' % node.hex(c.node()))
-                ui.debug('origextra: %r\n' % fixedextra)
-                newtopic = None if clear else topic
-                oldtopic = fixedextra.get(constants.extrakey, None)
-                if oldtopic == newtopic:
-                    continue
-                if clear:
-                    del fixedextra[constants.extrakey]
-                else:
-                    fixedextra[constants.extrakey] = topic
-                if 'amend_source' in fixedextra:
-                    # TODO: right now the commitctx wrapper in
-                    # topicrepo overwrites the topic in extra if
-                    # amend_source is set to support 'hg commit
-                    # --amend'. Support for amend should be adjusted
-                    # to not be so invasive.
-                    del fixedextra['amend_source']
-                ui.debug('changing topic of %s from %s to %s\n' % (
-                    c, oldtopic, newtopic))
-                ui.debug('fixedextra: %r\n' % fixedextra)
-                mc = context.memctx(
-                    repo, (c.p1().node(), c.p2().node()), c.description(),
-                    c.files(), filectxfn,
-                    user=c.user(), date=c.date(), extra=fixedextra)
-                newnode = repo.commitctx(mc)
-                ui.debug('new node id is %s\n' % node.hex(newnode))
-                needevolve = needevolve or (len(c.children()) > 0)
-                obsolete.createmarkers(repo, [(c, (repo[newnode],))])
-                rewrote += 1
-            txn.close()
-        except:
-            try:
-                txn.abort()
-            finally:
-                repo.invalidate()
-            raise
-        finally:
-            lock.release(txn, l)
-        ui.status('changed topic on %d changes\n' % rewrote)
-        if needevolve:
-            evolvetarget = 'topic(%s)' % topic if topic else 'not topic()'
-            ui.status('please run hg evolve --rev "%s" now\n' % evolvetarget)
+        return _changetopics(ui, repo, rev, topic)
+
     if clear:
-        if repo.vfs.exists('topic'):
-            repo.vfs.unlink('topic')
-        return
+        return _changecurrenttopic(repo, None)
+
     if topic:
-        with repo.wlock():
-            with repo.vfs.open('topic', 'w') as f:
-                f.write(topic)
-        return
+        return _changecurrenttopic(repo, topic)
+
     _listtopics(ui, repo, opts)
 
 @command('stack [TOPIC]', [] + commands.formatteropts)
@@ -348,13 +319,95 @@
 
     List the current topic by default."""
     if not topic:
+        topic = None
+    branch = None
+    if topic is None and repo.currenttopic:
         topic = repo.currenttopic
-    if not topic:
-        raise error.Abort(_('no active topic to list'))
-    return stack.showstack(ui, repo, topic, opts)
+    if topic is None:
+        branch = repo[None].branch()
+    return stack.showstack(ui, repo, branch=branch, topic=topic, opts=opts)
+
+def _changecurrenttopic(repo, newtopic):
+    """changes the current topic."""
+
+    if newtopic:
+        with repo.wlock():
+            with repo.vfs.open('topic', 'w') as f:
+                f.write(newtopic)
+    else:
+        if repo.vfs.exists('topic'):
+            repo.vfs.unlink('topic')
+
+def _changetopics(ui, repo, revset, newtopic):
+    rewrote = 0
+    wl = l = txn = None
+    try:
+        wl = repo.wlock()
+        l = repo.lock()
+        txn = repo.transaction('rewrite-topics')
+        p1 = None
+        p2 = None
+        successors = {}
+        for c in repo.set('%r', revset):
+            def filectxfn(repo, ctx, path):
+                try:
+                    return c[path]
+                except error.ManifestLookupError:
+                    return None
+            fixedextra = dict(c.extra())
+            ui.debug('old node id is %s\n' % node.hex(c.node()))
+            ui.debug('origextra: %r\n' % fixedextra)
+            oldtopic = fixedextra.get(constants.extrakey, None)
+            if oldtopic == newtopic:
+                continue
+            if newtopic is None:
+                del fixedextra[constants.extrakey]
+            else:
+                fixedextra[constants.extrakey] = newtopic
+            fixedextra[constants.changekey] = c.hex()
+            if 'amend_source' in fixedextra:
+                # TODO: right now the commitctx wrapper in
+                # topicrepo overwrites the topic in extra if
+                # amend_source is set to support 'hg commit
+                # --amend'. Support for amend should be adjusted
+                # to not be so invasive.
+                del fixedextra['amend_source']
+            ui.debug('changing topic of %s from %s to %s\n' % (
+                c, oldtopic, newtopic))
+            ui.debug('fixedextra: %r\n' % fixedextra)
+            # While changing topic of set of linear commits, make sure that
+            # we base our commits on new parent rather than old parent which
+            # was obsoleted while changing the topic
+            p1 = c.p1().node()
+            p2 = c.p2().node()
+            if p1 in successors:
+                p1 = successors[p1]
+            if p2 in successors:
+                p2 = successors[p2]
+            mc = context.memctx(
+                repo, (p1, p2), c.description(),
+                c.files(), filectxfn,
+                user=c.user(), date=c.date(), extra=fixedextra)
+            newnode = repo.commitctx(mc)
+            successors[c.node()] = newnode
+            ui.debug('new node id is %s\n' % node.hex(newnode))
+            obsolete.createmarkers(repo, [(c, (repo[newnode],))])
+            rewrote += 1
+        # move the working copy too
+        wctx = repo[None]
+        # in-progress merge is a bit too complex for now.
+        if len(wctx.parents()) == 1:
+            newid = successors.get(wctx.p1().node())
+            if newid is not None:
+                hg.update(repo, newid, quietempty=True)
+        txn.close()
+    finally:
+        lock.release(txn, l, wl)
+        repo.invalidate()
+    ui.status('changed topic on %d changes\n' % rewrote)
 
 def _listtopics(ui, repo, opts):
-    fm = ui.formatter('bookmarks', opts)
+    fm = ui.formatter('topics', opts)
     activetopic = repo.currenttopic
     namemask = '%s'
     if repo.topics and ui.verbose:
@@ -375,7 +428,7 @@
         fm.data(active=active)
         if ui.verbose:
             # XXX we should include the data even when not verbose
-            data = stack.stackdata(repo, topic)
+            data = stack.stackdata(repo, topic=topic)
             fm.plain(' (')
             fm.write('branches+', 'on branch: %s',
                      '+'.join(data['branches']), # XXX use list directly after 4.0 is released
@@ -434,9 +487,17 @@
     matcher = kwargs.get('matcher')
     partial = not (matcher is None or matcher.always())
     wlock = repo.wlock()
+    isrebase = False
     try:
         ret = orig(repo, node, branchmerge, force, *args, **kwargs)
-        if not partial and not branchmerge:
+        # The mergeupdatewrap function makes the destination's topic as the
+        # current topic. This is right for merge but wrong for rebase. We check
+        # if rebase is running and update the currenttopic to topic of new
+        # rebased commit. We have explicitly stored in config if rebase is
+        # running.
+        if repo.ui.hasconfig('experimental', 'topicrebase'):
+            isrebase = True
+        if (not partial and not branchmerge) or isrebase:
             ot = repo.currenttopic
             t = ''
             pctx = repo[node]
@@ -461,9 +522,18 @@
     def newmakeextrafn(orig, copiers):
         return orig(copiers + [savetopic])
 
+    def setrebaseconfig(orig, ui, repo, **opts):
+        repo.ui.setconfig('experimental', 'topicrebase', 'yes',
+                          source='topic-extension')
+        return orig(ui, repo, **opts)
+
     try:
         rebase = extensions.find("rebase")
         extensions.wrapfunction(rebase, '_makeextrafn', newmakeextrafn)
+        # This exists to store in the config that rebase is running so that we can
+        # update the topic according to rebase. This is a hack and should be removed
+        # when we have better options.
+        extensions.wrapcommand(rebase.cmdtable, 'rebase', setrebaseconfig)
     except KeyError:
         pass
 
@@ -486,3 +556,18 @@
     cmdutil.extrapreimport.append('topic')
     cmdutil.extrapreimportmap['topic'] = _importtopic
     patch.patchheadermap.append(('EXP-Topic', 'topic'))
+
+## preserve topic during split
+
+def presplitupdatetopic(original, repo, ui, prev, ctx):
+    # Save topic of revision
+    topic = None
+    if util.safehasattr(ctx, 'topic'):
+        topic = ctx.topic()
+
+    # Update the working directory
+    original(repo, ui, prev, ctx)
+
+    # Restore the topic if need
+    if topic:
+        _changecurrenttopic(repo, topic)
--- a/hgext3rd/topic/constants.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/topic/constants.py	Sun Jul 02 17:24:56 2017 +0200
@@ -1,1 +1,2 @@
 extrakey = 'topic'
+changekey = '_rewrite_noise'
--- a/hgext3rd/topic/destination.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/topic/destination.py	Sun Jul 02 17:24:56 2017 +0200
@@ -89,14 +89,14 @@
     # but that is expensive
     #
     # we should write plain code instead
-    with topicmap.usetopicmap(repo):
-        tmap = repo.branchmap()
-        if branch not in tmap:
-            return []
-        elif all:
-            return tmap.branchheads(branch)
-        else:
-            return [tmap.branchtip(branch)]
+
+    tmap = topicmap.gettopicrepo(repo).branchmap()
+    if branch not in tmap:
+        return []
+    elif all:
+        return tmap.branchheads(branch)
+    else:
+        return [tmap.branchtip(branch)]
 
 def modsetup(ui):
     """run a uisetup time to install all destinations wrapping"""
--- a/hgext3rd/topic/discovery.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/topic/discovery.py	Sun Jul 02 17:24:56 2017 +0200
@@ -4,24 +4,24 @@
 
 from mercurial.i18n import _
 from mercurial import (
-    branchmap,
     bundle2,
     discovery,
     error,
     exchange,
     extensions,
+    util,
     wireproto,
 )
 
-from . import topicmap
-
 def _headssummary(orig, *args):
     # In mercurial < 4.2, we receive repo, remote and outgoing as arguments
     if len(args) == 3:
+        pushoparg = False
         repo, remote, outgoing = args
 
     # In mercurial > 4.3, we receive the pushop as arguments
     elif len(args) == 1:
+        pushoparg = True
         pushop = args[0]
         repo = pushop.repo.unfiltered()
         remote = pushop.remote
@@ -33,38 +33,56 @@
                   or bool(remote.listkeys('phases').get('publishing', False)))
     if publishing or not remote.capable('topics'):
         return orig(*args)
-    oldrepo = repo.__class__
-    oldbranchcache = branchmap.branchcache
-    oldfilename = branchmap._filename
-    try:
-        class repocls(repo.__class__):
-            def __getitem__(self, key):
-                ctx = super(repocls, self).__getitem__(key)
-                oldbranch = ctx.branch
+
+    class repocls(repo.__class__):
+        # awful hack to see branch as "branch:topic"
+        def __getitem__(self, key):
+            ctx = super(repocls, self).__getitem__(key)
+            oldbranch = ctx.branch
+
+            def branch():
+                branch = oldbranch()
+                topic = ctx.topic()
+                if topic:
+                    branch = "%s:%s" % (branch, topic)
+                return branch
+
+            ctx.branch = branch
+            return ctx
+
+        def revbranchcache(self):
+            rbc = super(repocls, self).revbranchcache()
+            changelog = self.changelog
 
-                def branch():
-                    branch = oldbranch()
-                    topic = ctx.topic()
-                    if topic:
-                        branch = "%s:%s" % (branch, topic)
-                    return branch
+            def branchinfo(rev):
+                branch, close = changelog.branchinfo(rev)
+                topic = repo[rev].topic()
+                if topic:
+                    branch = "%s:%s" % (branch, topic)
+                return branch, close
+
+            rbc.branchinfo = branchinfo
+            return rbc
 
-                ctx.branch = branch
-                return ctx
-
+    oldrepocls = repo.__class__
+    try:
         repo.__class__ = repocls
-        branchmap.branchcache = topicmap.topiccache
-        branchmap._filename = topicmap._filename
-        summary = orig(*args)
+        unxx = repo.filtered('unfiltered-topic')
+        repo.unfiltered = lambda: unxx
+        if pushoparg:
+            pushop.repo = repo
+            summary = orig(pushop)
+        else:
+            summary = orig(repo, remote, outgoing)
         for key, value in summary.iteritems():
             if ':' in key: # This is a topic
                 if value[0] is None and value[1]:
-                    summary[key] = ([value[1].pop(0)], ) + value[1:]
+                    summary[key] = ([value[1][0]], ) + value[1:]
         return summary
     finally:
-        repo.__class__ = oldrepo
-        branchmap.branchcache = oldbranchcache
-        branchmap._filename = oldfilename
+        if 'unfiltered' in vars(repo):
+            del repo.unfiltered
+        repo.__class__ = oldrepocls
 
 def wireprotobranchmap(orig, repo, proto):
     oldrepo = repo.__class__
@@ -94,6 +112,7 @@
     return data
 
 def handlecheckheads(orig, op, inpart):
+    """This is used to check for new heads when publishing changeset"""
     orig(op, inpart)
     if op.repo.publishing():
         return
@@ -124,8 +143,9 @@
 handlecheckheads.params = frozenset()
 
 def _pushb2phases(orig, pushop, bundler):
-    hascheck = any(p.type == 'check:heads' for p in bundler._parts)
-    if pushop.outdatedphases and not hascheck:
+    checktypes = ('check:heads', 'check:updated-heads')
+    hascheck = any(p.type in checktypes for p in bundler._parts)
+    if not hascheck and pushop.outdatedphases:
         exchange._pushb2ctxcheckheads(pushop, bundler)
     return orig(pushop, bundler)
 
@@ -140,9 +160,14 @@
     extensions.wrapfunction(discovery, '_headssummary', _headssummary)
     extensions.wrapfunction(wireproto, 'branchmap', wireprotobranchmap)
     extensions.wrapfunction(wireproto, '_capabilities', wireprotocaps)
+    # we need a proper wrap b2 part stuff
     extensions.wrapfunction(bundle2, 'handlecheckheads', handlecheckheads)
-    # we need a proper wrap b2 part stuff
     bundle2.handlecheckheads.params = frozenset()
     bundle2.parthandlermapping['check:heads'] = bundle2.handlecheckheads
+    if util.safehasattr(bundle2, 'handlecheckupdatedheads'):
+        # we still need a proper wrap b2 part stuff
+        extensions.wrapfunction(bundle2, 'handlecheckupdatedheads', handlecheckheads)
+        bundle2.handlecheckupdatedheads.params = frozenset()
+        bundle2.parthandlermapping['check:updated-heads'] = bundle2.handlecheckupdatedheads
     extensions.wrapfunction(exchange, '_pushb2phases', _pushb2phases)
     exchange.b2partsgenmapping['phase'] = exchange._pushb2phases
--- a/hgext3rd/topic/evolvebits.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/topic/evolvebits.py	Sun Jul 02 17:24:56 2017 +0200
@@ -1,6 +1,16 @@
 import collections
 from mercurial import obsolete
 
+successorssets = None
+try:
+    from mercurial import obsutil
+    successorssets = getattr(obsutil, 'successorssets', None)
+except ImportError:
+    pass
+
+if successorssets is None:
+    successorssets = obsolete.successorssets
+
 # Copied from evolve 081605c2e9b6
 
 def _orderrevs(repo, revs):
@@ -72,14 +82,14 @@
         return p.rev()
     obs = repo[p]
     ui = repo.ui
-    newer = obsolete.successorssets(repo, obs.node())
+    newer = successorssets(repo, obs.node())
     # search of a parent which is not killed
     while not newer:
         ui.debug("stabilize target %s is plain dead,"
                  " trying to stabilize on its parent\n" %
                  obs)
         obs = obs.parents()[0]
-        newer = obsolete.successorssets(repo, obs.node())
+        newer = successorssets(repo, obs.node())
     if len(newer) > 1 or len(newer[0]) > 1:
         raise MultipleSuccessorsError(newer)
 
--- a/hgext3rd/topic/revset.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/topic/revset.py	Sun Jul 02 17:24:56 2017 +0200
@@ -1,8 +1,6 @@
 from __future__ import absolute_import
 
-from mercurial.i18n import _
 from mercurial import (
-    error,
     revset,
     util,
 )
@@ -37,9 +35,18 @@
         _kind, _pattern, matcher = mkmatcher(topic)
     else:
         matcher = lambda t: bool(t)
-    drafts = subset.filter(lambda r: repo[r].mutable())
-    return drafts.filter(
-        lambda r: matcher(repo[r].extra().get(constants.extrakey, '')))
+
+    mutable = revset._notpublic(repo, revset.fullreposet(repo), ())
+
+    rawchange = repo.changelog.changelogrevision
+    key = constants.extrakey
+
+    def matchtopic(r):
+        topic = rawchange(r).extra.get(key)
+        if topic is None:
+            return False
+        return matcher(topic)
+    return (subset & mutable).filter(matchtopic)
 
 def ngtipset(repo, subset, x):
     """`ngtip([branch])`
@@ -62,11 +69,16 @@
     This is roughly equivalent to 'topic(.) - obsolete' with a sorting moving
     unstable changeset after there future parent (as if evolve where already
     run)."""
+    err = 'stack() takes no argument, it works on current topic'
+    revset.getargs(x, 0, 0, err)
     topic = repo.currenttopic
+    topic = None
+    branch = None
+    if not topic and repo.currenttopic:
+        topic = repo.currenttopic
     if not topic:
-        raise error.Abort(_('no active topic to list'))
-    # ordering hack, boo
-    return revset.baseset(stack.getstack(repo, topic)) & subset
+        branch = repo[None].branch()
+    return revset.baseset(stack.getstack(repo, branch=branch, topic=topic)) & subset
 
 
 def modsetup(ui):
--- a/hgext3rd/topic/stack.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/topic/stack.py	Sun Jul 02 17:24:56 2017 +0200
@@ -10,9 +10,16 @@
 )
 from .evolvebits import builddependencies, _orderrevs, _singlesuccessor
 
-def getstack(repo, topic):
+def getstack(repo, branch=None, topic=None):
     # XXX need sorting
-    trevs = repo.revs("topic(%s) - obsolete()", topic)
+    if topic is not None and branch is not None:
+        raise error.ProgrammingError('both branch and topic specified (not defined yet)')
+    elif topic is not None:
+        trevs = repo.revs("topic(%s) - obsolete()", topic)
+    elif branch is not None:
+        trevs = repo.revs("branch(%s) - public() - obsolete() - topic()", branch)
+    else:
+        raise error.ProgrammingError('neither branch and topic specified (not defined yet)')
     return _orderrevs(repo, trevs)
 
 def labelsgen(prefix, labelssuffix):
@@ -21,7 +28,23 @@
     """
     return ' '.join(prefix % suffix for suffix in labelssuffix)
 
-def showstack(ui, repo, topic, opts):
+def showstack(ui, repo, branch=None, topic=None, opts=None):
+    if opts is None:
+        opts = {}
+
+    if topic is not None and branch is not None:
+        msg = 'both branch and topic specified [%s]{%s}(not defined yet)'
+        msg %= (branch, topic)
+        raise error.ProgrammingError(msg)
+    elif topic is not None:
+        prefix = 't'
+        if topic not in repo.topics:
+            raise error.Abort(_('cannot resolve "%s": no such topic found') % topic)
+    elif branch is not None:
+        prefix = 'b'
+    else:
+        raise error.ProgrammingError('neither branch and topic specified (not defined yet)')
+
     fm = ui.formatter('topicstack', opts)
     prev = None
     entries = []
@@ -31,28 +54,37 @@
     if topic == repo.currenttopic:
         label = 'topic.active'
 
-    data = stackdata(repo, topic)
-    fm.plain(_('### topic: %s') % ui.label(topic, label),
-             label='topic.stack.summary.topic')
+    data = stackdata(repo, branch=branch, topic=topic)
+    if topic is not None:
+        fm.plain(_('### topic: %s')
+                 % ui.label(topic, label),
+                 label='topic.stack.summary.topic')
 
-    if 1 < data['headcount']:
-        fm.plain(' (')
-        fm.plain('%d heads' % data['headcount'],
-                 label='topic.stack.summary.headcount.multiple')
-        fm.plain(')')
-    fm.plain('\n')
+        if 1 < data['headcount']:
+            fm.plain(' (')
+            fm.plain('%d heads' % data['headcount'],
+                     label='topic.stack.summary.headcount.multiple')
+            fm.plain(')')
+        fm.plain('\n')
     fm.plain(_('### branch: %s')
              % '+'.join(data['branches']), # XXX handle multi branches
              label='topic.stack.summary.branches')
-    if data['behindcount'] == -1:
-        fm.plain(', ')
-        fm.plain('ambigious rebase destination', label='topic.stack.summary.behinderror')
-    elif data['behindcount']:
-        fm.plain(', ')
-        fm.plain('%d behind' % data['behindcount'], label='topic.stack.summary.behindcount')
+    if topic is None:
+        if 1 < data['headcount']:
+            fm.plain(' (')
+            fm.plain('%d heads' % data['headcount'],
+                     label='topic.stack.summary.headcount.multiple')
+            fm.plain(')')
+    else:
+        if data['behindcount'] == -1:
+            fm.plain(', ')
+            fm.plain('ambigious rebase destination', label='topic.stack.summary.behinderror')
+        elif data['behindcount']:
+            fm.plain(', ')
+            fm.plain('%d behind' % data['behindcount'], label='topic.stack.summary.behindcount')
     fm.plain('\n')
 
-    for idx, r in enumerate(getstack(repo, topic), 1):
+    for idx, r in enumerate(getstack(repo, branch=branch, topic=topic), 1):
         ctx = repo[r]
         p1 = ctx.p1()
         if p1.obsolete():
@@ -69,19 +101,22 @@
         states = []
         iscurrentrevision = repo.revs('%d and parents()', ctx.rev())
 
-        if iscurrentrevision:
-            states.append('current')
-
         if not isentry:
             symbol = '^'
             # "base" is kind of a "ghost" entry
             # skip other label for them (no current, no unstable)
             states = ['base']
-        elif iscurrentrevision:
-            symbol = '@'
-        elif repo.revs('%d and unstable()', ctx.rev()):
+        elif ctx.unstable():
+            # current revision can be unstable also, so in that case show both
+            # the states and the symbol '@' (issue5553)
+            if iscurrentrevision:
+                states.append('current')
+                symbol = '@'
             symbol = '$'
             states.append('unstable')
+        elif iscurrentrevision:
+            states.append('current')
+            symbol = '@'
         else:
             symbol = ':'
             states.append('clean')
@@ -91,7 +126,7 @@
         if idx is None:
             fm.plain('  ')
         else:
-            fm.write('topic.stack.index', 't%d', idx,
+            fm.write('topic.stack.index', '%s%%d' % prefix, idx,
                      label='topic.stack.index ' + labelsgen('topic.stack.index.%s', states))
         fm.write('topic.stack.state.symbol', '%s', symbol,
                  label='topic.stack.state ' + labelsgen('topic.stack.state.%s', states))
@@ -104,7 +139,7 @@
         fm.plain('\n')
     fm.end()
 
-def stackdata(repo, topic):
+def stackdata(repo, branch=None, topic=None):
     """get various data about a stack
 
     :changesetcount: number of non-obsolete changesets in the stack
@@ -113,7 +148,7 @@
     :behindcount: number of changeset on rebase destination
     """
     data = {}
-    revs = repo.revs("topic(%s) - obsolete()", topic)
+    revs = getstack(repo, branch, topic)
     data['changesetcount'] = len(revs)
     data['troubledcount'] = len([r for r in revs if repo[r].troubled()])
     deps, rdeps = builddependencies(repo, revs)
--- a/hgext3rd/topic/topicmap.py	Sun Jun 25 16:37:56 2017 +0200
+++ b/hgext3rd/topic/topicmap.py	Sun Jul 02 17:24:56 2017 +0200
@@ -1,27 +1,63 @@
 import contextlib
 import hashlib
 
-from mercurial.node import hex, bin, nullid
+from mercurial.node import nullid
 from mercurial import (
     branchmap,
     changegroup,
     cmdutil,
-    encoding,
-    error,
     extensions,
-    scmutil,
+    repoview,
 )
 
-def _filename(repo):
-    """name of a branchcache file for a given repo or repoview"""
-    filename = "cache/topicmap"
-    if repo.filtername:
-        filename = '%s-%s' % (filename, repo.filtername)
-    return filename
+basefilter = set(['base', 'immutable'])
+def topicfilter(name):
+    """return a "topic" version of a filter level"""
+    if name in basefilter:
+        return name
+    elif name is None:
+        return None
+    elif name.endswith('-topic'):
+        return name
+    else:
+        return name + '-topic'
+
+def istopicfilter(filtername):
+    if filtername is None:
+        return False
+    return filtername.endswith('-topic')
+
+def gettopicrepo(repo):
+    filtername = topicfilter(repo.filtername)
+    if filtername == repo.filtername:
+        return repo
+    return repo.filtered(filtername)
 
-oldbranchcache = branchmap.branchcache
+def _setuptopicfilter(ui):
+    """extend the filter related mapping with topic related one"""
+    funcmap = repoview.filtertable
+    partialmap = branchmap.subsettable
+
+    # filter level not affected by topic that we should not override
+
+    for plainname in list(funcmap):
+        newfilter = topicfilter(plainname)
+        if newfilter == plainname:
+            continue
+
+        def revsfunc(repo, name=plainname):
+            return repoview.filterrevs(repo, name)
+
+        base = topicfilter(partialmap[plainname])
+
+        if newfilter not in funcmap:
+            funcmap[newfilter] = revsfunc
+            partialmap[newfilter] = base
+    funcmap['unfiltered-topic'] = lambda repo: frozenset()
+    partialmap['unfiltered-topic'] = 'visible-topic'
 
 def _phaseshash(repo, maxrev):
+    """uniq ID for a phase matching a set of rev"""
     revs = set()
     cl = repo.changelog
     fr = cl.filteredrevs
@@ -40,61 +76,65 @@
         key = s.digest()
     return key
 
-@contextlib.contextmanager
-def usetopicmap(repo):
-    """use awful monkey patching to ensure topic map usage
+def modsetup(ui):
+    """call at uisetup time to install various wrappings"""
+    _setuptopicfilter(ui)
+    _wrapbmcache(ui)
+    extensions.wrapfunction(changegroup.cg1unpacker, 'apply', cgapply)
+    extensions.wrapfunction(cmdutil, 'commitstatus', commitstatus)
 
-    During the extend of the context block, The topicmap should be used and
-    updated instead of the branchmap."""
-    oldbranchcache = branchmap.branchcache
-    oldfilename = branchmap._filename
-    oldread = branchmap.read
-    oldcaches = getattr(repo, '_branchcaches', {})
-    try:
-        branchmap.branchcache = topiccache
-        branchmap._filename = _filename
-        branchmap.read = readtopicmap
-        repo._branchcaches = getattr(repo, '_topiccaches', {})
-        yield
-        repo._topiccaches = repo._branchcaches
-    finally:
-        repo._branchcaches = oldcaches
-        branchmap.branchcache = oldbranchcache
-        branchmap._filename = oldfilename
-        branchmap.read = oldread
-
-def cgapply(orig, repo, *args, **kwargs):
+def cgapply(orig, self, repo, *args, **kwargs):
     """make sure a topicmap is used when applying a changegroup"""
-    with usetopicmap(repo):
-        return orig(repo, *args, **kwargs)
+    other = repo.filtered(topicfilter(repo.filtername))
+    return orig(self, other, *args, **kwargs)
 
 def commitstatus(orig, repo, node, branch, bheads=None, opts=None):
     # wrap commit status use the topic branch heads
     ctx = repo[node]
     if ctx.topic() and ctx.branch() == branch:
-        bheads = repo.branchheads("%s:%s" % (branch, ctx.topic()))
+        subbranch = "%s:%s" % (branch, ctx.topic())
+        bheads = repo.branchheads("%s:%s" % (subbranch, ctx.topic()))
     return orig(repo, node, branch, bheads=bheads, opts=opts)
 
-class topiccache(oldbranchcache):
+def _wrapbmcache(ui):
+    class topiccache(_topiccache, branchmap.branchcache):
+        pass
+    branchmap.branchcache = topiccache
+    extensions.wrapfunction(branchmap, 'updatecache', _wrapupdatebmcache)
+
+def _wrapupdatebmcache(orig, repo):
+    previous = getattr(repo, '_autobranchmaptopic', False)
+    try:
+        repo._autobranchmaptopic = False
+        return orig(repo)
+    finally:
+        repo._autobranchmaptopic = previous
+
+# needed to prevent reference used for 'super()' call using in branchmap.py to
+# no go into cycle. (yes, URG)
+_oldbranchmap = branchmap.branchcache
+
+@contextlib.contextmanager
+def oldbranchmap():
+    previous = branchmap.branchcache
+    try:
+        branchmap.branchcache = _oldbranchmap
+        yield
+    finally:
+        branchmap.branchcache = previous
+
+class _topiccache(object): # combine me with branchmap.branchcache
 
     def __init__(self, *args, **kwargs):
-        otherbranchcache = branchmap.branchcache
-        try:
-            # super() call may fail otherwise
-            branchmap.branchcache = oldbranchcache
-            super(topiccache, self).__init__(*args, **kwargs)
-            if self.filteredhash is None:
-                self.filteredhash = nullid
-            self.phaseshash = nullid
-        finally:
-            branchmap.branchcache = otherbranchcache
+        # super() call may fail otherwise
+        with oldbranchmap():
+            super(_topiccache, self).__init__(*args, **kwargs)
+        self.phaseshash = None
 
     def copy(self):
         """return an deep copy of the branchcache object"""
-        new = topiccache(self, self.tipnode, self.tiprev, self.filteredhash,
-                         self._closednodes)
-        if self.filteredhash is None:
-            self.filteredhash = nullid
+        new = self.__class__(self, self.tipnode, self.tiprev, self.filteredhash,
+                             self._closednodes)
         new.phaseshash = self.phaseshash
         return new
 
@@ -104,144 +144,61 @@
         Raise KeyError for unknown branch.'''
         if topic:
             branch = '%s:%s' % (branch, topic)
-        return super(topiccache, self).branchtip(branch)
+        return super(_topiccache, self).branchtip(branch)
 
     def branchheads(self, branch, closed=False, topic=''):
         if topic:
             branch = '%s:%s' % (branch, topic)
-        return super(topiccache, self).branchheads(branch, closed=closed)
+        return super(_topiccache, self).branchheads(branch, closed=closed)
 
     def validfor(self, repo):
         """Is the cache content valid regarding a repo
 
         - False when cached tipnode is unknown or if we detect a strip.
         - True when cache is up to date or a subset of current repo."""
-        # This is copy paste of mercurial.branchmap.branchcache.validfor in
-        # 69077c65919d With a small changes to the cache key handling to
-        # include phase information that impact the topic cache.
-        #
-        # All code changes should be flagged on site.
-        try:
-            if (self.tipnode == repo.changelog.node(self.tiprev)):
-                fh = scmutil.filteredhash(repo, self.tiprev)
-                if fh is None:
-                    fh = nullid
-                if ((self.filteredhash == fh)
-                    and (self.phaseshash == _phaseshash(repo, self.tiprev))):
-                    return True
+        valid = super(_topiccache, self).validfor(repo)
+        if not valid:
             return False
-        except IndexError:
-            return False
+        elif not istopicfilter(repo.filtername) or self.phaseshash is None:
+            # phasehash at None means this is a branchmap
+            # come from non topic thing
+            return True
+        else:
+            try:
+                valid = self.phaseshash == _phaseshash(repo, self.tiprev)
+                return valid
+            except IndexError:
+                return False
 
     def write(self, repo):
-        # This is copy paste of mercurial.branchmap.branchcache.write in
-        # 69077c65919d With a small changes to the cache key handling to
-        # include phase information that impact the topic cache.
-        #
-        # All code changes should be flagged on site.
-        try:
-            f = repo.vfs(_filename(repo), "w", atomictemp=True)
-            cachekey = [hex(self.tipnode), str(self.tiprev)]
-            # [CHANGE] we need a hash in all cases
-            assert self.filteredhash is not None
-            cachekey.append(hex(self.filteredhash))
-            cachekey.append(hex(self.phaseshash))
-            f.write(" ".join(cachekey) + '\n')
-            nodecount = 0
-            for label, nodes in sorted(self.iteritems()):
-                for node in nodes:
-                    nodecount += 1
-                    if node in self._closednodes:
-                        state = 'c'
-                    else:
-                        state = 'o'
-                    f.write("%s %s %s\n" % (hex(node), state,
-                                            encoding.fromlocal(label)))
-            f.close()
-            repo.ui.log('branchcache',
-                        'wrote %s branch cache with %d labels and %d nodes\n',
-                        repo.filtername, len(self), nodecount)
-        except (IOError, OSError, error.Abort) as inst:
-            repo.ui.debug("couldn't write branch cache: %s\n" % inst)
-            # Abort may be raise by read only opener
-            pass
+        # we expect mutable set to be small enough to be that computing it all
+        # the time will be fast enough
+        if not istopicfilter(repo.filtername):
+            super(_topiccache, self).write(repo)
 
     def update(self, repo, revgen):
         """Given a branchhead cache, self, that may have extra nodes or be
         missing heads, and a generator of nodes that are strictly a superset of
         heads missing, this function updates self to be correct.
         """
-        oldgetbranchinfo = repo.revbranchcache().branchinfo
+        if not istopicfilter(repo.filtername):
+            return super(_topiccache, self).update(repo, revgen)
+        unfi = repo.unfiltered()
+        oldgetbranchinfo = unfi.revbranchcache().branchinfo
+
+        def branchinfo(r):
+            info = oldgetbranchinfo(r)
+            topic = ''
+            ctx = unfi[r]
+            if ctx.mutable():
+                topic = ctx.topic()
+            branch = info[0]
+            if topic:
+                branch = '%s:%s' % (branch, topic)
+            return (branch, info[1])
         try:
-            def branchinfo(r):
-                info = oldgetbranchinfo(r)
-                topic = ''
-                ctx = repo[r]
-                if ctx.mutable():
-                    topic = ctx.topic()
-                branch = info[0]
-                if topic:
-                    branch = '%s:%s' % (branch, topic)
-                return (branch, info[1])
-            repo.revbranchcache().branchinfo = branchinfo
-            super(topiccache, self).update(repo, revgen)
-            if self.filteredhash is None:
-                self.filteredhash = nullid
+            unfi.revbranchcache().branchinfo = branchinfo
+            super(_topiccache, self).update(repo, revgen)
             self.phaseshash = _phaseshash(repo, self.tiprev)
         finally:
-            repo.revbranchcache().branchinfo = oldgetbranchinfo
-
-def readtopicmap(repo):
-    # This is copy paste of mercurial.branchmap.read in 69077c65919d
-    # With a small changes to the cache key handling to include phase
-    # information that impact the topic cache.
-    #
-    # All code changes should be flagged on site.
-    try:
-        f = repo.vfs(_filename(repo))
-        lines = f.read().split('\n')
-        f.close()
-    except (IOError, OSError):
-        return None
-
-    try:
-        cachekey = lines.pop(0).split(" ", 2)
-        last, lrev = cachekey[:2]
-        last, lrev = bin(last), int(lrev)
-        filteredhash = bin(cachekey[2]) # [CHANGE] unconditional filteredhash
-        partial = topiccache(tipnode=last, tiprev=lrev,
-                             filteredhash=filteredhash)
-        partial.phaseshash = bin(cachekey[3]) # [CHANGE] read phaseshash
-        if not partial.validfor(repo):
-            # invalidate the cache
-            raise ValueError('tip differs')
-        cl = repo.changelog
-        for l in lines:
-            if not l:
-                continue
-            node, state, label = l.split(" ", 2)
-            if state not in 'oc':
-                raise ValueError('invalid branch state')
-            label = encoding.tolocal(label.strip())
-            node = bin(node)
-            if not cl.hasnode(node):
-                raise ValueError('node %s does not exist' % hex(node))
-            partial.setdefault(label, []).append(node)
-            if state == 'c':
-                partial._closednodes.add(node)
-    except KeyboardInterrupt:
-        raise
-    except Exception as inst:
-        if repo.ui.debugflag:
-            msg = 'invalid branchheads cache'
-            if repo.filtername is not None:
-                msg += ' (%s)' % repo.filtername
-            msg += ': %s\n'
-            repo.ui.debug(msg % inst)
-        partial = None
-    return partial
-
-def modsetup(ui):
-    """call at uisetup time to install various wrappings"""
-    extensions.wrapfunction(changegroup.cg1unpacker, 'apply', cgapply)
-    extensions.wrapfunction(cmdutil, 'commitstatus', commitstatus)
+            unfi.revbranchcache().branchinfo = oldgetbranchinfo
--- a/tests/test-check-commit.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-check-commit.t	Sun Jul 02 17:24:56 2017 +0200
@@ -3,6 +3,8 @@
 Enable obsolescence to avoid the warning issue when obsmarker are found
 
   $ cat << EOF >> $HGRCPATH
+  > [diff]
+  > git = yes
   > [experimental]
   > evolution=all
   > EOF
--- a/tests/test-evolve-obshistory.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-evolve-obshistory.t	Sun Jul 02 17:24:56 2017 +0200
@@ -55,11 +55,26 @@
   
 Actual test
 -----------
-  $ hg obslog 4ae3a4151de9
+  $ hg obslog --patch 4ae3a4151de9
   @  4ae3a4151de9 (3) A1
   |
   x  471f378eab4c (1) A0
        rewritten(description, content) by test (*) as 4ae3a4151de9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/4ae3a4151de9-changeset-description
+         @@ -1,1 +1,3 @@
+         -A0
+         +A1
+         +
+         +Better commit message
+  
+         diff -r 471f378eab4c -r 4ae3a4151de9 A0
+         --- a/A0	Thu Jan 01 00:00:00 1970 +0000
+         +++ b/A0	Thu Jan 01 00:00:00 1970 +0000
+         @@ -1,1 +1,2 @@
+          A0
+         +42
+  
   
   $ hg obslog 4ae3a4151de9 --no-graph -Tjson | python -m json.tool
   [
@@ -92,9 +107,24 @@
           "debugobshistory.shortdescription": "A0"
       }
   ]
-  $ hg obslog --hidden 471f378eab4c
+  $ hg obslog --hidden --patch 471f378eab4c
   x  471f378eab4c (1) A0
        rewritten(description, content) by test (*) as 4ae3a4151de9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/4ae3a4151de9-changeset-description
+         @@ -1,1 +1,3 @@
+         -A0
+         +A1
+         +
+         +Better commit message
+  
+         diff -r 471f378eab4c -r 4ae3a4151de9 A0
+         --- a/A0	Thu Jan 01 00:00:00 1970 +0000
+         +++ b/A0	Thu Jan 01 00:00:00 1970 +0000
+         @@ -1,1 +1,2 @@
+          A0
+         +42
+  
   
   $ hg obslog --hidden 471f378eab4c --no-graph -Tjson | python -m json.tool
   [
@@ -183,9 +213,10 @@
 Actual test
 -----------
 
-  $ hg obslog 'desc(B0)' --hidden
+  $ hg obslog 'desc(B0)' --hidden --patch
   x  0dec01379d3b (2) B0
        pruned by test (*) (glob)
+         (No patch available yet, no successors)
   
   $ hg obslog 'desc(B0)' --hidden --no-graph -Tjson | python -m json.tool
   [
@@ -205,7 +236,7 @@
           "debugobshistory.shortdescription": "B0"
       }
   ]
-  $ hg obslog 'desc(A0)'
+  $ hg obslog 'desc(A0)' --patch
   @  471f378eab4c (1) A0
   
   $ hg obslog 'desc(A0)' --no-graph -Tjson | python -m json.tool
@@ -316,9 +347,10 @@
 -----------
 
 Check that debugobshistory on splitted commit show both targets
-  $ hg obslog 471597cad322 --hidden
+  $ hg obslog 471597cad322 --hidden --patch
   x  471597cad322 (1) A0
        rewritten(parent, content) by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+         (No patch available yet, too many successors (2))
   
   $ hg obslog 471597cad322 --hidden --no-graph -Tjson | python -m json.tool
   [
@@ -348,56 +380,62 @@
   ]
 Check that debugobshistory on the first successor after split show
 the revision plus the splitted one
-  $ hg obslog 337fec4d2edc
+  $ hg obslog 337fec4d2edc --patch
   o  337fec4d2edc (2) A0
   |
   x  471597cad322 (1) A0
        rewritten(parent, content) by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+         (No patch available yet, too many successors (2))
   
 With the all option, it should show the three changesets
-  $ hg obslog --all 337fec4d2edc
+  $ hg obslog --all 337fec4d2edc --patch
   o  337fec4d2edc (2) A0
   |
   | @  f257fde29c7a (3) A0
   |/
   x  471597cad322 (1) A0
        rewritten(parent, content) by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+         (No patch available yet, too many successors (2))
   
 Check that debugobshistory on the second successor after split show
 the revision plus the splitted one
-  $ hg obslog f257fde29c7a
+  $ hg obslog f257fde29c7a --patch
   @  f257fde29c7a (3) A0
   |
   x  471597cad322 (1) A0
        rewritten(parent, content) by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+         (No patch available yet, too many successors (2))
   
 With the all option, it should show the three changesets
-  $ hg obslog f257fde29c7a --all
+  $ hg obslog f257fde29c7a --all --patch
   o  337fec4d2edc (2) A0
   |
   | @  f257fde29c7a (3) A0
   |/
   x  471597cad322 (1) A0
        rewritten(parent, content) by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+         (No patch available yet, too many successors (2))
   
 Obslog with all option all should also works on the splitted commit
-  $ hg obslog -a 471597cad322 --hidden
+  $ hg obslog -a 471597cad322 --hidden --patch
   o  337fec4d2edc (2) A0
   |
   | @  f257fde29c7a (3) A0
   |/
   x  471597cad322 (1) A0
        rewritten(parent, content) by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+         (No patch available yet, too many successors (2))
   
 Check that debugobshistory on both successors after split show
 a coherent graph
-  $ hg obslog 'f257fde29c7a+337fec4d2edc'
+  $ hg obslog 'f257fde29c7a+337fec4d2edc' --patch
   o  337fec4d2edc (2) A0
   |
   | @  f257fde29c7a (3) A0
   |/
   x  471597cad322 (1) A0
        rewritten(parent, content) by test (*) as 337fec4d2edc, f257fde29c7a (glob)
+         (No patch available yet, too many successors (2))
   
   $ hg update 471597cad322
   abort: hidden revision '471597cad322'!
@@ -561,11 +599,12 @@
 Actual test
 -----------
 
-  $ hg obslog de7290d8b885 --hidden
+  $ hg obslog de7290d8b885 --hidden --patch
   x  de7290d8b885 (1) A0
        rewritten(parent, content) by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+         (No patch available yet, too many successors (4))
   
-  $ hg obslog de7290d8b885 --hidden --all
+  $ hg obslog de7290d8b885 --hidden --all --patch
   o  1ae8bc733a14 (4) A0
   |
   | o  337fec4d2edc (2) A0
@@ -576,6 +615,7 @@
   |/
   x  de7290d8b885 (1) A0
        rewritten(parent, content) by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+         (No patch available yet, too many successors (4))
   
   $ hg obslog de7290d8b885 --hidden --no-graph -Tjson | python -m json.tool
   [
@@ -605,11 +645,12 @@
           "debugobshistory.shortdescription": "A0"
       }
   ]
-  $ hg obslog c7f044602e9b
+  $ hg obslog c7f044602e9b --patch
   @  c7f044602e9b (5) A0
   |
   x  de7290d8b885 (1) A0
        rewritten(parent, content) by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+         (No patch available yet, too many successors (4))
   
   $ hg obslog c7f044602e9b --no-graph -Tjson | python -m json.tool
   [
@@ -646,7 +687,7 @@
       }
   ]
 Check that debugobshistory on all heads show a coherent graph
-  $ hg obslog 2::5
+  $ hg obslog 2::5 --patch
   o  1ae8bc733a14 (4) A0
   |
   | o  337fec4d2edc (2) A0
@@ -657,8 +698,9 @@
   |/
   x  de7290d8b885 (1) A0
        rewritten(parent, content) by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+         (No patch available yet, too many successors (4))
   
-  $ hg obslog 5 --all
+  $ hg obslog 5 --all --patch
   o  1ae8bc733a14 (4) A0
   |
   | o  337fec4d2edc (2) A0
@@ -669,6 +711,7 @@
   |/
   x  de7290d8b885 (1) A0
        rewritten(parent, content) by test (*) as 1ae8bc733a14, 337fec4d2edc, c7f044602e9b, f257fde29c7a (glob)
+         (No patch available yet, too many successors (4))
   
   $ hg update de7290d8b885
   abort: hidden revision 'de7290d8b885'!
@@ -738,46 +781,98 @@
 
 Check that debugobshistory on the first folded revision show only
 the revision with the target
-  $ hg obslog --hidden 471f378eab4c
+  $ hg obslog --hidden 471f378eab4c --patch
   x  471f378eab4c (1) A0
        rewritten(description, content) by test (*) as eb5a0daa2192 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/eb5a0daa2192-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +C0
+  
+         diff -r 471f378eab4c -r eb5a0daa2192 B0
+         --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+         +++ b/B0	Thu Jan 01 00:00:00 1970 +0000
+         @@ -0,0 +1,1 @@
+         +B0
+  
   
 Check that with all option, all changesets are shown
-  $ hg obslog --hidden --all 471f378eab4c
+  $ hg obslog --hidden --all 471f378eab4c --patch
   @    eb5a0daa2192 (3) C0
   |\
   x |  0dec01379d3b (2) B0
    /     rewritten(description, parent, content) by test (*) as eb5a0daa2192 (glob)
+  |        (No patch available yet, changesets rebased)
   |
   x  471f378eab4c (1) A0
        rewritten(description, content) by test (*) as eb5a0daa2192 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/eb5a0daa2192-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +C0
+  
+         diff -r 471f378eab4c -r eb5a0daa2192 B0
+         --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+         +++ b/B0	Thu Jan 01 00:00:00 1970 +0000
+         @@ -0,0 +1,1 @@
+         +B0
+  
   
 Check that debugobshistory on the second folded revision show only
 the revision with the target
-  $ hg obslog --hidden 0dec01379d3b
+  $ hg obslog --hidden 0dec01379d3b --patch
   x  0dec01379d3b (2) B0
        rewritten(description, parent, content) by test (*) as eb5a0daa2192 (glob)
+         (No patch available yet, changesets rebased)
   
 Check that with all option, all changesets are shown
-  $ hg obslog --hidden --all 0dec01379d3b
+  $ hg obslog --hidden --all 0dec01379d3b --patch
   @    eb5a0daa2192 (3) C0
   |\
   x |  0dec01379d3b (2) B0
    /     rewritten(description, parent, content) by test (*) as eb5a0daa2192 (glob)
+  |        (No patch available yet, changesets rebased)
   |
   x  471f378eab4c (1) A0
        rewritten(description, content) by test (*) as eb5a0daa2192 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/eb5a0daa2192-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +C0
+  
+         diff -r 471f378eab4c -r eb5a0daa2192 B0
+         --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+         +++ b/B0	Thu Jan 01 00:00:00 1970 +0000
+         @@ -0,0 +1,1 @@
+         +B0
+  
   
 Check that debugobshistory on the successor revision show a coherent
 graph
-  $ hg obslog eb5a0daa2192
+  $ hg obslog eb5a0daa2192 --patch
   @    eb5a0daa2192 (3) C0
   |\
   x |  0dec01379d3b (2) B0
    /     rewritten(description, parent, content) by test (*) as eb5a0daa2192 (glob)
+  |        (No patch available yet, changesets rebased)
   |
   x  471f378eab4c (1) A0
        rewritten(description, content) by test (*) as eb5a0daa2192 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/eb5a0daa2192-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +C0
+  
+         diff -r 471f378eab4c -r eb5a0daa2192 B0
+         --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+         +++ b/B0	Thu Jan 01 00:00:00 1970 +0000
+         @@ -0,0 +1,1 @@
+         +B0
+  
   
   $ hg obslog eb5a0daa2192 --no-graph -Tjson | python -m json.tool
   [
@@ -917,21 +1012,45 @@
 -----------
 
 Check that debugobshistory on the divergent revision show both destinations
-  $ hg obslog --hidden 471f378eab4c
+  $ hg obslog --hidden 471f378eab4c --patch
   x  471f378eab4c (1) A0
        rewritten(description) by test (*) as 65b757b745b9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/65b757b745b9-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A2
+  
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/fdf9bde5129a-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A1
+  
   
 
 Check that with all option, every changeset is shown
-  $ hg obslog --hidden --all 471f378eab4c
+  $ hg obslog --hidden --all 471f378eab4c --patch
   @  65b757b745b9 (3) A2
   |
   | o  fdf9bde5129a (2) A1
   |/
   x  471f378eab4c (1) A0
        rewritten(description) by test (*) as 65b757b745b9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/65b757b745b9-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A2
+  
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/fdf9bde5129a-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A1
+  
   
   $ hg obslog --hidden 471f378eab4c --no-graph -Tjson | python -m json.tool
   [
@@ -973,53 +1092,113 @@
   ]
 Check that debugobshistory on the first diverged revision show the revision
 and the diverent one
-  $ hg obslog fdf9bde5129a
+  $ hg obslog fdf9bde5129a --patch
   o  fdf9bde5129a (2) A1
   |
   x  471f378eab4c (1) A0
        rewritten(description) by test (*) as 65b757b745b9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/65b757b745b9-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A2
+  
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/fdf9bde5129a-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A1
+  
   
 
 Check that all option show all of them
-  $ hg obslog fdf9bde5129a -a
+  $ hg obslog fdf9bde5129a -a --patch
   @  65b757b745b9 (3) A2
   |
   | o  fdf9bde5129a (2) A1
   |/
   x  471f378eab4c (1) A0
        rewritten(description) by test (*) as 65b757b745b9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/65b757b745b9-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A2
+  
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/fdf9bde5129a-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A1
+  
   
 Check that debugobshistory on the second diverged revision show the revision
 and the diverent one
-  $ hg obslog 65b757b745b9
+  $ hg obslog 65b757b745b9 --patch
   @  65b757b745b9 (3) A2
   |
   x  471f378eab4c (1) A0
        rewritten(description) by test (*) as 65b757b745b9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/65b757b745b9-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A2
+  
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/fdf9bde5129a-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A1
+  
   
 Check that all option show all of them
-  $ hg obslog 65b757b745b9 -a
+  $ hg obslog 65b757b745b9 -a --patch
   @  65b757b745b9 (3) A2
   |
   | o  fdf9bde5129a (2) A1
   |/
   x  471f378eab4c (1) A0
        rewritten(description) by test (*) as 65b757b745b9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/65b757b745b9-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A2
+  
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/fdf9bde5129a-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A1
+  
   
 Check that debugobshistory on the both diverged revision show a coherent
 graph
-  $ hg obslog '65b757b745b9+fdf9bde5129a'
+  $ hg obslog '65b757b745b9+fdf9bde5129a' --patch
   @  65b757b745b9 (3) A2
   |
   | o  fdf9bde5129a (2) A1
   |/
   x  471f378eab4c (1) A0
        rewritten(description) by test (*) as 65b757b745b9 (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/65b757b745b9-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A2
+  
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/fdf9bde5129a-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A1
+  
   
   $ hg obslog '65b757b745b9+fdf9bde5129a' --no-graph -Tjson | python -m json.tool
   [
@@ -1151,30 +1330,68 @@
  -----------
 
 Check that debugobshistory on head show a coherent graph
-  $ hg obslog eb5a0daa2192
+  $ hg obslog eb5a0daa2192 --patch
   @    eb5a0daa2192 (4) C0
   |\
   x |  471f378eab4c (1) A0
    /     rewritten(description, content) by test (*) as eb5a0daa2192 (glob)
+  |        --- a/471f378eab4c-changeset-description
+  |        +++ b/eb5a0daa2192-changeset-description
+  |        @@ -1,1 +1,1 @@
+  |        -A0
+  |        +C0
+  |
+  |        diff -r 471f378eab4c -r eb5a0daa2192 B0
+  |        --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  |        +++ b/B0	Thu Jan 01 00:00:00 1970 +0000
+  |        @@ -0,0 +1,1 @@
+  |        +B0
+  |
   |
   x  b7ea6d14e664 (3) B1
   |    rewritten(description, parent, content) by test (*) as eb5a0daa2192 (glob)
+  |      (No patch available yet, changesets rebased)
   |
   x  0dec01379d3b (2) B0
        rewritten(description) by test (*) as b7ea6d14e664 (glob)
+         --- a/0dec01379d3b-changeset-description
+         +++ b/b7ea6d14e664-changeset-description
+         @@ -1,1 +1,1 @@
+         -B0
+         +B1
+  
   
 Check that obslog on ROOT with all option show everything
-  $ hg obslog 1 --hidden --all
+  $ hg obslog 1 --hidden --all --patch
   @    eb5a0daa2192 (4) C0
   |\
   x |  471f378eab4c (1) A0
    /     rewritten(description, content) by test (*) as eb5a0daa2192 (glob)
+  |        --- a/471f378eab4c-changeset-description
+  |        +++ b/eb5a0daa2192-changeset-description
+  |        @@ -1,1 +1,1 @@
+  |        -A0
+  |        +C0
+  |
+  |        diff -r 471f378eab4c -r eb5a0daa2192 B0
+  |        --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  |        +++ b/B0	Thu Jan 01 00:00:00 1970 +0000
+  |        @@ -0,0 +1,1 @@
+  |        +B0
+  |
   |
   x  b7ea6d14e664 (3) B1
   |    rewritten(description, parent, content) by test (*) as eb5a0daa2192 (glob)
+  |      (No patch available yet, changesets rebased)
   |
   x  0dec01379d3b (2) B0
        rewritten(description) by test (*) as b7ea6d14e664 (glob)
+         --- a/0dec01379d3b-changeset-description
+         +++ b/b7ea6d14e664-changeset-description
+         @@ -1,1 +1,1 @@
+         -B0
+         +B1
+  
   
   $ hg obslog eb5a0daa2192 --no-graph -Tjson | python -m json.tool
   [
@@ -1340,14 +1557,26 @@
  Actual test
  -----------
 
-  $ hg obslog 7a230b46bf61
+  $ hg obslog 7a230b46bf61 --patch
   @  7a230b46bf61 (3) A2
   |
   x  fdf9bde5129a (2) A1
   |    rewritten(description) by test (*) as 7a230b46bf61 (glob)
+  |      --- a/fdf9bde5129a-changeset-description
+  |      +++ b/7a230b46bf61-changeset-description
+  |      @@ -1,1 +1,1 @@
+  |      -A1
+  |      +A2
+  |
   |
   x  471f378eab4c (1) A0
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         --- a/471f378eab4c-changeset-description
+         +++ b/fdf9bde5129a-changeset-description
+         @@ -1,1 +1,1 @@
+         -A0
+         +A1
+  
   
   $ cd $TESTTMP/local-remote-markers-2
   $ hg pull
@@ -1363,21 +1592,25 @@
   (use 'hg evolve' to update to its successor: 7a230b46bf61)
 Check that debugobshistory works with markers pointing to missing local
 changectx
-  $ hg obslog 7a230b46bf61
+  $ hg obslog 7a230b46bf61 --patch
   o  7a230b46bf61 (2) A2
   |
   x  fdf9bde5129a
   |    rewritten(description) by test (*) as 7a230b46bf61 (glob)
+  |      (No patch available yet, context is not local)
   |
   @  471f378eab4c (1) A0
        rewritten(description) by test (*) as fdf9bde5129a (glob)
+         (No patch available yet, succ is unknown locally)
   
-  $ hg obslog 7a230b46bf61 --color=debug
+  $ hg obslog 7a230b46bf61 --color=debug --patch
   o  [evolve.node|7a230b46bf61] [evolve.rev|(2)] [evolve.short_description|A2]
   |
   x  [evolve.node evolve.missing_change_ctx|fdf9bde5129a]
   |    [evolve.verb|rewritten](description) by [evolve.user|test] [evolve.date|(*)] as [evolve.node|7a230b46bf61] (glob)
+  |      (No patch available yet, context is not local)
   |
   @  [evolve.node|471f378eab4c] [evolve.rev|(1)] [evolve.short_description|A0]
        [evolve.verb|rewritten](description) by [evolve.user|test] [evolve.date|(*)] as [evolve.node|fdf9bde5129a] (glob)
+         (No patch available yet, succ is unknown locally)
   
--- a/tests/test-evolve-templates.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-evolve-templates.t	Sun Jul 02 17:24:56 2017 +0200
@@ -236,11 +236,11 @@
   $ hg fatelogjson --hidden
   @  d004c8f274b9 ""
   |
-  | x  a468dc9b3633 [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["ef1", "1"], ["user", "test2"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], "users": ["test2"], "verb": "rewritten"}] (glob)
+  | x  a468dc9b3633 [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["ef1", "1"], ["user", "test2"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], "users": ["test2"], "verb": "rewritten"}] (glob)
   |/
-  | x  f137d23bb3e1 [{"markers": [["f137d23bb3e11dc1daeb6264fac9cb2433782e15", [], 0, [["ef1", "0"], ["user", "test1"]], [*, *], ["471f378eab4c5e25f6c77f785b27c936efb22874"]]], "max_date": [*, *], "min_date": [*, *], "successors": [], "users": ["test1"], "verb": "pruned"}] (glob)
+  | x  f137d23bb3e1 [{"markers": [["f137d23bb3e11dc1daeb6264fac9cb2433782e15", [], 0, [["ef1", "0"], ["user", "test1"]], [*, 0], ["471f378eab4c5e25f6c77f785b27c936efb22874"]]], "max_date": [*, 0], "min_date": [*, 0], "successors": [], "users": ["test1"], "verb": "pruned"}] (glob)
   | |
-  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["ef1", "9"], ["user", "test1"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], "users": ["test1"], "verb": "rewritten"}] (glob)
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["ef1", "9"], ["user", "test1"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], "users": ["test1"], "verb": "rewritten"}] (glob)
   |/
   o  ea207398892e ""
   
@@ -405,7 +405,7 @@
   |
   o  337fec4d2edc ""
   |
-  | x  471597cad322 [{"markers": [["471597cad322d1f659bb169751be9133dad92ef3", ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], 0, [["ef1", "12"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], "users": ["test"], "verb": "split"}] (glob)
+  | x  471597cad322 [{"markers": [["471597cad322d1f659bb169751be9133dad92ef3", ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], 0, [["ef1", "12"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], "users": ["test"], "verb": "split"}] (glob)
   |/
   o  ea207398892e ""
   
@@ -570,9 +570,9 @@
   $ hg fatelogjson --hidden
   @  eb5a0daa2192 ""
   |
-  | x  0dec01379d3b [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["ef1", "13"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}] (glob)
+  | x  0dec01379d3b [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["ef1", "13"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}] (glob)
   | |
-  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["ef1", "9"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}] (glob)
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["ef1", "9"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}] (glob)
   |/
   o  ea207398892e ""
   
@@ -734,11 +734,11 @@
   $ hg fatelogjson --hidden
   o  019fadeab383 ""
   |
-  | x  65b757b745b9 [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["ef1", "1"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], "users": ["test"], "verb": "rewritten"}] (glob)
+  | x  65b757b745b9 [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["ef1", "1"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], "users": ["test"], "verb": "rewritten"}] (glob)
   |/
   | @  fdf9bde5129a ""
   |/
-  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["ef1", "1"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], "users": ["test"], "verb": "rewritten"}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["ef1", "1"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"], "users": ["test"], "verb": "rewritten"}] (glob)
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["ef1", "1"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], "users": ["test"], "verb": "rewritten"}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["ef1", "1"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"], "users": ["test"], "verb": "rewritten"}] (glob)
   |/
   o  ea207398892e ""
   
@@ -947,11 +947,11 @@
   $ hg fatelogjson --hidden
   @  eb5a0daa2192 ""
   |
-  | x  b7ea6d14e664 [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["ef1", "13"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}] (glob)
+  | x  b7ea6d14e664 [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["ef1", "13"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}] (glob)
   | |
-  | | x  0dec01379d3b [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["ef1", "1"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], "users": ["test"], "verb": "rewritten"}] (glob)
+  | | x  0dec01379d3b [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["ef1", "1"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], "users": ["test"], "verb": "rewritten"}] (glob)
   | |/
-  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["ef1", "9"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}] (glob)
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["ef1", "9"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], "users": ["test"], "verb": "rewritten"}] (glob)
   |/
   o  ea207398892e ""
   
@@ -1096,7 +1096,7 @@
   $ hg fatelogjson --hidden
   @  7a230b46bf61 ""
   |
-  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["ef1", "1"], ["user", "test"]], [*, *], null], ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e", ["7a230b46bf61e50b30308c6cfd7bd1269ef54702"], 0, [["ef1", "1"], ["user", "test"]], [*, *], null]], "max_date": [*, *], "min_date": [*, *], "successors": ["7a230b46bf61e50b30308c6cfd7bd1269ef54702"], "users": ["test"], "verb": "rewritten"}] (glob)
+  | x  471f378eab4c [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["ef1", "1"], ["user", "test"]], [*, 0], null], ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e", ["7a230b46bf61e50b30308c6cfd7bd1269ef54702"], 0, [["ef1", "1"], ["user", "test"]], [*, 0], null]], "max_date": [*, 0], "min_date": [*, 0], "successors": ["7a230b46bf61e50b30308c6cfd7bd1269ef54702"], "users": ["test"], "verb": "rewritten"}] (glob)
   |/
   o  ea207398892e ""
   
--- a/tests/test-split.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-split.t	Sun Jul 02 17:24:56 2017 +0200
@@ -385,3 +385,84 @@
   $ hg commit -m "empty"
   $ hg split
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Check that split keeps the right topic
+
+  $ hg up -r tip
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Add topic to the hgrc
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "topic=$(echo $(dirname $TESTDIR))/hgext3rd/topic/" >> $HGRCPATH
+  $ hg topic mytopic
+  $ echo babar > babar
+  $ echo celeste > celeste
+  $ hg add babar celeste
+  $ hg commit -m "Works on mytopic" babar celeste
+  $ hg summary
+  parent: 21:615c369f47f0 tip
+   Works on mytopic
+  branch: new-branch
+  commit: 2 unknown (clean)
+  update: (current)
+  phases: 9 draft
+  topic:  mytopic
+
+Split it
+
+  $ hg split << EOF
+  > Y
+  > Y
+  > N
+  > Y
+  > Y
+  > Y
+  > EOF
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  adding babar
+  adding celeste
+  diff --git a/babar b/babar
+  new file mode 100644
+  examine changes to 'babar'? [Ynesfdaq?] Y
+  
+  @@ -0,0 +1,1 @@
+  +babar
+  record change 1/2 to 'babar'? [Ynesfdaq?] Y
+  
+  diff --git a/celeste b/celeste
+  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
+
+Check that the topic is still here
+
+  $ hg log -r "tip~1::"
+  changeset:   22:f879ab83f991
+  branch:      new-branch
+  topic:       mytopic
+  parent:      20:89e64a2c68b3
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     split7
+  
+  changeset:   23:db75dbc1a3a6
+  branch:      new-branch
+  tag:         tip
+  topic:       mytopic
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     split8
+  
+  $ hg topic
+   * mytopic
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-stack-branch.t	Sun Jul 02 17:24:56 2017 +0200
@@ -0,0 +1,288 @@
+
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+
+Initial setup
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > logtemplate = {rev} {branch} \{{get(namespaces, "topics")}} {phase} {desc|firstline}\n
+  > [experimental]
+  > evolution=createmarkers,exchange,allowunstable
+  > EOF
+
+  $ hg init main
+  $ cd main
+  $ hg branch other
+  marked working directory as branch other
+  (branches are permanent and global, did you want a bookmark?)
+  $ echo aaa > aaa
+  $ hg add aaa
+  $ hg commit -m c_a
+  $ echo aaa > bbb
+  $ hg add bbb
+  $ hg commit -m c_b
+  $ hg branch foo
+  marked working directory as branch foo
+  $ echo aaa > ccc
+  $ hg add ccc
+  $ hg commit -m c_c
+  $ echo aaa > ddd
+  $ hg add ddd
+  $ hg commit -m c_d
+  $ echo aaa > eee
+  $ hg add eee
+  $ hg commit -m c_e
+  $ echo aaa > fff
+  $ hg add fff
+  $ hg commit -m c_f
+  $ hg log -G
+  @  5 foo {} draft c_f
+  |
+  o  4 foo {} draft c_e
+  |
+  o  3 foo {} draft c_d
+  |
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} draft c_b
+  |
+  o  0 other {} draft c_a
+  
+
+Check that topic without any parent does not crash --list
+---------------------------------------------------------
+
+  $ hg up other
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ hg stack
+  ### branch: other
+  b2@ c_b (current)
+  b1: c_a
+  $ hg phase --public 'branch("other")'
+  $ hg up foo
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Simple test
+-----------
+
+'hg stack' list all changeset in the topic
+
+  $ hg branch
+  foo
+  $ hg stack
+  ### branch: foo
+  b4@ c_f (current)
+  b3: c_e
+  b2: c_d
+  b1: c_c
+    ^ c_b
+
+Test "t#" reference
+-------------------
+
+  $ hg up b2
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg up foo
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg up b42
+  abort: cannot resolve "b42": branch "foo" has only 4 changesets
+  [255]
+  $ hg up b2
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg summary
+  parent: 3:f61adbacd17a 
+   c_d
+  branch: foo
+  commit: (clean)
+  update: 2 new changesets (update)
+  phases: 4 draft
+
+Case with some of the branch unstable
+------------------------------------
+
+  $ echo bbb > ddd
+  $ hg commit --amend
+  $ hg log -G
+  @  7 foo {} draft c_d
+  |
+  | o  5 foo {} draft c_f
+  | |
+  | o  4 foo {} draft c_e
+  | |
+  | x  3 foo {} draft c_d
+  |/
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} public c_b
+  |
+  o  0 other {} public c_a
+  
+  $ hg stack
+  ### branch: foo
+  b4$ c_f (unstable)
+  b3$ c_e (unstable)
+  b2@ c_d (current)
+  b1: c_c
+    ^ c_b
+  $ hg up b3
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg stack
+  ### branch: foo
+  b4$ c_f (unstable)
+  b3$ c_e (current unstable)
+  b2: c_d
+  b1: c_c
+    ^ c_b
+  $ hg up b2
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+Also test the revset:
+
+  $ hg log -r 'stack()'
+  2 foo {} draft c_c
+  7 foo {} draft c_d
+  4 foo {} draft c_e
+  5 foo {} draft c_f
+
+Case with multiple heads on the topic
+-------------------------------------
+
+Make things linear again
+
+  $ hg rebase -s 'desc(c_e)' -d 'desc(c_d) - obsolete()'
+  rebasing 4:4f2a69f6d380 "c_e"
+  rebasing 5:913c298d8b0a "c_f"
+  $ hg log -G
+  o  9 foo {} draft c_f
+  |
+  o  8 foo {} draft c_e
+  |
+  @  7 foo {} draft c_d
+  |
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} public c_b
+  |
+  o  0 other {} public c_a
+  
+
+Create the second branch
+
+  $ hg up 'desc(c_d)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo aaa > ggg
+  $ hg add ggg
+  $ hg commit -m c_g
+  created new head
+  $ echo aaa > hhh
+  $ hg add hhh
+  $ hg commit -m c_h
+  $ hg log -G
+  @  11 foo {} draft c_h
+  |
+  o  10 foo {} draft c_g
+  |
+  | o  9 foo {} draft c_f
+  | |
+  | o  8 foo {} draft c_e
+  |/
+  o  7 foo {} draft c_d
+  |
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} public c_b
+  |
+  o  0 other {} public c_a
+  
+
+Test output
+
+  $ hg stack
+  ### branch: foo (2 heads)
+  b6: c_f
+  b5: c_e
+  b2^ c_d (base)
+  b4@ c_h (current)
+  b3: c_g
+  b2: c_d
+  b1: c_c
+    ^ c_b
+
+Case with multiple heads on the topic with unstability involved
+---------------------------------------------------------------
+
+We amend the message to make sure the display base pick the right changeset
+
+  $ hg up 'desc(c_d)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo ccc > ddd
+  $ hg commit --amend -m 'c_D' 
+  $ hg rebase -d . -s 'desc(c_g)'
+  rebasing 10:2ebb6e48ab8a "c_g"
+  rebasing 11:634f38e27a1d "c_h"
+  $ hg log -G
+  o  15 foo {} draft c_h
+  |
+  o  14 foo {} draft c_g
+  |
+  @  13 foo {} draft c_D
+  |
+  | o  9 foo {} draft c_f
+  | |
+  | o  8 foo {} draft c_e
+  | |
+  | x  7 foo {} draft c_d
+  |/
+  o  2 foo {} draft c_c
+  |
+  o  1 other {} public c_b
+  |
+  o  0 other {} public c_a
+  
+
+  $ hg stack
+  ### branch: foo (2 heads)
+  b6$ c_f (unstable)
+  b5$ c_e (unstable)
+  b2^ c_D (base)
+  b4: c_h
+  b3: c_g
+  b2@ c_D (current)
+  b1: c_c
+    ^ c_b
+
+Check that stack doesn't show draft changesets on a branch
+----------------------------------------------------------
+
+  $ hg stack
+  ### branch: foo (2 heads)
+  b6$ c_f (unstable)
+  b5$ c_e (unstable)
+  b2^ c_D (base)
+  b4: c_h
+  b3: c_g
+  b2@ c_D (current)
+  b1: c_c
+    ^ c_b
+  $ hg phase --public b1
+  $ hg stack
+  ### branch: foo (2 heads)
+  b5$ c_f (unstable)
+  b4$ c_e (unstable)
+  b1^ c_D (base)
+  b3: c_h
+  b2: c_g
+  b1@ c_D (current)
+    ^ c_c
+
+Check that stack doesn't show changeset with a topic
+----------------------------------------------------
+
+  $ hg topic --rev b4::b5 sometopic
+  changed topic on 2 changes
+  $ hg stack
+  ### branch: foo
+  b3: c_h
+  b2: c_g
+  b1@ c_D (current)
+    ^ c_c
--- a/tests/test-topic-dest.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-topic-dest.t	Sun Jul 02 17:24:56 2017 +0200
@@ -108,6 +108,7 @@
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg rebase
   rebasing 4:cb7ae72f4a80 "babar"
+  switching to topic elephant
   $ hg log -G
   @  7 (elephant) babar
   |
@@ -128,6 +129,7 @@
   1 files updated, 0 files merged, 3 files removed, 0 files unresolved
   $ hg rebase
   rebasing 5:d832ddc604ec "zephir"
+  switching to topic monkey
   $ hg log -G
   @  8 (monkey) zephir
   |
@@ -222,6 +224,7 @@
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg rebase -d 'desc(c_zeta)' # make sure tip is elsewhere
   rebasing 7:8d0b77140b05 "babar"
+  switching to topic elephant
   $ hg up monkey
   switching to topic monkey
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-fold.t	Sun Jul 02 17:24:56 2017 +0200
@@ -0,0 +1,107 @@
+test of the fold command
+------------------------
+
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > fold=-d "0 0"
+  > split=-d "0 0"
+  > amend=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [diff]
+  > git = 1
+  > unified = 0
+  > [ui]
+  > interactive = true
+  > [extensions]
+  > hgext.graphlog=
+  > EOF
+  $ echo "topic=$(echo $(dirname $TESTDIR))/hgext3rd/topic/" >> $HGRCPATH
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1" $2 $3
+  > }
+  $ logtopic() {
+  >    hg log -G -T "{rev}:{node}\ntopics: {topics}" 
+  > }
+
+Check that fold keep the topic if all revisions have the topic
+--------------------------------------------------------------
+
+  $ hg init testfold
+  $ cd testfold
+  $ mkcommit ROOT
+  $ hg topic myfeature
+  $ mkcommit feature1
+  $ mkcommit feature2
+  $ logtopic
+  @  2:d76a6166b18c835be9a487c5e21c7d260f0a1676
+  |  topics: myfeature
+  o  1:39e7a938055e87615edf675c24a10997ff05bb06
+  |  topics: myfeature
+  o  0:3e7df3b3b17c6deb4a1c70e790782fdf17af96a7
+     topics:
+  $ hg fold --exact -r "(tip~1)::" -m "folded"
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg stack
+  ### topic: myfeature
+  ### branch: default
+  t1@ folded (current)
+    ^ add ROOT
+  $ logtopic
+  @  3:4fd43e5bdc443dc8489edffac19bd8f93ccf1a5c
+  |  topics: myfeature
+  o  0:3e7df3b3b17c6deb4a1c70e790782fdf17af96a7
+     topics:
+  $ hg summary
+  parent: 3:4fd43e5bdc44 tip
+   folded
+  branch: default
+  commit: (clean)
+  update: (current)
+  phases: 2 draft
+  topic:  myfeature
+
+Check that fold dismis the topic if not all revisions have the topic
+--------------------------------------------------------------------
+
+(I'm not sure this behavior make senses, but now it is tested)
+
+  $ hg topic --clear
+  $ mkcommit feature3
+  created new head
+  $ hg topic myotherfeature
+  $ mkcommit feature4
+  $ logtopic
+  @  5:5ded4d6d578c37f339b0716de2e46e12ece7cbde
+  |  topics: myotherfeature
+  o  4:bdf6950b9b5b7c6b377c8132667c73ec86d5734f
+  |  topics:
+  o  3:4fd43e5bdc443dc8489edffac19bd8f93ccf1a5c
+  |  topics: myfeature
+  o  0:3e7df3b3b17c6deb4a1c70e790782fdf17af96a7
+     topics:
+  $ hg fold --exact -r "(tip~1)::" -m "folded 2"
+  2 changesets folded
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ logtopic
+  @  6:03da8f7238e9a4d708d6b8af402c91c68f271477
+  |  topics:
+  o  3:4fd43e5bdc443dc8489edffac19bd8f93ccf1a5c
+  |  topics: myfeature
+  o  0:3e7df3b3b17c6deb4a1c70e790782fdf17af96a7
+     topics:
+  $ hg summary
+  parent: 6:03da8f7238e9 tip
+   folded 2
+  branch: default
+  commit: (clean)
+  update: (current)
+  phases: 3 draft
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-push-concurrent-on.t	Sun Jul 02 17:24:56 2017 +0200
@@ -0,0 +1,417 @@
+# same as test-topic-push but with the concurrent push feature on
+
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > logtemplate = {rev} {branch} {get(namespaces, "topics")} {phase} {desc|firstline}\n
+  > ssh =python "$RUNTESTDIR/dummyssh"
+  > [server]
+  > concurrent-push-mode=check-related
+  > EOF
+
+  $ hg init main
+  $ hg init draft
+  $ cat << EOF >> draft/.hg/hgrc
+  > [phases]
+  > publish=False
+  > EOF
+  $ hg clone main client
+  updating to branch default
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> client/.hg/hgrc
+  > [paths]
+  > draft=../draft
+  > EOF
+
+
+Testing core behavior to make sure we did not break anything
+============================================================
+
+Pushing a first changeset
+
+  $ cd client
+  $ echo aaa > aaa
+  $ hg add aaa
+  $ hg commit -m 'CA'
+  $ hg outgoing -G
+  comparing with $TESTTMP/main (glob)
+  searching for changes
+  @  0 default  draft CA
+  
+  $ hg push
+  pushing to $TESTTMP/main (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+Pushing two heads
+
+  $ echo aaa > bbb
+  $ hg add bbb
+  $ hg commit -m 'CB'
+  $ echo aaa > ccc
+  $ hg up 'desc(CA)'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg add ccc
+  $ hg commit -m 'CC'
+  created new head
+  $ hg outgoing -G
+  comparing with $TESTTMP/main (glob)
+  searching for changes
+  @  2 default  draft CC
+  
+  o  1 default  draft CB
+  
+  $ hg push
+  pushing to $TESTTMP/main (glob)
+  searching for changes
+  abort: push creates new remote head 9fe81b7f425d!
+  (merge or see 'hg help push' for details about pushing new heads)
+  [255]
+  $ hg outgoing -r 'desc(CB)' -G
+  comparing with $TESTTMP/main (glob)
+  searching for changes
+  o  1 default  draft CB
+  
+  $ hg push -r 'desc(CB)'
+  pushing to $TESTTMP/main (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+Pushing a new branch
+
+  $ hg branch mountain
+  marked working directory as branch mountain
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg commit --amend
+  $ hg outgoing -G
+  comparing with $TESTTMP/main (glob)
+  searching for changes
+  @  4 mountain  draft CC
+  
+  $ hg push 
+  pushing to $TESTTMP/main (glob)
+  searching for changes
+  abort: push creates new remote branches: mountain!
+  (use 'hg push --new-branch' to create new remote branches)
+  [255]
+  $ hg push --new-branch
+  pushing to $TESTTMP/main (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  2 new obsolescence markers
+
+Including on non-publishing
+
+  $ hg push --new-branch draft
+  pushing to $TESTTMP/draft (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files (+1 heads)
+  2 new obsolescence markers
+
+Testing topic behavior
+======================
+
+Local peer tests
+----------------
+
+  $ hg up -r 'desc(CA)'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg topic babar
+  $ echo aaa > ddd
+  $ hg add ddd
+  $ hg commit -m 'CD'
+  $ hg log -G # keep track of phase because I saw some strange bug during developement
+  @  5 default babar draft CD
+  |
+  | o  4 mountain  public CC
+  |/
+  | o  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+Pushing a new topic to a non publishing server should not be seen as a new head
+
+  $ hg push draft
+  pushing to $TESTTMP/draft (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  $ hg log -G
+  @  5 default babar draft CD
+  |
+  | o  4 mountain  public CC
+  |/
+  | o  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+Pushing a new topic to a publishing server should be seen as a new head
+
+  $ hg push
+  pushing to $TESTTMP/main (glob)
+  searching for changes
+  abort: push creates new remote head 67f579af159d!
+  (merge or see 'hg help push' for details about pushing new heads)
+  [255]
+  $ hg log -G
+  @  5 default babar draft CD
+  |
+  | o  4 mountain  public CC
+  |/
+  | o  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+wireprotocol tests
+------------------
+
+  $ hg up -r 'desc(CA)'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg topic celeste
+  $ echo aaa > eee
+  $ hg add eee
+  $ hg commit -m 'CE'
+  $ hg log -G # keep track of phase because I saw some strange bug during developement
+  @  6 default celeste draft CE
+  |
+  | o  5 default babar draft CD
+  |/
+  | o  4 mountain  public CC
+  |/
+  | o  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+Pushing a new topic to a non publishing server without topic -> new head
+
+  $ cat << EOF >> ../draft/.hg/hgrc
+  > [extensions]
+  > topic=!
+  > EOF
+  $ hg push ssh://user@dummy/draft
+  pushing to ssh://user@dummy/draft
+  searching for changes
+  abort: push creates new remote head 84eaf32db6c3!
+  (merge or see 'hg help push' for details about pushing new heads)
+  [255]
+  $ hg log -G
+  @  6 default celeste draft CE
+  |
+  | o  5 default babar draft CD
+  |/
+  | o  4 mountain  public CC
+  |/
+  | o  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+Pushing a new topic to a non publishing server should not be seen as a new head
+
+  $ printf "topic=" >> ../draft/.hg/hgrc
+  $ hg config extensions.topic >> ../draft/.hg/hgrc
+  $ hg push ssh://user@dummy/draft
+  pushing to ssh://user@dummy/draft
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files (+1 heads)
+  $ hg log -G
+  @  6 default celeste draft CE
+  |
+  | o  5 default babar draft CD
+  |/
+  | o  4 mountain  public CC
+  |/
+  | o  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+Pushing a new topic to a publishing server should be seen as a new head
+
+  $ hg push ssh://user@dummy/main
+  pushing to ssh://user@dummy/main
+  searching for changes
+  abort: push creates new remote head 67f579af159d!
+  (merge or see 'hg help push' for details about pushing new heads)
+  [255]
+  $ hg log -G
+  @  6 default celeste draft CE
+  |
+  | o  5 default babar draft CD
+  |/
+  | o  4 mountain  public CC
+  |/
+  | o  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+Check that we reject multiple head on the same topic
+----------------------------------------------------
+
+  $ hg up 'desc(CB)'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg topic babar
+  $ echo aaa > fff
+  $ hg add fff
+  $ hg commit -m 'CF'
+  $ hg log -G
+  @  7 default babar draft CF
+  |
+  | o  6 default celeste draft CE
+  | |
+  | | o  5 default babar draft CD
+  | |/
+  | | o  4 mountain  public CC
+  | |/
+  o |  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+  $ hg push draft
+  pushing to $TESTTMP/draft (glob)
+  searching for changes
+  abort: push creates new remote head f0bc62a661be on branch 'default:babar'!
+  (merge or see 'hg help push' for details about pushing new heads)
+  [255]
+
+Multiple head on a branch merged in a topic changesets
+------------------------------------------------------------------------
+
+
+  $ hg up 'desc(CA)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo aaa > ggg
+  $ hg add ggg
+  $ hg commit -m 'CG'
+  created new head
+  $ hg up 'desc(CF)'
+  switching to topic babar
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge 'desc(CG)'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -m 'CM'
+  $ hg log -G
+  @    9 default babar draft CM
+  |\
+  | o  8 default  draft CG
+  | |
+  o |  7 default babar draft CF
+  | |
+  | | o  6 default celeste draft CE
+  | |/
+  | | o  5 default babar draft CD
+  | |/
+  | | o  4 mountain  public CC
+  | |/
+  o |  1 default  public CB
+  |/
+  o  0 default  public CA
+  
+
+Reject when pushing to draft
+
+  $ hg push draft -r .
+  pushing to $TESTTMP/draft (glob)
+  searching for changes
+  abort: push creates new remote head 4937c4cad39e!
+  (merge or see 'hg help push' for details about pushing new heads)
+  [255]
+
+
+Reject when pushing to publishing
+
+  $ hg push -r .
+  pushing to $TESTTMP/main (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files
+
+  $ cd ..
+
+Test phase move
+==================================
+
+setup, two repo knowns about two small topic branch
+
+  $ hg init repoA
+  $ hg clone repoA repoB
+  updating to branch default
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repoA/.hg/hgrc
+  > [phases]
+  > publish=False
+  > EOF
+  $ cat << EOF >> repoB/.hg/hgrc
+  > [phases]
+  > publish=False
+  > EOF
+  $ cd repoA
+  $ echo aaa > base
+  $ hg add base
+  $ hg commit -m 'CBASE'
+  $ echo aaa > aaa
+  $ hg add aaa
+  $ hg topic topicA
+  $ hg commit -m 'CA'
+  $ hg up 'desc(CBASE)'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo aaa > bbb
+  $ hg add bbb
+  $ hg topic topicB
+  $ hg commit -m 'CB'
+  $ cd ..
+  $ hg push -R repoA repoB
+  pushing to repoB
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files (+1 heads)
+  $ hg log -G -R repoA
+  @  2 default topicB draft CB
+  |
+  | o  1 default topicA draft CA
+  |/
+  o  0 default  draft CBASE
+  
+
+We turn different topic to public on each side,
+
+  $ hg -R repoA phase --public topicA
+  $ hg -R repoB phase --public topicB
+
+Pushing should complain because it create to heads on default
+
+  $ hg push -R repoA repoB
+  pushing to repoB
+  searching for changes
+  no changes found
+  abort: push create a new head on branch "default"
+  [255]
--- a/tests/test-topic-push.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-topic-push.t	Sun Jul 02 17:24:56 2017 +0200
@@ -3,7 +3,6 @@
   $ cat << EOF >> $HGRCPATH
   > [ui]
   > logtemplate = {rev} {branch} {get(namespaces, "topics")} {phase} {desc|firstline}\n
-  > [ui]
   > ssh =python "$RUNTESTDIR/dummyssh"
   > EOF
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-rebase.t	Sun Jul 02 17:24:56 2017 +0200
@@ -0,0 +1,162 @@
+test of the rebase command
+--------------------------
+
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > fold=-d "0 0"
+  > split=-d "0 0"
+  > amend=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [diff]
+  > git = 1
+  > unified = 0
+  > [ui]
+  > interactive = true
+  > [extensions]
+  > hgext.graphlog=
+  > rebase=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
+  $ echo "topic=$(echo $(dirname $TESTDIR))/hgext3rd/topic/" >> $HGRCPATH
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1" $2 $3
+  > }
+  $ logtopic() {
+  >    hg log -G -T "{rev}:{node}\ntopics: {topics}" 
+  > }
+
+Check that rebase keep the topic in the simple case (1 changeset, no merge conflict)
+------------------------------------------------------------------------------------
+
+  $ hg init testrebase
+  $ cd testrebase
+  $ mkcommit ROOT
+
+Work on myfeature
+  $ hg topic myfeature
+  $ mkcommit feature1
+  $ hg stack
+  ### topic: myfeature
+  ### branch: default
+  t1@ add feature1 (current)
+    ^ add ROOT
+  $ logtopic
+  @  1:39e7a938055e87615edf675c24a10997ff05bb06
+  |  topics: myfeature
+  o  0:3e7df3b3b17c6deb4a1c70e790782fdf17af96a7
+     topics:
+
+Create another commit on default
+  $ hg update --rev default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit default
+  $ logtopic
+  @  2:be7622a7a0f43ba713e152f56441275f8e8711ef
+  |  topics:
+  | o  1:39e7a938055e87615edf675c24a10997ff05bb06
+  |/   topics: myfeature
+  o  0:3e7df3b3b17c6deb4a1c70e790782fdf17af96a7
+     topics:
+
+Rebase the commit
+  $ hg update --rev 1
+  switching to topic myfeature
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg rebase
+  rebasing 1:39e7a938055e "add feature1"
+  switching to topic myfeature
+  $ hg stack
+  ### topic: myfeature
+  ### branch: default
+  t1@ add feature1 (current)
+    ^ add default
+  $ logtopic
+  @  3:fc6593661cf3256ba165cbccd6019ead17cc3726
+  |  topics: myfeature
+  o  2:be7622a7a0f43ba713e152f56441275f8e8711ef
+  |  topics:
+  o  0:3e7df3b3b17c6deb4a1c70e790782fdf17af96a7
+     topics:
+  $ hg up 3
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg stack
+  ### topic: myfeature
+  ### branch: default
+  t1@ add feature1 (current)
+    ^ add default
+
+Check that rebase keep the topic in case of merge conflict
+----------------------------------------------------------
+
+Create a common base
+  $ hg topic --clear
+  $ echo "A" > file
+  $ hg commit -A -m "default2" file
+  created new head
+
+Update the common file in a topic
+  $ hg topic myotherfeature
+  $ echo "B" >> file
+  $ hg commit -m "myotherfeature1"
+
+Update the common file in default
+  $ hg update --rev default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo "A2" > file
+  $ hg commit -m "default3"
+
+Rebase the topic
+  $ hg update --rev 5
+  switching to topic myotherfeature
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg rebase
+  rebasing 5:81f854012ec5 "myotherfeature1"
+  merging file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  switching to topic myotherfeature
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+Resolve the conflict
+  $ echo A2 > file
+  $ echo B >> file
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg rebase --continue
+  $ hg rebase --continue
+  rebasing 5:81f854012ec5 "myotherfeature1"
+
+Check the the commit has the right topic
+
+  $ logtopic
+  @  7:6ccb9ec4913b64f3ad719ff1ba66495a70bf35a4
+  |  topics: myotherfeature
+  o  6:0b124ef641a7a6f4715d962650d3b367e8c800be
+  |  topics:
+  o  4:0cd2e1a45ac4e3f9603a05ccfa6d1c70cd759bc5
+  |  topics:
+  o  3:fc6593661cf3256ba165cbccd6019ead17cc3726
+  |  topics: myfeature
+  o  2:be7622a7a0f43ba713e152f56441275f8e8711ef
+  |  topics:
+  o  0:3e7df3b3b17c6deb4a1c70e790782fdf17af96a7
+     topics:
+  $ hg stack
+  ### topic: myotherfeature
+  ### branch: default
+  t1@ myotherfeature1 (current)
+    ^ default3
+  $ hg update --rev 7
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg stack
+  ### topic: myotherfeature
+  ### branch: default
+  t1@ myotherfeature1 (current)
+    ^ default3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-shelve.t	Sun Jul 02 17:24:56 2017 +0200
@@ -0,0 +1,50 @@
+testing topic with shelve extension
+------------------------------------
+
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+
+  $ hg init repo
+  $ cd repo
+  $ cat <<EOF >>.hg/hgrc
+  > [extensions]
+  > shelve=
+  > EOF
+
+  $ touch a
+  $ echo "Hello" >> a
+  $ hg topic "testing-shelve"
+  $ hg topic
+   * testing-shelve
+  $ hg ci -m "First commit" -A
+  adding a
+  $ hg topic
+   * testing-shelve
+  $ echo " World" >> a
+  $ hg stack
+  ### topic: testing-shelve
+  ### branch: default
+  t1@ First commit (current)
+
+shelve test
+-----------
+
+  $ hg shelve
+  shelved as default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg topic
+   * testing-shelve
+  $ hg stack
+  ### topic: testing-shelve
+  ### branch: default
+  t1@ First commit (current)
+
+unshelve test
+-------------
+  $ hg unshelve
+  unshelving change 'default'
+  $ hg topic
+   * testing-shelve
+  $ hg stack
+  ### topic: testing-shelve
+  ### branch: default
+  t1@ First commit (current)
--- a/tests/test-topic-stack.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-topic-stack.t	Sun Jul 02 17:24:56 2017 +0200
@@ -129,8 +129,7 @@
 
   $ hg topic --clear
   $ hg stack
-  abort: no active topic to list
-  [255]
+  ### branch: 
 
 Test "t#" reference
 -------------------
@@ -188,7 +187,7 @@
   ### topic: foo
   ### branch: default
   t4$ c_f (unstable)
-  t3@ c_e (current)
+  t3$ c_e (current unstable)
   t2: c_d
   t1: c_c
     ^ c_b
@@ -196,7 +195,7 @@
   [topic.stack.summary.topic|### topic: [topic.active|foo]]
   [topic.stack.summary.branches|### branch: default]
   [topic.stack.index topic.stack.index.unstable|t4][topic.stack.state topic.stack.state.unstable|$] [topic.stack.desc topic.stack.desc.unstable|c_f][topic.stack.state topic.stack.state.unstable| (unstable)]
-  [topic.stack.index topic.stack.index.current|t3][topic.stack.state topic.stack.state.current|@] [topic.stack.desc topic.stack.desc.current|c_e][topic.stack.state topic.stack.state.current| (current)]
+  [topic.stack.index topic.stack.index.current topic.stack.index.unstable|t3][topic.stack.state topic.stack.state.current topic.stack.state.unstable|$] [topic.stack.desc topic.stack.desc.current topic.stack.desc.unstable|c_e][topic.stack.state topic.stack.state.current topic.stack.state.unstable| (current unstable)]
   [topic.stack.index topic.stack.index.clean|t2][topic.stack.state topic.stack.state.clean|:] [topic.stack.desc topic.stack.desc.clean|c_d]
   [topic.stack.index topic.stack.index.clean|t1][topic.stack.state topic.stack.state.clean|:] [topic.stack.desc topic.stack.desc.clean|c_c]
     [topic.stack.state topic.stack.state.base|^] [topic.stack.desc topic.stack.desc.base|c_b]
@@ -211,6 +210,14 @@
   4 default {foo} draft c_e
   5 default {foo} draft c_f
 
+  $ hg log -r 'stack(foo)'
+  hg: parse error: stack() takes no argument, it works on current topic
+  [255]
+
+  $ hg log -r 'stack(foobar)'
+  hg: parse error: stack() takes no argument, it works on current topic
+  [255]
+
 Case with multiple heads on the topic
 -------------------------------------
 
@@ -319,3 +326,12 @@
   t2@ c_D (current)
   t1: c_c
     ^ c_b
+
+Trying to list non existing topic
+  $ hg stack thisdoesnotexist
+  abort: cannot resolve "thisdoesnotexist": no such topic found
+  [255]
+  $ hg topic --list thisdoesnotexist
+  abort: cannot resolve "thisdoesnotexist": no such topic found
+  [255]
+
--- a/tests/test-topic-tutorial.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-topic-tutorial.t	Sun Jul 02 17:24:56 2017 +0200
@@ -243,6 +243,7 @@
   $ hg rebase
   rebasing 1:13900241408b "adding condiments"
   merging shopping
+  switching to topic food
   rebasing 2:287de11b401f "adding fruits"
   merging shopping
   $ hg log --graph
@@ -274,7 +275,7 @@
 The topic information will fade out when we publish the changesets::
 
   $ hg topic
-     food
+   * food
   $ hg push
   pushing to $TESTTMP/server (glob)
   searching for changes
@@ -284,6 +285,7 @@
   added 2 changesets with 2 changes to 1 files
   2 new obsolescence markers
   $ hg topic
+   * food
   $ hg log --graph
   @  changeset:   5:2d50db8b5b4c
   |  tag:         tip
@@ -405,6 +407,7 @@
   $ hg rebase
   rebasing 6:183984ef46d1 "Adding hammer"
   merging shopping
+  switching to topic tools
   rebasing 7:cffff85af537 "Adding saw"
   merging shopping
   rebasing 8:34255b455dac "Adding drill"
@@ -414,7 +417,7 @@
 
   $ hg topic --verbose
      drinks (on branch: default, 2 changesets, 2 behind)
-     tools  (on branch: default, 3 changesets)
+   * tools  (on branch: default, 3 changesets)
 
 The "2 behind" is telling you that there is 2 new changesets on the named branch of the topic. You need to merge or rebase to incorporate them.
 
@@ -433,6 +436,7 @@
   $ hg rebase -b drinks
   rebasing 9:8dfa45bd5e0c "Adding apple juice"
   merging shopping
+  switching to topic drinks
   rebasing 10:70dfa201ed73 "Adding orange juice"
   merging shopping
   switching to topic tools
--- a/tests/test-topic.t	Sun Jun 25 16:37:56 2017 +0200
+++ b/tests/test-topic.t	Sun Jul 02 17:24:56 2017 +0200
@@ -22,9 +22,9 @@
   
   options:
   
-      --clear        clear active topic if any
-      --change VALUE revset of existing revisions to change topic
-   -l --list         show the stack of changeset in the topic
+      --clear   clear active topic if any
+   -r --rev REV revset of existing revisions
+   -l --list    show the stack of changeset in the topic
   
   (some details hidden, use --verbose to show complete help)
   $ hg topics
@@ -32,8 +32,8 @@
 Test topics interaction with evolution:
 
   $ hg topics --config experimental.evolution=
-  $ hg topics --config experimental.evolution= --change . bob
-  abort: must have obsolete enabled to use --change
+  $ hg topics --config experimental.evolution= --rev . bob
+  abort: must have obsolete enabled to change topics
   [255]
 
 Create some changes:
@@ -542,21 +542,20 @@
   $ hg topics
      fran
 Changing topic fails if we don't give a topic
-  $ hg topic --change 9
+  $ hg topic --rev 9
   abort: changing topic requires a topic name or --clear
   [255]
 
 Can't change topic of a public change
-  $ hg topic --change 1:: --clear
+  $ hg topic --rev 1:: --clear
   abort: can't change topic of a public change
   [255]
 
 Can clear topics
-  $ hg topic --change 9 --clear
+  $ hg topic --rev 9 --clear
   changed topic on 1 changes
-  please run hg evolve --rev "not topic()" now
   $ hg log -Gr 'draft() and not obsolete()'
-  o  changeset:   11:783930e1d79e
+  o  changeset:   11:0beca5ab56c3
   |  tag:         tip
   |  parent:      3:a53952faf762
   |  user:        test
@@ -577,11 +576,35 @@
   rebasing 10:4073470c35e1 "fran?"
 
 Can add a topic to an existing change
-  $ hg topic --change 11 wat
+  $ hg topic
+  $ hg sum
+  parent: 12:18b70b8de1f0 tip
+   fran?
+  branch: default
+  commit: (clean)
+  update: 5 new changesets, 2 branch heads (merge)
+  phases: 2 draft
+  $ hg topic --rev 11 wat
   changed topic on 1 changes
-  please run hg evolve --rev "topic(wat)" now
+  $ hg log -r .
+  changeset:   12:18b70b8de1f0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  trouble:     unstable
+  summary:     fran?
+  
+  $ hg sum
+  parent: 12:18b70b8de1f0  (unstable)
+   fran?
+  branch: default
+  commit: (clean)
+  update: 5 new changesets, 2 branch heads (merge)
+  phases: 3 draft
+  unstable: 1 changesets
+  $ hg topic
+     wat
   $ hg log -Gr 'draft() and not obsolete()'
-  o  changeset:   13:d91cd8fd490e
+  o  changeset:   13:686a642006db
   |  tag:         tip
   |  topic:       wat
   |  parent:      3:a53952faf762
@@ -589,7 +612,7 @@
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     start on fran
   |
-  | @  changeset:   12:d9e32f4c4806
+  | @  changeset:   12:18b70b8de1f0
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
   | |  trouble:     unstable
@@ -599,18 +622,22 @@
 Normally you'd do this with evolve, but we'll use rebase to avoid
 bonus deps in the testsuite.
 
+  $ hg topic
+     wat
   $ hg rebase -d tip -s .
-  rebasing 12:d9e32f4c4806 "fran?"
+  rebasing 12:18b70b8de1f0 "fran?"
+  switching to topic wat
+  $ hg topic
+     wat
 
   $ hg log -Gr 'draft()'
-  @  changeset:   14:cf24ad8bbef5
+  @  changeset:   14:45358f7a5892
   |  tag:         tip
-  |  topic:       wat
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     fran?
   |
-  o  changeset:   13:d91cd8fd490e
+  o  changeset:   13:686a642006db
   |  topic:       wat
   |  parent:      3:a53952faf762
   |  user:        test
@@ -623,15 +650,15 @@
   $ hg topic watwat
   $ hg ci --amend
   $ hg log -Gr 'draft()'
-  @  changeset:   16:893ffcf66c1f
+  @  changeset:   16:6c40a4c21bbe
   |  tag:         tip
   |  topic:       watwat
-  |  parent:      13:d91cd8fd490e
+  |  parent:      13:686a642006db
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     fran?
   |
-  o  changeset:   13:d91cd8fd490e
+  o  changeset:   13:686a642006db
   |  topic:       wat
   |  parent:      3:a53952faf762
   |  user:        test
@@ -644,13 +671,109 @@
   $ hg topic --clear
   $ hg ci --amend
   $ hg log -r .
-  changeset:   18:a13639e22b65
+  changeset:   18:0f9cd5070654
   tag:         tip
-  parent:      13:d91cd8fd490e
+  parent:      13:686a642006db
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     fran?
   
-Readding the same topic with topic --change should work:
-  $ hg topic --change . watwat
+Reading the same topic with topic --rev should work:
+  $ hg topic --rev . watwat
+  switching to topic watwat
   changed topic on 1 changes
+
+Testing issue5441
+  $ hg co 19
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log -Gr 'draft()'
+  @  changeset:   19:980a0f608481
+  |  tag:         tip
+  |  topic:       watwat
+  |  parent:      13:686a642006db
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     fran?
+  |
+  o  changeset:   13:686a642006db
+  |  topic:       wat
+  |  parent:      3:a53952faf762
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start on fran
+  |
+
+  $ hg topics --rev '13::19' changewat
+  switching to topic changewat
+  changed topic on 2 changes
+  $ hg log -Gr 'draft()'
+  @  changeset:   21:56c83be6105f
+  |  tag:         tip
+  |  topic:       changewat
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     fran?
+  |
+  o  changeset:   20:ceba5be9d56f
+  |  topic:       changewat
+  |  parent:      3:a53952faf762
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start on fran
+  |
+
+Case with branching:
+
+  $ hg up changewat
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg up t1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo gamma >> gamma
+  $ hg ci -m gamma
+  $ hg log -Gr 'draft()'
+  @  changeset:   22:0d3d805542b4
+  |  tag:         tip
+  |  topic:       changewat
+  |  parent:      20:ceba5be9d56f
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     gamma
+  |
+  | o  changeset:   21:56c83be6105f
+  |/   topic:       changewat
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     fran?
+  |
+  o  changeset:   20:ceba5be9d56f
+  |  topic:       changewat
+  |  parent:      3:a53952faf762
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start on fran
+  |
+  $ hg topics --rev 't1::' changewut
+  switching to topic changewut
+  changed topic on 3 changes
+  $ hg log -Gr 'draft()'
+  @  changeset:   25:729ed5717393
+  |  tag:         tip
+  |  topic:       changewut
+  |  parent:      23:62e49f09f883
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     gamma
+  |
+  | o  changeset:   24:369c6e2e5474
+  |/   topic:       changewut
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     fran?
+  |
+  o  changeset:   23:62e49f09f883
+  |  topic:       changewut
+  |  parent:      3:a53952faf762
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start on fran
+  |