changeset 3167:f089741039e5 mercurial-4.2

test-compat: merge mercurial-4.3 into mercurial-4.2
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Wed, 01 Nov 2017 23:58:27 +0100
parents e1a230cc4527 (diff) ef04514bca47 (current diff)
children 78eb1c1d987b a7050ab9d8c1
files tests/test-discovery-obshashrange.t tests/test-evolve-obshistory.t tests/test-evolve-serveronly-bundle2.t tests/test-evolve-templates.t tests/test-obsolete.t tests/test-sharing.t tests/test-topic-debugcb.t tests/test-topic-flow-single-head.t tests/test-uncommit-interactive.t
diffstat 31 files changed, 803 insertions(+), 1106 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGELOG	Mon Oct 23 15:50:19 2017 +0200
+++ b/CHANGELOG	Wed Nov 01 23:58:27 2017 +0100
@@ -1,6 +1,23 @@
 Changelog
 =========
 
+7.0.0 - in progress
+-------------------
+
+  * drop compatibility with Mercurial 3.8, 3.9 and 4.0,
+  * drop support for old and deprecated method to exchange obsmarkers,
+  * forbid usage of the old pushbey based protocol to exchange obsmarkers,
+  * evolve: rename '--contentdivergent' flag to '--content-divergent'
+  * evolve: rename '--phasedivergent' flag to '--phase-divergent'
+
+topic
+
+  * add an experimental flag to enforce one head per name policy,
+    (off by default, see 'hg help -e topic' for details)
+  * add an experimental flag to have changesets without topic published on push,
+    (off by default, see 'hg help -e topic' for details)
+  * add a '--publish' flag to `hg push` (4.4+ only),
+
 6.8.0 -- 2017-10-23
 -------------------
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/merge-test-compat.sh	Wed Nov 01 23:58:27 2017 +0100
@@ -0,0 +1,15 @@
+#!/bin/bash
+set -euox pipefail
+
+unset GREP_OPTIONS
+NOTOPIC="--config experimental.topic-mode=ignore"
+
+compatbranches=`hg branches --quiet | grep 'mercurial-' | grep -v ':' | sort -n --reverse`
+prev='stable'
+for branch in $compatbranches; do
+    hg up $branch
+    hg merge $prev
+    hg commit -m "test-compat: merge $prev into $branch"
+    prev=$branch
+done
+
--- a/hgext3rd/evolve/__init__.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/evolve/__init__.py	Wed Nov 01 23:58:27 2017 +0100
@@ -32,7 +32,7 @@
 backported to older version of Mercurial by this extension. Some older
 experimental protocol are also supported for a longer time in the extensions to
 help people transitioning. (The extensions is currently compatible down to
-Mercurial version 3.8).
+Mercurial version 4.1).
 
 New Config::
 
@@ -420,6 +420,8 @@
     if not evolveopts:
         evolveopts = ['all']
         ui.setconfig('experimental', 'evolution', evolveopts, 'evolve')
+    if obsolete.isenabled(repo, 'exchange'):
+        repo.ui.setconfig('server', 'bundle1', False)
 
 @eh.uisetup
 def _configurecmdoptions(ui):
@@ -703,7 +705,7 @@
     if reason == 'pruned':
         solvemsg = _("use 'hg evolve' to update to its parent successor")
     elif reason == 'diverged':
-        debugcommand = "hg evolve --list --contentdivergent"
+        debugcommand = "hg evolve --list --content-divergent"
         basemsg = _("%s has diverged, use '%s' to resolve the issue")
         solvemsg = basemsg % (shortnode, debugcommand)
     elif reason == 'superseed':
@@ -1058,28 +1060,28 @@
     }
 
     hintmap = {
-        'phasedivergent': _("do you want to use --phasedivergent"),
+        'phasedivergent': _("do you want to use --phase-divergent"),
         'phasedivergent+contentdivergent': _("do you want to use "
-                                             "--phasedivergent or"
-                                             " --contentdivergent"),
-        'phasedivergent+orphan': _("do you want to use --phasedivergent"
+                                             "--phase-divergent or"
+                                             " --content-divergent"),
+        'phasedivergent+orphan': _("do you want to use --phase-divergent"
                                    " or --orphan"),
-        'contentdivergent': _("do you want to use --contentdivergent"),
-        'contentdivergent+orphan': _("do you want to use --contentdivergent"
+        'contentdivergent': _("do you want to use --content-divergent"),
+        'contentdivergent+orphan': _("do you want to use --content-divergent"
                                      " or --orphan"),
         'orphan': _("do you want to use --orphan"),
         'any+phasedivergent': _("do you want to use --any (or --rev) and"
-                                " --phasedivergent"),
+                                " --phase-divergent"),
         'any+phasedivergent+contentdivergent': _("do you want to use --any"
                                                  " (or --rev) and"
-                                                 " --phasedivergent or"
-                                                 " --contentdivergent"),
+                                                 " --phase-divergent or"
+                                                 " --content-divergent"),
         'any+phasedivergent+orphan': _("do you want to use --any (or --rev)"
-                                       " and --phasedivergent or --orphan"),
+                                       " and --phase-divergent or --orphan"),
         'any+contentdivergent': _("do you want to use --any (or --rev) and"
-                                  " --contentdivergent"),
+                                  " --content-divergent"),
         'any+contentdivergent+orphan': _("do you want to use --any (or --rev)"
-                                         " and --contentdivergent or "
+                                         " and --content-divergent or "
                                          "--orphan"),
         'any+orphan': _("do you want to use --any (or --rev)"
                         "and --orphan"),
@@ -1416,9 +1418,9 @@
         'directory')),
      ('r', 'rev', [], _('solves troubles of these revisions')),
      ('', 'bumped', False, _('solves only bumped changesets')),
-     ('', 'phasedivergent', False, _('solves only phase-divergent changesets')),
+     ('', 'phase-divergent', False, _('solves only phase-divergent changesets')),
      ('', 'divergent', False, _('solves only divergent changesets')),
-     ('', 'contentdivergent', False, _('solves only content-divergent changesets')),
+     ('', 'content-divergent', False, _('solves only content-divergent changesets')),
      ('', 'unstable', False, _('solves only unstable changesets')),
      ('', 'orphan', False, _('solves only orphan changesets (default)')),
      ('a', 'all', False, _('evolve all troubled changesets related to the '
@@ -1518,20 +1520,22 @@
 
     if opts['divergent']:
         msg = ("'evolve --divergent' is deprecated, "
-               "use 'evolve --contentdivergent'")
+               "use 'evolve --content-divergent'")
         repo.ui.deprecwarn(msg, '4.4')
 
-        opts['contentdivergent'] = opts['divergent']
+        opts['content_divergent'] = opts['divergent']
 
     if opts['bumped']:
         msg = ("'evolve --bumped' is deprecated, "
-               "use 'evolve --phasedivergent'")
+               "use 'evolve --phase-divergent'")
         repo.ui.deprecwarn(msg, '4.4')
 
-        opts['phasedivergent'] = opts['bumped']
+        opts['phase_divergent'] = opts['bumped']
 
-    troublecategories = ['phasedivergent', 'contentdivergent', 'orphan']
-    specifiedcategories = [t for t in troublecategories if opts[t]]
+    troublecategories = ['phase_divergent', 'content_divergent', 'orphan']
+    specifiedcategories = [t.replace('_', '')
+                           for t in troublecategories
+                           if opts[t]]
     if listopt:
         compat.startpager(ui, 'evolve')
         listtroubles(ui, repo, specifiedcategories, **opts)
