changeset 5820:5341d7c30e68 stable

branching: merge into stable in preparation for release # no-check-commit
author Anton Shestakov <av6@dwimlabs.net>
date Thu, 11 Mar 2021 11:49:34 +0800
parents 87006dcf2bb7 (current diff) 82040a455e71 (diff)
children 84da58ad69a1
files hgext3rd/evolve/evolvecmd.py
diffstat 50 files changed, 1894 insertions(+), 548 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGELOG	Wed Feb 24 14:30:21 2021 -0800
+++ b/CHANGELOG	Thu Mar 11 11:49:34 2021 +0800
@@ -1,10 +1,25 @@
 Changelog
 =========
 
+10.3.0 - in progress
+--------------------
+
+  * evolve: add a experimental.evolution.in-memory config for running evolve
+    in memory (hg >= 5.6)
+  * evolve: improve content-divergence resolution that involves parent changes
+  * evolve: preserve wdir parent when using `hg evolve --stop`
+  * obslog: clarify the command name in the help,
+  * pdiff, pstatus: drop some irrelevant command flags inherited from `hg diff`
+    and `hg status` respectively
+  * rewind: detect and abort on cases when we rewind to changesets that are
+    precessors / successors of each other
+  * rewind: when user gives only some parts of a fold, include the other parts
+    as well, or abort if they are missing from local repo
+
 10.2.1 - in progress
 --------------------
 
- * doc: document stack as a substitue for MQ's qseries
+  * doc: document stack as a substitue for MQ's qseries
 
 10.2.0.post1 -- 2021-02-01
 --------------------------
--- a/docs/troubles-handling.rst	Wed Feb 24 14:30:21 2021 -0800
+++ b/docs/troubles-handling.rst	Thu Mar 11 11:49:34 2021 +0800
@@ -323,6 +323,21 @@
 
 Set the parent of moved changeset as resolution parent.
 
+Special-case: When parent of moved one is obsolete with a successor
+'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
+By default, evolution will set the successor of obsolete parent as resolution
+parent and will relocate both the divergent cset on it to perform 3-way merge.
+But if the following config is set to True, it will set the obsolete parent as
+resolution parent, so now resolved cset will be orphan, as it will be based on
+the obsolete parent. Some users might not like the evolve to automatically
+resovle this orphan instability as well (while they only wanted to resolve the
+divergence), which is why we are providing this config.
+
+`experimental.evolution.divergence-resolution-minimal=False(default)`
+
+(The default resolution that automatically evolve the orphan instability as well
+seems the best approach for now, but let's also gather user feedback, then we can decide accordingly)
+
 D-A3.2: both moved forward; same branch
 """""""""""""""""""""""""""""""""""""""
 
--- a/hgext3rd/evolve/__init__.py	Wed Feb 24 14:30:21 2021 -0800
+++ b/hgext3rd/evolve/__init__.py	Thu Mar 11 11:49:34 2021 +0800
@@ -52,7 +52,7 @@
     evolution.obsdiscovery = yes
 
 Obsolescence Markers Discovery
-==============================
+------------------------------
 
 The evolve extension containts an experimental new protocol to discover common
 markers between local and remote repositories.
@@ -111,7 +111,7 @@
     evolution.obsdiscovery = no
 
 Effect Flag Experiment
-======================
+----------------------
 
 Evolve also records what changed between two evolutions of a changeset. For
 example, having this information is helpful to understand what changed between
@@ -153,8 +153,21 @@
 you create will not cause interference with other clients or servers without
 the effect flag recording.
 
+In-memory Evolve Experiment
+---------------------------
+
+The :hg:`evolve` command normally creates new changesets by writing the
+files to the working copy and then committing them from there. You can
+tell it to create the changesets without touching the working copy by
+setting this config::
+
+  [experimental]
+  evolution.in-memory = yes
+
+It will still update the working copy in case of conflicts.
+
 Template keywords
-=================
+-----------------
 
 Evolve provides one template keyword that helps explore obsolescence history:
 
@@ -175,6 +188,28 @@
   - precursors (deprecated, use predecessors instead)
   - successors (deprecated, use successorssets instead)
   - troubles (deprecated, use instabilities instead)
+
+Revset predicates
+-----------------
+
+Evolve provides several revset predicates:
+
+  - unstable
+  - troubled (deprecated, use unstable instead)
+  - suspended
+  - predecessors
+  - precursors (deprecated, use predecessors instead)
+  - allpredecessors
+  - allprecursors (deprecated, use allpredecessors instead)
+  - successors
+  - allsuccessors
+
+Note that successors revset in evolve is not the same as successors revset in
+core Mercurial 4.3+. In evolve this revset returns only immediate successors,
+as opposed to all successors. Use "allsuccessors(set)" to obtain all
+successors.
+
+See :hg:`help revsets -v` for more information.
 """
 
 evolutionhelptext = b"""
@@ -241,22 +276,11 @@
 
     [experimental]
     evolution=all
-""".strip()
+"""
 
 import sys
 
-try:
-    from mercurial import registrar
-    registrar.templatekeyword # new in hg-3.8
-except ImportError:
-    from . import metadata
-    raise ImportError(b'evolve needs Mercurial version %s or above' %
-                      min(metadata.testedwith.split()))
-
 import mercurial
-from mercurial import util
-
-from mercurial import obsolete
 
 from mercurial import (
     bookmarks as bookmarksmod,
@@ -267,8 +291,9 @@
     hg,
     lock as lockmod,
     node as nodemod,
+    obsolete,
     pycompat,
-    revset,
+    util,
 )
 
 from mercurial.i18n import _
@@ -287,6 +312,7 @@
     obsexchange,
     obshashtree,
     obshistory,
+    revset,
     rewind,
     safeguard,
     templatekw,
@@ -337,17 +363,20 @@
 eh.merge(cmdrewrite.eh)
 eh.merge(rewind.eh)
 eh.merge(headchecking.eh)
+eh.merge(revset.eh)
 uisetup = eh.finaluisetup
 extsetup = eh.finalextsetup
 reposetup = eh.finalreposetup
 cmdtable = eh.cmdtable
 configtable = eh.configtable
+templatekeyword = eh.templatekeyword
 revsetpredicate = eh.revsetpredicate
-templatekeyword = eh.templatekeyword
 
 # Configuration
 eh.configitem(b'experimental', b'evolutioncommands', [])
 eh.configitem(b'experimental', b'evolution.allnewcommands', None)
+eh.configitem(b'experimental', b'evolution.divergence-resolution-minimal', False)
+eh.configitem(b'experimental', b'evolution.in-memory', b'false')
 
 #####################################################################
 ### Option configuration                                          ###
@@ -402,12 +431,6 @@
             del cmdtable[disabledcmd]
 
 #####################################################################
-### experimental behavior                                         ###
-#####################################################################
-
-getrevs = obsolete.getrevs
-
-#####################################################################
 ### Additional Utilities                                          ###
 #####################################################################
 
@@ -426,7 +449,8 @@
 def setupparentcommand(ui):
 
     _alias, statuscmd = cmdutil.findcmd(b'status', commands.table)
-    pstatusopts = [o for o in statuscmd[1] if o[1] != b'rev']
+    inapplicable = {b'rev', b'change'}
+    pstatusopts = [o for o in statuscmd[1] if o[1] not in inapplicable]
 
     @eh.command(b'pstatus', pstatusopts,
                 **compat.helpcategorykwargs('CATEGORY_WORKING_DIRECTORY'))
@@ -442,7 +466,8 @@
         return statuscmd[0](ui, repo, *args, **kwargs)
 
     _alias, diffcmd = cmdutil.findcmd(b'diff', commands.table)
-    pdiffopts = [o for o in diffcmd[1] if o[1] != b'rev']
+    inapplicable = {b'rev', b'from', b'to', b'change'}
+    pdiffopts = [o for o in diffcmd[1] if o[1] not in inapplicable]
 
     @eh.command(b'pdiff', pdiffopts,
                 **compat.helpcategorykwargs('CATEGORY_WORKING_DIRECTORY'))
@@ -464,179 +489,6 @@
                      b"diff --hidden --rev 'limit(predecessors(.),1)' --rev .",
                      b'evolve')
 
-### Unstable revset symbol
-
-@eh.revsetpredicate(b'unstable()')
-def revsetunstable(repo, subset, x):
-    """Changesets with instabilities.
-    """
-    revset.getargs(x, 0, 0, b'unstable takes no arguments')
-    troubled = set()
-    troubled.update(getrevs(repo, b'orphan'))
-    troubled.update(getrevs(repo, b'phasedivergent'))
-    troubled.update(getrevs(repo, b'contentdivergent'))
-    troubled = revset.baseset(troubled)
-    troubled.sort() # set is non-ordered, enforce order
-    return subset & troubled
-
-@eh.revsetpredicate(b'troubled()')    # legacy name
-def revsettroubled(repo, subset, x):
-    return revsetunstable(repo, subset, x)
-
-### Obsolescence graph
-
-# XXX SOME MAJOR CLEAN UP TO DO HERE XXX
-
-def _precursors(repo, s, includeidentical=False):
-    """Precursor of a changeset"""
-    cs = set()
-    getrev = compat.getgetrev(repo.changelog)
-    markerbysubj = repo.obsstore.predecessors
-    node = repo.changelog.node
-    for r in s:
-        for p in markerbysubj.get(node(r), ()):
-            if not includeidentical and p[2] & rewind.identicalflag:
-                continue
-            pr = getrev(p[0])
-            if pr is not None:
-                cs.add(pr)
-    cs -= repo.changelog.filteredrevs # nodemap has no filtering
-    return cs
-
-def _allprecursors(repo, s):  # XXX we need a better naming
-    """transitive precursors of a subset"""
-    node = repo.changelog.node
-    toproceed = [node(r) for r in s]
-    seen = set()
-    allsubjects = repo.obsstore.predecessors
-    while toproceed:
-        nc = toproceed.pop()
-        for mark in allsubjects.get(nc, ()):
-            np = mark[0]
-            if np not in seen:
-                seen.add(np)
-                toproceed.append(np)
-    getrev = compat.getgetrev(repo.changelog)
-    cs = set()
-    for p in seen:
-        pr = getrev(p)
-        if pr is not None:
-            cs.add(pr)
-    cs -= repo.changelog.filteredrevs # nodemap has no filtering
-    return cs
-
-def _successors(repo, s):
-    """Successors of a changeset"""
-    cs = set()
-    node = repo.changelog.node
-    getrev = compat.getgetrev(repo.changelog)
-    markerbyobj = repo.obsstore.successors
-    for r in s:
-        for p in markerbyobj.get(node(r), ()):
-            for sub in p[1]:
-                sr = getrev(sub)
-                if sr is not None:
-                    cs.add(sr)
-    cs -= repo.changelog.filteredrevs # nodemap has no filtering
-    return cs
-
-def _allsuccessors(repo, s, haltonflags=0):  # XXX we need a better naming
-    """transitive successors of a subset
-
-    haltonflags allows to provide flags which prevent the evaluation of a
-    marker.  """
-    node = repo.changelog.node
-    toproceed = [node(r) for r in s]
-    seen = set()
-    allobjects = repo.obsstore.successors
-    while toproceed:
-        nc = toproceed.pop()
-        for mark in allobjects.get(nc, ()):
-            if mark[2] & haltonflags:
-                continue
-            for sub in mark[1]:
-                if sub == nullid:
-                    continue # should not be here!
-                if sub not in seen:
-                    seen.add(sub)
-                    toproceed.append(sub)
-    getrev = compat.getgetrev(repo.changelog)
-    cs = set()
-    for s in seen:
-        sr = getrev(s)
-        if sr is not None:
-            cs.add(sr)
-    cs -= repo.changelog.filteredrevs # nodemap has no filtering
-    return cs
-
-#####################################################################
-### Extending revset and template                                 ###
-#####################################################################
-
-# this section add several useful revset symbol not yet in core.
-# they are subject to changes
-
-
-### XXX I'm not sure this revset is useful
-@eh.revsetpredicate(b'suspended()')
-def revsetsuspended(repo, subset, x):
-    """Obsolete changesets with non-obsolete descendants.
-    """
-    revset.getargs(x, 0, 0, b'suspended takes no arguments')
-    suspended = revset.baseset(getrevs(repo, b'suspended'))
-    suspended.sort()
-    return subset & suspended
-
-
-@eh.revsetpredicate(b'predecessors(set)')
-def revsetpredecessors(repo, subset, x):
-    """Immediate predecessors of changesets in set.
-    """
-    s = revset.getset(repo, revset.fullreposet(repo), x)
-    s = revset.baseset(_precursors(repo, s))
-    s.sort()
-    return subset & s
-
-
-@eh.revsetpredicate(b'precursors(set)')   # legacy name for predecessors
-def revsetprecursors(repo, subset, x):
-    return revsetpredecessors(repo, subset, x)
-
-
-@eh.revsetpredicate(b'allpredecessors(set)')
-def revsetallpredecessors(repo, subset, x):
-    """Transitive predecessors of changesets in set.
-    """
-    s = revset.getset(repo, revset.fullreposet(repo), x)
-    s = revset.baseset(_allprecursors(repo, s))
-    s.sort()
-    return subset & s
-
-
-@eh.revsetpredicate(b'allprecursors(set)')   # legacy name for allpredecessors
-def revsetallprecursors(repo, subset, x):
-    return revsetallpredecessors(repo, subset, x)
-
-
-@eh.revsetpredicate(b'successors(set)')
-def revsetsuccessors(repo, subset, x):
-    """Immediate successors of changesets in set.
-    """
-    s = revset.getset(repo, revset.fullreposet(repo), x)
-    s = revset.baseset(_successors(repo, s))
-    s.sort()
-    return subset & s
-
-@eh.revsetpredicate(b'allsuccessors(set)')
-def revsetallsuccessors(repo, subset, x):
-    """Transitive successors of changesets in set.
-    """
-    s = revset.getset(repo, revset.fullreposet(repo), x)
-    s = revset.baseset(_allsuccessors(repo, s))
-    s.sort()
-    return subset & s
-
-
 #####################################################################
 ### Various trouble warning                                       ###
 #####################################################################
@@ -1095,6 +947,10 @@
                                                              False))
     # making sure a next commit is formed
     if result[0] and result[1]:
+        # If using in-memory merge, _solveone() will not have updated the
+        # working copy, so we need to do that.
+        if evolvecmd.use_in_memory_merge(repo) and result[1]:
+            compat.update(repo[result[1]])
         ui.status(_(b'working directory is now at %s\n')
                   % ui.label(bytes(repo[b'.']), b'evolve.node'))
     return 0
--- a/hgext3rd/evolve/cmdrewrite.py	Wed Feb 24 14:30:21 2021 -0800
+++ b/hgext3rd/evolve/cmdrewrite.py	Thu Mar 11 11:49:34 2021 +0800
@@ -579,7 +579,7 @@
         c.write(fp)
 
     fp.seek(0)
-    oldnode = node.hex(old.node())[:12]
+    oldnode = node.short(old.node())
     message = b'temporary commit for uncommiting %s' % oldnode
     tempnode = _patchtocommit(ui, repo, old, fp, message, oldnode)
     return tempnode
@@ -1484,7 +1484,7 @@
     pickstate.load()
     pctxnode = pickstate[b'oldpctx']
     ui.status(_(b"aborting pick, updating to %s\n") %
-              node.hex(pctxnode)[:12])
+              node.short(pctxnode))
     compat.clean_update(repo[pctxnode])
     pickstate.delete()
     return 0
--- a/hgext3rd/evolve/evolvecmd.py	Wed Feb 24 14:30:21 2021 -0800
+++ b/hgext3rd/evolve/evolvecmd.py	Thu Mar 11 11:49:34 2021 +0800
@@ -32,6 +32,8 @@
     util,
 )
 
+from mercurial.utils import stringutil
+
 from mercurial.i18n import _
 
 from . import (
@@ -164,7 +166,7 @@
             progresscb()
         with state.saver(evolvestate, {b'current': orig.node()}):
             newid = _relocate(repo, orig, target, evolvestate, pctx,
-                              keepbranch, b'orphan')
+                              keepbranch, b'orphan', update=False)
             return (True, newid)
 
 def _solvephasedivergence(ui, repo, bumped, evolvestate, display,
@@ -316,7 +318,7 @@
 
     otherp1 = succsotherp1 = other.p1().rev()
     divp1 = succsdivp1 = divergent.p1().rev()
-    basep1 = base.p1().rev()
+    basep1 = succsbasep1 = base.p1().rev()
 
     # finding single successors of divp1 and otherp1
     try:
@@ -334,7 +336,7 @@
         ui.write_err(msg)
         return (False, b".")
     try:
-        utility._singlesuccessor(repo, base.p1())
+        succsbasep1 = utility._singlesuccessor(repo, base.p1())
     except utility.MultipleSuccessorsError:
         msg = (b"ambiguous orphan resolution parent for "
                b"%s (base)\n") % base
@@ -343,43 +345,41 @@
     # the changeset on which resolution changeset will be based on
     resolutionparent = succsdivp1
 
-    # divonly: non-obsolete csets which are topological ancestor of "divergent"
-    # but not "other"
-    divonly = repo.revs(b"only(%d, %d) - obsolete()" % (divergent.rev(),
-                                                        other.rev()))
-    # otheronly: non-obsolete csets which are topological ancestor of "other"
-    # but not "div"
-    otheronly = repo.revs(b"only(%d, %d) - obsolete()" % (other.rev(),
-                                                          divergent.rev()))
-    # make it exclusive set
-    divonly = set(divonly) - {divergent.rev()}
-    otheronly = set(otheronly) - {other.rev()}
-
     # testing how both the divergent changesets are arranged, there can be 4
     # possible cases here:
     #
-    # 1) both have the same parents
-    # 2) both have different parents but greatest common anscestor of them is
-    #    parent of one of them
-    # 3) both have different parents and gca is not parent of any of them
-    # 4) one of them is parent of other
+    # 1) both have the same parent (or parent's successor)
+    # 2) one of them is parent of other
+    # 3) one of them moved elsewhere
+    # 4) both of them moved elsewhere
     #
-    # we are handling 1) very good now.
-    # for 2) we will relocate one which is behind to the parent of ahead one and
-    # then solve the content-divergence the way we solve 1)
-    # for 3) and 4), we still have to decide
+    # we are handling 1) 2) 3) very well now.
+    # for 4), we still have to decide
     if otherp1 == divp1:
         # both are on the same parents
         pass
     elif divergent == other.p1():
         # both are in parent-child relation
         pass
-    elif basep1 in (divp1, otherp1):
-        # only one side moved, set parent of moved one as resolution parent
-        if basep1 == divp1:
+    elif succsbasep1 in (succsdivp1, succsotherp1):
+        # 1) either, only one side moved
+        #                -> set parent of moved one as resolution parent
+        # 2) or, both moved but one moved to succs of basep1
+        #                -> set parent of other one as resolution parent
+        # 3) or, both have same parent's successor
+        #                -> set parent's successor as resolution parent
+        if succsbasep1 == succsdivp1:
             resolutionparent = succsotherp1
         else:
             pass
+        # a special case of 3) where parent of moved one is obsolete with a
+        # successor. Also, look at section 'D-A3.1' in troubles-handling.rst
+        minimal_resolution = (
+            ui.configbool(b'experimental', b'evolution.divergence-resolution-minimal')
+            and succsdivp1 == succsotherp1 and basep1 in (divp1, otherp1)
+        )
+        if minimal_resolution:
+            resolutionparent = otherp1 if basep1 == divp1 else divp1
     else:
         # the nullrev has to be handled specially because -1 is overloaded to both
         # mean nullrev (this meaning is used for the result of changectx.rev(), as
@@ -390,22 +390,7 @@
             gca = [nodemod.nullrev]
         else:
             gca = repo.revs(b"ancestor(%d, %d)" % (succsotherp1, succsdivp1))
-
-        if succsotherp1 in gca and succsdivp1 in gca:
-            # both are not on the same parent but have same parents's succs.
-            if otheronly and divonly:
-                # case: we have visible csets on both side diverging from
-                # tca of "divergent" and "other". We still need to decide what
-                # to do in this case
-                pass
-            if otheronly:
-                resolutionparent = succsotherp1
-            elif divonly:
-                pass
-            else:
-                # no extra cset on either side
-                pass
-        elif succsotherp1 in gca and succsdivp1 not in gca:
+        if succsotherp1 in gca and succsdivp1 not in gca:
             pass
         elif succsdivp1 in gca and succsotherp1 not in gca:
             resolutionparent = succsotherp1
@@ -588,11 +573,11 @@
     assert local != other
     if local not in repo[None].parents():
         repo.ui.note(_(b"updating to \"local\" side of the conflict: %s\n") %
-                     local.hex()[:12])
+                     local)
         compat.update(local)
     # merging the two content-divergent changesets
     repo.ui.note(_(b"merging \"other\" %s changeset '%s'\n") %
-                 (TROUBLES['CONTENTDIVERGENT'], other.hex()[:12]))
+                 (TROUBLES['CONTENTDIVERGENT'], other))
     if progresscb:
         progresscb()
     with state.saver(evolvestate):
@@ -896,29 +881,18 @@
     ordering.extend(sorted(dependencies))
     return ordering
 
-def _relocate(repo, orig, dest, evolvestate, pctx=None, keepbranch=False,
-              category=None):
-    """rewrites the orig rev on dest rev
+def _rewrite_commit_message_hashes(repo, commitmsg):
+    """filter a commit description to update has to their successors
+
+    The goal of this function is to avoid description referencing obsolete
+    hashes when a stack is rewritten or evolved.
 
-    returns the node of new commit which is formed
+    Each hash in the description will be detected and, if matching an obsolete
+    changeset, it will be replaced by its successors.
+
+    Note: They might be case were such behavior might be is wrong, for example
+    if the commit message is explicitely referencing an older, obsolete changesets.
     """
-    if orig.rev() == dest.rev():
-        msg = _(b'tried to relocate a node on top of itself')
-        hint = _(b"This shouldn't happen. If you still need to move changesets, "
-                 b"please do so manually with nothing to rebase - working "
-                 b"directory parent is also destination")
-        raise error.ProgrammingError(msg, hint=hint)
-
-    if pctx is None:
-        if len(orig.parents()) == 2:
-            msg = _(b"tried to relocate a merge commit without specifying which "
-                    b"parent should be moved")
-            hint = _(b"Specify the parent by passing in pctx")
-            raise error.ProgrammingError(msg, hint)
-        pctx = orig.p1()
-
-    commitmsg = orig.description()
-
     cache = {}
     sha1s = re.findall(sha1re, commitmsg)
     unfi = repo.unfiltered()
@@ -940,17 +914,87 @@
         else:
             repo.ui.note(_(b'The stale commit message reference to %s could '
                            b'not be updated\n') % sha1)
+    return commitmsg
+
+def use_in_memory_merge(repo):
+    try:
+        from mercurial import mergestate as mergestatemod
+        mergestatemod.memmergestate
+    except (AttributeError, ImportError):
+        # no in-memory evolve if Mercurial lacks the required code
+        # hg <= 5.5 (19590b126764)
+        return False
+    config_value = repo.ui.config(b'experimental', b'evolution.in-memory')
+    if config_value == b'force':
+        return True
+    if repo.ui.hasconfig(b'hooks', b'precommit'):
+        return False
+    return stringutil.parsebool(config_value)
+
+def _relocate(repo, orig, dest, evolvestate, pctx=None, keepbranch=False,
+              category=None, update=True):
+    """rewrites the orig rev on dest rev
+
+    Also updates bookmarks and creates obsmarkers.
+
+    returns the node of new commit which is formed
+    """
+    if orig.rev() == dest.rev():
+        msg = _(b'tried to relocate a node on top of itself')
+        hint = _(b"This shouldn't happen. If you still need to move changesets, "
+                 b"please do so manually with nothing to rebase - working "
+                 b"directory parent is also destination")
+        raise error.ProgrammingError(msg, hint=hint)
+
+    if pctx is None:
+        if len(orig.parents()) == 2:
+            msg = _(b"tried to relocate a merge commit without specifying which "
+                    b"parent should be moved")
+            hint = _(b"Specify the parent by passing in pctx")
+            raise error.ProgrammingError(msg, hint)
+        pctx = orig.p1()
+
+    commitmsg = _rewrite_commit_message_hashes(repo, orig.description())
 
     tr = repo.currenttransaction()
     assert tr is not None
     if repo._activebookmark:
         repo.ui.status(_(b"(leaving bookmark %s)\n") % repo._activebookmark)
     bookmarksmod.deactivate(repo)
-    nodenew = _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg)
+    nodenew = _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg,
+                              update)
     _finalizerelocate(repo, orig, dest, nodenew, tr, category, evolvestate)
     return nodenew
 
