merge with other duplicate head
authorPierre-Yves David <pierre-yves.david@fb.com>
Tue, 22 Mar 2016 00:19:14 -0700
changeset 1912 692a1aa1350c
parent 1911 442a7cb8404e (diff)
parent 1900 27ea12c05e99 (current diff)
child 1913 3d3c02e6848a
merge with other duplicate head
hgext3rd/topic/destination.py
--- a/README.md	Wed Mar 16 12:14:20 2016 -0700
+++ b/README.md	Tue Mar 22 00:19:14 2016 -0700
@@ -13,3 +13,8 @@
 
     [extensions]
     topics=path/to/hg-topics/src
+
+If you are using Mercurial 3.7 use:
+
+    [extensions]
+    hgext3rd.topics=path/to/hg-topics/src
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/__init__.py	Tue Mar 22 00:19:14 2016 -0700
@@ -0,0 +1,3 @@
+from __future__ import absolute_import
+import pkgutil
+__path__ = pkgutil.extend_path(__path__, __name__)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/__init__.py	Tue Mar 22 00:19:14 2016 -0700
@@ -0,0 +1,373 @@
+# __init__.py - topic extension
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""support for topic branches
+
+Topic branches are lightweight branches which
+disappear when changes are finalized.
+
+This is sort of similar to a bookmark, but it applies to a whole
+series instead of a single revision.
+"""
+import functools
+import contextlib
+import re
+
+from mercurial.i18n import _
+from mercurial import branchmap
+from mercurial import bundle2
+from mercurial import changegroup
+from mercurial import cmdutil
+from mercurial import commands
+from mercurial import context
+from mercurial import discovery as discoverymod
+from mercurial import error
+from mercurial import exchange
+from mercurial import extensions
+from mercurial import localrepo
+from mercurial import lock
+from mercurial import merge
+from mercurial import namespaces
+from mercurial import node
+from mercurial import obsolete
+from mercurial import patch
+from mercurial import phases
+from mercurial import util
+from mercurial import wireproto
+
+from . import constants
+from . import revset as topicrevset
+from . import destination
+from . import stack
+from . import topicmap
+from . import discovery
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+colortable = {'topic.stack.index': 'yellow',
+              'topic.stack.state.base': 'dim',
+              'topic.stack.state.clean': 'green',
+              'topic.stack.index.current': 'cyan', # random pick
+              'topic.stack.state.current': 'cyan bold', # random pick
+              'topic.stack.desc.current': 'cyan', # random pick
+              'topic.stack.state.unstable': 'red',
+             }
+
+testedwith = '3.7'
+
+def _contexttopic(self):
+    return self.extra().get(constants.extrakey, '')
+context.basectx.topic = _contexttopic
+
+topicrev = re.compile(r'^t\d+$')
+
+def _namemap(repo, name):
+    if topicrev.match(name):
+        idx = int(name[1:])
+        topic = repo.currenttopic
+        if not topic:
+            raise error.Abort(_('cannot resolve "%s": no active topic') % name)
+        revs = list(stack.getstack(repo, topic))
+        try:
+            r = revs[idx]
+        except IndexError:
+            msg = _('cannot resolve "%s": topic "%s" has only %d changesets')
+            raise error.Abort(msg % (name, topic, len(revs)))
+        return [repo[r].node()]
+    return [ctx.node() for ctx in
+            repo.set('not public() and extra(topic, %s)', name)]
+
+def _nodemap(repo, node):
+    ctx = repo[node]
+    t = ctx.topic()
+    if t and ctx.phase() > phases.public:
+        return [t]
+    return []
+
+def uisetup(ui):
+    destination.setupdest()
+
+@contextlib.contextmanager
+def usetopicmap(repo):
+    """use awful monkey patching to update the topic cache"""
+    oldbranchcache = branchmap.branchcache
+    oldfilename = branchmap._filename
+    oldread = branchmap.read
+    oldcaches =  getattr(repo, '_branchcaches', {})
+    try:
+        branchmap.branchcache = topicmap.topiccache
+        branchmap._filename = topicmap._filename
+        branchmap.read = topicmap.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):
+    with usetopicmap(repo):
+        return orig(repo, *args, **kwargs)
+
+def reposetup(ui, repo):
+    orig = repo.__class__
+    if not isinstance(repo, localrepo.localrepository):
+        return # this can be a peer in the ssh case (puzzling)
+    class topicrepo(repo.__class__):
+
+        def _restrictcapabilities(self, caps):
+            caps = super(topicrepo, self)._restrictcapabilities(caps)
+            caps.add('topics')
+            return caps
+
+        def commit(self, *args, **kwargs):
+            backup = self.ui.backupconfig('ui', 'allowemptycommit')
+            try:
+                if repo.currenttopic != repo['.'].topic():
+                    # bypass the core "nothing changed" logic
+                    self.ui.setconfig('ui', 'allowemptycommit', True)
+                return orig.commit(self, *args, **kwargs)
+            finally:
+                self.ui.restoreconfig(backup)
+
+        def commitctx(self, ctx, error=None):
+            if isinstance(ctx, context.workingcommitctx):
+                current = self.currenttopic
+                if current:
+                    ctx.extra()[constants.extrakey] = current
+            if (isinstance(ctx, context.memctx) and
+                ctx.extra().get('amend_source') and
+                ctx.topic() and
+                not self.currenttopic):
+                # we are amending and need to remove a topic
+                del ctx.extra()[constants.extrakey]
+            with usetopicmap(self):
+                return orig.commitctx(self, ctx, error=error)
+
+        @property
+        def topics(self):
+            topics = set(['', self.currenttopic])
+            for c in self.set('not public()'):
+                topics.add(c.topic())
+            topics.remove('')
+            return topics
+
+        @property
+        def currenttopic(self):
+            return self.vfs.tryread('topic')
+
+        def branchmap(self, topic=True):
+            if not topic:
+                super(topicrepo, self).branchmap()
+            with usetopicmap(self):
+                branchmap.updatecache(self)
+            return self._topiccaches[self.filtername]
+
+        def destroyed(self, *args, **kwargs):
+            with usetopicmap(self):
+                return super(topicrepo, self).destroyed(*args, **kwargs)
+
+        def invalidatecaches(self):
+            super(topicrepo, self).invalidatecaches()
+            if '_topiccaches' in vars(self.unfiltered()):
+                self.unfiltered()._topiccaches.clear()
+
+        def peer(self):
+            peer = super(topicrepo, self).peer()
+            if getattr(peer, '_repo', None) is not None: # localpeer
+                class topicpeer(peer.__class__):
+                    def branchmap(self):
+                        usetopic = not self._repo.publishing()
+                        return self._repo.branchmap(topic=usetopic)
+                peer.__class__ = topicpeer
+            return peer
+
+
+    repo.__class__ = topicrepo
+    if util.safehasattr(repo, 'names'):
+        repo.names.addnamespace(namespaces.namespace(
+            'topics', 'topic', namemap=_namemap, nodemap=_nodemap,
+            listnames=lambda repo: repo.topics))
+
+@command('topics [TOPIC]', [
+    ('', 'clear', False, 'clear active topic if any'),
+    ('', 'change', '', 'revset of existing revisions to change topic'),
+    ('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):
+    """View current topic, set current topic, or see all topics."""
+    if list:
+        if clear or change:
+            raise error.Abort(_("cannot use --clear or --change with --list"))
+        return stack.showstack(ui, repo, topic, opts)
+
+    if change:
+        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('changing topic requires a topic name or --clear')
+        if any(not c.mutable() for c in repo.set('%r and public()', change)):
+            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)
+    if clear:
+        if repo.vfs.exists('topic'):
+            repo.vfs.unlink('topic')
+        return
+    if topic:
+        with repo.vfs.open('topic', 'w') as f:
+            f.write(topic)
+        return
+    current = repo.currenttopic
+    for t in sorted(repo.topics):
+        marker = '*' if t == current else ' '
+        ui.write(' %s %s\n' % (marker, t))
+
+def summaryhook(ui, repo):
+    t = repo.currenttopic
+    if not t:
+        return
+    # i18n: column positioning for "hg summary"
+    ui.write(_("topic:  %s\n") % t)
+
+def commitwrap(orig, ui, repo, *args, **opts):
+    if opts.get('topic'):
+        t = opts['topic']
+        with repo.vfs.open('topic', 'w') as f:
+            f.write(t)
+    return orig(ui, repo, *args, **opts)
+
+def committextwrap(orig, repo, ctx, subs, extramsg):
+    ret = orig(repo, ctx, subs, extramsg)
+    t = repo.currenttopic
+    if t:
+        ret = ret.replace("\nHG: branch",
+                          "\nHG: topic '%s'\nHG: branch" % t)
+    return ret
+
+def mergeupdatewrap(orig, repo, node, branchmerge, force, *args, **kwargs):
+    partial = bool(len(args)) or 'matcher' in kwargs
+    wlock = repo.wlock()
+    try:
+        ret = orig(repo, node, branchmerge, force, *args, **kwargs)
+        if not partial and not branchmerge:
+            ot = repo.currenttopic
+            t = ''
+            pctx = repo[node]
+            if pctx.phase() > phases.public:
+                t = pctx.topic()
+            with repo.vfs.open('topic', 'w') as f:
+                f.write(t)
+            if t and t != ot:
+                repo.ui.status(_("switching to topic %s\n") % t)
+        return ret
+    finally:
+        wlock.release()
+
+def _fixrebase(loaded):
+    if not loaded:
+        return
+
+    def savetopic(ctx, extra):
+        if ctx.topic():
+            extra[constants.extrakey] = ctx.topic()
+
+    def newmakeextrafn(orig, copiers):
+        return orig(copiers + [savetopic])
+
+    rebase = extensions.find("rebase")
+    extensions.wrapfunction(rebase, '_makeextrafn', newmakeextrafn)
+
+def _exporttopic(seq, ctx):
+    topic = ctx.topic()
+    if topic:
+        return 'EXP-Topic %s'  % topic
+    return None
+
+def _importtopic(repo, patchdata, extra, opts):
+    if 'topic' in patchdata:
+        extra['topic'] = patchdata['topic']
+
+extensions.afterloaded('rebase', _fixrebase)
+
+entry = extensions.wrapcommand(commands.table, 'commit', commitwrap)
+entry[1].append(('t', 'topic', '',
+                 _("use specified topic"), _('TOPIC')))
+
+extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap)
+extensions.wrapfunction(merge, 'update', mergeupdatewrap)
+extensions.wrapfunction(discoverymod, '_headssummary', discovery._headssummary)
+extensions.wrapfunction(wireproto, 'branchmap', discovery.wireprotobranchmap)
+extensions.wrapfunction(wireproto, '_capabilities', discovery.wireprotocaps)
+extensions.wrapfunction(bundle2, 'handlecheckheads', discovery.handlecheckheads)
+bundle2.handlecheckheads.params = frozenset() # we need a proper wrape b2 part stuff
+bundle2.parthandlermapping['check:heads'] = bundle2.handlecheckheads
+extensions.wrapfunction(exchange, '_pushb2phases', discovery._pushb2phases)
+extensions.wrapfunction(changegroup.cg1unpacker, 'apply', cgapply)
+exchange.b2partsgenmapping['phase'] = exchange._pushb2phases
+topicrevset.modsetup()
+cmdutil.summaryhooks.add('topic', summaryhook)
+
+if util.safehasattr(cmdutil, 'extraexport'):
+    cmdutil.extraexport.append('topic')
+    cmdutil.extraexportmap['topic'] = _exporttopic
+if util.safehasattr(cmdutil, 'extrapreimport'):
+    cmdutil.extrapreimport.append('topic')
+    cmdutil.extrapreimportmap['topic'] = _importtopic
+if util.safehasattr(patch, 'patchheadermap'):
+    patch.patchheadermap.append(('EXP-Topic', 'topic'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/constants.py	Tue Mar 22 00:19:14 2016 -0700
@@ -0,0 +1,1 @@
+extrakey = 'topic'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/destination.py	Tue Mar 22 00:19:14 2016 -0700
@@ -0,0 +1,98 @@
+from mercurial import error
+from mercurial import util
+from mercurial import destutil
+from mercurial import extensions
+from mercurial import bookmarks
+from mercurial.i18n import _
+
+def _destmergebranch(orig, repo, action='merge', sourceset=None, onheadcheck=True):
+    p1 = repo['.']
+    top = p1.topic()
+    if top:
+        heads = repo.revs('heads(topic(.)::topic(.))')
+        if p1.rev() not in heads:
+            raise error.Abort(_("not at topic head, update or explicit"))
+        elif 1 == len(heads):
+            # should look at all branch involved but... later
+            bhead = ngtip(repo, p1.branch(), all=True)
+            if not bhead:
+                raise error.Abort(_("nothing to merge"))
+            elif 1 == len(bhead):
+                return bhead.first()
+            else:
+                raise error.Abort(_("branch '%s' has %d heads - "
+                                   "please merge with an explicit rev")
+                                 % (p1.branch(), len(bhead)),
+                                 hint=_("run 'hg heads .' to see heads"))
+        elif 2 == len(heads):
+            heads = [r for r in heads if r != p1.rev()]
+            # XXX: bla bla bla bla bla
+            if 1 < len(heads):
+                raise error.Abort(_('working directory not at a head revision'),
+                                 hint=_("use 'hg update' or merge with an "
+                                        "explicit revision"))
+            return heads[0]
+        elif 2 < len(heads):
+            raise error.Abort(_("topic '%s' has %d heads - "
+                                "please merge with an explicit rev")
+                              % (top, len(heads)))
+        else:
+            assert False # that's impossible
+    if getattr(orig, 'func_default', ()): # version above hg-3.7
+        return orig(repo, action, sourceset, onheadcheck)
+    else:
+        return orig(repo)
+
+def _destupdatetopic(repo, clean, check):
+    """decide on an update destination from current topic"""
+    movemark = node = None
+    topic = repo.currenttopic
+    revs = repo.revs('.::topic("%s")' % topic)
+    if not revs:
+        return None, None, None
+    node = revs.last()
+    if bookmarks.isactivewdirparent(repo):
+        movemark = repo['.'].node()
+    return node, movemark, None
+
+def desthistedit(orig, ui, repo):
+    if not (ui.config('histedit', 'defaultrev', None) is None
+            and repo.currenttopic):
+        return orig(ui, repo)
+    revs = repo.revs('::. and stack()')
+    if revs:
+        return revs.min()
+    return None
+
+def setupdest():
+    if util.safehasattr(destutil, '_destmergebranch'):
+        extensions.wrapfunction(destutil, '_destmergebranch', _destmergebranch)
+    rebase = extensions.find('rebase')
+    if (util.safehasattr(rebase, '_destrebase')
+            # logic not shared with merge yet < hg-3.8
+            and not util.safehasattr(rebase, '_definesets')):
+        extensions.wrapfunction(rebase, '_destrebase', _destmergebranch)
+    if util.safehasattr(destutil, 'destupdatesteps'):
+        bridx = destutil.destupdatesteps.index('branch')
+        destutil.destupdatesteps.insert(bridx, 'topic')
+        destutil.destupdatestepmap['topic'] = _destupdatetopic
+    if util.safehasattr(destutil, 'desthistedit'):
+        extensions.wrapfunction(destutil, 'desthistedit', desthistedit)
+
+def ngtip(repo, branch, all=False):
+    """tip new generation"""
+    ## search for untopiced heads of branch
+    # could be heads((::branch(x) - topic()))
+    # but that is expensive
+    #
+    # we should write plain code instead
+    subquery = '''heads(
+                    parents(
+                       ancestor(
+                         (head() and branch(%s)
+                         or (topic() and branch(%s)))))
+                   ::(head() and branch(%s))
+                   - topic())'''
+    if not all:
+        subquery = 'max(%s)' % subquery
+    return repo.revs(subquery, branch, branch, branch)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/discovery.py	Tue Mar 22 00:19:14 2016 -0700
@@ -0,0 +1,109 @@
+import weakref
+from mercurial import branchmap
+from mercurial import error
+from mercurial import exchange
+from mercurial.i18n import _
+from . import topicmap
+
+def _headssummary(orig, repo, remote, outgoing):
+    publishing = ('phases' not in remote.listkeys('namespaces')
+                  or bool(remote.listkeys('phases').get('publishing', False)))
+    if publishing or not remote.capable('topics'):
+        return orig(repo, remote, outgoing)
+    oldgetitem = repo.__getitem__
+    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
+                def branch():
+                    branch = oldbranch()
+                    topic = ctx.topic()
+                    if topic:
+                        branch = "%s:%s" % (branch, topic)
+                    return branch
+                ctx.branch = branch
+                return ctx
+        repo.__class__ = repocls
+        branchmap.branchcache = topicmap.topiccache
+        branchmap._filename = topicmap._filename
+        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:]
+        return summary
+    finally:
+        repo.__class__ = oldrepo
+        branchmap.branchcache = oldbranchcache
+        branchmap._filename = oldfilename
+
+def wireprotobranchmap(orig, repo, proto):
+    oldrepo = repo.__class__
+    try:
+        class repocls(repo.__class__):
+            def branchmap(self):
+                usetopic = not self.publishing()
+                return super(repocls, self).branchmap(topic=usetopic)
+        repo.__class__ = repocls
+        return orig(repo, proto)
+    finally:
+        repo.__class__ = oldrepo
+
+
+# Discovery have deficiency around phases, branch can get new heads with pure
+# phases change. This happened with a changeset was allowed to be pushed
+# because it had a topic, but it later become public and create a new branch
+# head.
+#
+# Handle this by doing an extra check for new head creation server side
+def _nbheads(repo):
+    data = {}
+    for b in repo.branchmap().iterbranches():
+        if ':' in b[0]:
+            continue
+        data[b[0]] = len(b[1])
+    return data
+
+def handlecheckheads(orig, op, inpart):
+    orig(op, inpart)
+    if op.repo.publishing():
+        return
+    tr = op.gettransaction()
+    if tr.hookargs['source'] not in ('push', 'serve'): # not a push
+        return
+    tr._prepushheads = _nbheads(op.repo)
+    reporef = weakref.ref(op.repo)
+    oldvalidator = tr.validator
+    def validator(tr):
+        repo = reporef()
+        if repo is not None:
+            repo.invalidatecaches()
+            finalheads = _nbheads(repo)
+            for branch, oldnb in tr._prepushheads.iteritems():
+                newnb = finalheads.pop(branch, 0)
+                if oldnb < newnb:
+                    msg = _('push create a new head on branch "%s"' % branch)
+                    raise error.Abort(msg)
+            for branch, newnb in finalheads.iteritems():
+                if 1 < newnb:
+                    msg = _('push create more than 1 head on new branch "%s"' % branch)
+                    raise error.Abort(msg)
+        return oldvalidator(tr)
+    tr.validator = validator
+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:
+        exchange._pushb2ctxcheckheads(pushop, bundler)
+    return orig(pushop, bundler)
+
+def wireprotocaps(orig, repo, proto):
+    caps = orig(repo, proto)
+    if repo.peer().capable('topics'):
+        caps.append('topics')
+    return caps
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/revset.py	Tue Mar 22 00:19:14 2016 -0700
@@ -0,0 +1,66 @@
+from mercurial import revset
+from mercurial import util
+
+from . import constants, destination, stack
+
+try:
+    mkmatcher = revset._stringmatcher
+except AttributeError:
+    mkmatcher = util.stringmatcher
+
+
+def topicset(repo, subset, x):
+    """`topic([topic])`
+    Specified topic or all changes with any topic specified.
+
+    If `topic` starts with `re:` the remainder of the name is treated
+    as a regular expression.
+
+    TODO: make `topic(revset)` work the same as `branch(revset)`.
+    """
+    args = revset.getargs(x, 0, 1, 'topic takes one or no arguments')
+    if args:
+        # match a specific topic
+        topic = revset.getstring(args[0], 'topic() argument must be a string')
+        if topic == '.':
+            topic = repo['.'].extra().get('topic', '')
+        _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, '')))
+
+def ngtipset(repo, subset, x):
+    """`ngtip([branch])`
+
+    The untopiced tip.
+
+    Name is horrible so that people change it.
+    """
+    args = revset.getargs(x, 1, 1, 'topic takes one')
+    # match a specific topic
+    branch = revset.getstring(args[0], 'ngtip() argument must be a string')
+    if branch == '.':
+        branch = repo['.'].branch()
+    return subset & destination.ngtip(repo, branch)
+
+def stackset(repo, subset, x):
+    """`stack()`
+    All relevant changes in the current topic,
+
+    This is roughly equivalent to 'topic(.) - obsolete' with a sorting moving
+    unstable changeset after there future parent (as if evolve where already
+    run)."""
+    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
+
+
+
+def modsetup():
+    revset.symbols.update({'topic': topicset})
+    revset.symbols.update({'ngtip': ngtipset})
+    revset.symbols.update({'stack': stackset})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/stack.py	Tue Mar 22 00:19:14 2016 -0700
@@ -0,0 +1,157 @@
+# stack.py - code related to stack workflow
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+import collections
+from mercurial.i18n import _
+from mercurial import error
+from mercurial import extensions
+from mercurial import obsolete
+
+def getstack(repo, topic):
+    # XXX need sorting
+    trevs = repo.revs("topic(%s) - obsolete()", topic)
+    return _orderrevs(repo, trevs)
+
+def showstack(ui, repo, topic, opts):
+    if not topic:
+        topic = repo.currenttopic
+    if not topic:
+        raise error.Abort(_('no active topic to list'))
+    fm = ui.formatter('topicstack', opts)
+    prev = None
+    for idx, r in enumerate(getstack(repo, topic)):
+        ctx =  repo[r]
+        p1 = ctx.p1()
+        if p1.obsolete():
+            p1 = repo[_singlesuccessor(repo, p1)]
+        if p1.rev() != prev:
+            # display the parent of the chanset
+            state = 'base'
+            symbol= '_'
+            fm.startitem()
+            fm.plain('  ') # XXX 2 is the right padding by chance
+            fm.write('topic.stack.state', '%s', symbol,
+                     label='topic.stack.state topic.stack.state.%s' % state)
+            fm.plain(' ')
+            fm.write('topic.stack.desc', '%s',
+                     p1.description().splitlines()[0],
+                     label='topic.stack.desc topic.stack.desc.%s' % state)
+            fm.plain('\n')
+            fm.end()
+        # super crude initial version
+        symbol = ':'
+        state = 'clean'
+        if repo.revs('%d and parents()', r):
+            symbol = '@'
+            state = 'current'
+        if repo.revs('%d and unstable()', r):
+            symbol = '$'
+            state = 'unstable'
+        fm.startitem()
+        fm.write('topic.stack.index', 't%d', idx,
+                  label='topic.stack.index topic.stack.index.%s' % state)
+        fm.write('topic.stack.state.symbol', '%s', symbol,
+                 label='topic.stack.state topic.stack.state.%s' % state)
+        fm.plain(' ')
+        fm.write('topic.stack.desc', '%s', ctx.description().splitlines()[0],
+                 label='topic.stack.desc topic.stack.desc.%s' % state)
+        fm.condwrite(state != 'clean', 'topic.stack.state', ' (%s)', state,
+                    label='topic.stack.state topic.stack.state.%s' % state)
+        fm.plain('\n')
+        fm.end()
+        prev = r
+
+# Copied from evolve 081605c2e9b6
+
+def _orderrevs(repo, revs):
+    """Compute an ordering to solve instability for the given revs
+
+    revs is a list of unstable revisions.
+
+    Returns the same revisions ordered to solve their instability from the
+    bottom to the top of the stack that the stabilization process will produce
+    eventually.
+
+    This ensures the minimal number of stabilizations, as we can stabilize each
+    revision on its final stabilized destination.
+    """
+    # Step 1: Build the dependency graph
+    dependencies, rdependencies = builddependencies(repo, revs)
+    # Step 2: Build the ordering
+    # Remove the revisions with no dependency(A) and add them to the ordering.
+    # Removing these revisions leads to new revisions with no dependency (the
+    # one depending on A) that we can remove from the dependency graph and add
+    # to the ordering. We progress in a similar fashion until the ordering is
+    # built
+    solvablerevs = [r for r in sorted(dependencies.keys())
+                    if not dependencies[r]]
+    ordering = []
+    while solvablerevs:
+        rev = solvablerevs.pop()
+        for dependent in rdependencies[rev]:
+            dependencies[dependent].remove(rev)
+            if not dependencies[dependent]:
+                solvablerevs.append(dependent)
+        del dependencies[rev]
+        ordering.append(rev)
+
+    ordering.extend(sorted(dependencies))
+    return ordering
+
+def builddependencies(repo, revs):
+    """returns dependency graphs giving an order to solve instability of revs
+    (see _orderrevs for more information on usage)"""
+
+    # For each troubled revision we keep track of what instability if any should
+    # be resolved in order to resolve it. Example:
+    # dependencies = {3: [6], 6:[]}
+    # Means that: 6 has no dependency, 3 depends on 6 to be solved
+    dependencies = {}
+    # rdependencies is the inverted dict of dependencies
+    rdependencies = collections.defaultdict(set)
+
+    for r in revs:
+        dependencies[r] = set()
+        for p in repo[r].parents():
+            try:
+                succ = _singlesuccessor(repo, p)
+            except MultipleSuccessorsError as exc:
+                dependencies[r] = exc.successorssets
+                continue
+            if succ in revs:
+                dependencies[r].add(succ)
+                rdependencies[succ].add(r)
+    return dependencies, rdependencies
+
+def _singlesuccessor(repo, p):
+    """returns p (as rev) if not obsolete or its unique latest successors
+
+    fail if there are no such successor"""
+
+    if not p.obsolete():
+        return p.rev()
+    obs = repo[p]
+    ui = repo.ui
+    newer = obsolete.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())
+    if len(newer) > 1 or len(newer[0]) > 1:
+        raise MultipleSuccessorsError(newer)
+
+    return repo[newer[0][0]].rev()
+
+class MultipleSuccessorsError(RuntimeError):
+    """Exception raised by _singlesuccessor when multiple successor sets exists
+
+    The object contains the list of successorssets in its 'successorssets'
+    attribute to call to easily recover.
+    """
+
+    def __init__(self, successorssets):
+        self.successorssets = successorssets
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/topicmap.py	Tue Mar 22 00:19:14 2016 -0700
@@ -0,0 +1,200 @@
+from mercurial import branchmap
+from mercurial import encoding
+from mercurial import error
+from mercurial import scmutil
+from mercurial import util
+from mercurial.node import hex, bin, nullid
+
+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
+
+oldbranchcache = branchmap.branchcache
+
+def _phaseshash(repo, maxrev):
+    revs = set()
+    cl = repo.changelog
+    fr = cl.filteredrevs
+    nm = cl.nodemap
+    for roots in repo._phasecache.phaseroots[1:]:
+        for n in roots:
+            r = nm.get(n)
+            if r not in fr and r < maxrev:
+                revs.add(r)
+    key = nullid
+    revs = sorted(revs)
+    if revs:
+        s = util.sha1()
+        for rev in revs:
+            s.update('%s;' % rev)
+        key = s.digest()
+    return key
+
+class topiccache(oldbranchcache):
+
+    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
+
+    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.phaseshash = self.phaseshash
+        return new
+
+    def branchtip(self, branch, topic=''):
+        '''Return the tipmost open head on branch head, otherwise return the
+        tipmost closed head on branch.
+        Raise KeyError for unknown branch.'''
+        if topic:
+            branch = '%s:%s' % (branch, topic)
+        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)
+
+    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
+            return False
+        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
+
+    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
+        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
+            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 = branchcache(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
--- a/setup.py	Wed Mar 16 12:14:20 2016 -0700
+++ b/setup.py	Tue Mar 22 00:19:14 2016 -0700
@@ -14,9 +14,9 @@
     maintainer_email='augie@google.com',
     url='http://bitbucket.org/durin42/hg-topics/',
     description='Experimental tinkering with workflow ideas for topic branches.',
-    long_description=open('README').read(),
+    long_description=open('README.md').read(),
     keywords='hg mercurial',
     license='GPLv2+',
-    py_modules=['src'],
+    packages=['hgext3rd.topic'],
     install_requires=requires,
 )
--- a/src/topic/__init__.py	Wed Mar 16 12:14:20 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,343 +0,0 @@
-# __init__.py - topic extension
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-"""support for topic branches
-
-Topic branches are lightweight branches which
-disappear when changes are finalized.
-
-This is sort of similar to a bookmark, but it applies to a whole
-series instead of a single revision.
-"""
-import functools
-import contextlib
-
-from mercurial.i18n import _
-from mercurial import branchmap
-from mercurial import bundle2
-from mercurial import changegroup
-from mercurial import cmdutil
-from mercurial import commands
-from mercurial import context
-from mercurial import discovery as discoverymod
-from mercurial import error
-from mercurial import exchange
-from mercurial import extensions
-from mercurial import localrepo
-from mercurial import lock
-from mercurial import merge
-from mercurial import namespaces
-from mercurial import node
-from mercurial import obsolete
-from mercurial import patch
-from mercurial import phases
-from mercurial import util
-from mercurial import wireproto
-
-from . import constants
-from . import revset as topicrevset
-from . import destination
-from . import stack
-from . import topicmap
-from . import discovery
-
-cmdtable = {}
-command = cmdutil.command(cmdtable)
-
-testedwith = '3.7'
-
-def _contexttopic(self):
-    return self.extra().get(constants.extrakey, '')
-context.basectx.topic = _contexttopic
-
-def _namemap(repo, name):
-    return [ctx.node() for ctx in
-            repo.set('not public() and extra(topic, %s)', name)]
-
-def _nodemap(repo, node):
-    ctx = repo[node]
-    t = ctx.topic()
-    if t and ctx.phase() > phases.public:
-        return [t]
-    return []
-
-def uisetup(ui):
-    destination.setupdest()
-
-@contextlib.contextmanager
-def usetopicmap(repo):
-    """use awful monkey patching to update the topic cache"""
-    oldbranchcache = branchmap.branchcache
-    oldfilename = branchmap._filename
-    oldread = branchmap.read
-    oldcaches =  getattr(repo, '_branchcaches', {})
-    try:
-        branchmap.branchcache = topicmap.topiccache
-        branchmap._filename = topicmap._filename
-        branchmap.read = topicmap.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):
-    with usetopicmap(repo):
-        return orig(repo, *args, **kwargs)
-
-def reposetup(ui, repo):
-    orig = repo.__class__
-    if not isinstance(repo, localrepo.localrepository):
-        return # this can be a peer in the ssh case (puzzling)
-    class topicrepo(repo.__class__):
-        def commit(self, *args, **kwargs):
-            backup = self.ui.backupconfig('ui', 'allowemptycommit')
-            try:
-                if repo.currenttopic != repo['.'].topic():
-                    # bypass the core "nothing changed" logic
-                    self.ui.setconfig('ui', 'allowemptycommit', True)
-                return orig.commit(self, *args, **kwargs)
-            finally:
-                self.ui.restoreconfig(backup)
-
-        def commitctx(self, ctx, error=None):
-            if isinstance(ctx, context.workingcommitctx):
-                current = self.currenttopic
-                if current:
-                    ctx.extra()[constants.extrakey] = current
-            if (isinstance(ctx, context.memctx) and
-                ctx.extra().get('amend_source') and
-                ctx.topic() and
-                not self.currenttopic):
-                # we are amending and need to remove a topic
-                del ctx.extra()[constants.extrakey]
-            with usetopicmap(self):
-                return orig.commitctx(self, ctx, error=error)
-
-        @property
-        def topics(self):
-            topics = set(['', self.currenttopic])
-            for c in self.set('not public()'):
-                topics.add(c.topic())
-            topics.remove('')
-            return topics
-
-        @property
-        def currenttopic(self):
-            return self.vfs.tryread('topic')
-
-        def branchmap(self, topic=True):
-            if not topic:
-                super(topicrepo, self).branchmap()
-            with usetopicmap(self):
-                branchmap.updatecache(self)
-            return self._topiccaches[self.filtername]
-
-        def destroyed(self, *args, **kwargs):
-            with usetopicmap(self):
-                return super(topicrepo, self).destroyed(*args, **kwargs)
-
-        def invalidatecaches(self):
-            super(topicrepo, self).invalidatecaches()
-            if '_topiccaches' in vars(self.unfiltered()):
-                self.unfiltered()._topiccaches.clear()
-
-        def peer(self):
-            peer = super(topicrepo, self).peer()
-            if getattr(peer, '_repo', None) is not None: # localpeer
-                class topicpeer(peer.__class__):
-                    def branchmap(self):
-                        usetopic = not self._repo.publishing()
-                        return self._repo.branchmap(topic=usetopic)
-                peer.__class__ = topicpeer
-            return peer
-
-
-    repo.__class__ = topicrepo
-    if util.safehasattr(repo, 'names'):
-        repo.names.addnamespace(namespaces.namespace(
-            'topics', 'topic', namemap=_namemap, nodemap=_nodemap,
-            listnames=lambda repo: repo.topics))
-
-@command('topics [TOPIC]', [
-    ('', 'clear', False, 'clear active topic if any'),
-    ('', 'change', '', 'revset of existing revisions to change topic'),
-    ('l', 'list', False, 'show the stack of changeset in the topic'),
-])
-def topics(ui, repo, topic='', clear=False, change=None, list=False):
-    """View current topic, set current topic, or see all topics."""
-    if list:
-        if clear or change:
-            raise error.Abort(_("cannot use --clear or --change with --list"))
-        return stack.showstack(ui, repo, topic)
-
-    if change:
-        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('changing topic requires a topic name or --clear')
-        if any(not c.mutable() for c in repo.set('%r and public()', change)):
-            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)
-    if clear:
-        if repo.vfs.exists('topic'):
-            repo.vfs.unlink('topic')
-        return
-    if topic:
-        with repo.vfs.open('topic', 'w') as f:
-            f.write(topic)
-        return
-    current = repo.currenttopic
-    for t in sorted(repo.topics):
-        marker = '*' if t == current else ' '
-        ui.write(' %s %s\n' % (marker, t))
-
-def summaryhook(ui, repo):
-    t = repo.currenttopic
-    if not t:
-        return
-    # i18n: column positioning for "hg summary"
-    ui.write(_("topic:  %s\n") % t)
-
-def commitwrap(orig, ui, repo, *args, **opts):
-    if opts.get('topic'):
-        t = opts['topic']
-        with repo.vfs.open('topic', 'w') as f:
-            f.write(t)
-    return orig(ui, repo, *args, **opts)
-
-def committextwrap(orig, repo, ctx, subs, extramsg):
-    ret = orig(repo, ctx, subs, extramsg)
-    t = repo.currenttopic
-    if t:
-        ret = ret.replace("\nHG: branch",
-                          "\nHG: topic '%s'\nHG: branch" % t)
-    return ret
-
-def mergeupdatewrap(orig, repo, node, branchmerge, force, *args, **kwargs):
-    partial = bool(len(args)) or 'matcher' in kwargs
-    wlock = repo.wlock()
-    try:
-        ret = orig(repo, node, branchmerge, force, *args, **kwargs)
-        if not partial and not branchmerge:
-            ot = repo.currenttopic
-            t = ''
-            pctx = repo[node]
-            if pctx.phase() > phases.public:
-                t = pctx.topic()
-            with repo.vfs.open('topic', 'w') as f:
-                f.write(t)
-            if t and t != ot:
-                repo.ui.status(_("switching to topic %s\n") % t)
-        return ret
-    finally:
-        wlock.release()
-
-def _fixrebase(loaded):
-    if not loaded:
-        return
-
-    def savetopic(ctx, extra):
-        if ctx.topic():
-            extra[constants.extrakey] = ctx.topic()
-
-    def newmakeextrafn(orig, copiers):
-        return orig(copiers + [savetopic])
-
-    rebase = extensions.find("rebase")
-    extensions.wrapfunction(rebase, '_makeextrafn', newmakeextrafn)
-
-def _exporttopic(seq, ctx):
-    topic = ctx.topic()
-    if topic:
-        return 'EXP-Topic %s'  % topic
-    return None
-
-def _importtopic(repo, patchdata, extra, opts):
-    if 'topic' in patchdata:
-        extra['topic'] = patchdata['topic']
-
-extensions.afterloaded('rebase', _fixrebase)
-
-entry = extensions.wrapcommand(commands.table, 'commit', commitwrap)
-entry[1].append(('t', 'topic', '',
-                 _("use specified topic"), _('TOPIC')))
-
-extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap)
-extensions.wrapfunction(merge, 'update', mergeupdatewrap)
-extensions.wrapfunction(discoverymod, '_headssummary', discovery._headssummary)
-extensions.wrapfunction(wireproto, 'branchmap', discovery.wireprotobranchmap)
-extensions.wrapfunction(bundle2, 'handlecheckheads', discovery.handlecheckheads)
-bundle2.handlecheckheads.params = frozenset() # we need a proper wrape b2 part stuff
-bundle2.parthandlermapping['check:heads'] = bundle2.handlecheckheads
-extensions.wrapfunction(exchange, '_pushb2phases', discovery._pushb2phases)
-extensions.wrapfunction(changegroup.cg1unpacker, 'apply', cgapply)
-exchange.b2partsgenmapping['phase'] = exchange._pushb2phases
-topicrevset.modsetup()
-cmdutil.summaryhooks.add('topic', summaryhook)
-
-if util.safehasattr(cmdutil, 'extraexport'):
-    cmdutil.extraexport.append('topic')
-    cmdutil.extraexportmap['topic'] = _exporttopic
-if util.safehasattr(cmdutil, 'extrapreimport'):
-    cmdutil.extrapreimport.append('topic')
-    cmdutil.extrapreimportmap['topic'] = _importtopic
-if util.safehasattr(patch, 'patchheadermap'):
-    patch.patchheadermap.append(('EXP-Topic', 'topic'))
--- a/src/topic/constants.py	Wed Mar 16 12:14:20 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-extrakey = 'topic'
--- a/src/topic/destination.py	Wed Mar 16 12:14:20 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +0,0 @@
-from mercurial import error
-from mercurial import util
-from mercurial import destutil
-from mercurial import extensions
-from mercurial import bookmarks
-from mercurial.i18n import _
-
-def _destmergebranch(orig, repo, action='merge', sourceset=None, onheadcheck=True):
-    p1 = repo['.']
-    top = p1.topic()
-    if top:
-        heads = repo.revs('heads(topic(.)::topic(.))')
-        if p1.rev() not in heads:
-            raise error.Abort(_("not at topic head, update or explicit"))
-        elif 1 == len(heads):
-            # should look at all branch involved but... later
-            bhead = ngtip(repo, p1.branch(), all=True)
-            if not bhead:
-                raise error.Abort(_("nothing to merge"))
-            elif 1 == len(bhead):
-                return bhead.first()
-            else:
-                raise error.Abort(_("branch '%s' has %d heads - "
-                                   "please merge with an explicit rev")
-                                 % (p1.branch(), len(bhead)),
-                                 hint=_("run 'hg heads .' to see heads"))
-        elif 2 == len(heads):
-            heads = [r for r in heads if r != p1.rev()]
-            # XXX: bla bla bla bla bla
-            if 1 < len(heads):
-                raise error.Abort(_('working directory not at a head revision'),
-                                 hint=_("use 'hg update' or merge with an "
-                                        "explicit revision"))
-            return heads[0]
-        elif 2 < len(heads):
-            raise error.Abort(_("topic '%s' has %d heads - "
-                                "please merge with an explicit rev")
-                              % (top, len(heads)))
-        else:
-            assert False # that's impossible
-    if getattr(orig, 'func_default', ()): # version above hg-3.7
-        return orig(repo, action, sourceset, onheadcheck)
-    else:
-        return orig(repo)
-
-def _destupdatetopic(repo, clean, check):
-    """decide on an update destination from current topic"""
-    movemark = node = None
-    topic = repo.currenttopic
-    revs = repo.revs('.::topic("%s")' % topic)
-    if not revs:
-        return None, None, None
-    node = revs.last()
-    if bookmarks.isactivewdirparent(repo):
-        movemark = repo['.'].node()
-    return node, movemark, None
-
-def setupdest():
-    if util.safehasattr(destutil, '_destmergebranch'):
-        extensions.wrapfunction(destutil, '_destmergebranch', _destmergebranch)
-    rebase = extensions.find('rebase')
-    if (util.safehasattr(rebase, '_destrebase')
-            # logic not shared with merge yet < hg-3.8
-            and not util.safehasattr(rebase, '_definesets')):
-        extensions.wrapfunction(rebase, '_destrebase', _destmergebranch)
-    if util.safehasattr(destutil, 'destupdatesteps'):
-        bridx = destutil.destupdatesteps.index('branch')
-        destutil.destupdatesteps.insert(bridx, 'topic')
-        destutil.destupdatestepmap['topic'] = _destupdatetopic
-
-def ngtip(repo, branch, all=False):
-    """tip new generation"""
-    ## search for untopiced heads of branch
-    # could be heads((::branch(x) - topic()))
-    # but that is expensive
-    #
-    # we should write plain code instead
-    subquery = '''heads(
-                    parents(
-                       ancestor(
-                         (head() and branch(%s)
-                         or (topic() and branch(%s)))))
-                   ::(head() and branch(%s))
-                   - topic())'''
-    if not all:
-        subquery = 'max(%s)' % subquery
-    return repo.revs(subquery, branch, branch, branch)
--- a/src/topic/discovery.py	Wed Mar 16 12:14:20 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-import weakref
-from mercurial import branchmap
-from mercurial import error
-from mercurial import exchange
-from mercurial.i18n import _
-from . import topicmap
-
-def _headssummary(orig, repo, remote, outgoing):
-    publishing = ('phases' not in remote.listkeys('namespaces')
-                  or bool(remote.listkeys('phases').get('publishing', False)))
-    if publishing:
-        return orig(repo, remote, outgoing)
-    oldgetitem = repo.__getitem__
-    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
-                def branch():
-                    branch = oldbranch()
-                    topic = ctx.topic()
-                    if topic:
-                        branch = "%s:%s" % (branch, topic)
-                    return branch
-                ctx.branch = branch
-                return ctx
-        repo.__class__ = repocls
-        branchmap.branchcache = topicmap.topiccache
-        branchmap._filename = topicmap._filename
-        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:]
-        return summary
-    finally:
-        repo.__class__ = oldrepo
-        branchmap.branchcache = oldbranchcache
-        branchmap._filename = oldfilename
-
-def wireprotobranchmap(orig, repo, proto):
-    oldrepo = repo.__class__
-    try:
-        class repocls(repo.__class__):
-            def branchmap(self):
-                usetopic = not self.publishing()
-                return super(repocls, self).branchmap(topic=usetopic)
-        repo.__class__ = repocls
-        return orig(repo, proto)
-    finally:
-        repo.__class__ = oldrepo
-
-
-# Discovery have deficiency around phases, branch can get new heads with pure
-# phases change. This happened with a changeset was allowed to be pushed
-# because it had a topic, but it later become public and create a new branch
-# head.
-#
-# Handle this by doing an extra check for new head creation server side
-def _nbheads(repo):
-    data = {}
-    for b in repo.branchmap().iterbranches():
-        if ':' in b[0]:
-            continue
-        data[b[0]] = len(b[1])
-    return data
-
-def handlecheckheads(orig, op, inpart):
-    orig(op, inpart)
-    if op.repo.publishing():
-        return
-    tr = op.gettransaction()
-    if tr.hookargs['source'] not in ('push', 'serve'): # not a push
-        return
-    tr._prepushheads = _nbheads(op.repo)
-    reporef = weakref.ref(op.repo)
-    oldvalidator = tr.validator
-    def validator(tr):
-        repo = reporef()
-        if repo is not None:
-            repo.invalidatecaches()
-            finalheads = _nbheads(repo)
-            for branch, oldnb in tr._prepushheads.iteritems():
-                newnb = finalheads.pop(branch, 0)
-                if oldnb < newnb:
-                    msg = _('push create a new head on branch "%s"' % branch)
-                    raise error.Abort(msg)
-            for branch, newnb in finalheads.iteritems():
-                if 1 < newnb:
-                    msg = _('push create more than 1 head on new branch "%s"' % branch)
-                    raise error.Abort(msg)
-        return oldvalidator(tr)
-    tr.validator = validator
-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:
-        exchange._pushb2ctxcheckheads(pushop, bundler)
-    return orig(pushop, bundler)
-
-
-
--- a/src/topic/revset.py	Wed Mar 16 12:14:20 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-from mercurial import revset
-from mercurial import util
-
-from . import constants, destination
-
-try:
-    mkmatcher = revset._stringmatcher
-except AttributeError:
-    mkmatcher = util.stringmatcher
-
-
-def topicset(repo, subset, x):
-    """`topic([topic])`
-    Specified topic or all changes with any topic specified.
-
-    If `topic` starts with `re:` the remainder of the name is treated
-    as a regular expression.
-
-    TODO: make `topic(revset)` work the same as `branch(revset)`.
-    """
-    args = revset.getargs(x, 0, 1, 'topic takes one or no arguments')
-    if args:
-        # match a specific topic
-        topic = revset.getstring(args[0], 'topic() argument must be a string')
-        if topic == '.':
-            topic = repo['.'].extra().get('topic', '')
-        _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, '')))
-
-def ngtipset(repo, subset, x):
-    """`ngtip([branch])`
-
-    The untopiced tip.
-
-    Name is horrible so that people change it.
-    """
-    args = revset.getargs(x, 1, 1, 'topic takes one')
-    # match a specific topic
-    branch = revset.getstring(args[0], 'ngtip() argument must be a string')
-    if branch == '.':
-        branch = repo['.'].branch()
-    return subset & destination.ngtip(repo, branch)
-
-def modsetup():
-    revset.symbols.update({'topic': topicset})
-    revset.symbols.update({'ngtip': ngtipset})
--- a/src/topic/stack.py	Wed Mar 16 12:14:20 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-# stack.py - code related to stack workflow
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-import collections
-from mercurial.i18n import _
-from mercurial import error
-from mercurial import extensions
-from mercurial import obsolete
-
-def _getstack(repo, topic):
-    # XXX need sorting
-    trevs = repo.revs("topic(%s) - obsolete()", topic)
-    return _orderrevs(repo, trevs)
-
-def showstack(ui, repo, topic):
-    if not topic:
-        topic = repo.currenttopic
-    if not topic:
-        raise error.Abort(_('no active topic to list'))
-    for idx, r in enumerate(_getstack(repo, topic)):
-        # super crude initial version
-        l = "%d: %s\n" % (idx, repo[r].description().splitlines()[0])
-        ui.write(l)
-
-# Copied from evolve 081605c2e9b6
-
-def _orderrevs(repo, revs):
-    """Compute an ordering to solve instability for the given revs
-
-    revs is a list of unstable revisions.
-
-    Returns the same revisions ordered to solve their instability from the
-    bottom to the top of the stack that the stabilization process will produce
-    eventually.
-
-    This ensures the minimal number of stabilizations, as we can stabilize each
-    revision on its final stabilized destination.
-    """
-    # Step 1: Build the dependency graph
-    dependencies, rdependencies = builddependencies(repo, revs)
-    # Step 2: Build the ordering
-    # Remove the revisions with no dependency(A) and add them to the ordering.
-    # Removing these revisions leads to new revisions with no dependency (the
-    # one depending on A) that we can remove from the dependency graph and add
-    # to the ordering. We progress in a similar fashion until the ordering is
-    # built
-    solvablerevs = collections.deque([r for r in sorted(dependencies.keys())
-                                      if not dependencies[r]])
-    ordering = []
-    while solvablerevs:
-        rev = solvablerevs.popleft()
-        for dependent in rdependencies[rev]:
-            dependencies[dependent].remove(rev)
-            if not dependencies[dependent]:
-                solvablerevs.append(dependent)
-        del dependencies[rev]
-        ordering.append(rev)
-
-    ordering.extend(sorted(dependencies))
-    return ordering
-
-def builddependencies(repo, revs):
-    """returns dependency graphs giving an order to solve instability of revs
-    (see _orderrevs for more information on usage)"""
-
-    # For each troubled revision we keep track of what instability if any should
-    # be resolved in order to resolve it. Example:
-    # dependencies = {3: [6], 6:[]}
-    # Means that: 6 has no dependency, 3 depends on 6 to be solved
-    dependencies = {}
-    # rdependencies is the inverted dict of dependencies
-    rdependencies = collections.defaultdict(set)
-
-    for r in revs:
-        dependencies[r] = set()
-        for p in repo[r].parents():
-            try:
-                succ = _singlesuccessor(repo, p)
-            except MultipleSuccessorsError as exc:
-                dependencies[r] = exc.successorssets
-                continue
-            if succ in revs:
-                dependencies[r].add(succ)
-                rdependencies[succ].add(r)
-    return dependencies, rdependencies
-
-def _singlesuccessor(repo, p):
-    """returns p (as rev) if not obsolete or its unique latest successors
-
-    fail if there are no such successor"""
-
-    if not p.obsolete():
-        return p.rev()
-    obs = repo[p]
-    ui = repo.ui
-    newer = obsolete.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())
-    if len(newer) > 1 or len(newer[0]) > 1:
-        raise MultipleSuccessorsError(newer)
-
-    return repo[newer[0][0]].rev()
-
-class MultipleSuccessorsError(RuntimeError):
-    """Exception raised by _singlesuccessor when multiple successor sets exists
-
-    The object contains the list of successorssets in its 'successorssets'
-    attribute to call to easily recover.
-    """
-
-    def __init__(self, successorssets):
-        self.successorssets = successorssets
--- a/src/topic/topicmap.py	Wed Mar 16 12:14:20 2016 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,200 +0,0 @@
-from mercurial import branchmap
-from mercurial import encoding
-from mercurial import error
-from mercurial import scmutil
-from mercurial import util
-from mercurial.node import hex, bin, nullid
-
-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
-
-oldbranchcache = branchmap.branchcache
-
-def _phaseshash(repo, maxrev):
-    revs = set()
-    cl = repo.changelog
-    fr = cl.filteredrevs
-    nm = cl.nodemap
-    for roots in repo._phasecache.phaseroots[1:]:
-        for n in roots:
-            r = nm.get(n)
-            if r not in fr and r < maxrev:
-                revs.add(r)
-    key = nullid
-    revs = sorted(revs)
-    if revs:
-        s = util.sha1()
-        for rev in revs:
-            s.update('%s;' % rev)
-        key = s.digest()
-    return key
-
-class topiccache(oldbranchcache):
-
-    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
-
-    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.phaseshash = self.phaseshash
-        return new
-
-    def branchtip(self, branch, topic=''):
-        '''Return the tipmost open head on branch head, otherwise return the
-        tipmost closed head on branch.
-        Raise KeyError for unknown branch.'''
-        if topic:
-            branch = '%s:%s' % (branch, topic)
-        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)
-
-    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
-            return False
-        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
-
-    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
-        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
-            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 = branchcache(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
--- a/tests/test-topic-dest.t	Wed Mar 16 12:14:20 2016 -0700
+++ b/tests/test-topic-dest.t	Tue Mar 22 00:19:14 2016 -0700
@@ -5,6 +5,7 @@
   $ cat <<EOF >> .hg/hgrc
   > [extensions]
   > rebase=
+  > histedit=
   > [phases]
   > publish=false
   > EOF
@@ -479,3 +480,17 @@
   |
   o  0 () c_alpha
   
+
+Default destination for histedit
+================================
+
+By default hisetdit should edit with the current topic only
+(even when based on other draft
+
+  $ hg phase 'desc(c_zeta)'
+  11: draft
+  $ HGEDITOR=cat hg histedit | grep pick
+  pick e44744d9ad73 12 babar
+  pick 38eea8439aee 14 arthur
+  pick 411315c48bdc 15 pompadour
+  #  p, pick = use commit
--- a/tests/test-topic-push.t	Wed Mar 16 12:14:20 2016 -0700
+++ b/tests/test-topic-push.t	Tue Mar 22 00:19:14 2016 -0700
@@ -200,8 +200,34 @@
   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
--- a/tests/test-topic-stack.t	Wed Mar 16 12:14:20 2016 -0700
+++ b/tests/test-topic-stack.t	Tue Mar 22 00:19:14 2016 -0700
@@ -57,10 +57,11 @@
   $ hg topic
    * foo
   $ hg topic --list
-  0: c_c
-  1: c_d
-  2: c_e
-  3: c_f
+    _ c_b
+  t0: c_c
+  t1: c_d
+  t2: c_e
+  t3@ c_f (current)
 
 error case, nothing to list
 
@@ -69,12 +70,23 @@
   abort: no active topic to list
   [255]
 
+Test "t#" reference
+-------------------
+
+
+  $ hg up t1
+  abort: cannot resolve "t1": no active topic
+  [255]
+  $ hg topic foo
+  $ hg up t42
+  abort: cannot resolve "t42": topic "foo" has only 4 changesets
+  [255]
+  $ hg up t1
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
 Case with some of the topic unstable
 ------------------------------------
 
-  $ hg up 'desc(c_d)'
-  switching to topic foo
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ echo bbb > ddd
   $ hg commit --amend
   $ hg log -G
@@ -93,7 +105,123 @@
   o  0 default {} draft c_a
   
   $ hg topic --list
-  0: c_c
-  1: c_d
-  2: c_e
-  3: c_f
+    _ c_b
+  t0: c_c
+  t1@ c_d (current)
+  t2$ c_e (unstable)
+  t3$ c_f (unstable)
+
+Also test the revset:
+
+  $ hg log -r 'stack()'
+  2 default {foo} draft c_c
+  7 default {foo} draft c_d
+  4 default {foo} draft c_e
+  5 default {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:91fa8808d101 "c_e"
+  rebasing 5:4ec5094907b7 "c_f"
+  $ hg log -G
+  o  9 default {foo} draft c_f
+  |
+  o  8 default {foo} draft c_e
+  |
+  @  7 default {foo} draft c_d
+  |
+  o  2 default {foo} draft c_c
+  |
+  o  1 default {} draft c_b
+  |
+  o  0 default {} draft 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
+  created new head
+  $ hg log -G
+  @  11 default {foo} draft c_h
+  |
+  o  10 default {foo} draft c_g
+  |
+  | o  9 default {foo} draft c_f
+  | |
+  | o  8 default {foo} draft c_e
+  |/
+  o  7 default {foo} draft c_d
+  |
+  o  2 default {foo} draft c_c
+  |
+  o  1 default {} draft c_b
+  |
+  o  0 default {} draft c_a
+  
+
+Test output
+
+  $ hg top -l
+    _ c_b
+  t0: c_c
+  t1: c_d
+  t2: c_g
+  t3@ c_h (current)
+    _ c_d
+  t4: c_e
+  t5: c_f
+
+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:11286b4fcb3d "c_g"
+  rebasing 11:3ad57527186d "c_h"
+  $ hg log -G
+  o  15 default {foo} draft c_h
+  |
+  o  14 default {foo} draft c_g
+  |
+  @  13 default {foo} draft c_D
+  |
+  | o  9 default {foo} draft c_f
+  | |
+  | o  8 default {foo} draft c_e
+  | |
+  | x  7 default {foo} draft c_d
+  |/
+  o  2 default {foo} draft c_c
+  |
+  o  1 default {} draft c_b
+  |
+  o  0 default {} draft c_a
+  
+
+  $ hg topic --list
+    _ c_b
+  t0: c_c
+  t1@ c_D (current)
+  t2: c_g
+  t3: c_h
+    _ c_D
+  t4$ c_e (unstable)
+  t5$ c_f (unstable)
--- a/tests/testlib	Wed Mar 16 12:14:20 2016 -0700
+++ b/tests/testlib	Tue Mar 22 00:19:14 2016 -0700
@@ -12,4 +12,4 @@
 [extensions]
 rebase=
 EOF
-echo "topic=$(echo $(dirname $TESTDIR))/src/topic" >> $HGRCPATH
+echo "topic=$(echo $(dirname $TESTDIR))/hgext3rd/topic" >> $HGRCPATH