--- a/hgext3rd/evolve/hack/directaccess.py	Mon Oct 23 15:50:19 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-""" This extension provides direct access
-It is the ability to refer and access hidden sha in commands provided that you
-know their value.
-For example hg log -r xxx where xxx is a commit has should work whether xxx is
-hidden or not as we assume that the user knows what he is doing when referring
-to xxx.
-"""
-from mercurial import extensions
-from mercurial import repoview
-from mercurial import branchmap
-from mercurial import registrar
-from mercurial import revset
-from mercurial import error
-from mercurial import commands
-from mercurial import hg
-from mercurial import util
-from mercurial.i18n import _
-
-cmdtable = {}
-
-if util.safehasattr(registrar, 'command'):
-    command = registrar.command(cmdtable)
-else: # compat with hg < 4.3
-    from mercurial import cmdutil
-    command = cmdutil.command(cmdtable)
-
-if util.safehasattr(registrar, 'configitem'):
-    configtable = {}
-    configitem = registrar.configitem(configtable)
-
-    configitem('directaccess', 'loadsafter',
-               default=[],
-    )
-
-# By default, all the commands have directaccess with warnings
-# List of commands that have no directaccess and directaccess with no warning
-directaccesslevel = [
-    # Format:
-    # ('nowarning', 'evolve', 'prune'),
-    # means: no directaccess warning, for the command in evolve named prune
-    #
-    # ('error', None, 'serve'),
-    # means: no directaccess for the command in core named serve
-    #
-    # The list is ordered alphabetically by command names, starting with all
-    # the commands in core then all the commands in the extensions
-    #
-    # The general guideline is:
-    # - remove directaccess warnings for read only commands
-    # - no direct access for commands with consequences outside of the repo
-    # - leave directaccess warnings for all the other commands
-    #
-    ('nowarning', None, 'annotate'),
-    ('nowarning', None, 'archive'),
-    ('nowarning', None, 'bisect'),
-    ('nowarning', None, 'bookmarks'),
-    ('nowarning', None, 'bundle'),
-    ('nowarning', None, 'cat'),
-    ('nowarning', None, 'diff'),
-    ('nowarning', None, 'export'),
-    ('nowarning', None, 'identify'),
-    ('nowarning', None, 'incoming'),
-    ('nowarning', None, 'log'),
-    ('nowarning', None, 'manifest'),
-    ('error', None, 'outgoing'), # confusing if push errors and not outgoing
-    ('error', None, 'push'), # destructive
-    ('nowarning', None, 'revert'),
-    ('error', None, 'serve'),
-    ('nowarning', None, 'tags'),
-    ('nowarning', None, 'unbundle'),
-    ('nowarning', None, 'update'),
-]
-
-def reposetup(ui, repo):
-    repo._explicitaccess = set()
-
-def _computehidden(repo):
-    hidden = repoview.filterrevs(repo, 'visible')
-    cl = repo.changelog
-    dynamic = hidden & repo._explicitaccess
-    if dynamic:
-        blocked = cl.ancestors(dynamic, inclusive=True)
-        hidden = frozenset(r for r in hidden if r not in blocked)
-    return hidden
-
-def setupdirectaccess():
-    """ Add two new filtername that behave like visible to provide direct access
-    and direct access with warning. Wraps the commands to setup direct access
-    """
-    repoview.filtertable.update({'visible-directaccess-nowarn': _computehidden})
-    repoview.filtertable.update({'visible-directaccess-warn': _computehidden})
-    branchmap.subsettable['visible-directaccess-nowarn'] = 'visible'
-    branchmap.subsettable['visible-directaccess-warn'] = 'visible'
-
-    for warn, ext, cmd in directaccesslevel:
-        try:
-            cmdtable = extensions.find(ext).cmdtable if ext else commands.table
-            wrapper = wrapwitherror if warn == 'error' else wrapwithoutwarning
-            extensions.wrapcommand(cmdtable, cmd, wrapper)
-        except (error.UnknownCommand, KeyError):
-            pass
-
-def wrapwitherror(orig, ui, repo, *args, **kwargs):
-    if repo and repo.filtername == 'visible-directaccess-warn':
-        repo = repo.filtered('visible')
-    return orig(ui, repo, *args, **kwargs)
-
-def wrapwithoutwarning(orig, ui, repo, *args, **kwargs):
-    if repo and repo.filtername == 'visible-directaccess-warn':
-        repo = repo.filtered("visible-directaccess-nowarn")
-    return orig(ui, repo, *args, **kwargs)
-
-def uisetup(ui):
-    """ Change ordering of extensions to ensure that directaccess extsetup comes
-    after the one of the extensions in the loadsafter list """
-    loadsafter = ui.configlist('directaccess', 'loadsafter')
-    order = list(extensions._order)
-    directaccesidx = order.index('directaccess')
-
-    # The min idx for directaccess to load after all the extensions in loadafter
-    minidxdirectaccess = directaccesidx
-
-    for ext in loadsafter:
-        try:
-            minidxdirectaccess = max(minidxdirectaccess, order.index(ext))
-        except ValueError:
-            pass # extension not loaded
-
-    if minidxdirectaccess > directaccesidx:
-        order.insert(minidxdirectaccess + 1, 'directaccess')
-        order.remove('directaccess')
-        extensions._order = order
-
-def _repository(orig, *args, **kwargs):
-    """Make visible-directaccess-warn the default filter for new repos"""
-    repo = orig(*args, **kwargs)
-    return repo.filtered("visible-directaccess-warn")
-
-def extsetup(ui):
-    extensions.wrapfunction(revset, 'posttreebuilthook', _posttreebuilthook)
-    extensions.wrapfunction(hg, 'repository', _repository)
-    setupdirectaccess()
-
-hashre = util.re.compile('[0-9a-fA-F]{1,40}')
-
-_listtuple = ('symbol', '_list')
-
-def _ishashsymbol(symbol, maxrev):
-    # Returns true if symbol looks like a hash
-    try:
-        n = int(symbol)
-        if n <= maxrev:
-            # It's a rev number
-            return False
-    except ValueError:
-        pass
-    return hashre.match(symbol)
-
-def gethashsymbols(tree, maxrev):
-    # Returns the list of symbols of the tree that look like hashes
-    # for example for the revset 3::abe3ff it will return ('abe3ff')
-    if not tree:
-        return []
-
-    results = []
-    if len(tree) in (2, 3) and tree[0] == "symbol":
-        results.append(tree[1])
-    elif tree[0] == "func" and tree[1] == _listtuple:
-        # the optimiser will group sequence of hash request
-        results += tree[2][1].split('\0')
-    elif len(tree) >= 2:
-        for subtree in tree[1:]:
-            results += gethashsymbols(subtree, maxrev)
-        # return directly, we don't need to filter symbols again
-        return results
-    return [s for s in results if _ishashsymbol(s, maxrev)]
-
-def _posttreebuilthook(orig, tree, repo):
-    # This is use to enabled direct hash access
-    # We extract the symbols that look like hashes and add them to the
-    # explicitaccess set
-    orig(tree, repo)
-    filternm = ""
-    if repo is not None:
-        filternm = repo.filtername
-    if filternm is not None and filternm.startswith('visible-directaccess'):
-        prelength = len(repo._explicitaccess)
-        accessbefore = set(repo._explicitaccess)
-        cl = repo.unfiltered().changelog
-        repo.symbols = gethashsymbols(tree, len(cl))
-        for node in repo.symbols:
-            try:
-                node = cl._partialmatch(node)
-            except error.LookupError:
-                node = None
-            if node is not None:
-                rev = cl.rev(node)
-                if rev not in repo.changelog:
-                    repo._explicitaccess.add(rev)
-        if prelength != len(repo._explicitaccess):
-            if repo.filtername != 'visible-directaccess-nowarn':
-                unhiddencommits = repo._explicitaccess - accessbefore
-                repo.ui.warn(_("Warning: accessing hidden changesets %s "
-                               "for write operation\n") %
-                             (",".join([str(repo.unfiltered()[l])
-                              for l in unhiddencommits])))
-            repo.invalidatevolatilesets()
--- a/hgext3rd/evolve/hack/inhibit.py	Mon Oct 23 15:50:19 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,319 +0,0 @@
-"""reduce the changesets evolution feature scope for early and noob friendly ui
-
-the full scale changeset evolution have some massive bleeding edge and it is
-very easy for people not very intimate with the concept to end up in intricate
-situation. in order to get some of the benefit sooner, this extension is
-disabling some of the less polished aspect of evolution. it should gradually
-get thinner and thinner as changeset evolution will get more polished. this
-extension is only recommended for large scale organisations. individual user
-should probably stick on using evolution in its current state, understand its
-concept and provide feedback
-
-This extension provides the ability to "inhibit" obsolescence markers. obsolete
-revision can be cheaply brought back to life that way.
-However as the inhibitor are not fitting in an append only model, this is
-incompatible with sharing mutable history.
-"""
-from mercurial import bookmarks
-from mercurial import commands
-from mercurial import error
-from mercurial import extensions
-from mercurial import localrepo
-from mercurial import lock as lockmod
-from mercurial import obsolete
-from mercurial import registrar
-from mercurial import scmutil
-from mercurial import util
-from mercurial.i18n import _
-
-cmdtable = {}
-
-if util.safehasattr(registrar, 'command'):
-    command = registrar.command(cmdtable)
-else: # compat with hg < 4.3
-    from mercurial import cmdutil
-    command = cmdutil.command(cmdtable)
-
-def _inhibitenabled(repo):
-    return util.safehasattr(repo, '_obsinhibit')
-
-def reposetup(ui, repo):
-
-    class obsinhibitedrepo(repo.__class__):
-
-        @localrepo.storecache('obsinhibit')
-        def _obsinhibit(self):
-            # XXX we should make sure it is invalidated by transaction failure
-            obsinhibit = set()
-            raw = self.svfs.tryread('obsinhibit')
-            for i in xrange(0, len(raw), 20):
-                obsinhibit.add(raw[i:i + 20])
-            return obsinhibit
-
-        def commit(self, *args, **kwargs):
-            newnode = super(obsinhibitedrepo, self).commit(*args, **kwargs)
-            if newnode is not None:
-                _inhibitmarkers(repo, [newnode])
-            return newnode
-
-    repo.__class__ = obsinhibitedrepo
-
-def _update(orig, ui, repo, *args, **kwargs):
-    """
-    When moving to a commit we want to inhibit any obsolete commit affecting
-    the changeset we are updating to. In other words we don't want any visible
-    commit to be obsolete.
-    """
-    wlock = None
-    try:
-        # Evolve is running a hook on lock release to display a warning message
-        # if the workind dir's parent is obsolete.
-        # We take the lock here to make sure that we inhibit the parent before
-        # that hook get a chance to run.
-        wlock = repo.wlock()
-        res = orig(ui, repo, *args, **kwargs)
-        newhead = repo['.'].node()
-        _inhibitmarkers(repo, [newhead])
-        return res
-    finally:
-        lockmod.release(wlock)
-
-def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
-    """ Add inhibition markers to every obsolete bookmarks """
-    repo = bkmstoreinst._repo
-    bkmstorenodes = [repo[v].node() for v in bkmstoreinst.values()]
-    _inhibitmarkers(repo, bkmstorenodes)
-    return orig(bkmstoreinst, *args, **kwargs)
-
-def _bookmark(orig, ui, repo, *bookmarks, **opts):
-    """ Add a -D option to the bookmark command, map it to prune -B """
-    haspruneopt = opts.get('prune', False)
-    if not haspruneopt:
-        return orig(ui, repo, *bookmarks, **opts)
-    elif opts.get('rename'):
-        raise error.Abort('Cannot use both -m and -D')
-    elif len(bookmarks) == 0:
-        hint = _('make sure to put a space between -D and your bookmark name')
-        raise error.Abort(_('Error, please check your command'), hint=hint)
-
-    # Call prune -B
-    evolve = extensions.find('evolve')
-    optsdict = {
-        'new': [],
-        'succ': [],
-        'rev': [],
-        'bookmark': bookmarks,
-        'keep': None,
-        'biject': False,
-    }
-    evolve.cmdrewrite.cmdprune(ui, repo, **optsdict)
-
-# obsolescence inhibitor
-########################
-
-def _schedulewrite(tr, obsinhibit):
-    """Make sure on disk content will be updated on transaction commit"""
-    def writer(fp):
-        """Serialize the inhibited list to disk.
-        """
-        raw = ''.join(obsinhibit)
-        fp.write(raw)
-    tr.addfilegenerator('obsinhibit', ('obsinhibit',), writer)
-    tr.hookargs['obs_inbihited'] = '1'
-
-def _filterpublic(repo, nodes):
-    """filter out inhibitor on public changeset
-
-    Public changesets are already immune to obsolescence"""
-    getrev = repo.changelog.nodemap.get
-    getphase = repo._phasecache.phase
-    return (n for n in nodes
-            if getrev(n) is not None and getphase(repo, getrev(n)))
-
-def _inhibitmarkers(repo, nodes):
-    """add marker inhibitor for all obsolete revision under <nodes>
-
-    Content of <nodes> and all mutable ancestors are considered. Marker for
-    obsolete revision only are created.
-    """
-    if not _inhibitenabled(repo):
-        return
-
-    # we add (non public()) as a lower boundary to
-    # - use the C code in 3.6 (no ancestors in C as this is written)
-    # - restrict the search space. Otherwise, the ancestors can spend a lot of
-    #   time iterating if you have a check very low in the repo. We do not need
-    #   to iterate over tens of thousand of public revisions with higher
-    #   revision number
-    #
-    # In addition, the revset logic could be made significantly smarter here.
-    newinhibit = repo.revs('(not public())::%ln and obsolete()', nodes)
-    if newinhibit:
-        node = repo.changelog.node
-        lock = tr = None
-        try:
-            lock = repo.lock()
-            tr = repo.transaction('obsinhibit')
-            repo._obsinhibit.update(node(r) for r in newinhibit)
-            _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
-            repo.invalidatevolatilesets()
-            tr.close()
-        finally:
-            lockmod.release(tr, lock)
-
-def _deinhibitmarkers(repo, nodes):
-    """lift obsolescence inhibition on a set of nodes
-
-    This will be triggered when inhibited nodes received new obsolescence
-    markers. Otherwise the new obsolescence markers would also be inhibited.
-    """
-    if not _inhibitenabled(repo):
-        return
-
-    deinhibited = repo._obsinhibit & set(nodes)
-    if deinhibited:
-        tr = repo.transaction('obsinhibit')
-        try:
-            repo._obsinhibit -= deinhibited
-            _schedulewrite(tr, _filterpublic(repo, repo._obsinhibit))
-            repo.invalidatevolatilesets()
-            tr.close()
-        finally:
-            tr.release()
-
-def _createmarkers(orig, repo, relations, *args, **kwargs):
-    """wrap markers create to make sure we de-inhibit target nodes"""
-    # wrapping transactio to unify the one in each function
-    lock = tr = None
-    try:
-        lock = repo.lock()
-        tr = repo.transaction('add-obsolescence-marker')
-        orig(repo, relations, *args, **kwargs)
-        precs = (r[0].node() for r in relations)
-        _deinhibitmarkers(repo, precs)
-        tr.close()
-    finally:
-        lockmod.release(tr, lock)
-
-def _filterobsoleterevswrap(orig, repo, rebasesetrevs, *args, **kwargs):
-    repo._notinhibited = rebasesetrevs
-    try:
-        repo.invalidatevolatilesets()
-        r = orig(repo, rebasesetrevs, *args, **kwargs)
-    finally:
-        del repo._notinhibited
-        repo.invalidatevolatilesets()
-    return r
-
-def transactioncallback(orig, repo, desc, *args, **kwargs):
-    """ Wrap localrepo.transaction to inhibit new obsolete changes """
-    def inhibitposttransaction(transaction):
-        # At the end of the transaction we catch all the new visible and
-        # obsolete commit to inhibit them
-        visibleobsolete = repo.revs('obsolete() - hidden()')
-        ignoreset = set(getattr(repo, '_rebaseset', []))
-        ignoreset |= set(getattr(repo, '_obsoletenotrebased', []))
-        visibleobsolete = list(r for r in visibleobsolete if r not in ignoreset)
-        if visibleobsolete:
-            _inhibitmarkers(repo, [repo[r].node() for r in visibleobsolete])
-    transaction = orig(repo, desc, *args, **kwargs)
-    if desc != 'strip' and _inhibitenabled(repo):
-        transaction.addpostclose('inhibitposttransaction',
-                                 inhibitposttransaction)
-    return transaction
-
-
-# We wrap these two functions to address the following scenario:
-# - Assuming that we have markers between commits in the rebase set and
-#   destination and that these markers are inhibited
-# - At the end of the rebase the nodes are still visible because rebase operate
-#   without inhibition and skip these nodes
-# We keep track in repo._obsoletenotrebased of the obsolete commits skipped by
-# the rebase and lift the inhibition in the end of the rebase.
-
-def _computeobsoletenotrebased(orig, repo, *args, **kwargs):
-    r = orig(repo, *args, **kwargs)
-    repo._obsoletenotrebased = r.keys()
-    return r
-
-def _clearrebased(orig, ui, repo, dest, state, skipped, collapsedas=None,
-                  keepf=False, **kwargs):
-    r = orig(ui, repo, dest, state, skipped, collapsedas, keepf, **kwargs)
-    if keepf:
-        return r
-    tonode = repo.changelog.node
-    if util.safehasattr(repo, '_obsoletenotrebased'):
-        _deinhibitmarkers(repo, [tonode(k) for k in repo._obsoletenotrebased])
-    return r
-
-
-def extsetup(ui):
-    # lets wrap the computation of the obsolete set
-    # We apply inhibition there
-    obsfunc = obsolete.cachefuncs['obsolete']
-
-    def _computeobsoleteset(repo):
-        """remove any inhibited nodes from the obsolete set
-
-        This will trickle down to other part of mercurial (hidden, log, etc)"""
-        obs = obsfunc(repo)
-        if _inhibitenabled(repo):
-            getrev = repo.changelog.nodemap.get
-            blacklist = getattr(repo, '_notinhibited', set())
-            for n in repo._obsinhibit:
-                if getrev(n) not in blacklist:
-                    obs.discard(getrev(n))
-        return obs
-    try:
-        extensions.find('directaccess')
-    except KeyError:
-        errormsg = _('cannot use inhibit without the direct access extension\n')
-        hint = _("(please enable it or inhibit won\'t work)\n")
-        ui.warn(errormsg)
-        ui.warn(hint)
-        return
-
-    # Wrapping this to inhibit obsolete revs resulting from a transaction
-    extensions.wrapfunction(localrepo.localrepository,
-                            'transaction', transactioncallback)
-
-    obsolete.cachefuncs['obsolete'] = _computeobsoleteset
-    # wrap create marker to make it able to lift the inhibition
-    extensions.wrapfunction(obsolete, 'createmarkers', _createmarkers)
-    # drop divergence computation since it is incompatible with "light revive"
-    obsolete.cachefuncs['divergent'] = lambda repo: set()
-    # drop bumped computation since it is incompatible with "light revive"
-    obsolete.cachefuncs['bumped'] = lambda repo: set()
-    # wrap update to make sure that no obsolete commit is visible after an
-    # update
-    extensions.wrapcommand(commands.table, 'update', _update)
-    try:
-        rebase = extensions.find('rebase')
-        if rebase:
-            if util.safehasattr(rebase, '_filterobsoleterevs'):
-                extensions.wrapfunction(rebase,
-                                        '_filterobsoleterevs',
-                                        _filterobsoleterevswrap)
-            extensions.wrapfunction(rebase, 'clearrebased', _clearrebased)
-            if util.safehasattr(rebase, '_computeobsoletenotrebased'):
-                extensions.wrapfunction(rebase,
-                                        '_computeobsoletenotrebased',
-                                        _computeobsoletenotrebased)
-
-    except KeyError:
-        pass
-    # There are two ways to save bookmark changes during a transation, we
-    # wrap both to add inhibition markers.
-    extensions.wrapfunction(bookmarks.bmstore, 'recordchange', _bookmarkchanged)
-    if getattr(bookmarks.bmstore, 'write', None) is not None:# mercurial < 3.9
-        extensions.wrapfunction(bookmarks.bmstore, 'write', _bookmarkchanged)
-    # Add bookmark -D option
-    entry = extensions.wrapcommand(commands.table, 'bookmark', _bookmark)
-    entry[1].append(('D', 'prune', None,
-                    _('delete the bookmark and prune the commits underneath')))
-
-@command('debugobsinhibit', [], '')
-def cmddebugobsinhibit(ui, repo, *revs):
-    """inhibit obsolescence markers effect on a set of revs"""
-    nodes = (repo[r].node() for r in scmutil.revrange(repo, revs))
-    _inhibitmarkers(repo, nodes)
--- a/hgext3rd/evolve/metadata.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/evolve/metadata.py	Wed Nov 01 23:58:27 2017 +0100
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-__version__ = '6.8.0'
-testedwith = '3.8.4 3.9.2 4.0.2 4.1.3 4.2.3 4.3.2 4.4'
-minimumhgversion = '3.8'
+__version__ = '7.0.0.dev'
+testedwith = '4.1.3 4.2.3 4.3.2 4.4'
+minimumhgversion = '4.1'
 buglink = 'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obscache.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/evolve/obscache.py	Wed Nov 01 23:58:27 2017 +0100