-def _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg):
+def _relocatecommit(repo, orig, dest, pctx, keepbranch, commitmsg, update):
+    extra = dict(orig.extra())
+    if b'branch' in extra:
+        del extra[b'branch']
+    extra[b'rebase_source'] = orig.hex()
+    targetphase = max(orig.phase(), phases.draft)
+    configoverrides = {
+        (b'phases', b'new-commit'): targetphase
+    }
+    with repo.ui.configoverride(configoverrides, source=b'evolve'):
+        if not update and use_in_memory_merge(repo):
+            try:
+                return _relocatecommitinmem(
+                    repo, orig, dest, pctx, keepbranch, commitmsg, extra
+                )
+            except error.InMemoryMergeConflictsError:
+                repo.ui.status(
+                    b'hit merge conflicts; retrying merge in working copy\n')
+        return _relocatecommitondisk(repo, orig, dest, pctx, keepbranch,
+                                     commitmsg, extra)
+
+def _relocatecommitondisk(repo, orig, dest, pctx, keepbranch, commitmsg, extra):
+    """rewrites the orig rev on dest rev on disk
+
+    Creates the new commit by using the old-fashioned way of creating
+    commits by writing files to the working copy.
+
+    returns the node of new commit which is formed
+    """
     if repo[b'.'].rev() != dest.rev():
         compat._update(repo, dest, branchmerge=False, force=True)
     if keepbranch:
@@ -971,19 +1015,50 @@
         raise error.InterventionRequired(_(b"unresolved merge conflicts"),
                                          hint=hint)
 
-    extra = dict(orig.extra())
-    if b'branch' in extra:
-        del extra[b'branch']
-    extra[b'rebase_source'] = orig.hex()
+    # Commit might fail if unresolved files exist
+    return repo.commit(text=commitmsg, user=orig.user(),
+                       date=orig.date(), extra=extra)
+
+def _relocatecommitinmem(repo, orig, dest, pctx, keepbranch, commitmsg, extra):
+    """rewrites the orig rev on dest rev in memory
+
+    Creates the new commit by using the modern way of creating
+    commits without writing files to the working copy.
+
+    returns the node of new commit which is formed
+    """
+    wctx = context.overlayworkingctx(repo)
+    wctx.setbase(dest)
 
-    targetphase = max(orig.phase(), phases.draft)
-    configoverride = repo.ui.configoverride({
-        (b'phases', b'new-commit'): targetphase
-    }, source=b'evolve')
-    with configoverride:
-        # Commit might fail if unresolved files exist
-        return repo.commit(text=commitmsg, user=orig.user(),
-                           date=orig.date(), extra=extra)
+    stats = merge.graft(
+        repo,
+        orig,
+        pctx,
+        [b'destination', b'evolving'],
+        keepparent=True,
+        wctx=wctx
+    )
+
+    if stats.unresolvedcount: # some conflict
+        raise error.InMemoryMergeConflictsError()
+
+    branch = None
+    if keepbranch:
+        branch = orig.branch()
+    # FIXME: We call _compact() because it's required to correctly detect
+    # changed files. This was added to fix a regression shortly before the 5.5
+    # release. A proper fix will be done in the default branch.
+    wctx._compact()
+    memctx = wctx.tomemctx(
+        commitmsg,
+        date=orig.date(),
+        extra=extra,
+        user=orig.user(),
+        branch=branch,
+    )
+    if memctx.isempty() and not repo.ui.configbool(b'ui', b'allowemptycommit'):
+        return None
+    return repo.commitctx(memctx)
 
 def _finalizerelocate(repo, orig, dest, nodenew, tr, category, evolvestate):
     if nodenew is not None:
@@ -1397,8 +1472,20 @@
 
     return opts
 