@@ -49,15 +49,7 @@
 except ImportError:
     from mercurial import scmutil as vfsmod
 
-try:
-    obsstorefilecache = localrepo.localrepository.obsstore
-except AttributeError:
-    # XXX hg-3.8 compat
-    #
-    # mercurial 3.8 has issue with accessing file cache property from their
-    # cache. This is fix by 36fbd72c2f39fef8ad52d7c559906c2bc388760c in core
-    # and shipped in 3.9
-    obsstorefilecache = localrepo.localrepository.__dict__['obsstore']
+obsstorefilecache = localrepo.localrepository.obsstore
 
 # obsstore is a filecache so we have do to some spacial dancing
 @eh.wrapfunction(obsstorefilecache, 'func')
--- a/hgext3rd/evolve/obsdiscovery.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/evolve/obsdiscovery.py	Wed Nov 01 23:58:27 2017 +0100
@@ -557,15 +557,7 @@
     obsstore.rangeobshashcache.clear()
     return orig(obsstore, *args, **kwargs)
 
-try:
-    obsstorefilecache = localrepo.localrepository.obsstore
-except AttributeError:
-    # XXX hg-3.8 compat
-    #
-    # mercurial 3.8 has issue with accessing file cache property from their
-    # cache. This is fix by 36fbd72c2f39fef8ad52d7c559906c2bc388760c in core
-    # and shipped in 3.9
-    obsstorefilecache = localrepo.localrepository.__dict__['obsstore']
+obsstorefilecache = localrepo.localrepository.obsstore
 
 
 # obsstore is a filecache so we have do to some spacial dancing
--- a/hgext3rd/evolve/obsexchange.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/evolve/obsexchange.py	Wed Nov 01 23:58:27 2017 +0100
@@ -14,24 +14,18 @@
     import io
     StringIO = io.StringIO
 
-import errno
-import socket
-
 from mercurial import (
     bundle2,
     error,
     exchange,
     extensions,
-    httppeer,
-    localrepo,
     lock as lockmod,
     node,
     obsolete,
+    pushkey,
     util,
     wireproto,
 )
-from mercurial.hgweb import hgweb_mod
-from mercurial.i18n import _
 
 from . import (
     exthelper,
@@ -182,17 +176,6 @@
     obsdata.seek(0)
     return obsdata
 
-# The wireproto.streamres API changed, handling chunking and compression
-# directly. Handle either case.
-if util.safehasattr(wireproto.abstractserverproto, 'groupchunks'):
-    # We need to handle chunking and compression directly
-    def streamres(d, proto):
-        return wireproto.streamres(proto.groupchunks(d))
-else:
-    # Leave chunking and compression to streamres
-    def streamres(d, proto):
-        return wireproto.streamres(reader=d, v1compressible=True)
-
 def srv_pullobsmarkers(repo, proto, others):
     """serves a binary stream of markers.
 
@@ -209,245 +192,15 @@
     finaldata.write('%20i' % len(obsdata))
     finaldata.write(obsdata)
     finaldata.seek(0)
-    return streamres(finaldata, proto)
-
-###############################################
-### Support for old legacy exchange methods ###
-###############################################
-
-class pushobsmarkerStringIO(StringIO):
-    """hacky string io for progress"""
-
-    @util.propertycache
-    def length(self):
-        return len(self.getvalue())
-
-    def read(self, size=None):
-        obsexcprg(self.ui, self.tell(), unit=_("bytes"), total=self.length)
-        return StringIO.read(self, size)
-
-    def __iter__(self):
-        d = self.read(4096)
-        while d:
-            yield d
-            d = self.read(4096)
-
-# compat-code: _pushobsolete
-#
-# the _pushobsolete function is a core function used to exchange
-# obsmarker with repository that does not support bundle2
+    return wireproto.streamres(reader=finaldata, v1compressible=True)
 
-@eh.wrapfunction(exchange, '_pushobsolete')
-def _pushobsolete(orig, pushop):
-    """utility function to push obsolete markers to a remote"""
-    if not obsolete.isenabled(pushop.repo, obsolete.exchangeopt):
-        return
-    if 'obsmarkers' in pushop.stepsdone:
-        return
-    pushop.stepsdone.add('obsmarkers')
-    if pushop.cgresult == 0:
-        return
-    pushop.ui.debug('try to push obsolete markers to remote\n')
-    repo = pushop.repo
-    remote = pushop.remote
-    if (repo.obsstore and 'obsolete' in remote.listkeys('namespaces')):
-        markers = pushop.outobsmarkers
-        if not markers:
-            obsexcmsg(repo.ui, "no marker to push\n")
-        elif remote.capable('_evoext_pushobsmarkers_0'):
-            msg = ('the remote repository use years old versions of Mercurial'
-                   ' and evolve\npushing obsmarker using legacy method\n')
-            repo.ui.warn(msg)
-            repo.ui.warn('(please upgrade your server)\n')
-            obsdata = pushobsmarkerStringIO()
-            for chunk in obsolete.encodemarkers(markers, True):
-                obsdata.write(chunk)
-            obsdata.seek(0)
-            obsdata.ui = repo.ui
-            obsexcmsg(repo.ui, "pushing %i obsolescence markers (%i bytes)\n"
-                               % (len(markers), len(obsdata.getvalue())),
-                      True)
-            remote.evoext_pushobsmarkers_0(obsdata)
-            obsexcprg(repo.ui, None)
-
-        else:
-            # XXX core could be able do the same things but without the debug
-            # and progress output.
-            msg = ('the remote repository usea years old version of Mercurial'
-                   ' and not evolve extension\n')
-            repo.ui.warn(msg)
-            msg = 'pushing obsmarker using and extremely slow legacy method\n'
-            repo.ui.warn(msg)
-            repo.ui.warn('(please upgrade your server and enable evolve.serveronly on it)\n')
-            rslts = []
-            remotedata = obsolete._pushkeyescape(markers).items()
-            totalbytes = sum(len(d) for k, d in remotedata)
-            sentbytes = 0
-            obsexcmsg(repo.ui, "pushing %i obsolescence markers in %i "
-                               "pushkey payload (%i bytes)\n"
-                               % (len(markers), len(remotedata), totalbytes),
-                      True)
-            for key, data in remotedata:
-                obsexcprg(repo.ui, sentbytes, item=key, unit=_("bytes"),
-                          total=totalbytes)
-                rslts.append(remote.pushkey('obsolete', key, '', data))
-                sentbytes += len(data)
-                obsexcprg(repo.ui, sentbytes, item=key, unit=_("bytes"),
-                          total=totalbytes)
-            obsexcprg(repo.ui, None)
-            if [r for r in rslts if not r]:
-                msg = _('failed to push some obsolete markers!\n')
-                repo.ui.warn(msg)
-        obsexcmsg(repo.ui, "DONE\n")
-
-# Supporting legacy way to push obsmarker so that old client can still push
-# them somewhat efficiently
-
-@eh.addattr(wireproto.wirepeer, 'evoext_pushobsmarkers_0')
-def client_pushobsmarkers(self, obsfile):
-    """wireprotocol peer method"""
-    self.requirecap('_evoext_pushobsmarkers_0',
-                    _('push obsolete markers faster'))
-    ret, output = self._callpush('evoext_pushobsmarkers_0', obsfile)
-    for l in output.splitlines(True):
-        self.ui.status(_('remote: '), l)
-    return ret
-
-@eh.addattr(httppeer.httppeer, 'evoext_pushobsmarkers_0')
-def httpclient_pushobsmarkers(self, obsfile):
-    """httpprotocol peer method
-    (Cannot simply use _callpush as http is doing some special handling)"""
-    self.requirecap('_evoext_pushobsmarkers_0',
-                    _('push obsolete markers faster'))
-    try:
-        r = self._call('evoext_pushobsmarkers_0', data=obsfile)
-        vals = r.split('\n', 1)
-        if len(vals) < 2:
-            raise error.ResponseError(_("unexpected response:"), r)
+abortmsg = "won't exchange obsmarkers through pushkey"
+hint = "upgrade your client or server to use the bundle2 protocol"
 
-        for l in vals[1].splitlines(True):
-            if l.strip():
-                self.ui.status(_('remote: '), l)
-        return vals[0]
-    except socket.error as err:
-        if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
-            raise error.Abort(_('push failed: %s') % err.args[1])
-        raise error.Abort(err.args[1])
-
-@eh.wrapfunction(localrepo.localrepository, '_restrictcapabilities')
-def local_pushobsmarker_capabilities(orig, repo, caps):
-    caps = orig(repo, caps)
-    caps.add('_evoext_pushobsmarkers_0')
-    return caps
-
-@eh.addattr(localrepo.localpeer, 'evoext_pushobsmarkers_0')
-def local_pushobsmarkers(peer, obsfile):
-    data = obsfile.read()
-    _pushobsmarkers(peer._repo, data)
-
-# compat-code: _pullobsolete
-#
-# the _pullobsolete function is a core function used to exchange
-# obsmarker with repository that does not support bundle2
-
-@eh.wrapfunction(exchange, '_pullobsolete')
-def _pullobsolete(orig, pullop):
-    if not obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
-        return None
-    if 'obsmarkers' in pullop.stepsdone:
-        return None
-    wirepull = pullop.remote.capable('_evoext_pullobsmarkers_0')
-    if 'obsolete' not in pullop.remote.listkeys('namespaces'):
-        return None # remote opted out of obsolescence marker exchange
-    if not wirepull:
-        return orig(pullop)
-    tr = None
-    ui = pullop.repo.ui
-    boundaries = obsdiscovery.buildpullobsmarkersboundaries(pullop, bundle2=False)
-    if 'missing' in boundaries and not boundaries['missing']:
-        obsexcmsg(ui, "nothing to pull\n")
-        return None
-    elif not set(boundaries['heads']) - set(boundaries['common']):
-        obsexcmsg(ui, "nothing to pull\n")
-        return None
-
-    obsexcmsg(ui, "pull obsolescence markers\n", True)
-    new = 0
-
-    msg = ('the remote repository use years old versions of Mercurial and evolve\n'
-           'pulling obsmarker using legacy method\n')
-    ui.warn(msg)
-    ui.warn('(please upgrade your server)\n')
+def forbidpushkey(repo=None, key=None, old=None, new=None):
+    """prevent exchange through pushkey"""
+    raise error.Abort(abortmsg, hint=hint)
 
-    obsdata = pullop.remote.evoext_pullobsmarkers_0(**boundaries)
-    obsdata = obsdata.read()
-    if len(obsdata) > 5:
-        msg = "merging obsolescence markers (%i bytes)\n" % len(obsdata)
-        obsexcmsg(ui, msg)
-        tr = pullop.gettransaction()
-        old = len(pullop.repo.obsstore._all)
-        pullop.repo.obsstore.mergemarkers(tr, obsdata)
-        new = len(pullop.repo.obsstore._all) - old
-        obsexcmsg(ui, "%i obsolescence markers added\n" % new, True)
-    else:
-        obsexcmsg(ui, "no unknown remote markers\n")
-    obsexcmsg(ui, "DONE\n")
-    if new:
-        pullop.repo.invalidatevolatilesets()
-    return tr
-
-@eh.addattr(wireproto.wirepeer, 'evoext_pullobsmarkers_0')
-def client_pullobsmarkers(self, heads=None, common=None):
-    self.requirecap('_evoext_pullobsmarkers_0', _('look up remote obsmarkers'))
-    opts = {}
-    if heads is not None:
-        opts['heads'] = wireproto.encodelist(heads)
-    if common is not None:
-        opts['common'] = wireproto.encodelist(common)
-    f = self._callcompressable("evoext_pullobsmarkers_0", **opts)
-    length = int(f.read(20))
-    chunk = 4096
-    current = 0
-    data = StringIO()
-    ui = self.ui
-    obsexcprg(ui, current, unit=_("bytes"), total=length)
-    while current < length:
-        readsize = min(length - current, chunk)
-        data.write(f.read(readsize))
-        current += readsize
-        obsexcprg(ui, current, unit=_("bytes"), total=length)
-    obsexcprg(ui, None)
-    data.seek(0)
-    return data
-
-@eh.addattr(localrepo.localpeer, 'evoext_pullobsmarkers_0')
-def local_pullobsmarkers(self, heads=None, common=None):
-    return _getobsmarkersstream(self._repo, heads=heads,
-                                common=common)
-
-def _legacypush_capabilities(orig, repo, proto):
-    """wrapper to advertise new capability"""
-    caps = orig(repo, proto)
-    if obsolete.isenabled(repo, obsolete.exchangeopt):
-        caps = caps.split()
-        caps.append('_evoext_pushobsmarkers_0')
-        caps.append('_evoext_pullobsmarkers_0')
-        caps.sort()
-        caps = ' '.join(caps)
-    return caps
-
-@eh.extsetup
-def extsetup(ui):
-    # legacy standalone method
-    hgweb_mod.perms['evoext_pushobsmarkers_0'] = 'push'
-    hgweb_mod.perms['evoext_pullobsmarkers_0'] = 'pull'
-    wireproto.commands['evoext_pushobsmarkers_0'] = (srv_pushobsmarkers, '')
-    wireproto.commands['evoext_pullobsmarkers_0'] = (srv_pullobsmarkers, '*')
-
-    extensions.wrapfunction(wireproto, 'capabilities', _legacypush_capabilities)
-    # wrap command content
-    oldcap, args = wireproto.commands['capabilities']
-
-    def newcap(repo, proto):
-        return _legacypush_capabilities(oldcap, repo, proto)
-    wireproto.commands['capabilities'] = (newcap, args)
+@eh.uisetup
+def setuppushkeyforbidding(ui):
+    pushkey._namespaces['obsolete'] = (forbidpushkey, forbidpushkey)
--- a/hgext3rd/evolve/rewriteutil.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/evolve/rewriteutil.py	Wed Nov 01 23:58:27 2017 +0100
@@ -24,7 +24,6 @@
     phases,
     repair,
     revset,
-    util,
 )
 
 from mercurial.i18n import _
@@ -47,10 +46,7 @@
         shorts = [node.short(tonode(r)) for r in revs]
         summary = ', '.join(shorts)
     else:
-        if util.safehasattr(revs, 'first'):
-            first = revs.first()
-        else:
-            first = revs[0]
+        first = revs.first()
         summary = _('%s and %d others')
         summary %= (node.short(tonode(first)), numrevs - 1)
     return summary
--- a/hgext3rd/evolve/serveronly.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/evolve/serveronly.py	Wed Nov 01 23:58:27 2017 +0100
@@ -13,6 +13,8 @@
 import sys
 import os
 
+from mercurial import obsolete
+
 try:
     from . import (
         compat,
@@ -55,3 +57,5 @@
     if not evolveopts:
         evolveopts = 'all'
         ui.setconfig('experimental', 'evolution', evolveopts)
+    if obsolete.isenabled(repo, 'exchange'):
+        repo.ui.setconfig('server', 'bundle1', False)
--- a/hgext3rd/evolve/stablerange.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/evolve/stablerange.py	Wed Nov 01 23:58:27 2017 +0100
@@ -395,13 +395,7 @@
             # data about the merge. But I'm not sure this function will be even
             # call for the general case.
 
-            # Lrudict.get in hg-3.9 returns the lrunode instead of the
-            # value, use __getitem__ instead and catch the exception directly
-            try:
-                allrevs = self._stablesortcache[headrev]
-            except KeyError:
-                allrevs = None
-
+            allrevs = self._stablesortcache.get(headrev)
             if allrevs is None:
                 allrevs = self._getrevsfrommerge(repo, headrev)
                 if allrevs is None:
@@ -450,11 +444,8 @@
             self._stablesortprepared[merge] = (sortedrevs, len(sortedrevs))
 
     def _getrevsfrommerge(self, repo, merge):
-        # Lrudict.get in hg-3.9 returns the lrunode instead of the
-        # value, use __getitem__ instead and catch the exception directly
-        try:
-            prepared = self._stablesortprepared[merge]
-        except KeyError:
+        prepared = self._stablesortprepared.get(merge)
+        if prepared is None:
             return None
 
         mergedepth = self.depthrev(repo, merge)
--- a/hgext3rd/topic/__init__.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/topic/__init__.py	Wed Nov 01 23:58:27 2017 +0100
@@ -63,6 +63,40 @@
     topic-mode = enforce-all # abort the commit (even for merge)
     topic-mode = random # use a randomized generated topic (except for merge)
     topic-mode = random-all # use a randomized generated topic (even for merge)
+
+Single head enforcing
+=====================
+
+The extensions come with an option to enforce that there is only one heads for
+each name in the repository at any time.
+
+    [experimental]
+    enforce-single-head = yes
+
+Publishing behavior
+===================
+
+Topic vanish when changeset move to the public phases. Moving to the public
+phase usually happens on push, but it is possible ot update that behavior. The
+server needs to have specific config for this.
+
+    # everything pushed become public (the default)
+    [phase]
+    publish = yes
+
+    # nothing push turned public
+    [phase]
+    publish = no
+
+    # topic branches are not published, changeset without topic are
+    [phase]
+    publish = no
+    [experimental]
+    topic.publish-bare-branch = yes
+
+In addition, the topic extension adds a ``--publish`` flag on :hg:`push`. When
+used, the pushed revisions are published if the push succeeds. It also applies
+to common revisions selected by the push.
 """
 
 from __future__ import absolute_import
@@ -99,6 +133,7 @@
 from . import (
     compat,
     constants,
+    flow,
     revset as topicrevset,
     destination,
     stack,
@@ -139,10 +174,10 @@
               'topic.active': 'green',
              }
 
-__version__ = '0.4.0'
+__version__ = '0.5.0.dev'
 
-testedwith = '4.0.2 4.1.3 4.2.3 4.3.3 4.4'
-minimumhgversion = '4.0'
+testedwith = '4.1.3 4.2.3 4.3.3 4.4'
+minimumhgversion = '4.1'
 buglink = 'https://bz.mercurial-scm.org/'
 
 if util.safehasattr(registrar, 'configitem'):
@@ -152,9 +187,15 @@
     configitem('experimental', 'enforce-topic',
                default=False,
     )
+    configitem('experimental', 'enforce-single-head',
+               default=False,
+    )
     configitem('experimental', 'topic-mode',
                default=None,
     )
+    configitem('experimental', 'topic.publish-bare-branch',
+               default=False,
+    )
     configitem('_internal', 'keep-topic',
                default=False,
     )
@@ -245,6 +286,8 @@
 
     extensions.afterloaded('rebase', _fixrebase)
 
+    flow.installpushflag(ui)
+
     entry = extensions.wrapcommand(commands.table, 'commit', commitwrap)
     entry[1].append(('t', 'topic', '',
                      _("use specified topic"), _('TOPIC')))
@@ -368,6 +411,30 @@
             if desc in ('strip', 'repair') or ctr is not None:
                 return tr
 
+            reporef = weakref.ref(self)
+            if repo.ui.configbool('experimental', 'enforce-single-head'):
+                origvalidator = tr.validator
+
+                def validator(tr2):
+                    repo = reporef()
+                    flow.enforcesinglehead(repo, tr2)
+                    origvalidator(tr2)
+                tr.validator = validator
+
+            if (repo.ui.configbool('experimental', 'topic.publish-bare-branch')
+                    and (desc.startswith('push')
+                         or desc.startswith('serve'))
+                    ):
+                origclose = tr.close
+                trref = weakref.ref(tr)
+
+                def close():
+                    repo = reporef()
+                    tr2 = trref()
+                    flow.publishbarebranch(repo, tr2)
+                    origclose()
+                tr.close = close
+
             # real transaction start
             ct = self.currenttopic
             if not ct:
@@ -769,11 +836,8 @@
 
         # phase handling
         commitphase = c.phase()
-        if util.safehasattr(repo.ui, 'configoverride'):
-            overrides = {('phases', 'new-commit'): commitphase}
-            with repo.ui.configoverride(overrides, 'changetopic'):
-                newnode = repo.commitctx(mc)
-        else: # do not attempt to preserver phase (hg <= 4.0)
+        overrides = {('phases', 'new-commit'): commitphase}
+        with repo.ui.configoverride(overrides, 'changetopic'):
             newnode = repo.commitctx(mc)
 
         successors[c.node()] = (newnode,)
--- a/hgext3rd/topic/destination.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/topic/destination.py	Wed Nov 01 23:58:27 2017 +0100
@@ -6,7 +6,6 @@
     destutil,
     error,
     extensions,
-    util,
 )
 from . import topicmap
 from .evolvebits import builddependencies