-def _cleanup(ui, repo, startnode, shouldupdate):
-    if not shouldupdate:
+def _cleanup(ui, repo, startnode, shouldupdate, headnode):
+    """Update to the right destination after evolving, if necessary
+
+    headnode is the last node created by the evolve operation (which
+    we may need to update to when using in-memory merge)
+    """
+    # If --update was passed, we should update to some head of the evolved set,
+    # but we only need to do that in the in-memory case. If --update was not
+    # passed, we should still update the working copy to its successor if there
+    # is one.
+    if shouldupdate:
+        if use_in_memory_merge(repo) and headnode:
+            compat.update(repo[headnode])
+    else:
         # Move back to startnode, or to its successor if the start node is
         # obsolete (perhaps made obsolete by the current `hg evolve`)
         unfi = repo.unfiltered()
@@ -1593,13 +1680,14 @@
 
     ui.setconfig(b'ui', b'forcemerge', opts.get('tool', r''), b'evolve')
 
+    headnode = None
     evolvestate = state.cmdstate(repo)
     # Continuation handling
     if contopt:
         if not evolvestate:
             raise error.Abort(_(b'no interrupted evolve to continue'))
         evolvestate.load()
-        continueevolve(ui, repo, evolvestate)
+        headnode = continueevolve(ui, repo, evolvestate)
         if evolvestate[b'command'] != b'evolve':
             evolvestate.delete()
             return
@@ -1612,8 +1700,11 @@
             raise error.Abort(_(b'no interrupted evolve to stop'))
         evolvestate.load()
         stopevolve(ui, repo, evolvestate)
+        if evolvestate[b'command'] != b'evolve':
+            evolvestate.delete()
+            return
+        startnode = repo.unfiltered()[evolvestate[b'startnode']]
         evolvestate.delete()
-        return
     elif abortopt:
         if not evolvestate:
             raise error.Abort(_(b'no interrupted evolve to abort'))
@@ -1624,7 +1715,7 @@
             compat.clean_update(pctx)
             ui.status(_(b'evolve aborted\n'))
             ui.status(_(b'working directory is now at %s\n')
-                      % pctx.hex()[:12])
+                      % pctx)
             evolvestate.delete()
             return 0
         return abortevolve(ui, repo, evolvestate)
@@ -1672,21 +1763,26 @@
         tr = repo.transaction(b"evolve")
         with util.acceptintervention(tr):
             for rev in revs:
-                _solveonerev(ui, repo, rev, evolvestate, activetopic, dryrunopt,
-                             confirmopt, progresscb, targetcat)
+                (solved, newnode) = _solveonerev(ui, repo, rev, evolvestate,
+                                                 activetopic, dryrunopt,
+                                                 confirmopt, progresscb,
+                                                 targetcat)
+                if solved:
+                    headnode = newnode
                 seen += 1
 
         if showprogress:
             compat.progress(ui, _(b'evolve'), None)
 
-    _cleanup(ui, repo, startnode, shouldupdate)
+    _cleanup(ui, repo, startnode, shouldupdate, headnode)
 
 def _solveonerev(ui, repo, rev, evolvestate, activetopic, dryrunopt, confirmopt,
                  progresscb, targetcat):
     """solves one trouble, including orphan merges
 
     Like _solveone(), this solves one trouble. Unlike _solveone(), it
-    stabilizes for both parents of orphan merges.
+    stabilizes for both parents of orphan merges. Returns the same value as
+    _solveone().
     """
     curctx = repo[rev]
     revtopic = getattr(curctx, 'topic', lambda: b'')()
@@ -1719,6 +1815,7 @@
             evolvestate[b'skippedrevs'].append(curctx.node())
 
         evolvestate[b'orphanmerge'] = False
+    return ret
 
 def solveobswdp(ui, repo, opts):
     """this function updates to the successor of obsolete wdir parent"""
@@ -1787,7 +1884,6 @@
         pctx = repo[b'.']
         compat.clean_update(pctx)
     ui.status(_(b'stopped the interrupted evolve\n'))
-    ui.status(_(b'working directory is now at %s\n') % pctx)
 
 def abortevolve(ui, repo, evolvestate):
     """ logic for handling of `hg evolve --abort`"""
@@ -1862,7 +1958,7 @@
         evolvestate.delete()
         ui.status(_(b'evolve aborted\n'))
         ui.status(_(b'working directory is now at %s\n')
-                  % nodemod.hex(startnode)[:12])
+                  % nodemod.short(startnode))
     else:
         raise error.Abort(_(b"unable to abort interrupted evolve, use 'hg "
                             b"evolve --stop' to stop evolve"))
@@ -1877,7 +1973,7 @@
             compat.clean_update(pctx)
             ui.status(_(b'evolve aborted\n'))
             ui.status(_(b'working directory is now at %s\n')
-                      % pctx.hex()[:12])
+                      % pctx)
             evolvestate.delete()
             return 0
         return abortevolve(ui, repo, evolvestate)
@@ -1911,6 +2007,7 @@
         compat.progress(ui, _(b'evolve'), seen, unit=_(b'changesets'),
                         total=count)
 
+    headnode = None
     category = evolvestate[b'category']
     confirm = evolvestate[b'confirm']
     unfi = repo.unfiltered()
@@ -1937,9 +2034,11 @@
                 if newnode[0]:
                     evolvestate[b'replacements'][curctx.node()] = newnode[1]
                     evolvestate[b'lastsolved'] = newnode[1]
+                    headnode = newnode[1]
                 else:
                     evolvestate[b'skippedrevs'].append(curctx.node())
             seen += 1
+    return headnode
 
 def _continuecontentdivergent(ui, repo, evolvestate, progresscb):
     """function to continue the interrupted content-divergence resolution."""
--- a/hgext3rd/evolve/metadata.py	Wed Feb 24 14:30:21 2021 -0800
+++ b/hgext3rd/evolve/metadata.py	Thu Mar 11 11:49:34 2021 +0800
@@ -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__ = b'10.2.1.dev'
+__version__ = b'10.3.0.dev'
 testedwith = b'4.6.2 4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7'
 minimumhgversion = b'4.6'
 buglink = b'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obshistory.py	Wed Feb 24 14:30:21 2021 -0800
+++ b/hgext3rd/evolve/obshistory.py	Thu Mar 11 11:49:34 2021 +0800
@@ -61,7 +61,7 @@
      (b'f', b'filternonlocal', False, _(b'filter out non local commits')),
      (b'o', b'origin', True, _(b'show origin of changesets instead of fate')),
      ] + commands.formatteropts,