@@ -54,12 +53,7 @@
             msg = _("topic '%s' has %d heads "
                     "- please merge with an explicit rev") % (top, len(heads))
             raise error.ManyMergeDestAbort(msg)
-    if len(getattr(orig, 'func_defaults', ())) == 3: # version hg-3.7
-        return orig(repo, action, sourceset, onheadcheck)
-    if 3 < len(getattr(orig, 'func_defaults', ())): # version hg-3.8 and above
-        return orig(repo, action, sourceset, onheadcheck, destspace=destspace)
-    else:
-        return orig(repo)
+    return orig(repo, action, sourceset, onheadcheck, destspace=destspace)
 
 def _destupdatetopic(repo, clean, check=None):
     """decide on an update destination from current topic"""
@@ -100,24 +94,8 @@
 
 def modsetup(ui):
     """run a uisetup time to install all destinations wrapping"""
-    if util.safehasattr(destutil, '_destmergebranch'):
-        extensions.wrapfunction(destutil, '_destmergebranch', _destmergebranch)
-    try:
-        rebase = extensions.find('rebase')
-    except KeyError:
-        rebase = None
-
-    # Mercurial 4.4 rename _definesets into _definedestmap
-    rebasebefore38 = not util.safehasattr(rebase, '_definesets')
-    rebasebefore44 = not util.safehasattr(rebase, '_definedestmap')
-
-    if (util.safehasattr(rebase, '_destrebase')
-            # logic not shared with merge yet < hg-3.8
-            and rebasebefore38 and rebasebefore44):
-        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)
+    extensions.wrapfunction(destutil, '_destmergebranch', _destmergebranch)
+    bridx = destutil.destupdatesteps.index('branch')
+    destutil.destupdatesteps.insert(bridx, 'topic')
+    destutil.destupdatestepmap['topic'] = _destupdatetopic
+    extensions.wrapfunction(destutil, 'desthistedit', desthistedit)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/topic/flow.py	Wed Nov 01 23:58:27 2017 +0100
@@ -0,0 +1,69 @@
+from __future__ import absolute_import
+
+from mercurial import (
+    commands,
+    error,
+    exchange,
+    extensions,
+    node,
+    phases,
+    util,
+)
+
+from mercurial.i18n import _
+
+def enforcesinglehead(repo, tr):
+    for name, heads in repo.filtered('visible').branchmap().iteritems():
+        if len(heads) > 1:
+            hexs = [node.short(n) for n in heads]
+            raise error.Abort(_('%d heads on "%s"') % (len(heads), name),
+                              hint=(', '.join(hexs)))
+
+def publishbarebranch(repo, tr):
+    """Publish changeset without topic"""
+    if 'node' not in tr.hookargs: # no new node
+        return
+    startnode = node.bin(tr.hookargs['node'])
+    topublish = repo.revs('not public() and (%n:) - hidden() - topic()', startnode)
+    if topublish:
+        cl = repo.changelog
+        nodes = [cl.node(r) for r in topublish]
+        repo._phasecache.advanceboundary(repo, tr, phases.public, nodes)
+
+def wrappush(orig, repo, remote, *args, **kwargs):
+    """interpret the --publish flag and pass it to the push operation"""
+    newargs = kwargs.copy()
+    if kwargs.pop('publish', False):
+        opargs = kwargs.get('opargs')
+        if opargs is None:
+            opargs = {}
+        newargs['opargs'] = opargs.copy()
+        newargs['opargs']['publish'] = True
+    return orig(repo, remote, *args, **newargs)
+
+def extendpushoperation(orig, *args, **kwargs):
+    publish = kwargs.pop('publish', False)
+    op = orig(*args, **kwargs)
+    op.publish = publish
+    return op
+
+def wrapphasediscovery(orig, pushop):
+    orig(pushop)
+    if pushop.publish:
+        if not util.safehasattr(pushop, 'remotephases'):
+            msg = _('--publish flag only supported from Mercurial 4.4 and higher')
+            raise error.Abort(msg)
+        if not pushop.remotephases.publishing:
+            unfi = pushop.repo.unfiltered()
+            droots = pushop.remotephases.draftroots
+            revset = '%ln and (not public() or %ln::)'
+            future = list(unfi.set(revset, pushop.futureheads, droots))
+            pushop.outdatedphases = future
+
+def installpushflag(ui):
+    entry = extensions.wrapcommand(commands.table, 'push', wrappush)
+    entry[1].append(('', 'publish', False,
+                    _('push the changeset as public')))
+    extensions.wrapfunction(exchange, 'pushoperation', extendpushoperation)
+    extensions.wrapfunction(exchange, '_pushdiscoveryphase', wrapphasediscovery)
+    exchange.pushdiscoverymapping['phase'] = exchange._pushdiscoveryphase
--- a/hgext3rd/topic/revset.py	Mon Oct 23 15:50:19 2017 +0200
+++ b/hgext3rd/topic/revset.py	Wed Nov 01 23:58:27 2017 +0100
@@ -72,11 +72,10 @@
     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:
+    if repo.currenttopic:
         topic = repo.currenttopic
-    if not topic:
+    else:
         branch = repo[None].branch()
     return revset.baseset(stack.stack(repo, branch=branch, topic=topic)[1:]) & subset
--- a/tests/test-discovery-obshashrange.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-discovery-obshashrange.t	Wed Nov 01 23:58:27 2017 +0100
@@ -194,8 +194,8 @@
   running python "*/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob)
   sending hello command
   sending between command
-  remote: 516
-  remote: capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v0 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch branchmap bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps changegroupsubset getbundle known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 466
+  remote: capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_obshashrange_v0 batch branchmap bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps changegroupsubset getbundle known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1
   preparing listkeys for "phases"
   sending listkeys command
--- a/tests/test-divergent.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-divergent.t	Wed Nov 01 23:58:27 2017 +0100
@@ -53,7 +53,7 @@
   |/
   o  0:135f39f4bd78@default(draft) add _a []
   
-  $ hg evolve --all --any --contentdivergent
+  $ hg evolve --all --any --content-divergent
   merge:[2] add bdivergent1
   with: [3] add bdivergent2
   base: [1] add _b
@@ -98,7 +98,7 @@
   |/
   o  0:135f39f4bd78@default(draft) add _a []
   
-  $ hg evolve --all --any --contentdivergent
+  $ hg evolve --all --any --content-divergent
   merge:[7] add cdivergent1
   with: [8] cdivergent2
   base: [6] add _c
@@ -145,9 +145,9 @@
   > EOF
   $ hg evolve --all
   nothing to evolve on current working copy parent
-  (do you want to use --contentdivergent)
+  (do you want to use --content-divergent)
   [2]
-  $ hg evolve --contentdivergent
+  $ hg evolve --content-divergent
   merge:[3] add bdivergent2
   with: [2] add bdivergent1
   base: [1] add _b
--- a/tests/test-evolve-bumped.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-evolve-bumped.t	Wed Nov 01 23:58:27 2017 +0100
@@ -69,7 +69,7 @@
   no changes found
   1 new bumped changesets
 
-  $ hg evolve -a -A --phasedivergent
+  $ hg evolve -a -A --phase-divergent
   recreate:[2] tweak a
   atop:[1] modify a
   computing new diff
@@ -121,5 +121,5 @@
   |
   o  0:d3873e73d99e@default(public) init
   
-  $ hg evolve --all --phasedivergent
+  $ hg evolve --all --phase-divergent
   skipping b28e84916d8c : we do not handle merge yet
--- a/tests/test-evolve-obshistory.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-evolve-obshistory.t	Wed Nov 01 23:58:27 2017 +0100
@@ -1283,7 +1283,7 @@
   $ hg update --hidden 'desc(A0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory parent is obsolete! (471f378eab4c)
-  (471f378eab4c has diverged, use 'hg evolve --list --contentdivergent' to resolve the issue)
+  (471f378eab4c has diverged, use 'hg evolve --list --content-divergent' to resolve the issue)
 
 Test output with amended + folded commit
 ========================================
--- a/tests/test-evolve-serveronly-bundle2.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-evolve-serveronly-bundle2.t	Wed Nov 01 23:58:27 2017 +0100
@@ -85,9 +85,9 @@
 ===================
 
   $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (glob)
+  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (glob)
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (no-eol) (glob)
 
   $ curl -s "http://localhost:$HGPORT/?cmd=listkeys&namespace=namespaces" | sort
   bookmarks	
@@ -147,9 +147,9 @@
   obsolete	
   phases	
   $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (glob)
+  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (glob)
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (no-eol) (glob)
 
   $ echo '[experimental]' >> server/.hg/hgrc
   $ echo 'evolution=!' >> server/.hg/hgrc
@@ -174,9 +174,9 @@
   phases	
 
   $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (glob)
+  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (glob)
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 batch * (no-eol) (glob)
 
 Test obshashrange discover
 ===========================================
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-serveronly-legacy.t	Wed Nov 01 23:58:27 2017 +0100
@@ -0,0 +1,63 @@
+
+  $ . ${TESTDIR}/testlib/pythonpath.sh
+
+  $ cat >> $HGRCPATH <<EOF
+  > [defaults]
+  > amend=-d "0 0"
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish = False
+  > [experimental]
+  > bundle2-exp=False # < Mercurial-4.0
+  > [devel]
+  > legacy.exchange=bundle1
+  > [extensions]
+  > EOF
+
+  $ mkcommit() {
+  >    echo "$1" > "$1"
+  >    hg add "$1"
+  >    hg ci -m "add $1"
+  > }
+
+
+  $ hg init server
+
+Try the multiple ways to setup the extension
+
+  $ hg -R server log --config 'extensions.evolve.serveronly='
+  $ hg -R server log --config "extensions.evolve.serveronly=${SRCDIR}/hgext3rd/evolve/serveronly.py"
+  $ PYTHONPATH=$HGTEST_ORIG_PYTHONPATH hg -R server log --config "extensions.evolve.serveronly=${SRCDIR}/hgext3rd/evolve/serveronly.py"
+
+setup repo
+
+  $ echo "[extensions]" >> ./server/.hg/hgrc
+  $ echo "evolve.serveronly=" >> ./server/.hg/hgrc
+  $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log --traceback
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ client
+  no changes found
+  updating to branch default
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat ./errors.log
+  $ echo "[extensions]" >> ./client/.hg/hgrc
+  $ echo "evolve=" >> ./client/.hg/hgrc
+  $ cp -r client other
+
+Smoke testing
+===============
+
+  $ cd client
+  $ mkcommit 0
+  $ mkcommit a
+  $ hg push
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: remote error:
+  incompatible Mercurial client; bundle2 required
+  (see https://www.mercurial-scm.org/wiki/IncompatibleClient)
+  [255]
+  $ cat ../errors.log
--- a/tests/test-evolve-serveronly.t	Mon Oct 23 15:50:19 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-
-  $ . ${TESTDIR}/testlib/pythonpath.sh
-
-  $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
-  > [experimental]
-  > bundle2-exp=False # < Mercurial-4.0
-  > [devel]
-  > legacy.exchange=bundle1
-  > [extensions]
-  > EOF
-
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "add $1"
-  > }
-
-
-  $ hg init server
-
-Try the multiple ways to setup the extension
-
-  $ hg -R server log --config 'extensions.evolve.serveronly='
-  $ hg -R server log --config "extensions.evolve.serveronly=${SRCDIR}/hgext3rd/evolve/serveronly.py"
-  $ PYTHONPATH=$HGTEST_ORIG_PYTHONPATH hg -R server log --config "extensions.evolve.serveronly=${SRCDIR}/hgext3rd/evolve/serveronly.py"
-
-setup repo
-
-  $ echo "[extensions]" >> ./server/.hg/hgrc
-  $ echo "evolve.serveronly=" >> ./server/.hg/hgrc
-  $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log --traceback
-  $ cat hg.pid >> $DAEMON_PIDS
-
-  $ hg clone http://localhost:$HGPORT/ client
-  no changes found
-  updating to branch default
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cat ./errors.log
-  $ echo "[extensions]" >> ./client/.hg/hgrc
-  $ echo "evolve=" >> ./client/.hg/hgrc
-  $ cp -r client other
-
-Smoke testing
-===============
-
-  $ cd client
-  $ mkcommit 0
-  $ mkcommit a
-  $ hg push
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 2 changesets with 2 changes to 2 files
-  $ hg pull
-  pulling from http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  $ cat ../errors.log
-  $ hg pull -R ../other
-  pulling from http://localhost:$HGPORT/
-  requesting all changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 2 files
-  pull obsolescence markers
-  the remote repository use years old versions of Mercurial and evolve
-  pulling obsmarker using legacy method
-  (please upgrade your server)
-  (run 'hg update' to get a working copy)
-  $ cat ../errors.log
-  $ hg push -R ../other
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  [1]
-  $ cat ../errors.log
-
-Capacity testing
-===================
-
-  $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (glob)
-  $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (no-eol) (glob)
-
-  $ curl -s "http://localhost:$HGPORT/?cmd=listkeys&namespace=namespaces" | sort
-  bookmarks	
-  namespaces	
-  obsolete	
-  phases	
-
-Push
-=============
-
-  $ echo 'A' > a
-  $ hg amend
-  $ hg push
-  pushing to http://localhost:$HGPORT/
-  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)
-  the remote repository use years old versions of Mercurial and evolve
-  pushing obsmarker using legacy method
-  (please upgrade your server)
-  pushing 2 obsolescence markers (* bytes) (glob)
-  remote: 2 obsolescence markers added
-  $ cat ../errors.log
-  $ hg push
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  [1]
-  $ cat ../errors.log
-
-Pull
-=============
-
-  $ hg -R ../other pull
-  pulling from http://localhost:$HGPORT/
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to [12] files \(\+1 heads\) (re)
-  pull obsolescence markers
-  the remote repository use years old versions of Mercurial and evolve
-  pulling obsmarker using legacy method
-  (please upgrade your server)
-  2 obsolescence markers added
-  (run 'hg heads' to see heads)
-  $ cat ../errors.log
-  $ hg -R ../other pull
-  pulling from http://localhost:$HGPORT/
-  searching for changes
-  no changes found
-  $ cat ../errors.log
-
-  $ cd ..
-
-Test disabling obsolete advertisement
-===========================================
-(used by bitbucket to select which repo use evolve)
-
-  $ curl -s "http://localhost:$HGPORT/?cmd=listkeys&namespace=namespaces" | sort
-  bookmarks	
-  namespaces	
-  obsolete	
-  phases	
-  $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (glob)
-  $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (no-eol) (glob)
-
-  $ echo '[experimental]' >> server/.hg/hgrc
-  $ echo 'evolution=!' >> server/.hg/hgrc
-  $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
-  $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
-  $ cat hg.pid >> $DAEMON_PIDS
-
-  $ curl -s "http://localhost:$HGPORT/?cmd=listkeys&namespace=namespaces" | sort
-  bookmarks	
-  namespaces	
-  phases	
-  $ curl -s http://localhost:$HGPORT/?cmd=hello | grep _evoext_pushobsmarkers_0
-  [1]
-  $ curl -s http://localhost:$HGPORT/?cmd=capabilities | grep _evoext_pushobsmarkers_0
-  [1]
-
-  $ echo 'evolution=' >> server/.hg/hgrc
-  $ $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
-  $ hg serve -R server -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
-  $ cat hg.pid >> $DAEMON_PIDS
-
-  $ curl -s "http://localhost:$HGPORT/?cmd=listkeys&namespace=namespaces" | sort
-  bookmarks	
-  namespaces	
-  obsolete	
-  phases	
-  $ curl -s http://localhost:$HGPORT/?cmd=hello
-  capabilities: _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (glob)
-  $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshash_0 _evoext_obshash_1 _evoext_pullobsmarkers_0 _evoext_pushobsmarkers_0 batch * (no-eol) (glob)
--- a/tests/test-evolve-templates.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-evolve-templates.t	Wed Nov 01 23:58:27 2017 +0100
@@ -666,7 +666,7 @@
   $ hg up 'desc(A0)' --hidden
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory parent is obsolete! (471f378eab4c)
-  (471f378eab4c has diverged, use 'hg evolve --list --contentdivergent' to resolve the issue)
+  (471f378eab4c has diverged, use 'hg evolve --list --content-divergent' to resolve the issue)
 
 Precursors template should show current revision as it is the working copy
   $ hg tlog