-    _(b'hg olog [OPTION]... [[-r] REV]...'),
+    _(b'hg obslog [OPTION]... [[-r] REV]...'),
     **compat.helpcategorykwargs('CATEGORY_CHANGE_NAVIGATION'))
 def cmdobshistory(ui, repo, *revs, **opts):
     """show the obsolescence history of the specified revisions
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext3rd/evolve/revset.py	Thu Mar 11 11:49:34 2021 +0800
@@ -0,0 +1,188 @@
+from __future__ import absolute_import
+
+from mercurial import (
+    node as nodemod,
+    obsolete,
+    revset,
+)
+
+from . import (
+    compat,
+    exthelper,
+    rewind,
+)
+
+eh = exthelper.exthelper()
+
+#####################################################################
+### experimental behavior                                         ###
+#####################################################################
+
+getrevs = obsolete.getrevs
+
+### Unstable revset symbol
+
+# hg <= 5.3 (48b99af7b4b3)
+@eh.revsetpredicate(b'unstable()')
+def revsetunstable(repo, subset, x):
+    """Changesets with instabilities.
+    """
+    revset.getargs(x, 0, 0, b'unstable takes no arguments')
+    troubled = set()
+    troubled.update(getrevs(repo, b'orphan'))
+    troubled.update(getrevs(repo, b'phasedivergent'))
+    troubled.update(getrevs(repo, b'contentdivergent'))
+    troubled = revset.baseset(troubled)
+    troubled.sort() # set is non-ordered, enforce order
+    return subset & troubled
+
+@eh.revsetpredicate(b'troubled()')    # legacy name
+def revsettroubled(repo, subset, x):
+    return revsetunstable(repo, subset, x)
+
+### Obsolescence graph
+
+# XXX SOME MAJOR CLEAN UP TO DO HERE XXX
+
+def _precursors(repo, s, includeidentical=False):
+    """Precursor of a changeset"""
+    cs = set()
+    getrev = compat.getgetrev(repo.changelog)
+    markerbysubj = repo.obsstore.predecessors
+    node = repo.changelog.node
+    for r in s:
+        for p in markerbysubj.get(node(r), ()):
+            if not includeidentical and p[2] & rewind.identicalflag:
+                continue
+            pr = getrev(p[0])
+            if pr is not None:
+                cs.add(pr)
+    cs -= repo.changelog.filteredrevs # nodemap has no filtering
+    return cs
+
+def _allprecursors(repo, s):  # XXX we need a better naming
+    """transitive precursors of a subset"""
+    node = repo.changelog.node
+    toproceed = [node(r) for r in s]
+    seen = set()
+    allsubjects = repo.obsstore.predecessors
+    while toproceed:
+        nc = toproceed.pop()
+        for mark in allsubjects.get(nc, ()):
+            np = mark[0]
+            if np not in seen:
+                seen.add(np)
+                toproceed.append(np)
+    getrev = compat.getgetrev(repo.changelog)
+    cs = set()
+    for p in seen:
+        pr = getrev(p)
+        if pr is not None:
+            cs.add(pr)
+    cs -= repo.changelog.filteredrevs # nodemap has no filtering
+    return cs
+
+def _successors(repo, s):
+    """Successors of a changeset"""
+    cs = set()
+    node = repo.changelog.node
+    getrev = compat.getgetrev(repo.changelog)
+    markerbyobj = repo.obsstore.successors
+    for r in s:
+        for p in markerbyobj.get(node(r), ()):
+            for sub in p[1]:
+                sr = getrev(sub)
+                if sr is not None:
+                    cs.add(sr)
+    cs -= repo.changelog.filteredrevs # nodemap has no filtering
+    return cs
+
+def _allsuccessors(repo, s, haltonflags=0):  # XXX we need a better naming
+    """transitive successors of a subset
+
+    haltonflags allows to provide flags which prevent the evaluation of a
+    marker.  """
+    node = repo.changelog.node
+    toproceed = [node(r) for r in s]
+    seen = set()
+    allobjects = repo.obsstore.successors
+    while toproceed:
+        nc = toproceed.pop()
+        for mark in allobjects.get(nc, ()):
+            if mark[2] & haltonflags:
+                continue
+            for sub in mark[1]:
+                if sub == nodemod.nullid:
+                    continue # should not be here!
+                if sub not in seen:
+                    seen.add(sub)
+                    toproceed.append(sub)
+    getrev = compat.getgetrev(repo.changelog)
+    cs = set()
+    for s in seen:
+        sr = getrev(s)
+        if sr is not None:
+            cs.add(sr)
+    cs -= repo.changelog.filteredrevs # nodemap has no filtering
+    return cs
+
+#####################################################################
+### Extending revsets                                             ###
+#####################################################################
+
+# this section adds several useful revset predicates not yet in core.
+# they are subject to changes
+
+### XXX I'm not sure this revset is useful
+@eh.revsetpredicate(b'suspended()')
+def revsetsuspended(repo, subset, x):
+    """Obsolete changesets with non-obsolete descendants.
+    """
+    revset.getargs(x, 0, 0, b'suspended takes no arguments')
+    suspended = revset.baseset(getrevs(repo, b'suspended'))
+    suspended.sort()
+    return subset & suspended
+
+@eh.revsetpredicate(b'predecessors(set)')
+def revsetpredecessors(repo, subset, x):
+    """Immediate predecessors of changesets in set.
+    """
+    s = revset.getset(repo, revset.fullreposet(repo), x)
+    s = revset.baseset(_precursors(repo, s))
+    s.sort()
+    return subset & s
+
+@eh.revsetpredicate(b'precursors(set)')   # legacy name for predecessors
+def revsetprecursors(repo, subset, x):
+    return revsetpredecessors(repo, subset, x)
+
+@eh.revsetpredicate(b'allpredecessors(set)')
+def revsetallpredecessors(repo, subset, x):
+    """Transitive predecessors of changesets in set.
+    """
+    s = revset.getset(repo, revset.fullreposet(repo), x)
+    s = revset.baseset(_allprecursors(repo, s))
+    s.sort()
+    return subset & s
+
+@eh.revsetpredicate(b'allprecursors(set)')   # legacy name for allpredecessors
+def revsetallprecursors(repo, subset, x):
+    return revsetallpredecessors(repo, subset, x)
+
+@eh.revsetpredicate(b'successors(set)')
+def revsetsuccessors(repo, subset, x):
+    """Immediate successors of changesets in set.
+    """
+    s = revset.getset(repo, revset.fullreposet(repo), x)
+    s = revset.baseset(_successors(repo, s))
+    s.sort()
+    return subset & s
+
+@eh.revsetpredicate(b'allsuccessors(set)')
+def revsetallsuccessors(repo, subset, x):
+    """Transitive successors of changesets in set.
+    """
+    s = revset.getset(repo, revset.fullreposet(repo), x)
+    s = revset.baseset(_allsuccessors(repo, s))
+    s.sort()
+    return subset & s
--- a/hgext3rd/evolve/rewind.py	Wed Feb 24 14:30:21 2021 -0800
+++ b/hgext3rd/evolve/rewind.py	Thu Mar 11 11:49:34 2021 +0800
@@ -20,6 +20,7 @@
 from . import (
     compat,
     exthelper,
+    obshistory,
     rewriteutil,
 )
 
@@ -93,6 +94,9 @@
 
         targets = _select_rewind_targets(repo, opts)
 
+        extratargets = _walk_obsmarkers(ui, unfi, targets)
+        targets.extend(extratargets)
+
         for rev in targets:
             ctx = unfi[rev]
             ssets = obsutil.successorssets(repo, ctx.node(), cache=sscache)
@@ -188,6 +192,139 @@
     if update_target is not None and not opts.get('keep'):
         ui.status(_(b'working directory is now at %s\n') % repo[b'.'])
 
+def _check_multiple_predecessors(targetnode, successor, targetset):
+    """ check if a successor of one rewind target is already another target
+
+        An example obsolescence marker tree:
+        A0  A1  A2
+        x - x - o
+
+        When user tries to rewind A2 to both its predecessors with e.g.
+        `hg rewind --to A0+A1`, this function will at one point be called with
+        (A0, A1, [A0, A1]) as arguments. In that case it will raise an Abort
+        and prevent rewind from succeeding.
+    """
+    if successor in targetset:
+        msg = _(b'not rewinding, %s is a successor of %s')
+        msg %= (nodemod.short(successor), nodemod.short(targetnode))
+        hint = _(b'pick only one of these changesets, possibly with --exact')
+        raise error.Abort(msg, hint=hint)
+
+def _walk_successors(ui, unfi, targetset):
+    """follow successors of targets and find the latest successors
+
+    While doing that, check and abort if there are multiple unrelated
+    predecessors in targetset.
+
+    We also keep track of "source": when we reach a successor by following the
+    obsmarker-graph starting from a certain target, we add `successor: target`
+    pair to `source`. But we don't care about having more than one `target`
+    (i.e. predecessor) for each `successor`, because `source` is used later on
+    for finding "new" successors that we didn't find in this function.
+    """
+    source = {}
+    latest = set()
+    for targetnode in targetset:
+        # following successors for each target node separately
+        remaining = set([targetnode])
+        while remaining:
+            current = remaining.pop()
+            markers = unfi.obsstore.successors.get(current, ())
+            for marker in markers:
+                for successor in marker[1]:
+                    if successor in source:
+                        # We have already reached this successor while
+                        # processing this or any other targetnode. This means
+                        # not every target node will get their latest successor
+                        # found if e.g. there's a fold (and that is fine).
+                        # (Also basic cycle protection.)
+                        continue
+                    # TODO: this could be moved to a post-processing stage
+                    _check_multiple_predecessors(targetnode, successor, targetset)
+                    source[successor] = current
+                    remaining.add(successor)
+            if not markers:
+                latest.add(current)
+
+    return latest, source
+
+def _check_unknown_predecessors(unfi, nodes, source):
+    """ check if any nodes are absent from both source and unfiltered repo
+
+    If node is not in source, we might want it as an extra rewind target. But
+    if it's also absent from local (unfiltered) repo, we need to warn user and
+    abort.
+    """
+    missing = {
+        node for node in nodes
+        if node not in source and node not in unfi
+    }
+    if missing:
+        msg = _(b'not rewinding, some predecessors are unknown locally: %s')
+        msg %= b' '.join(nodemod.short(m) for m in missing)
+        hint = _(b'try selecting all changesets to rewind to manually, '
+                 b'possibly with --exact')
+        raise error.Abort(msg, hint=hint)
+
+def _walk_predecessors(ui, unfi, targetset, latest, source):
+    """follow predecessors of targets, validate and suggest extra targets
+
+    While doing that, check and abort if any fold components are unknown.
+
+    Skip predecessors that are only reachable by following obsmarkers with
+    "identical" flag, because these markers are for internal use and actual
+    users don't want to revive such predecessors.
+
+    Note: picking the first reached (IOW, the latest) predecessor is done on
+    purpose. We don't want rewind to assume too much, but also, and more
+    importantly, we don't want rewind to deal with any potential merge
+    conflicts. And when rewind revives one of the latest predecessors and it is
+    based on an obsolete changeset that was not revived, the revived changeset
+    will simply be an orphan, and it will be up to the user to decide how they
+    want to solve the trouble _after_ rewind is finished. This way of handling
+    rewinds that may produce merge conflicts means less chance to lose work.
+    """
+    remaining = set(latest)
+    seen = set(remaining)
+    extratargets = []
+    while remaining:
+        successor = remaining.pop()
+        markers = unfi.obsstore.predecessors.get(successor, ())
+        data = (((marker[0],), (marker,)) for marker in markers)
+        for (nodes, markers) in sorted(obshistory.groupbyfoldid(data)):
+            # TODO: this could be moved to a post-processing stage
+            _check_unknown_predecessors(unfi, nodes, source)
+            for node in nodes:
+                if node in seen:
+                    # basic cycle protection
+                    continue
+                if node in source:
+                    # we've been here while following successors
+                    seen.add(node)
+                    remaining.add(node)
+                elif node not in targetset:
+                    # skipping meta obsmarkers from previous rewind operations
+                    identical = [m[2] & identicalflag for m in markers]
+                    if not all(identical):
+                        extratargets.append(unfi[node].rev())
+
+    return extratargets
+
+def _walk_obsmarkers(ui, unfi, targets):
+    """walking of both successors and predecessors of changesets to rewind to
+
+    This function:
+        - traverses successors of every target, then
+        - traverses predecessors of every latest successor of each target
+        - returns reachable predecessors with content changes
+    """
+    node = unfi.changelog.node
+    targetset = set(node(target) for target in targets)
+    latest, source = _walk_successors(ui, unfi, targetset)
+    extratargets = _walk_predecessors(ui, unfi, targetset, latest, source)
+
+    return extratargets
+
 def _select_rewind_targets(repo, opts):
     """select the revisions we should rewind to
     """
--- a/hgext3rd/topic/__init__.py	Wed Feb 24 14:30:21 2021 -0800
+++ b/hgext3rd/topic/__init__.py	Thu Mar 11 11:49:34 2021 +0800
@@ -232,7 +232,7 @@
               b'topic.active': b'green',
               }
 
-__version__ = b'0.21.1.dev'
+__version__ = b'0.22.0.dev'
 
 testedwith = b'4.6.2 4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7'
 minimumhgversion = b'4.6'
--- a/tests/test-check-sdist.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-check-sdist.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,5 +1,3 @@
-#require test-repo
-
 Enable obsolescence to avoid the warning issue when obsmarkers are found
 
   $ cat << EOF >> "$HGRCPATH"
@@ -8,6 +6,16 @@
   > EOF
 
   $ cd "$TESTDIR"/..
+
+Archiving to a separate location to avoid hardlink mess when the repo is shared
+
+#if test-repo
+
+  $ hg archive "$TESTTMP"/hg-evolve
+  $ cd "$TESTTMP"/hg-evolve
+
+#endif
+
   $ "$PYTHON" setup.py sdist --dist-dir "$TESTTMP"/dist > /dev/null
   */dist.py:*: UserWarning: Unknown distribution option: 'python_requires' (glob)
     warnings.warn(msg)
@@ -27,7 +35,7 @@
 
   $ tar -tzf hg-evolve-*.tar.gz | sed 's|^hg-evolve-[^/]*/||' | sort > files
   $ wc -l files
-  343 files
+  347 files
   $ fgrep debian files
   tests/test-check-debian.t
   $ fgrep __init__.py files
--- a/tests/test-corrupt.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-corrupt.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,17 +1,9 @@
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
   > [phases]
   > publish = False
   > [alias]
   > qlog = log --template='{rev} - {node|short} {desc} ({phase})\n'
-  > [diff]
-  > git = 1
-  > unified = 0
   > [extensions]
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
--- a/tests/test-evolve-abort-orphan.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-abort-orphan.t	Thu Mar 11 11:49:34 2021 +0800
@@ -27,6 +27,14 @@
   > EOF
 #endif
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
   $ hg init abortrepo
   $ cd abortrepo
   $ echo ".*\.orig" > .hgignore
@@ -97,6 +105,8 @@
   $ hg evolve --all
   move:[4] added d
   atop:[5] added c
+  merging d (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -144,6 +154,8 @@
   $ hg evolve --all --update
   move:[4] added d
   atop:[5] added c
+  merging d (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -172,6 +184,8 @@
   move:[2] added b
   atop:[7] added a
   move:[5] added c
+  merging c (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -284,6 +298,8 @@
   atop:[7] added a
   move:[6] foo to a
   atop:[7] added a
+  merging a (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging a
   warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -349,6 +365,8 @@
   atop:[7] added a
   move:[6] foo to a
   atop:[7] added a
+  merging a (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging a
   warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -427,6 +445,8 @@
   atop:[9] added c
   move:[6] foo to a
   atop:[7] added a
+  merging a (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging a
   warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -503,6 +523,8 @@
   move:[2] added b
   atop:[4] added a
   move:[3] added c
+  merging c (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -550,6 +572,8 @@
   $ hg next --evolve
   move:[3] added c
   atop:[5] added b
+  merging c (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
--- a/tests/test-evolve-abort-phasediv.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-abort-phasediv.t	Thu Mar 11 11:49:34 2021 +0800
@@ -27,6 +27,14 @@
   > EOF
 #endif
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
   $ hg init abortrepo
   $ cd abortrepo
   $ echo ".*\.orig" > .hgignore
--- a/tests/test-evolve-content-divergent-basic.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-content-divergent-basic.t	Thu Mar 11 11:49:34 2021 +0800
@@ -8,14 +8,6 @@
   $ cat >> $HGRCPATH <<EOF
   > [alias]
   > glog = log -GT "{rev}:{node|short} {desc|firstline}\n ({bookmarks}) [{branch}] {phase}"
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
   > [diff]
   > git = 1
   > unified = 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-content-divergent-case-A5.t	Thu Mar 11 11:49:34 2021 +0800
@@ -0,0 +1,183 @@
+===============================================
+Testing content-divergence resolution: Case A.5
+===============================================
+
+Independent rewrites of same changeset can lead to content-divergence. In most
+common cases, it can occur when multiple users rewrite the same changeset
+independently and push it.
+
+This test belongs to a series of tests checking the resolution of content-divergent
+changesets.
+
+Category A: no parents are obsolete
+Testcase 5: one side relocated backward and other rebased to parent's successor
+Variants:
+# a: "local" is relocated backward
+# b: "other" is relocated backward
+
+A.5 Relocated backward; Rebased to parent's successor
+=====================================================
+
+.. (Divergence reason):
+..    local: relocated the changeset backward in the graph
+..    other: rebased to the successor of parent
+.. Since one side rebased to the successor of parent and other cset relocated backward,
+.. the most reasonable behaviour is to set the parent of "backward-relocated" cset
+.. as resolution parent of divergence.
+..
+.. (local):
+..
+..      C ø⇠○ C'
+..        | |
+..      B ○ |
+..        | /
+..      A ○
+..        |
+..      O ●
+..
+.. (other):
+..
+..      C ø⇠○ C''
+..        | |
+..      B ø⇠○ B'
+..        | /
+..      A ○
+..        |
+..      O ●
+..
+.. (Resolution):
+..
+..   B'○  ○ C'''
+..     | /
+..   A ○
+..     |
+..   O ●
+..
+
+Setup
+-----
+  $ . $TESTDIR/testlib/content-divergence-util.sh
+  $ setuprepos A.5
+  creating test repo for test case A.5
+  - upstream
+  - local
+  - other
+  cd into `local` and proceed with env setup
+
+initial
+
+  $ cd upstream
+  $ mkcommit A
+  $ mkcommit B
+  $ mkcommit C
+  $ cd ../local
+  $ hg pull -qu
+  $ hg rebase -r 'desc(C)' -d 'desc(A)'
+  rebasing 3:d90aa47aa5d3 tip "C"
+
+  $ cd ../other
+  $ hg pull -qu
+  $ hg prev
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [2] B
+  $ echo newB > B
+  $ hg amend
+  1 new orphan changesets
+  $ hg next
+  move:[3] C
+  atop:[4] B
+  working directory is now at f085ae420789
+  $ hg push -q
+
+  $ cd ../local
+  $ hg push -fq
+  2 new content-divergent changesets
+  $ hg pull -q
+  2 new content-divergent changesets
+
+
+Actual test of resolution
+-------------------------
+
+Variant_a: when "local" is rebased backward
+-------------------------------------------
+  $ hg evolve -l
+  b80b2bbeb664: C
+    content-divergent: f085ae420789 (draft) (precursor d90aa47aa5d3)
+  
+  f085ae420789: C
+    content-divergent: b80b2bbeb664 (draft) (precursor d90aa47aa5d3)
+  
+  $ hg log -G
+  *  6:f085ae420789 (draft): C [content-divergent]
+  |
+  o  5:7db72af2e30d (draft): B
+  |
+  | @  4:b80b2bbeb664 (draft): C [content-divergent]
+  |/
+  o  1:f5bc6836db60 (draft): A
+  |
+  o  0:a9bdc8b26820 (public): O
+  
+  $ hg evolve --content-divergent
+  merge:[4] C
+  with: [6] C
+  base: [3] C
+  rebasing "other" content-divergent changeset f085ae420789 on f5bc6836db60
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 88b737dc9dd8
+
+  $ hg log -G
+  @  8:88b737dc9dd8 (draft): C
+  |
+  | o  5:7db72af2e30d (draft): B
+  |/
+  o  1:f5bc6836db60 (draft): A
+  |
+  o  0:a9bdc8b26820 (public): O
+  
+  $ hg evolve -l
+
+
+Variant_b: when "other" is rebased backward
+-------------------------------------------
+
+  $ cd ../other
+  $ hg pull -q
+  2 new content-divergent changesets
+  $ hg evolve -l
+  f085ae420789: C
+    content-divergent: b80b2bbeb664 (draft) (precursor d90aa47aa5d3)
+  
+  b80b2bbeb664: C
+    content-divergent: f085ae420789 (draft) (precursor d90aa47aa5d3)
+  
+  $ hg log -G
+  *  6:b80b2bbeb664 (draft): C [content-divergent]
+  |
+  | @  5:f085ae420789 (draft): C [content-divergent]
+  | |
+  | o  4:7db72af2e30d (draft): B
+  |/
+  o  1:f5bc6836db60 (draft): A
+  |
+  o  0:a9bdc8b26820 (public): O
+  
+  $ hg evolve --content-divergent
+  merge:[5] C
+  with: [6] C
+  base: [3] C
+  rebasing "divergent" content-divergent changeset f085ae420789 on f5bc6836db60
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at fa4ff8bb3531
+
+  $ hg log -G
+  @  8:fa4ff8bb3531 (draft): C
+  |
+  | o  4:7db72af2e30d (draft): B
+  |/
+  o  1:f5bc6836db60 (draft): A
+  |
+  o  0:a9bdc8b26820 (public): O
+  
+  $ hg evolve -l
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-content-divergent-case-B1.t	Thu Mar 11 11:49:34 2021 +0800
@@ -0,0 +1,198 @@
+===============================================
+Testing content-divergence resolution: Case B.1
+===============================================
+
+Independent rewrites of same changeset can lead to content-divergence. In most
+common cases, it can occur when multiple users rewrite the same changeset
+independently and push it.
+
+This test belongs to a series of tests checking the resolution of content-divergent
+changesets.
+
+Category B: parents are obsolete
+Testcase 1: one side amended changes and other rebased to in-between successor of basep1
+Variants:
+# a: default resolution
+# b: minimal resolution using `experimental.evolution.divergence-resolution-minimal=True`
+
+B.1 Relocated backward; Rebased to parent's successor
+=====================================================
+
+.. (Divergence reason):
+..    local: rebased to the 'in-between' successor of basep1
+..    other: amended some changes
+.. The default resolution here is that we choose the final successor as resolution parent,
+.. but this behavior can be changed to use the 'in-between' successor as resolution parent
+.. by using a config option `experimental.evolution.divergence-resolution-minimal=True`
+..
+.. This test case is considered complicated and can change its behavior acc. to the user
+.. feedback. For more, please look at section 'D-A3.1' in troubles-handling.rst
+..
+.. (local):
+..
+..      B ø → ○ B'
+..        |   |
+..      A ø → ø A' → ○ A''
+..        |   |      |
+..        |----      |
+..        |-----------
+..        |
+..      O ●
+..
+.. (other):
+..
+..      B ø→○ B'
+..        | /
+..      A ○
+..        |
+..      O ●
+..
+.. (Resolution):
+..
+..     ○ B'''
+..     |
+..     ○ A''
+..     |
+..     ● O
+..
+
+Setup
+-----
+  $ . $TESTDIR/testlib/content-divergence-util.sh
+  $ setuprepos B.1
+  creating test repo for test case B.1
+  - upstream
+  - local
+  - other
+  cd into `local` and proceed with env setup
+
+initial
+
+  $ cd upstream
+  $ mkcommit A
+  $ mkcommit B
+  $ cd ../local
+  $ hg pull -qu
+  $ hg prev
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  [1] A
+  $ echo fooA >> A
+  $ hg amend -m 'new_A'
+  1 new orphan changesets
+  $ hg evolve
+  move:[2] B
+  atop:[3] new_A
+  $ echo barA >> A
+  $ hg amend -m 'latest_A'
+  1 new orphan changesets
+
+  $ cd ../other
+  $ hg pull -qu
+  $ echo fooB > B
+  $ hg amend -m 'new_B'
+  $ hg push -q
+
+  $ cd ../local
+  $ hg push -fq
+  2 new orphan changesets
+  2 new content-divergent changesets
+  $ hg pull -q
+  1 new orphan changesets
+  2 new content-divergent changesets
+
+
+Actual test of resolution
+-------------------------
+
+Variant_a: default resolution
+-----------------------------
+  $ hg evolve -l
+  429afd16ac76: B
+    orphan: 1ffcccee011c (obsolete parent)
+    content-divergent: 807cc2b37fb3 (draft) (precursor f6fbb35d8ac9)
+  
+  807cc2b37fb3: new_B
+    orphan: f5bc6836db60 (obsolete parent)
+    content-divergent: 429afd16ac76 (draft) (precursor f6fbb35d8ac9)
+  
+  $ hg log -G
+  *  6:807cc2b37fb3 (draft): new_B [orphan content-divergent]
+  |
+  | @  5:45ed635c7cfc (draft): latest_A
+  | |
+  | | *  4:429afd16ac76 (draft): B [orphan content-divergent]
+  | | |
+  | | x  3:1ffcccee011c (draft): new_A
+  | |/
+  x |  1:f5bc6836db60 (draft): A
+  |/
+  o  0:a9bdc8b26820 (public): O
+  
+  $ hg evolve --content-divergent
+  merge:[4] B
+  with: [6] new_B
+  base: [2] B
+  rebasing "divergent" content-divergent changeset 429afd16ac76 on 45ed635c7cfc
+  rebasing "other" content-divergent changeset 807cc2b37fb3 on 45ed635c7cfc
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg log -G
+  o  9:6f740085e668 (draft): new_B
+  |
+  @  5:45ed635c7cfc (draft): latest_A
+  |
+  o  0:a9bdc8b26820 (public): O
+  
+  $ hg evolve -l
+
+
+Variant_b: minimal resolution
+-----------------------------
+
+  $ cd ../other
+  $ hg pull -q
+  2 new orphan changesets
+  2 new content-divergent changesets
+  $ hg evolve -l
+  807cc2b37fb3: new_B
+    orphan: f5bc6836db60 (obsolete parent)
+    content-divergent: 429afd16ac76 (draft) (precursor f6fbb35d8ac9)
+  
+  429afd16ac76: B
+    orphan: 1ffcccee011c (obsolete parent)
+    content-divergent: 807cc2b37fb3 (draft) (precursor f6fbb35d8ac9)
+  
+  $ hg log -G
+  o  6:45ed635c7cfc (draft): latest_A
+  |
+  | *  5:429afd16ac76 (draft): B [orphan content-divergent]
+  | |
+  | x  4:1ffcccee011c (draft): new_A
+  |/
+  | @  3:807cc2b37fb3 (draft): new_B [orphan content-divergent]
+  | |
+  | x  1:f5bc6836db60 (draft): A
+  |/
+  o  0:a9bdc8b26820 (public): O
+  
+  $ hg evolve --content-divergent --config experimental.evolution.divergence-resolution-minimal=True
+  merge:[3] new_B
+  with: [5] B
+  base: [2] B
+  rebasing "divergent" content-divergent changeset 807cc2b37fb3 on 1ffcccee011c
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 2431e876af63
+
+  $ hg log -G
+  @  8:2431e876af63 (draft): new_B [orphan]
+  |
+  | o  6:45ed635c7cfc (draft): latest_A
+  | |
+  x |  4:1ffcccee011c (draft): new_A
+  |/
+  o  0:a9bdc8b26820 (public): O
+  
+  $ hg evolve -l
+  2431e876af63: new_B
+    orphan: 1ffcccee011c (obsolete parent)
+  
--- a/tests/test-evolve-content-divergent-corner-cases.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-content-divergent-corner-cases.t	Thu Mar 11 11:49:34 2021 +0800
@@ -8,14 +8,6 @@
   $ cat >> $HGRCPATH <<EOF
   > [alias]
   > glog = log -GT "{rev}:{node|short} {desc|firstline}\n ({bookmarks}) [{branch}] {phase}"
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
   > [diff]
   > git = 1
   > unified = 0
--- a/tests/test-evolve-content-divergent-interrupted.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-content-divergent-interrupted.t	Thu Mar 11 11:49:34 2021 +0800
@@ -259,12 +259,10 @@
   $ echo ".*\.orig" > .hgignore
   $ hg add .hgignore
   $ hg ci -m "added hgignore"
-  $ for ch in a b c d; do echo foo > $ch; hg add $ch; hg ci -qm "added "$ch; done;
+  $ for ch in a b c; do echo foo > $ch; hg add $ch; hg ci -qm "added "$ch; done;
 
   $ hg glog
-  @  4:c41c793e0ef1 added d
-  |   () draft
-  o  3:ca1b80f7960a added c
+  @  3:ca1b80f7960a added c
   |   () draft
   o  2:b1661037fa25 added b
   |   () draft
@@ -273,28 +271,36 @@
   o  0:8fa14d15e168 added hgignore
       () draft
 
+changes to get merge conflict during relocation
+  $ echo "some_changes" >> a
+  $ hg amend
+  $ echo foo > d
+  $ hg add d
+  $ hg ci -m "added d"
+
   $ hg rebase -r . -d .^^^ --config extensions.rebase=
-  rebasing 4:c41c793e0ef1 tip "added d"
+  rebasing 5:f8b09dd867e5 tip "added d"
   $ echo bar > c
   $ hg add c
   $ hg amend
 
   $ hg up --hidden 'min(desc("added d"))'
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  updated to hidden changeset c41c793e0ef1
-  (hidden revision 'c41c793e0ef1' was rewritten as: 69bdd23a9b0d)
-  working directory parent is obsolete! (c41c793e0ef1)
-  (use 'hg evolve' to update to its successor: 69bdd23a9b0d)
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset f8b09dd867e5
+  (hidden revision 'f8b09dd867e5' was rewritten as: 6f7eaf1944c0)
+  working directory parent is obsolete! (f8b09dd867e5)
+  (use 'hg evolve' to update to its successor: 6f7eaf1944c0)
   $ echo bar > d
+  $ echo "latest_changes" >> a
   $ hg amend
   2 new content-divergent changesets
 
   $ hg glog
-  @  7:e49523854bc8 added d
+  @  8:a8673909e314 added d
   |   () draft
-  | *  6:69bdd23a9b0d added d
+  | *  7:6f7eaf1944c0 added d
   | |   () draft
-  o |  3:ca1b80f7960a added c
+  o |  4:33c16a2e0eb8 added c
   | |   () draft
   o |  2:b1661037fa25 added b
   |/    () draft
@@ -304,28 +310,26 @@
       () draft
 
   $ hg evolve --content-divergent
-  merge:[6] added d
-  with: [7] added d
-  base: [4] added d
-  rebasing "other" content-divergent changeset e49523854bc8 on c7586e2a9264
-  file 'c' was deleted in other but was modified in local.
-  You can use (c)hanged version, (d)elete, or leave (u)nresolved.
-  What do you want to do? u
-  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  merge:[7] added d
+  with: [8] added d
+  base: [5] added d
+  rebasing "other" content-divergent changeset a8673909e314 on c7586e2a9264
+  merging a
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
   (see 'hg help evolve.interrupted')
   [240]
 
   $ hg evolve --abort
   evolve aborted
-  working directory is now at e49523854bc8
+  working directory is now at a8673909e314
 
   $ hg glog
-  @  7:e49523854bc8 added d
+  @  8:a8673909e314 added d
   |   () draft
-  | *  6:69bdd23a9b0d added d
+  | *  7:6f7eaf1944c0 added d
   | |   () draft
-  o |  3:ca1b80f7960a added c
+  o |  4:33c16a2e0eb8 added c
   | |   () draft
   o |  2:b1661037fa25 added b
   |/    () draft
@@ -339,17 +343,17 @@
 ---------------------------------------------------------------------------------
 
   $ hg up 'min(desc("added d"))'
-  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  3 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg rm c
   $ echo wat > d
   $ hg amend
 
   $ hg glog
-  @  8:33e4442acf98 added d
+  @  9:b6a3f3ee0c44 added d
   |   () draft
-  | *  7:e49523854bc8 added d
+  | *  8:a8673909e314 added d
   | |   () draft
-  | o  3:ca1b80f7960a added c
+  | o  4:33c16a2e0eb8 added c
   | |   () draft
   | o  2:b1661037fa25 added b
   |/    () draft
@@ -359,27 +363,44 @@
       () draft
 
   $ hg evolve --content-divergent
-  merge:[7] added d
-  with: [8] added d
-  base: [4] added d
-  rebasing "divergent" content-divergent changeset e49523854bc8 on c7586e2a9264
+  merge:[8] added d
+  with: [9] added d
+  base: [5] added d
+  rebasing "divergent" content-divergent changeset a8673909e314 on c7586e2a9264
+  merging a
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [240]
+
+this test case is mainly to test that we hit merge conlict while merging the
+two divergent csets, so resolving this one which happened during relocation
+  $ echo a > a
+  $ hg res -m
+  (no more unresolved files)
+  continue: hg evolve --continue
+
+  $ hg evolve -c
+  evolving 8:a8673909e314 "added d"
+  merging a
   merging d
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
-  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 2 files unresolved
   unresolved merge conflicts
   (see 'hg help evolve.interrupted')
   [240]
 
   $ hg evolve --abort
   evolve aborted
-  working directory is now at 33e4442acf98
+  working directory is now at b6a3f3ee0c44
 
   $ hg glog
-  @  8:33e4442acf98 added d
+  @  9:b6a3f3ee0c44 added d
   |   () draft
-  | *  7:e49523854bc8 added d
+  | *  8:a8673909e314 added d
   | |   () draft
-  | o  3:ca1b80f7960a added c
+  | o  4:33c16a2e0eb8 added c
   | |   () draft
   | o  2:b1661037fa25 added b
   |/    () draft
@@ -517,12 +538,12 @@
 
   $ hg evolve --stop
   stopped the interrupted evolve
-  working directory is now at e49523854bc8
+  working directory is now at 517d4375cb72
 
   $ hg glog
-  *  7:517d4375cb72 added d
+  @  7:517d4375cb72 added d
   |   () draft
-  | @  5:e49523854bc8 added d
+  | *  5:e49523854bc8 added d
   | |   () draft
   | o  3:ca1b80f7960a added c
   | |   () draft
@@ -537,17 +558,25 @@
 relocation
 ---------------------------------------------------------------------------
 
-  $ echo babar > c
-  $ hg add c
-  c already tracked!
+Making changes to make sure that it hits conflict while relocating
+  $ hg up -r 3
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo "some_changes" >> a
+  $ hg amend
+  1 new orphan changesets
+  $ hg next
+  move:[5] added d
+  atop:[8] added c
+  working directory is now at dc9ba677cba1
+  $ echo "latest_changes" >> a
   $ hg amend
   $ hg glog
-  @  8:2d664a4ab749 added d
+  @  10:0892835a581f added d
+  |   () draft
+  o  8:33c16a2e0eb8 added c
   |   () draft
   | *  7:517d4375cb72 added d
   | |   () draft
-  o |  3:ca1b80f7960a added c
-  | |   () draft
   o |  2:b1661037fa25 added b
   |/    () draft
   o  1:c7586e2a9264 added a
@@ -557,47 +586,43 @@
 
   $ hg evolve --content-divergent
   merge:[7] added d
-  with: [8] added d
+  with: [10] added d
   base: [4] added d
-  rebasing "other" content-divergent changeset 2d664a4ab749 on c7586e2a9264
-  file 'c' was deleted in local but was modified in other.
-  You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
-  What do you want to do? u
-  merging d
-  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
-  0 files updated, 0 files merged, 0 files removed, 2 files unresolved
+  rebasing "other" content-divergent changeset 0892835a581f on c7586e2a9264
+  merging a
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
   (see 'hg help evolve.interrupted')
   [240]
 
   $ hg diff
-  diff -r 517d4375cb72 c
+  diff -r c7586e2a9264 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,6 @@
+   foo
+  +<<<<<<< destination: c7586e2a9264 - test: added a
+  +=======
+  +some_changes
+  +latest_changes
+  +>>>>>>> evolving:    0892835a581f - test: added d
+  diff -r c7586e2a9264 d
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/c	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/d	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
-  +babar
-  diff -r 517d4375cb72 d
-  --- a/d	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/d	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +1,5 @@
-  +<<<<<<< local: 517d4375cb72 - test: added d
-   foobar
-  +=======
   +bar
-  +>>>>>>> other: e315463d94bd - test: added d
 
   $ hg evolve --stop
   stopped the interrupted evolve
-  working directory is now at 2d664a4ab749
+  working directory is now at 0892835a581f
 
-XXX: we should have preserved the wdir to be at rev 8
   $ hg glog
-  @  8:2d664a4ab749 added d
+  @  10:0892835a581f added d
+  |   () draft
+  o  8:33c16a2e0eb8 added c
   |   () draft
   | *  7:517d4375cb72 added d
   | |   () draft
-  o |  3:ca1b80f7960a added c
-  | |   () draft
   o |  2:b1661037fa25 added b
   |/    () draft
   o  1:c7586e2a9264 added a
--- a/tests/test-evolve-content-divergent-stack.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-content-divergent-stack.t	Thu Mar 11 11:49:34 2021 +0800
@@ -749,7 +749,7 @@
   $ hg evolve --stop
   2 new orphan changesets
   stopped the interrupted evolve
-  working directory is now at 2a955e808c53
+  working directory is now at 509103439e5e
   $ hg log -G
   o  changeset:   21:7e67dfb7ee31
   |  tag:         tip
@@ -770,7 +770,7 @@
   | |  instability: orphan, content-divergent
   | |  summary:     added c
   | |
-  | *  changeset:   17:509103439e5e
+  | @  changeset:   17:509103439e5e
   | |  parent:      5:8e222f257bbf
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
@@ -789,7 +789,7 @@
   | | |  instability: orphan, content-divergent
   | | |  summary:     added c
   | | |
-  | | @  changeset:   14:2a955e808c53
+  | | *  changeset:   14:2a955e808c53
   | | |  parent:      10:c04ff147ef79
   | | |  user:        test
   | | |  date:        Thu Jan 01 00:00:00 1970 +0000
@@ -837,10 +837,10 @@
   x  c7586e2a9264 (1) added a
   
   $ hg obslog -r 'desc("added b")' --all
-  @  2a955e808c53 (14) added b
+  *  2a955e808c53 (14) added b
   |    amended(content) from 6eb54b5af3fb using amend by test (Thu Jan 01 00:00:00 1970 +0000)
   |
-  | *  509103439e5e (17) added b
+  | @  509103439e5e (17) added b
   | |    amended(content) from d5f148423c16 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
   | |
   x |  6eb54b5af3fb (11) added b
--- a/tests/test-evolve-continue.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-continue.t	Thu Mar 11 11:49:34 2021 +0800
@@ -10,6 +10,14 @@
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
 Setting up the repo
 
   $ hg init repo
@@ -56,6 +64,8 @@
   $ hg evolve --all
   move:[4] added d
   atop:[5] added c
+  merging d (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -116,6 +126,8 @@
   $ hg evolve --update
   move:[7] added e
   atop:[8] added d
+  merging e (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging e
   warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -156,6 +168,8 @@
   $ hg evolve --all --update
   move:[2] added b
   atop:[9] added a
+  merging b (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging b
   warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -241,6 +255,8 @@
   move:[12] added d
   atop:[16] added c
   move:[13] added f
+  merging f (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging f
   warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -256,6 +272,8 @@
   move:[14] added g
   atop:[18] added f
   move:[15] added h
+  merging h (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging h
   warning: conflicts while merging h! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -303,6 +321,8 @@
   move:[19] added g
   atop:[21] added f
   perform evolve? [Ny] y
+  merging g (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging g
   warning: conflicts while merging g! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -352,6 +372,8 @@
   $ hg next --evolve
   move:[22] added g
   atop:[24] added f
+  merging g (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging g
   warning: conflicts while merging g! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -419,6 +441,8 @@
   $ hg evolve
   move:[3] added d, modified c
   atop:[5] added c
+  merging c (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-inmemory.t	Thu Mar 11 11:49:34 2021 +0800
@@ -0,0 +1,186 @@
+Tests running `hg evolve` with in-memory merge.
+
+  $ . $TESTDIR/testlib/common.sh
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > evolve =
+  > drawdag=$RUNTESTDIR/drawdag.py
+  > [alias]
+  > glog = log -G -T '{rev}:{node|short} {separate(" ", phase, tags)}\n{desc|firstline}'
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+
+Test evolving a single orphan
+
+  $ hg init single-orphan
+  $ cd single-orphan
+  $ hg debugdrawdag <<'EOS'
+  >     C  # C/c = c\n
+  > B2  |  # B2/b = b2\n
+  > |   B  # B/b = b\n
+  >  \ /   # replace: B -> B2
+  >   A
+  > EOS
+  1 new orphan changesets
+  $ hg evolve
+  move:[3] C
+  atop:[2] B2
+  $ hg glog
+  o  4:52da76e91abb draft tip
+  |  C
+  | x  3:bc77848cde3a draft C
+  | |  C
+  o |  2:377a194b9b8a draft B2
+  | |  B2
+  | x  1:830b6315076c draft B
+  |/   B
+  o  0:426bada5c675 draft A
+     A
+  $ hg cat -r tip b c
+  b2
+  c
+  $ cd ..
+
+Test that in-memory evolve works when there are conflicts
+and after continuing.
+
+  $ hg init conflicts
+  $ cd conflicts
+  $ hg debugdrawdag <<'EOS'
+  >     E  # E/e = e\n
+  >     |
+  >     D  # D/b = d\n
+  >     |
+  >     C  # C/c = c\n
+  > B2  |  # B2/b = b2\n
+  > |   B  # B/b = b\n
+  >  \ /   # replace: B -> B2
+  >   A
+  > EOS
+  3 new orphan changesets
+  $ hg evolve
+  move:[3] C
+  atop:[2] B2
+  move:[4] D
+  merging b
+  hit merge conflicts; retrying merge in working copy
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [240]
+  $ hg glog
+  @  6:52da76e91abb draft tip
+  |  C
+  | *  5:eae7899dd92b draft E
+  | |  E
+  | %  4:57e51f6a6d36 draft D
+  | |  D
+  | x  3:bc77848cde3a draft C
+  | |  C
+  o |  2:377a194b9b8a draft B2
+  | |  B2
+  | x  1:830b6315076c draft B
+  |/   B
+  o  0:426bada5c675 draft A
+     A
+  $ cat c
+  c
+  $ cat b
+  <<<<<<< destination: 52da76e91abb - test: C
+  b2
+  =======
+  d
+  >>>>>>> evolving:    57e51f6a6d36 D - test: D
+  $ echo d2 > b
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg evolve --continue
+  $ hg evolve --continue
+  evolving 4:57e51f6a6d36 "D"
+  move:[5] E
+  atop:[7] D
+  $ hg glog
+  o  8:3c658574f8ed draft tip
+  |  E
+  o  7:16e609b952e8 draft
+  |  D
+  o  6:52da76e91abb draft
+  |  C
+  | x  5:eae7899dd92b draft E
+  | |  E
+  | x  4:57e51f6a6d36 draft D
+  | |  D
+  | x  3:bc77848cde3a draft C
+  | |  C
+  o |  2:377a194b9b8a draft B2
+  | |  B2
+  | x  1:830b6315076c draft B
+  |/   B
+  o  0:426bada5c675 draft A
+     A
+  $ hg cat -r tip b c e
+  d2
+  c
+  e
+  $ cd ..
+
+Test that in-memory merge is disabled if there's a precommit hook
+
+  $ hg init precommit-hook
+  $ cd precommit-hook
+  $ hg debugdrawdag <<'EOS'
+  >     C  # C/c = c\n
+  > B2  |  # B2/b = b2\n
+  > |   B  # B/b = b\n
+  >  \ /   # replace: B -> B2
+  >   A
+  > EOS
+  1 new orphan changesets
+  $ cat >> .hg/hgrc <<EOF
+  > [hooks]
+  > precommit = echo "running precommit hook"
+  > EOF
+The hook is not run with in-memory=force
+  $ hg co B2
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg evolve --config experimental.evolution.in-memory=force --update
+  move:[3] C
+  atop:[2] B2
+  working directory is now at 52da76e91abb
+  $ hg glog
+  @  4:52da76e91abb draft tip
+  |  C
+  | x  3:bc77848cde3a draft C
+  | |  C
+  o |  2:377a194b9b8a draft B2
+  | |  B2
+  | x  1:830b6315076c draft B
+  |/   B
+  o  0:426bada5c675 draft A
+     A
+  $ hg co tip^
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg amend -m B3
+  1 new orphan changesets
+The hook is run with in-memory=yes
+  $ hg next --config experimental.evolution.in-memory=yes
+  move:[4] C
+  atop:[5] B3
+  running precommit hook
+  working directory is now at aeee7323c054
+  $ hg glog
+  @  6:aeee7323c054 draft tip
+  |  C
+  o  5:908ce5f9d7eb draft
+  |  B3
+  | x  3:bc77848cde3a draft C
+  | |  C
+  +---x  2:377a194b9b8a draft B2
+  | |    B2
+  | x  1:830b6315076c draft B
+  |/   B
+  o  0:426bada5c675 draft A
+     A
--- a/tests/test-evolve-interrupted.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-interrupted.t	Thu Mar 11 11:49:34 2021 +0800
@@ -48,7 +48,7 @@
   transaction abort!
   rollback completed
   abort: precommit hook exited with status 1
-  [255]
+  [40]
   $ hg l
   @  2 apricot and blueberry
   
@@ -107,7 +107,7 @@
   transaction abort!
   rollback completed
   abort: precommit hook exited with status 1
-  [255]
+  [40]
   $ cat b
   banana
   $ hg evolve --stop
@@ -132,7 +132,7 @@
   transaction abort!
   rollback completed
   abort: precommit hook exited with status 1
-  [255]
+  [40]
   $ hg evolve --continue
   evolving 1:e0486f65907d "banana"
   working directory is now at bd5ec7dfc2af
--- a/tests/test-evolve-noupdate.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-noupdate.t	Thu Mar 11 11:49:34 2021 +0800
@@ -18,6 +18,14 @@
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
   $ hg init stoprepo
   $ cd stoprepo
   $ echo ".*\.orig" > .hgignore
--- a/tests/test-evolve-order.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-order.t	Thu Mar 11 11:49:34 2021 +0800
@@ -2,17 +2,6 @@
 -----------------------
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
-  > [diff]
-  > git = 1
-  > unified = 0
   > [ui]
   > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
   > [experimental]
--- a/tests/test-evolve-phase.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-phase.t	Thu Mar 11 11:49:34 2021 +0800
@@ -9,6 +9,14 @@
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
 Testing when there are no conflicts during evolve
 
   $ hg init noconflict
@@ -83,6 +91,8 @@
   $ hg evolve
   move:[2] c
   atop:[3] b
+  merging a (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging a
   warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
--- a/tests/test-evolve-serveronly-bundle2.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-serveronly-bundle2.t	Thu Mar 11 11:49:34 2021 +0800
@@ -2,8 +2,6 @@
   $ . ${TESTDIR}/testlib/pythonpath.sh
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
   > [web]
   > push_ssl = false
   > allow_push = *
--- a/tests/test-evolve-serveronly-legacy.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-serveronly-legacy.t	Thu Mar 11 11:49:34 2021 +0800
@@ -2,8 +2,6 @@
   $ . ${TESTDIR}/testlib/pythonpath.sh
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
   > [web]
   > push_ssl = false
   > allow_push = *
--- a/tests/test-evolve-split.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-split.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,17 +1,6 @@
 Check that evolve shows error while handling split commits
 --------------------------------------
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
-  > [diff]
-  > git = 1
-  > unified = 0
   > [ui]
   > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
   > [extensions]
--- a/tests/test-evolve-stop-orphan.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-stop-orphan.t	Thu Mar 11 11:49:34 2021 +0800
@@ -16,6 +16,14 @@
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
   $ hg init stoprepo
   $ cd stoprepo
   $ echo ".*\.orig" > .hgignore
@@ -88,6 +96,8 @@
   $ hg evolve
   move:[4] added d
   atop:[5] added c
+  merging d (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -134,6 +144,8 @@
   $ hg next --evolve
   move:[4] added d
   atop:[5] added c
+  merging d (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -153,7 +165,6 @@
 
   $ hg evolve --stop
   stopped the interrupted evolve
-  working directory is now at cb6a2ab625bb
 
   $ hg glog
   @  5:cb6a2ab625bb added c
@@ -196,6 +207,8 @@
   $ hg evolve --update
   move:[4] added d
   atop:[5] added c
+  merging d (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -246,6 +259,8 @@
   atop:[7] added hgignore
   move:[2] added b
   move:[5] added c
+  merging c (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -258,15 +273,15 @@
 
   $ hg evolve --stop
   stopped the interrupted evolve
-  working directory is now at aec285328e90
+  working directory is now at 21817cd42526
 
 Only changeset which has a successor now are obsoleted
   $ hg glog
-  @  9:aec285328e90 added b
+  o  9:aec285328e90 added b
   |   () draft
   o  8:fd00db71edca added a
   |   () draft
-  o  7:21817cd42526 added hgignore
+  @  7:21817cd42526 added hgignore
       () draft
   *  6:2a4e03d422e2 added d
   |   () draft
@@ -284,6 +299,8 @@
   $ hg evolve --all
   move:[5] added c
   atop:[9] added b
+  merging c (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -303,11 +320,11 @@
   |   () draft
   o  10:cb1dd1086ef6 added c
   |   () draft
-  @  9:aec285328e90 added b
+  o  9:aec285328e90 added b
   |   () draft
   o  8:fd00db71edca added a
   |   () draft
-  o  7:21817cd42526 added hgignore
+  @  7:21817cd42526 added hgignore
       () draft
 
 Bookmarks should only be moved of the changesets which have been evolved,
@@ -315,7 +332,7 @@
 -------------------------------------------------------------------------
 
   $ hg up tip^
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg bookmark b1
   $ hg up .^
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -359,6 +376,8 @@
   move:[9] added b
   atop:[12] added a
   move:[10] added c
+  merging c (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
@@ -367,13 +386,13 @@
 
   $ hg evolve --stop
   stopped the interrupted evolve
-  working directory is now at a3f4b95da934
+  working directory is now at a3cc2042492f
 
 Bookmarks of only the changeset which are evolved is moved
   $ hg glog
-  @  13:a3f4b95da934 added b
+  o  13:a3f4b95da934 added b
   |   (b2) draft
-  o  12:a3cc2042492f added a
+  @  12:a3cc2042492f added a
   |   () draft
   | *  11:cd0909a30222 added d
   | |   () draft
--- a/tests/test-evolve-stop-phasediv.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-stop-phasediv.t	Thu Mar 11 11:49:34 2021 +0800
@@ -16,6 +16,14 @@
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
   $ hg init stoprepo
   $ cd stoprepo
   $ echo ".*\.orig" > .hgignore
@@ -90,15 +98,14 @@
 
   $ hg evolve --stop
   stopped the interrupted evolve
-  working directory is now at ca1b80f7960a
+  working directory is now at ddba58020bc0
 
-XXX: maybe we should update wdir to where it was
   $ hg glog
-  *  6:ddba58020bc0 added d
+  @  6:ddba58020bc0 added d
   |   () draft
   | o  4:c41c793e0ef1 added d
   | |   () public
-  | @  3:ca1b80f7960a added c
+  | o  3:ca1b80f7960a added c
   | |   () public
   | o  2:b1661037fa25 added b
   |/    () public
--- a/tests/test-evolve-topic.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-topic.t	Thu Mar 11 11:49:34 2021 +0800
@@ -2,16 +2,8 @@
 Check we can find the topic extensions
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > [phases]
-  > publish = False
   > [ui]
   > logtemplate = {rev} - \{{get(namespaces, "topics")}} {node|short} {desc} ({phase})\n
-  > [diff]
-  > git = 1
-  > unified = 0
   > [extensions]
   > rebase = 
   > EOF
--- a/tests/test-evolve-wdir.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve-wdir.t	Thu Mar 11 11:49:34 2021 +0800
@@ -35,6 +35,14 @@
   > glog = log --graph --template "{rev}:{node|short} ({phase}): {desc|firstline} {if(troubles, '[{troubles}]')}\n"
   > EOF
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
   $ hg init repo
   $ cd repo
   $ mkcommit c_A
--- a/tests/test-evolve.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-evolve.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,11 +1,4 @@
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > metaedit=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
   > [phases]
   > publish = False
   > [alias]
@@ -16,6 +9,15 @@
   > [extensions]
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
+
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
   $ mkcommit() {
   >    echo "$1" > "$1"
   >    hg add "$1"
@@ -1407,6 +1409,8 @@
   move:[30] will be evolved safely
   atop:[32] amended
   move:[31] will cause conflict at evolve
+  merging newfile (inmemory !)
+  hit merge conflicts; retrying merge in working copy (inmemory !)
   merging newfile
   warning: conflicts while merging newfile! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
--- a/tests/test-fold.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-fold.t	Thu Mar 11 11:49:34 2021 +0800
@@ -3,8 +3,6 @@
 setup
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > fold=-d "0 0"
   > [extensions]
   > evolve=
   > [alias]
--- a/tests/test-obsolete-push.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-obsolete-push.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,6 +1,4 @@
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
   > [extensions]
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
--- a/tests/test-oldconvert.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-oldconvert.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,11 +1,4 @@
   $ cat >> $HGRCPATH <<EOF
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish=False
-  > [alias]
-  > odiff=diff --rev 'limit(obsparents(.),1)' --rev .
   > [extensions]
   > EOF
   $ mkcommit() {
--- a/tests/test-prev-next.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-prev-next.t	Thu Mar 11 11:49:34 2021 +0800
@@ -431,7 +431,7 @@
   $ hg prev
   abort: conflicting changes
   (do you want --merge?)
-  [255]
+  [20]
 
   $ echo hi > bar
   $ hg prev
@@ -443,7 +443,7 @@
   $ hg next
   abort: conflicting changes
   (do you want --merge?)
-  [255]
+  [20]
 
 Test that --merge still works fine with commands.update.check set
 
@@ -454,7 +454,7 @@
   $ hg next
   abort: conflicting changes
   (do you want --merge?)
-  [255]
+  [20]
   $ hg next --merge
   merging bar
   warning: conflicts while merging bar! (edit, then use 'hg resolve --mark')
--- a/tests/test-rewind.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-rewind.t	Thu Mar 11 11:49:34 2021 +0800
@@ -8,6 +8,7 @@
   > publish = false
   > [alias]
   > glf = log -GT "{rev}: {desc} ({files})"
+  > glhf = log -GT "{rev}:{node|short} {desc} ({files})"
   > [extensions]
   > evolve =
   > EOF
@@ -666,16 +667,13 @@
   rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851
   $ hg rewind --to '9576e80d6851' --hidden --dry-run
   rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851
-
-XXX this should also give us 2 changesets
-
   $ hg rewind --to 'a0316c4c5417' --hidden --dry-run
-  rewinding 4535d0af405c to 1 changesets: a0316c4c5417
+  rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851
 
   $ hg rewind --to '9576e80d6851' --exact --hidden --dry-run
-  rewinding 4535d0af405c to 1 changesets: 9576e80d6851
+  rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851
   $ hg rewind --to 'a0316c4c5417' --exact --hidden --dry-run
-  rewinding 4535d0af405c to 1 changesets: a0316c4c5417
+  rewinding 4535d0af405c to 2 changesets: a0316c4c5417 9576e80d6851
 
 actual rewind
 
@@ -869,6 +867,19 @@
      summary:     c_ROOT
   
 
+  $ hg rewind --hidden --to 'allpredecessors(desc("c_B0"))' --dry-run
+  abort: not rewinding, a65fceb2324a is a successor of 7e594302a05d
+  (pick only one of these changesets, possibly with --exact)
+  [255]
+  $ hg rewind --hidden --to 'allpredecessors(desc("c_B0"))' --dry-run --exact
+  abort: not rewinding, a65fceb2324a is a successor of 7e594302a05d
+  (pick only one of these changesets, possibly with --exact)
+  [255]
+  $ hg rewind --hidden --to 'allpredecessors(desc("c_B0"))' --dry-run --as-divergence
+  abort: not rewinding, a65fceb2324a is a successor of 7e594302a05d
+  (pick only one of these changesets, possibly with --exact)
+  [255]
+
 Testing the defaults
 --------------------
 
@@ -1198,3 +1209,436 @@
   $ hg rewind --keep --to 'desc("amended")' --hidden
   abort: uncommitted changes
   [20]
+
+  $ cd ..
+
+Extra cases related to folds
+============================
+
+folding with a changeset created after the rewind target
+--------------------------------------------------------
+
+..      B0 ⇠\
+..      |    ⇠ AB2
+.. A0 ⇠ A1 ⇠/
+
+this simple test case introduces the idea of making rewind consider different
+evolutions of fold components: "parent" evolution of A has more predecessors
+
+  $ hg init extra-fold-case-1
+  $ cd extra-fold-case-1
+
+  $ echo R > R
+  $ hg ci -qAm R
+  $ echo A > A
+  $ hg ci -qAm A0
+  $ hg amend -m A1
+  $ echo B > B
+  $ hg ci -qAm B0
+  $ hg fold -r 'desc("A1")::' -m AB2 --exact -q
+
+  $ hg glhf --hidden
+  @  4:7f9a5314ef94 AB2 (A B)
+  |
+  | x  3:16429ed4b6cb B0 (B)
+  | |
+  | x  2:3748b241cad8 A1 (A)
+  |/
+  | x  1:fa8956746c52 A0 (A)
+  |/
+  o  0:167e04d3d1b2 R (R)
+  
+
+target selection
+
+when rewinding from a fold, rewind to all of its components (at various points
+in their evolution) to not lose work
+
+  $ hg rewind --hidden --to 'desc("A0")' --dry-run
+  rewinding 7f9a5314ef94 to 2 changesets: fa8956746c52 16429ed4b6cb
+  $ hg rewind --hidden --to 'desc("B0")' --dry-run
+  rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 16429ed4b6cb
+  $ hg rewind --from 'desc("AB2")' --dry-run
+  rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 16429ed4b6cb
+
+  $ hg rewind --hidden --to 'allpredecessors(desc("AB2"))' --dry-run
+  abort: not rewinding, 3748b241cad8 is a successor of fa8956746c52
+  (pick only one of these changesets, possibly with --exact)
+  [255]
+
+  $ cd ..
+
+folding with a changeset we rebased onto
+----------------------------------------
+
+.. A0 ⇠ A1 ⇠\
+..      |    ⇠ AB2
+..      B0 ⇠/
+
+similar to the previous case, but this time evolution of A has more
+predecessors and at some point starts to be based on B
+
+  $ hg init extra-fold-case-2
+  $ cd extra-fold-case-2
+
+  $ echo R > R
+  $ hg ci -qAm R
+  $ echo A > A
+  $ hg ci -qAm A0
+  $ hg up 'desc("R")' -q
+  $ echo B > B
+  $ hg ci -qAm B0
+  $ echo A > A
+  $ hg ci -qAm A1
+  $ hg prune -r 'desc("A0")' -s 'desc("A1")'
+  1 changesets pruned
+
+  $ hg fold -r 'desc("B0")::' -m AB2 --exact -q
+
+  $ hg glhf --hidden
+  @  4:1988e9fe9517 AB2 (A B)
+  |
+  | x  3:7175ff74409b A1 (A)
+  | |
+  | x  2:d6ed1d624918 B0 (B)
+  |/
+  | x  1:fa8956746c52 A0 (A)
+  |/
+  o  0:167e04d3d1b2 R (R)
+  
+
+target selection
+
+when rewinding from a fold, rewind to all of its components (at various points
+in their evolution) to not lose work
+
+  $ hg rewind --hidden --to 'desc("A0")' --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: fa8956746c52 d6ed1d624918
+  $ hg rewind --hidden --to 'desc("B0")' --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: d6ed1d624918 7175ff74409b
+  $ hg rewind --from 'desc("AB2")' --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: d6ed1d624918 7175ff74409b
+
+  $ cd ..
+
+folding with a changeset that rebased on us
+-------------------------------------------
+
+.. B0 ⇠⇠⇠⇠ B1 ⇠\
+.. |       |    ⇠ AB2
+.. |  A0 ⇠ A1 ⇠/
+
+now evolutions of A and B have the same amount of changesets, but at point 0
+they aren't yet related
+
+  $ hg init extra-fold-case-3
+  $ cd extra-fold-case-3
+
+  $ echo R > R
+  $ hg ci -qAm R
+  $ echo A > A
+  $ hg ci -qAm A0
+  $ hg amend -m A1
+  $ hg up 'desc("R")' -q
+  $ echo B > B
+  $ hg ci -qAm B0
+  $ hg up 'desc("A1")' -q
+  $ echo B > B
+  $ hg ci -qAm B1
+  $ hg prune -r 'desc("B0")' -s 'desc("B1")'
+  1 changesets pruned
+
+  $ hg fold -r 'desc("A1")::' -m AB2 --exact -q
+
+  $ hg glhf --hidden
+  @  5:7f9a5314ef94 AB2 (A B)
+  |
+  | x  4:fe7a7d317e16 B1 (B)
+  | |
+  +---x  3:d6ed1d624918 B0 (B)
+  | |
+  | x  2:3748b241cad8 A1 (A)
+  |/
+  | x  1:fa8956746c52 A0 (A)
+  |/
+  o  0:167e04d3d1b2 R (R)
+  
+
+target selection
+
+  $ hg rewind --hidden --to 'desc("A0")' --dry-run
+  rewinding 7f9a5314ef94 to 2 changesets: fa8956746c52 fe7a7d317e16
+  $ hg rewind --hidden --to 'desc("B0")' --dry-run
+  rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 d6ed1d624918
+  $ hg rewind --hidden --to 'desc("A1")' --dry-run
+  rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 fe7a7d317e16
+  $ hg rewind --hidden --to 'desc("B1")' --dry-run
+  rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 fe7a7d317e16
+  $ hg rewind --from 'desc("AB2")' --dry-run
+  rewinding 7f9a5314ef94 to 2 changesets: 3748b241cad8 fe7a7d317e16
+
+actual rewind
+
+  $ hg rewind --hidden --to 'desc("A0")'
+  1 new orphan changesets
+  rewound to 2 changesets
+  (1 changesets obsoleted)
+  working directory is now at e492d2f9be46
+
+after rewind to A0:
+- A0' and B1' are successors to AB2 (split using rewind)
+- A0' is a successor of A0 (operation: rewind)
+- A0' is a child of R (just like A0)
+- B1' is a successor of B1 (operation: rewind)
+- B1' is a child of A1 (just like B1), and therefore an orphan
+
+  $ hg obslog -a
+  o    54b340ce1d87 (6) A0
+  |\     split(description, meta, parent, content) from 7f9a5314ef94 using rewind by test (Thu Jan 01 00:00:06 1970 +0000)
+  | |    meta-changed(meta) from fa8956746c52 using rewind by test (Thu Jan 01 00:00:06 1970 +0000)
+  | |
+  +---@  e492d2f9be46 (7) B1
+  | | |    split(description, meta, parent, content) from 7f9a5314ef94 using rewind by test (Thu Jan 01 00:00:06 1970 +0000)
+  | | |    meta-changed(meta) from fe7a7d317e16 using rewind by test (Thu Jan 01 00:00:06 1970 +0000)
+  | | |
+  x---+  7f9a5314ef94 (5) AB2
+  | | |    folded(description, parent, content) from 3748b241cad8, fe7a7d317e16 using fold by test (Thu Jan 01 00:00:06 1970 +0000)
+  | | |
+  x | |  3748b241cad8 (2) A1
+  |/ /     reworded(description) from fa8956746c52 using amend by test (Thu Jan 01 00:00:06 1970 +0000)
+  | |
+  | x  fe7a7d317e16 (4) B1
+  | |    rewritten(description, parent) from d6ed1d624918 using prune by test (Thu Jan 01 00:00:06 1970 +0000)
+  | |
+  | x  d6ed1d624918 (3) B0
+  |
+  x  fa8956746c52 (1) A0
+  
+  $ hg glhf
+  @  7:e492d2f9be46 B1 (B)
+  |
+  | o  6:54b340ce1d87 A0 (A)
+  | |
+  x |  2:3748b241cad8 A1 (A)
+  |/
+  o  0:167e04d3d1b2 R (R)
+  
+
+  $ hg debugobsolete --exclusive -r 'first(head())'
+  7f9a5314ef94f5856ee90661268194cc5ce9b332 54b340ce1d87f3593fd9de2a742e7b444e5136ed e492d2f9be46b73c0cfa51709e92db864b8f3ed9 0 (Thu Jan 01 00:00:06 1970 +0000) {'ef1': '15', 'operation': 'rewind', 'user': 'test'}
+  fa8956746c5294ce3351309133b450c5930f30f5 54b340ce1d87f3593fd9de2a742e7b444e5136ed 4 (Thu Jan 01 00:00:06 1970 +0000) {'ef1': '2', 'operation': 'rewind', 'user': 'test'}
+  $ hg debugobsolete --exclusive -r 'last(head())'
+  7f9a5314ef94f5856ee90661268194cc5ce9b332 54b340ce1d87f3593fd9de2a742e7b444e5136ed e492d2f9be46b73c0cfa51709e92db864b8f3ed9 0 (Thu Jan 01 00:00:06 1970 +0000) {'ef1': '15', 'operation': 'rewind', 'user': 'test'}
+  fe7a7d317e168a15e8aa43131b54d3256443d728 e492d2f9be46b73c0cfa51709e92db864b8f3ed9 4 (Thu Jan 01 00:00:06 1970 +0000) {'ef1': '2', 'operation': 'rewind', 'user': 'test'}
+
+  $ cd ..
+
+simple fold with a missing part
+-------------------------------
+
+.. B0 ⇠ (B1) ⇠\
+.. |     |     ⇠ AB2
+.. A0 ⇠  A1  ⇠/
+
+a stack was rewritten, but then a part of it became unknown locally
+
+  $ hg init extra-fold-case-4
+  $ cd extra-fold-case-4
+
+  $ echo R > R
+  $ hg ci -qAm R
+  $ echo A > A
+  $ hg ci -qAm A0
+  $ echo B > B
+  $ hg ci -qAm B0
+  $ hg up 'desc("R")' -q
+  $ echo A > A
+  $ hg ci -qAm A1
+  $ echo B > B
+  $ hg ci -qAm B1
+  $ hg prune -r 'desc("A0")+desc("B0")' -s 'desc("A1")+desc("B1")' --biject
+  2 changesets pruned
+
+  $ hg fold -r 'desc("A1") + desc("B1")' -m AB2 --exact -q
+
+  $ hg glhf --hidden
+  @  5:1988e9fe9517 AB2 (A B)
+  |
+  | x  4:25210d726f52 B1 (B)
+  | |
+  | x  3:9c76368ab336 A1 (A)
+  |/
+  | x  2:a07c12c45197 B0 (B)
+  | |
+  | x  1:fa8956746c52 A0 (A)
+  |/
+  o  0:167e04d3d1b2 R (R)
+  
+
+target selection
+
+  $ hg rewind --hidden --to 'desc("A0")' --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: fa8956746c52 25210d726f52
+  $ hg rewind --hidden --to 'desc("A1")' --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: 9c76368ab336 25210d726f52
+  $ hg rewind --hidden --to 'desc("B1")' --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: 9c76368ab336 25210d726f52
+
+because B0 is a child of A0, we use A0 instead of A1 unless --exact is given
+
+XXX the semantic of --exact might need clarification here,
+XXX for example, shouln't --exact make sure we only rewind to the `--to` target ?
+
+  $ hg rewind --hidden --to 'desc("B0")' --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: fa8956746c52 a07c12c45197
+  $ hg rewind --hidden --to 'desc("B0")' --exact --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: a07c12c45197 9c76368ab336
+
+stripping one of the fold parts
+
+  $ hg strip --config extensions.strip= -r 'desc("B1")' --hidden -q
+
+  $ hg glhf --hidden
+  @  4:1988e9fe9517 AB2 (A B)
+  |
+  | x  3:9c76368ab336 A1 (A)
+  |/
+  | x  2:a07c12c45197 B0 (B)
+  | |
+  | x  1:fa8956746c52 A0 (A)
+  |/
+  o  0:167e04d3d1b2 R (R)
+  
+
+target selection
+
+the obvious challenge here is to somehow work around the missing fold
+component, but we can't do much because it is one of the latest predecessors of
+AB2 fold
+
+in future we might have a way to allow rewind to skip changesets unknown
+locally and still proceed (and lose the least amount of work possible)
+
+  $ hg rewind --hidden --to 'desc("A0")+desc("B0")' --exact --dry-run
+  rewinding 1988e9fe9517 to 2 changesets: fa8956746c52 a07c12c45197
+  $ hg rewind --hidden --to 'desc("A0")' --dry-run
+  abort: not rewinding, some predecessors are unknown locally: 25210d726f52
+  (try selecting all changesets to rewind to manually, possibly with --exact)
+  [255]
+  $ hg rewind --hidden --to 'desc("A1")' --dry-run
+  abort: not rewinding, some predecessors are unknown locally: 25210d726f52
+  (try selecting all changesets to rewind to manually, possibly with --exact)
+  [255]
+
+XXX the semantic of --exact might need clarification here,
+XXX for example, shouln't --exact make sure we only rewind to the `--to` target ?
+
+  $ hg rewind --hidden --to 'desc("A1")' --exact --dry-run
+  abort: not rewinding, some predecessors are unknown locally: 25210d726f52
+  (try selecting all changesets to rewind to manually, possibly with --exact)
+  [255]
+  $ hg rewind --from 'desc("AB2")' --dry-run
+  abort: not rewinding, some predecessors are unknown locally: 25210d726f52
+  (try selecting all changesets to rewind to manually, possibly with --exact)
+  [255]
+  $ hg rewind --from 'desc("AB2")' --exact --dry-run
+  abort: not rewinding, some predecessors are unknown locally: 25210d726f52
+  (try selecting all changesets to rewind to manually, possibly with --exact)
+  [255]
+
+  $ cd ..
+
+split and then fold with a missing part
+---------------------------------------
+
+..      /⇠ (C1) ⇠\
+.. BC0 ⇠    |     \
+..  |   \⇠  B1     ⇠ AC2
+..  A0 ⇠⇠⇠⇠⇠⇠⇠⇠⇠⇠⇠/
+
+here we have a case when walking successors and then predecessors of target
+revisions just once might not be enough, because it's a more complex DAG with a
+changeset missing from local repo
+
+  $ hg init extra-fold-case-5
+  $ cd extra-fold-case-5
+
+  $ echo R > R
+  $ hg ci -qAm R
+  $ echo A > A
+  $ hg ci -qAm A0
+  $ echo B > B
+  $ echo C > C
+  $ hg ci -qAm BC0
+  $ hg up 'desc("A0")' -q
+  $ echo B > B
+  $ hg ci -qAm B1
+  $ echo C > C
+  $ hg ci -qAm C1
+
+  $ hg prune -r 'desc("BC0")' -s 'desc("B1")+desc("C1")' --split
+  1 changesets pruned
+
+  $ hg up 'desc("R")' -q
+  $ echo A > A
+  $ echo C > C
+  $ hg ci -qAm AC2
+
+  $ hg prune -r 'desc("A0")+desc("C1")' -s 'desc("AC2")' --fold
+  2 changesets pruned
+  1 new orphan changesets
+
+  $ hg glhf --hidden
+  @  5:9ccaac2e5fbb AC2 (A C)
+  |
+  | x  4:2e4ab803d8ae C1 (C)
+  | |
+  | *  3:44774eafdc1c B1 (B)
+  | |
+  | | x  2:883d75400657 BC0 (B C)
+  | |/
+  | x  1:fa8956746c52 A0 (A)
+  |/
+  o  0:167e04d3d1b2 R (R)
+  
+
+target selection
+
+  $ hg rewind --hidden --to 'desc("A0")' --dry-run
+  rewinding 9ccaac2e5fbb to 2 changesets: fa8956746c52 2e4ab803d8ae
+  $ hg rewind --hidden --to 'desc("BC0")' --dry-run
+  rewinding 44774eafdc1c to 1 changesets: 883d75400657
+  rewinding 9ccaac2e5fbb to 2 changesets: fa8956746c52 883d75400657
+  $ hg rewind --from 'desc("AC2")' --dry-run
+  rewinding 9ccaac2e5fbb to 2 changesets: fa8956746c52 2e4ab803d8ae
+
+stripping a component of AC2 fold
+
+  $ hg strip --config extensions.strip= --hidden -r 'desc("C1")' -q
+  warning: ignoring unknown working parent 9ccaac2e5fbb!
+
+target selection
+
+at the moment, there's not much that we can do here because of missing C1
+
+in future we might have a way to allow rewind to skip changesets unknown
+locally and still proceed (and lose the least amount of work possible)
+
+XXX the semantic of --exact might need clarification here,
+XXX for example, shouln't --exact make sure we only rewind to the `--to` target ?
+
+  $ hg rewind --hidden --to 'desc("A0")' --dry-run
+  abort: not rewinding, some predecessors are unknown locally: 2e4ab803d8ae
+  (try selecting all changesets to rewind to manually, possibly with --exact)
+  [255]
+  $ hg rewind --hidden --to 'desc("BC0")' --dry-run
+  rewinding 44774eafdc1c to 1 changesets: 883d75400657
+  rewinding 9ccaac2e5fbb to 2 changesets: fa8956746c52 883d75400657
+  $ hg rewind --from 'desc("AC2")' --dry-run
+  abort: not rewinding, some predecessors are unknown locally: 2e4ab803d8ae
+  (try selecting all changesets to rewind to manually, possibly with --exact)
+  [255]
+  $ hg rewind --from 'desc("AC2")' --exact --dry-run
+  abort: not rewinding, some predecessors are unknown locally: 2e4ab803d8ae
+  (try selecting all changesets to rewind to manually, possibly with --exact)
+  [255]
--- a/tests/test-split.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-split.t	Thu Mar 11 11:49:34 2021 +0800
@@ -6,16 +6,6 @@
   $ cat >> $HGRCPATH <<EOF
   > [alias]
   > glog = log -G -T "{rev}:{node|short} {desc|firstline} ({phase})\n"
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > split=-d "0 0"
-  > amend=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
   > [diff]
   > git = 1
   > unified = 0
--- a/tests/test-stabilize-conflict.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-stabilize-conflict.t	Thu Mar 11 11:49:34 2021 +0800
@@ -9,8 +9,6 @@
   > interactive=false
   > merge=internal:merge
   > promptecho = True
-  > [defaults]
-  > amend=-d "0 0"
   > [merge-tools]
   > touch.checkchanged=true
   > touch.gui=true
--- a/tests/test-stabilize-order.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-stabilize-order.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,10 +1,16 @@
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
   > [extensions]
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
+#testcases inmemory ondisk
+#if inmemory
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.in-memory = yes
+  > EOF
+#endif
+
   $ glog() {
   >   hg log -G --template '{rev}:{node|short}@{branch}({phase}) {desc|firstline}\n' "$@"
   > }
@@ -72,8 +78,8 @@
   b
   committing manifest
   committing changelog
-  resolving manifests
-  removing b
+  resolving manifests (ondisk !)
+  removing b (ondisk !)
   $ glog
   o  6:81b8bbcd5892@default(draft) addb
   |
@@ -95,14 +101,17 @@
   move:[3] addc
   atop:[6] addb
   hg rebase -r 7a7552255fb5 -d 81b8bbcd5892
-  resolving manifests
-  getting b
+  resolving manifests (ondisk !)
+  getting b (ondisk !)
   resolving manifests
   getting c
   committing files:
   c
   committing manifest
   committing changelog
+  resolving manifests (inmemory !)
+  getting b (inmemory !)
+  getting c (inmemory !)
   working directory is now at 0f691739f917
   $ hg debugobsolete > successors.new
   $ diff -u successors.old successors.new
@@ -157,15 +166,17 @@
   move:[7] addc
   atop:[8] addb
   hg rebase -r 0f691739f917 -d 7a68bc4596ea
-  resolving manifests
-  removing c
-  getting b
+  resolving manifests (ondisk !)
+  removing c (ondisk !)
+  getting b (ondisk !)
   resolving manifests
   getting c
   committing files:
   c
   committing manifest
   committing changelog
+  resolving manifests (inmemory !)
+  getting b (inmemory !)
   working directory is now at 2256dae6521f
   $ glog
   @  9:2256dae6521f@default(draft) addc
--- a/tests/test-topic-fold.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-topic-fold.t	Thu Mar 11 11:49:34 2021 +0800
@@ -2,19 +2,6 @@
 ------------------------
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > split=-d "0 0"
-  > amend=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
-  > [diff]
-  > git = 1
-  > unified = 0
   > [ui]
   > interactive = true
   > [extensions]
--- a/tests/test-topic-rebase.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-topic-rebase.t	Thu Mar 11 11:49:34 2021 +0800
@@ -2,19 +2,6 @@
 --------------------------
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > split=-d "0 0"
-  > amend=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
-  > [diff]
-  > git = 1
-  > unified = 0
   > [ui]
   > interactive = true
   > [extensions]
--- a/tests/test-touch.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-touch.t	Thu Mar 11 11:49:34 2021 +0800
@@ -2,8 +2,6 @@
   $ cat >> $HGRCPATH <<EOF
   > [ui]
   > logtemplate={rev}:{node|short} {desc}\n
-  > [defaults]
-  > amend=-d "0 0"
   > [alias]
   > glog = log -GT "{rev}: {desc}"
   > [extensions]
--- a/tests/test-tutorial.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-tutorial.t	Thu Mar 11 11:49:34 2021 +0800
@@ -13,11 +13,6 @@
   > [diff]
   > # use "git" diff format, clearer and smarter format
   > git = 1
-  > [alias]
-  > # "-d '0 0'" means that the new commit will be at January 1st 1970.
-  > # This is used for stable hash during test
-  > # (this tutorial is automatically tested.)
-  > amend = amend -d '0 0'
   > EOF
 
   $ hg init local
--- a/tests/test-unstability-resolution-result.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-unstability-resolution-result.t	Thu Mar 11 11:49:34 2021 +0800
@@ -8,8 +8,6 @@
 XXX dispatching each these test case in appropriate file would make sense.
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
   > [extensions]
   > hgext.rebase=
   > EOF
--- a/tests/test-unstable-orphan.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-unstable-orphan.t	Thu Mar 11 11:49:34 2021 +0800
@@ -6,17 +6,6 @@
 instability happens when a changesets has obsolete ancestors.
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
-  > fold=-d "0 0"
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish = False
-  > [diff]
-  > git = 1
-  > unified = 0
   > [ui]
   > logtemplate = {rev}:{node|short}@{branch}({phase}) {desc|firstline}\n
   > [extensions]
--- a/tests/test-wireproto-bundle1.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-wireproto-bundle1.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,7 +1,5 @@
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
   > [ui]
   > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh"
   > [phases]
--- a/tests/test-wireproto.t	Wed Feb 24 14:30:21 2021 -0800
+++ b/tests/test-wireproto.t	Thu Mar 11 11:49:34 2021 +0800
@@ -1,7 +1,5 @@
 
   $ cat >> $HGRCPATH <<EOF
-  > [defaults]
-  > amend=-d "0 0"
   > [experimental]
   > obsmarkers-exchange-debug=true
   > bundle2-exp=true