--- a/tests/test-evolve.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-evolve.t	Wed Nov 01 23:58:27 2017 +0100
@@ -412,7 +412,7 @@
   |
   o  0	: base - test
   
-  $ hg evolve --any --traceback --phasedivergent
+  $ hg evolve --any --traceback --phase-divergent
   recreate:[8] another feature that rox
   atop:[7] another feature (child of ba0ec09b1bab)
   computing new diff
@@ -1062,7 +1062,7 @@
   |/
   o  0	: a0 - test
   
-  $ hg evolve -r "desc('add new file bumped')" --phasedivergent
+  $ hg evolve -r "desc('add new file bumped')" --phase-divergent
   recreate:[12] add new file bumped
   atop:[11] a2
   computing new diff
@@ -1123,7 +1123,7 @@
   set of specified revisions is empty
   [1]
 
-  $ hg evolve --rev "cce26b684bfe::" --phasedivergent
+  $ hg evolve --rev "cce26b684bfe::" --phase-divergent
   no phasedivergent changesets in specified revisions
   (do you want to use --orphan)
   [2]
--- a/tests/test-obsolete.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-obsolete.t	Wed Nov 01 23:58:27 2017 +0100
@@ -705,7 +705,7 @@
   $ hg up --hidden 2
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   working directory parent is obsolete! (4538525df7e2)
-  (4538525df7e2 has diverged, use 'hg evolve --list --contentdivergent' to resolve the issue)
+  (4538525df7e2 has diverged, use 'hg evolve --list --content-divergent' to resolve the issue)
   $ hg export 9468a5f5d8b2 | hg import -
   applying patch from stdin
   1 new unstable changesets
--- a/tests/test-sharing.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-sharing.t	Wed Nov 01 23:58:27 2017 +0100
@@ -507,7 +507,7 @@
   7:e3f99ce9d9cd  draft  fix bug 24 (v2 by alice)
 
 Use evolve to fix the divergence.
-  $ HGMERGE=internal:other hg evolve --contentdivergent
+  $ HGMERGE=internal:other hg evolve --content-divergent
   merge:[6] fix bug 24 (v2 by bob)
   with: [7] fix bug 24 (v2 by alice)
   base: [4] fix bug 24 (v1)
--- a/tests/test-stabilize-result.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-stabilize-result.t	Wed Nov 01 23:58:27 2017 +0100
@@ -156,20 +156,20 @@
 
 Stabilize!
 
-  $ hg evolve --any --dry-run --phasedivergent
+  $ hg evolve --any --dry-run --phase-divergent
   recreate:[12] newer a
   atop:[8] newer a
   hg rebase --rev (73b15c7566e9|d5c7ef82d003) --dest 66719795a494; (re)
   hg update 1cf0aacfd363;
   hg revert --all --rev (73b15c7566e9|d5c7ef82d003); (re)
   hg commit --msg "bumped update to %s" (no-eol)
-  $ hg evolve --any --confirm --phasedivergent
+  $ hg evolve --any --confirm --phase-divergent
   recreate:[12] newer a
   atop:[8] newer a
   perform evolve? [Ny] n
   abort: evolve aborted by user
   [255]
-  $ echo y | hg evolve --any --confirm --config ui.interactive=True --phasedivergent
+  $ echo y | hg evolve --any --confirm --config ui.interactive=True --phase-divergent
   recreate:[12] newer a
   atop:[8] newer a
   perform evolve? [Ny] y
@@ -247,14 +247,14 @@
 
 Stabilize it
 
-  $ hg evolve -qn --confirm --contentdivergent
+  $ hg evolve -qn --confirm --content-divergent
   merge:[19] More addition
   with: [17] More addition
   base: [15] More addition
   perform evolve? [Ny] n
   abort: evolve aborted by user
   [255]
-  $ echo y | hg evolve -qn --confirm --config ui.interactive=True --contentdivergent
+  $ echo y | hg evolve -qn --confirm --config ui.interactive=True --content-divergent
   merge:[19] More addition
   with: [17] More addition
   base: [15] More addition
@@ -265,7 +265,7 @@
   hg up -C 3932c176bbaa &&
   hg revert --all --rev tip &&
   hg commit -m "`hg log -r eacc9c8240fe --template={desc}`";
-  $ hg evolve -v --contentdivergent
+  $ hg evolve -v --content-divergent
   merge:[19] More addition
   with: [17] More addition
   base: [15] More addition
@@ -344,14 +344,14 @@
   $ hg phase 'contentdivergent()'
   21: draft
   24: draft
-  $ hg evolve -qn --contentdivergent
+  $ hg evolve -qn --content-divergent
   hg update -c 0b336205a5d0 &&
   hg merge f344982e63c4 &&
   hg commit -m "auto merge resolving conflict between 0b336205a5d0 and f344982e63c4"&&
   hg up -C 3932c176bbaa &&
   hg revert --all --rev tip &&
   hg commit -m "`hg log -r 0b336205a5d0 --template={desc}`";
-  $ hg evolve --contentdivergent
+  $ hg evolve --content-divergent
   merge:[24] More addition (2)
   with: [21] More addition
   base: [15] More addition
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-flow-publish-bare.t	Wed Nov 01 23:58:27 2017 +0100
@@ -0,0 +1,292 @@
+=====================
+Test workflow options
+=====================
+
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+  $ . "$TESTDIR/testlib/common.sh"
+
+Publishing of bare branch
+=========================
+
+  $ hg init bare-branch-server
+  $ cd bare-branch-server
+  $ cat <<EOF >> .hg/hgrc
+  > [phases]
+  > publish = no
+  > [experimental]
+  > topic.publish-bare-branch = yes
+  > EOF
+  $ mkcommit ROOT
+  $ mkcommit c_dA0
+  $ hg phase --public -r 'all()'
+  $ cd ..
+
+  $ hg clone bare-branch-server bare-client
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+pushing a simple branch publish it
+----------------------------------
+
+  $ cd bare-client
+  $ mkcommit c_dB0
+  $ hg push
+  pushing to $TESTTMP/bare-branch-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  $ hg log --rev 'sort(all(), "topo")' -GT '{rev}:{node|short} {desc} {phase} {branch} {topics}'
+  @  2:286d02a6e2a2 c_dB0 public default
+  |
+  o  1:134bc3852ad2 c_dA0 public default
+  |
+  o  0:ea207398892e ROOT public default
+  
+
+pushing two heads at the same time
+----------------------------------
+
+  $ hg update 'desc("c_dA0")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_dC0
+  created new head
+  $ hg update 'desc("c_dA0")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_dD0
+  created new head
+  $ hg push -f
+  pushing to $TESTTMP/bare-branch-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+2 heads)
+  $ hg log --rev 'sort(all(), "topo")' -GT '{rev}:{node|short} {desc} {phase} {branch} {topics}'
+  @  4:9bf953aa81f6 c_dD0 public default
+  |
+  | o  3:9d5b8e1f08a4 c_dC0 public default
+  |/
+  | o  2:286d02a6e2a2 c_dB0 public default
+  |/
+  o  1:134bc3852ad2 c_dA0 public default
+  |
+  o  0:ea207398892e ROOT public default
+  
+
+pushing something not on default
+--------------------------------
+
+  $ hg update 'desc("ROOT")'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg branch branchA
+  marked working directory as branch branchA
+  (branches are permanent and global, did you want a bookmark?)
+  $ mkcommit c_aE0
+  $ hg push --new-branch
+  pushing to $TESTTMP/bare-branch-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  $ hg log --rev 'sort(all(), "topo")' -GT '{rev}:{node|short} {desc} {phase} {branch} {topics}'
+  @  5:0db08e758601 c_aE0 public branchA
+  |
+  | o  4:9bf953aa81f6 c_dD0 public default
+  | |
+  | | o  3:9d5b8e1f08a4 c_dC0 public default
+  | |/
+  | | o  2:286d02a6e2a2 c_dB0 public default
+  | |/
+  | o  1:134bc3852ad2 c_dA0 public default
+  |/
+  o  0:ea207398892e ROOT public default
+  
+
+pushing topic
+-------------
+
+  $ hg update 'desc("c_dD0")'
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg topic foo
+  marked working directory as topic: foo
+  $ mkcommit c_dF0
+  active topic 'foo' grew its first changeset
+  $ hg push
+  pushing to $TESTTMP/bare-branch-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  $ hg log --rev 'sort(all(), "topo")' -GT '{rev}:{node|short} {desc} {phase} {branch} {topics}'
+  @  6:0867c4471796 c_dF0 draft default foo
+  |
+  o  4:9bf953aa81f6 c_dD0 public default
+  |
+  | o  3:9d5b8e1f08a4 c_dC0 public default
+  |/
+  | o  2:286d02a6e2a2 c_dB0 public default
+  |/
+  o  1:134bc3852ad2 c_dA0 public default
+  |
+  | o  5:0db08e758601 c_aE0 public branchA
+  |/
+  o  0:ea207398892e ROOT public default
+  
+
+pushing topic over a bare branch
+--------------------------------
+
+  $ hg update 'desc("c_dC0")'
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ mkcommit c_dG0
+  $ hg topic bar
+  marked working directory as topic: bar
+  $ mkcommit c_dH0
+  active topic 'bar' grew its first changeset
+  $ hg push
+  pushing to $TESTTMP/bare-branch-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  $ hg log --rev 'sort(all(), "topo")' -GT '{rev}:{node|short} {desc} {phase} {branch} {topics}'
+  @  8:858be9a8daaf c_dH0 draft default bar
+  |
+  o  7:0e4041d324d0 c_dG0 public default
+  |
+  o  3:9d5b8e1f08a4 c_dC0 public default
+  |
+  | o  2:286d02a6e2a2 c_dB0 public default
+  |/
+  | o  6:0867c4471796 c_dF0 draft default foo
+  | |
+  | o  4:9bf953aa81f6 c_dD0 public default
+  |/
+  o  1:134bc3852ad2 c_dA0 public default
+  |
+  | o  5:0db08e758601 c_aE0 public branchA
+  |/
+  o  0:ea207398892e ROOT public default
+  
+
+Pushing topic in between bare branch
+------------------------------------
+
+  $ hg update 'desc("c_dB0")'
+  1 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ mkcommit c_dI0
+  $ hg update 'desc("c_dH0")'
+  switching to topic bar
+  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ mkcommit c_dJ0
+  $ hg update 'desc("c_aE0")'
+  1 files updated, 0 files merged, 5 files removed, 0 files unresolved
+  $ mkcommit c_aK0
+  $ hg push
+  pushing to $TESTTMP/bare-branch-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  $ hg log --rev 'sort(all(), "topo")' -GT '{rev}:{node|short} {desc} {phase} {branch} {topics}'
+  @  11:b0a00ebdfd24 c_aK0 public branchA
+  |
+  o  5:0db08e758601 c_aE0 public branchA
+  |
+  | o  10:abb5c84eb9e9 c_dJ0 draft default bar
+  | |
+  | o  8:858be9a8daaf c_dH0 draft default bar
+  | |
+  | o  7:0e4041d324d0 c_dG0 public default
+  | |
+  | o  3:9d5b8e1f08a4 c_dC0 public default
+  | |
+  | | o  9:4b5570d89f0f c_dI0 public default
+  | | |
+  | | o  2:286d02a6e2a2 c_dB0 public default
+  | |/
+  | | o  6:0867c4471796 c_dF0 draft default foo
+  | | |
+  | | o  4:9bf953aa81f6 c_dD0 public default
+  | |/
+  | o  1:134bc3852ad2 c_dA0 public default
+  |/
+  o  0:ea207398892e ROOT public default
+  
+
+merging a topic in branch
+-------------------------
+
+  $ hg update default
+  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg merge foo
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m 'c_dL0'
+  $ hg push
+  pushing to $TESTTMP/bare-branch-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (-1 heads)
+  $ hg log --rev 'sort(all(), "topo")' -GT '{rev}:{node|short} {desc} {phase} {branch} {topics}'
+  @    12:a6f9f8c6c6cc c_dL0 public default
+  |\
+  | o  9:4b5570d89f0f c_dI0 public default
+  | |
+  o |  6:0867c4471796 c_dF0 public default
+  | |
+  o |  4:9bf953aa81f6 c_dD0 public default
+  | |
+  | | o  10:abb5c84eb9e9 c_dJ0 draft default bar
+  | | |
+  | | o  8:858be9a8daaf c_dH0 draft default bar
+  | | |
+  | | o  7:0e4041d324d0 c_dG0 public default
+  | | |
+  +---o  3:9d5b8e1f08a4 c_dC0 public default
+  | |
+  | o  2:286d02a6e2a2 c_dB0 public default
+  |/
+  o  1:134bc3852ad2 c_dA0 public default
+  |
+  | o  11:b0a00ebdfd24 c_aK0 public branchA
+  | |
+  | o  5:0db08e758601 c_aE0 public branchA
+  |/
+  o  0:ea207398892e ROOT public default
+  
+  $ hg log -R ../bare-branch-server --rev 'sort(all(), "topo")' -GT '{rev}:{node|short} {desc} {phase} {branch} {topics}'
+  o    12:a6f9f8c6c6cc c_dL0 public default
+  |\
+  | o  9:4b5570d89f0f c_dI0 public default
+  | |
+  o |  6:0867c4471796 c_dF0 public default
+  | |
+  o |  4:9bf953aa81f6 c_dD0 public default
+  | |
+  | | o  10:abb5c84eb9e9 c_dJ0 draft default bar
+  | | |
+  | | o  8:858be9a8daaf c_dH0 draft default bar
+  | | |
+  | | o  7:0e4041d324d0 c_dG0 public default
+  | | |
+  +---o  3:9d5b8e1f08a4 c_dC0 public default
+  | |
+  | o  2:286d02a6e2a2 c_dB0 public default
+  |/
+  @  1:134bc3852ad2 c_dA0 public default
+  |
+  | o  11:b0a00ebdfd24 c_aK0 public branchA
+  | |
+  | o  5:0db08e758601 c_aE0 public branchA
+  |/
+  o  0:ea207398892e ROOT public default
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-topic-flow-single-head.t	Wed Nov 01 23:58:27 2017 +0100
@@ -0,0 +1,188 @@
+=====================
+Test workflow options
+=====================
+
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+  $ . "$TESTDIR/testlib/common.sh"
+
+Test single head enforcing - Setup
+=============================================
+
+  $ hg init single-head-server
+  $ cd single-head-server
+  $ cat <<EOF >> .hg/hgrc
+  > [phases]
+  > publish = no
+  > [experimental]
+  > enforce-single-head = yes
+  > evolution = all
+  > EOF
+  $ mkcommit ROOT
+  $ mkcommit c_dA0
+  $ cd ..
+
+  $ hg clone single-head-server client
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Test single head enforcing - with branch only
+---------------------------------------------
+
+  $ cd client
+
+continuing the current defaultbranch
+
+  $ mkcommit c_dB0
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+creating a new branch
+
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg branch branch_A
+  marked working directory as branch branch_A
+  (branches are permanent and global, did you want a bookmark?)
+  $ mkcommit c_aC0
+  $ hg push --new-branch
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+
+Create a new head on the default branch
+
+  $ hg up 'desc("c_dA0")'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_dD0
+  created new head
+  $ hg push -f
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  transaction abort!
+  rollback completed
+  abort: 2 heads on "default"
+  (286d02a6e2a2, 9bf953aa81f6)
+  [255]
+
+remerge them
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ mkcommit c_dE0
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+
+Test single head enforcing - with topic
+---------------------------------------
+
+pushing a new topic
+
+  $ hg topic foo
+  marked working directory as topic: foo
+  $ mkcommit c_dF0
+  active topic 'foo' grew its first changeset
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+pushing a new topo branch (with a topic)
+
+  $ hg up 'desc("c_dD0")'
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg topic bar
+  marked working directory as topic: bar
+  $ mkcommit c_dG0
+  active topic 'bar' grew its first changeset
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+
+detect multiple heads on the topic
+
+  $ mkcommit c_dH0
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  $ hg up 'desc("c_dG0")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_dI0
+  $ hg push  -f
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  transaction abort!
+  rollback completed
+  abort: 2 heads on "default:bar"
+  (5194f5dcd542, 48a01453c1c5)
+  [255]
+
+merge works fine
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ mkcommit c_dJ0
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+
+Test single head enforcing - by phase move
+------------------------------------------
+
+  $ hg -R ../single-head-server phase --public 'desc("c_dJ0")'
+  abort: 2 heads on "default"
+  (6ed1df20edb1, 678bca4de98c)
+  [255]
+
+Test single head enforcing - after rewrite
+------------------------------------------
+
+  $ hg up foo
+  switching to topic foo
+  3 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ hg commit --amend -m c_dF1
+  $ hg push
+  pushing to $TESTTMP/single-head-server
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 1 files (+1 heads)
+  1 new obsolescence markers
--- a/tests/test-uncommit.t	Mon Oct 23 15:50:19 2017 +0200
+++ b/tests/test-uncommit.t	Wed Nov 01 23:58:27 2017 +0100
@@ -287,7 +287,7 @@
   $ hg up -C 3 --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   working directory parent is obsolete! (5eb72dbe0cb4)
-  (5eb72dbe0cb4 has diverged, use 'hg evolve --list --contentdivergent' to resolve the issue)
+  (5eb72dbe0cb4 has diverged, use 'hg evolve --list --content-divergent' to resolve the issue)
   $ hg --config extensions.purge= purge
   $ hg uncommit --all -X e
   1 new divergent changesets