changeset 6181:e6ae5af97683 mercurial-5.6

test-compat: merge mercurial-5.7 into mercurial-5.6
author Anton Shestakov <av6@dwimlabs.net>
date Tue, 22 Feb 2022 08:19:44 +0300
parents 0042d69b8648 (diff) 171d13d8f0ce (current diff)
children 1567b50fa006 35523be9534a
files tests/test-evolve-inmemory.t tests/test-evolve-orphan-split.t tests/test-evolve-phase-divergence.t tests/test-evolve-progress.t tests/test-evolve-wdir.t tests/test-fixup.t tests/test-next-abort.t tests/test-pick.t
diffstat 44 files changed, 1310 insertions(+), 307 deletions(-) [+]
line wrap: on
line diff
--- a/.gitlab-ci.yml	Thu Nov 18 19:26:14 2021 +0300
+++ b/.gitlab-ci.yml	Tue Feb 22 08:19:44 2022 +0300
@@ -1,6 +1,6 @@
 .prepare_hg: &prepare_hg
     - hg pull -R /ci/repos/mercurial/
-    - hg_branch=$(tests/testlib/map-hg-rev.sh "$(hg identify --branch)")
+    - hg_branch=${HG_BRANCH:-$(tests/testlib/map-hg-rev.sh "$(hg identify --branch)")}
     - hg -R /ci/repos/mercurial/ update "$hg_branch"
     - hg_rev=$(hg log -R /ci/repos/mercurial/ -r . -T '{node}')
     - echo testing with mercurial branch="$hg_branch", revision="$hg_rev"
@@ -72,8 +72,7 @@
 doc:
     image: registry.heptapod.net/mercurial/ci-images/py3-evolve-doc
     script:
-        - cd docs/
-        - make
+        - make doc
     variables:
         LANG: en_US.UTF-8
         PYTHON: python3
@@ -93,3 +92,30 @@
             - dist/*
     only:
         - tags
+
+.windows_runtests_template: &windows_runtests
+    before_script:
+        - C:/MinGW/msys/1.0/bin/sh.exe --login -c 'cd "$OLDPWD" && ls -1 tests/test-check-*.t > C:/Temp/check-tests.txt'
+    script:
+        - C:/MinGW/msys/1.0/bin/sh.exe --login -c 'cd "$OLDPWD" && tests/testlib/update-hg-repo.sh C:/Temp/hg'
+        - >
+          C:/MinGW/msys/1.0/bin/sh.exe --login -c '
+          cd "$OLDPWD" &&
+          evo_branch=$(hg identify --branch) &&
+          hg_branch=${HG_BRANCH:-$(tests/testlib/map-hg-rev.sh "$evo_branch")} &&
+          hg -R C:/Temp/hg update "$hg_branch" &&
+          hg_rev=$(hg log -R C:/Temp/hg -r . -T "\{node\}") &&
+          echo testing with mercurial branch="$hg_branch", revision="$hg_rev"'
+        - Invoke-Expression "$Env:PYTHON --version"
+        - echo "$Env:RUNTEST_ARGS"
+        - C:/MinGW/msys/1.0/bin/sh.exe --login -c 'cd "$OLDPWD" && HGMODULEPOLICY="$TEST_HGMODULEPOLICY" $PYTHON C:/Temp/hg/tests/run-tests.py --color=always $RUNTEST_ARGS'
+
+windows-py3:
+    <<: *windows_runtests
+    tags:
+        - windows
+    variables:
+        PYTHON: py -3
+        RUNTEST_ARGS: "--blacklist C:/Temp/check-tests.txt"
+        TEST_HGMODULEPOLICY: "c"
+    when: manual
--- a/.gitlab/issue_templates/new-version.md	Thu Nov 18 19:26:14 2021 +0300
+++ b/.gitlab/issue_templates/new-version.md	Tue Feb 22 08:19:44 2022 +0300
@@ -1,29 +1,30 @@
 This is the actual check list for releasing evolve version X.Y.Z
 
-More details in the [README.rst file](README.rst#L199).
+More details in the [README.rst file](README.rst#L210).
 
 Preparation
 
 * [ ] merges with compatibility branches
 * [ ] compatibility tests are happy
 * [ ] update `testedwith` field
-* [ ] make sure the changelog is up to data
-* [ ] [prepare an announcement for the list/website](https://mypads.framapad.org/mypads/?/mypads/group/octobus-public-5d3rw470w/pad/view/evolution-announce-iswcp7qx)
+* [ ] make sure CHANGELOG is up-to-date
+* [ ] [prepare an announcement for the list/website](https://mypads.framapad.org/p/evolution-announce-iswcp7qx)
 * [ ] sanity check install and sdist targets of setup.py
 
 Releasing
 
-* [ ] add the current date to the changelog
+* [ ] add the current date to CHANGELOG
 * [ ] update the `__version__` field of all relevant extensions
 * [ ] create a new Debian entry
 * [ ] tag the version
-* [ ] move the `@` bookmark on the new tag
+* [ ] move the `@` bookmark to the new tag
 * [ ] push/publish the tag to the main repository
 * [ ] upload the tarball to PyPI
 * [ ] add `.dev` to the `__version__` field
 * [ ] merge stable into default
-* [ ] push the result on the main repo
+* [ ] push the result to https://www.mercurial-scm.org/repo/evolve/
 * [ ] send the announcement by email to evolve-testers@mercurial-scm.org
 * [ ] send the announcement by email to mercurial@mercurial-scm.org
 * [ ] publish the announcement as a blog on https://octobus.net/blog/
+* [ ] update #hg-evolve topic
 * [ ] tweet about it
--- a/.hgignore	Thu Nov 18 19:26:14 2021 +0300
+++ b/.hgignore	Tue Feb 22 08:19:44 2022 +0300
@@ -1,24 +1,30 @@
-syntax: re
-/figures/[^/]+\.png$
-^html/
-\.pyc$
-~$
-\.swp$
-\.orig$
-\.rej$
-^tests/.*\.err$
-^tests/easy_run.sh$
-^build/
-^dist/
-^MANIFEST$
-^docs/tutorials/.*\.rst$
-\.ico$
-tests/\.testtimes
+syntax: glob
+*.py[cdo]
+*~
+*.swp
+*.orig
+*.rej
+*.egg-info
 
-^docs/training/graphs/
-^docs/training/html/
-^docs/training/index.html
-^docs/training/graphviz-images/
-^docs/training/img/
-^docs/training/output/
-^docs/training/pandocfilters/
+syntax: rootglob
+.idea/
+.mypy_cache/
+.pytype/
+.vscode/
+html/
+build/
+dist/
+MANIFEST
+tests/easy_run.sh
+tests/*.err
+tests/.testtimes*
+docs/training/graphs/
+docs/training/html/
+docs/training/index.html
+docs/training/graphviz-images/
+docs/training/img/
+docs/training/output/
+docs/training/pandocfilters/
+docs/tutorials/**.rst
+docs/figures/**.png
+docs/static/**.ico
--- a/.hgtags	Thu Nov 18 19:26:14 2021 +0300
+++ b/.hgtags	Tue Feb 22 08:19:44 2022 +0300
@@ -97,3 +97,4 @@
 30c8d8e6a7f4056c46a868b47ee949d3bdb48c4c 10.3.2
 cca465bf6a6a103449aa58deecdffba8e546f7c6 10.3.3
 de530d27554b43c00509696dc7a1496f5129e1a4 10.4.0
+64bb9c4a13d388233d4d6d9b761ece9c6ce77fb3 10.4.1
--- a/CHANGELOG	Thu Nov 18 19:26:14 2021 +0300
+++ b/CHANGELOG	Tue Feb 22 08:19:44 2022 +0300
@@ -1,6 +1,38 @@
 Changelog
 =========
 
+10.5.0 - in progress
+--------------------
+
+  * evolve: handle cases when working directory parent has multiple successors
+  * multiple commands: do not check for new divergence if divergence is allowed
+    via configuration
+  * fixup: a new experimental command to add working directory changes to a
+    specified revision
+  * pick: show abort message after pick is aborted for consistency
+
+  * evolve, pullbundle: drop compatibility with Mercurial 4.7
+
+topic (0.24.0)
+
+  * topic: make histedit preserve topics when the first changeset in a stack
+    is rewritten (issue6550)
+
+  * drop compatibility with Mercurial 4.7
+
+10.4.1 -- 2021-11-19
+--------------------
+
+  * compatibility with Mercurial 6.0
+
+  * documentation: add a help section about making evolve skip
+    content-divergence check with experimental.evolution.allowdivergence.
+  * documentation: mention that pick uses the active topic if it's set
+
+topic (0.23.1)
+
+  * compatibility with Mercurial 6.0
+
 10.4.0 -- 2021-10-12
 --------------------
 
--- a/Makefile	Thu Nov 18 19:26:14 2021 +0300
+++ b/Makefile	Tue Feb 22 08:19:44 2022 +0300
@@ -1,5 +1,5 @@
 PYTHON ?= python3
-VERSION = $(shell python setup.py --version)
+VERSION = $(shell $(PYTHON) setup.py --version)
 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
 HGTESTS = $(HGROOT)/tests
 
@@ -15,7 +15,7 @@
 
 .PHONY: deb-prepare
 deb-prepare:
-	python setup.py sdist --dist-dir ..
+	$(PYTHON) setup.py sdist --dist-dir ..
 	mv -f ../hg-evolve-$(VERSION).tar.gz ../mercurial-evolve_$(VERSION).orig.tar.gz
 	tar xf ../mercurial-evolve_$(VERSION).orig.tar.gz
 	rm -rf ../mercurial-evolve_$(VERSION).orig
--- a/README.rst	Thu Nov 18 19:26:14 2021 +0300
+++ b/README.rst	Tue Feb 22 08:19:44 2022 +0300
@@ -76,6 +76,17 @@
     [extensions]
     evolve = ~/evolve/hgext3rd/evolve
 
+Pitfalls
+--------
+
+If you get ``"failed to import extension evolve: No module named 'evolve'"``
+error, there are a couple of things to check:
+
+* make sure you gave pip the correct package name (it's hg-evolve),
+
+* make sure evolve is installed for the same version of Python that you use for
+  running Mercurial (``hg debuginstall | grep Python``).
+
 Server-only Setup
 =================
 
@@ -117,7 +128,7 @@
 How to Contribute
 =================
 
-Discussion happens on the #hg-evolve IRC on libera_.
+Discussion happens in #hg-evolve and #mercurial on libera_ IRC network.
 
 .. _libera: https://libera.chat/
 
@@ -199,31 +210,31 @@
 Release Checklist
 =================
 
+* use contrib/merge-test-compat.sh to merge with the test compatibility
+  branches,
+
 * make sure the tests are happy on all supported versions,
 
-  You can use the `contrib/merge-test-compat.sh` to merge with the test
-  compatibility branches.
+* make sure there is no code difference between the compatibility branches and
+  stable (no diff within hgext3rd/),
 
-* make sure there is no code difference between the compat branches and stable
-  (no diff within `hgext3rd/`),
-
-* update the `testedwith` variable for all extensions (remove '.dev'):
+* update the ``testedwith`` variable for all extensions (remove '.dev'):
 
   - hgext3rd/evolve/metadata.py
   - hgext3rd/topic/__init__.py
   - hgext3rd/pullbundle.py
 
-* make sure the changelog is up to date,
+* make sure CHANGELOG is up-to-date,
 
-* add a date to the changelog entry for the target version,
+* add a date to the CHANGELOG entry for the target version,
 
-* update the `__version__` field of all relevant extensions:
+* update the ``__version__`` field of all relevant extensions:
 
   - hgext3rd/evolve/metadata.py
   - hgext3rd/topic/__init__.py
   - hgext3rd/pullbundle.py (if touched)
 
-* create a new Debian entry:
+* create a new Debian changelog entry:
 
   - debchange --newversion x.y.z-1 "new upstream release"
   - debchange --release
@@ -235,21 +246,25 @@
 
 * tag the commit,
 
+* move ``@`` bookmark to the new tag,
+
 * push and publish the tag,
 
 * upload the tarball to PyPI,
 
-* make an announcement on evolve-testers@mercurial-scm.org (possibly on
-  mercurial@mercurial-scm.org too),
+* make an announcement on evolve-testers@mercurial-scm.org and
+  mercurial@mercurial-scm.org,
 
-* bump versions of all extensions and add '.dev' (see existing commits as an
+* bump versions of all extensions and add ``.dev`` (see existing commits as an
   example):
 
   - hgext3rd/evolve/metadata.py
   - hgext3rd/topic/__init__.py
   - hgext3rd/pullbundle.py
 
-  The version we use on the stable branch during development should be
-  `x.y.z+1.dev`. The version of the default branch should be `x.y+1.0.dev`.
+  Version bump rules:
+
+  - stable branch x.y.z+1.dev
+  - default branch x.y+1.0.dev
 
 * merge stable into default.
--- a/debian/changelog	Thu Nov 18 19:26:14 2021 +0300
+++ b/debian/changelog	Tue Feb 22 08:19:44 2022 +0300
@@ -1,3 +1,9 @@
+mercurial-evolve (10.4.1-1) unstable; urgency=medium
+
+  * new upstream release
+
+ -- Anton Shestakov <av6@dwimlabs.net>  Fri, 19 Nov 2021 16:17:59 +0300
+
 mercurial-evolve (10.4.0-1) unstable; urgency=medium
 
   * new upstream release
--- a/debian/control	Thu Nov 18 19:26:14 2021 +0300
+++ b/debian/control	Tue Feb 22 08:19:44 2022 +0300
@@ -7,15 +7,14 @@
  Pierre-Yves David <pierre-yves.david@logilab.fr>,
 Standards-Version: 4.3.0
 Build-Depends:
- mercurial (>= 4.7),
- python,
+ mercurial (>= 4.8),
  python3,
  debhelper (>= 10),
  dh-python,
- python-sphinx,
- imagemagick,
- librsvg2-bin,
- graphviz,
+ python3-sphinx <!nodoc>,
+ imagemagick <!nodoc>,
+ librsvg2-bin <!nodoc>,
+ graphviz <!nodoc>,
 X-Python3-Version: >= 3.6
 Homepage: https://www.mercurial-scm.org/doc/evolution/
 Vcs-Browser: https://www.mercurial-scm.org/repo/evolve/
@@ -24,11 +23,10 @@
 Package: mercurial-evolve
 Architecture: all
 Depends:
- ${python:Depends},
  ${python3:Depends},
  ${misc:Depends},
  ${sphinxdoc:Depends},
- mercurial (>= 4.7),
+ mercurial (>= 4.8),
 Built-Using: ${sphinxdoc:Built-Using}
 Description: evolve extension for Mercurial
  This package provides the experimental "evolve" extension for the Mercurial
--- a/debian/rules	Thu Nov 18 19:26:14 2021 +0300
+++ b/debian/rules	Tue Feb 22 08:19:44 2022 +0300
@@ -2,13 +2,12 @@
 #export DH_VERBOSE=1
 
 %:
-	dh $@ --with python2,python3,sphinxdoc --buildsystem=pybuild
+	dh $@ --with python3,sphinxdoc --buildsystem=pybuild
 
 ifeq (,$(filter nodoc, $(DEB_BUILD_OPTIONS)))
 override_dh_auto_build:
 	dh_auto_build
-	# Workaround for Sphinx in Debian Buster defaulting to Python 3
-	SPHINXBUILD="python -m sphinx -bhtml" $(MAKE) -C docs
+	$(MAKE) -C docs
 endif
 
 hgsrc_defined:
@@ -20,11 +19,6 @@
 	cd tests && python $(HGSRC)/tests/run-tests.py --with-hg=$(HGSRC)/hg --blacklist=$(CURDIR)/debian/test-blacklist
 endif
 
-override_dh_python2:
-	# avoid conflict with mercurial's own hgext3rd/__init__.py
-	find debian -path '*/hgext3rd/__init__.py' -delete
-	dh_python2
-
 override_dh_python3:
 	# avoid conflict with mercurial's own hgext3rd/__init__.py
 	find debian -path '*/hgext3rd/__init__.py' -delete
--- a/hgext3rd/evolve/__init__.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/evolve/__init__.py	Tue Feb 22 08:19:44 2022 +0300
@@ -33,7 +33,7 @@
 backported to older version of Mercurial by this extension. Some older
 experimental protocols are also supported for a longer time in the extension to
 help people transitioning. (The extension is currently compatible down to
-Mercurial version 4.7).
+Mercurial version 4.8).
 
 New Config::
 
@@ -1151,6 +1151,11 @@
              b'(also see `hg help evolve.interrupted`)')
     return cmdutil._commentlines(_msg)
 
+def _fixupmessage():
+    _msg = _(b'To continue:    hg fixup --continue\n'
+             b'To abort:       hg fixup --abort\n')
+    return cmdutil._commentlines(_msg)
+
 @eh.uisetup
 def setupevolveunfinished(ui):
     if not util.safehasattr(cmdutil, 'unfinishedstates'):
@@ -1165,6 +1170,10 @@
                                abortfunc=evolvecmd.hgabortevolve)
         statemod.addunfinished(b'pick', fname=b'pickstate', continueflag=True,
                                abortfunc=cmdrewrite.hgabortpick)
+        _fixup_msg = _(b'To continue:    hg fixup --continue\n'
+                       b'To abort:       hg fixup --abort\n')
+        statemod.addunfinished(b'fixup', fname=b'fixup-state',
+                               continueflag=True, statushint=_fixup_msg)
     else:
         # hg <= 5.0 (5f2f6912c9e6)
         estate = (b'evolvestate', False, False, _(b'evolve in progress'),
@@ -1173,16 +1182,23 @@
         pstate = (b'pickstate', False, False, _(b'pick in progress'),
                   _(b"use 'hg pick --continue' or 'hg pick --abort' to abort"))
         cmdutil.unfinishedstates.append(pstate)
+        fstate = (b'fixup-state', False, False, _(b'fixup in progress'),
+                  _(b"use 'hg fixup --continue' or 'hg fixup --abort' to abort"))
+        cmdutil.unfinishedstates.append(fstate)
 
         afterresolved = (b'evolvestate', _(b'hg evolve --continue'))
         pickresolved = (b'pickstate', _(b'hg pick --continue'))
+        fixupresolved = (b'fixup-state', _(b'hg fixup --continue'))
         cmdutil.afterresolvedstates.append(afterresolved)
         cmdutil.afterresolvedstates.append(pickresolved)
+        cmdutil.afterresolvedstates.append(fixupresolved)
 
+    # hg <= 5.0 (12243f15d53e)
     if util.safehasattr(cmdutil, 'STATES'):
-        statedata = (b'evolve', cmdutil.fileexistspredicate(b'evolvestate'),
-                     _evolvemessage)
-        cmdutil.STATES = (statedata, ) + cmdutil.STATES
+        cmdutil.STATES = (
+            (b'evolve', cmdutil.fileexistspredicate(b'evolvestate'), _evolvemessage),
+            (b'fixup', cmdutil.fileexistspredicate(b'fixup-state'), _fixupmessage),
+        ) + cmdutil.STATES
 
 @eh.wrapfunction(hg, 'clean')
 def clean(orig, repo, *args, **kwargs):
--- a/hgext3rd/evolve/cmdrewrite.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/evolve/cmdrewrite.py	Tue Feb 22 08:19:44 2022 +0300
@@ -30,6 +30,7 @@
     pycompat,
     scmutil,
     util,
+    repair,
 )
 
 from mercurial.utils import dateutil
@@ -42,6 +43,7 @@
     exthelper,
     rewriteutil,
     utility,
+    evolvecmd,
 )
 
 eh = exthelper.exthelper()
@@ -1430,10 +1432,11 @@
         raise error.Abort(_(b"no interrupted pick state exists"))
     pickstate.load()
     pctxnode = pickstate[b'oldpctx']
-    ui.status(_(b"aborting pick, updating to %s\n") %
-              node.short(pctxnode))
     compat.clean_update(repo[pctxnode])
     pickstate.delete()
+    ui.status(_(b'pick aborted\n'))
+    ui.status(_(b'working directory is now at %s\n')
+              % node.short(pctxnode))
     return 0
 
 def hgabortpick(ui, repo):
@@ -1441,3 +1444,153 @@
     with repo.wlock(), repo.lock():
         pickstate = state.cmdstate(repo, path=b'pickstate')
         return abortpick(ui, repo, pickstate, abortcmd=True)
+
+@eh.command(
+    b'fixup|fix-up',
+    [
+        (b'r', b'rev', b'', _(b'revision to amend'), _(b'REV')),
+        (b'c', b'continue', False, _(b'continue an interrupted fixup')),
+        (b'', b'abort', False, _(b'abort an interrupted fixup')),
+    ],
+    _(b'[OPTION]... [-r] REV'),
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_COMMITTING')
+)
+def fixup(ui, repo, node=None, **opts):
+    """add working directory changes to an arbitrary revision
+
+    A new changeset will be created, superseding the one specified. The new
+    changeset will combine working directory changes with the changes in the
+    target revision.
+
+    This operation requires the working directory changes to be relocated onto
+    the target revision, which might result in merge conflicts.
+
+    If fixup is interrupted to manually resolve a conflict, it can be continued
+    with --continue/-c, or aborted with --abort.
+
+    Note that this command is fairly new and its behavior is still
+    experimental. For example, the working copy will be left on a temporary,
+    obsolete commit containing the fixed-up changes after the operation. This
+    might change in the future.
+
+    Returns 0 on success, 1 if nothing changed.
+    """
+    compat.check_at_most_one_arg(opts, 'continue', 'abort')
+    with repo.wlock(), repo.lock():
+        return _perform_fixup(ui, repo, node, **opts)
+
+def _perform_fixup(ui, repo, node, **opts):
+    contopt = opts.get('continue')
+    abortopt = opts.get('abort')
+    if node or opts.get('rev'):
+        if contopt:
+            raise error.Abort(_(b'cannot specify a revision with --continue'))
+        if abortopt:
+            raise error.Abort(_(b'cannot specify a revision with --abort'))
+    # state file for --continue/--abort cases
+    fixup_state = state.cmdstate(repo, b'fixup-state')
+    if contopt:
+        if not fixup_state.exists():
+            raise error.Abort(_(b'no interrupted fixup to continue'))
+        fixup_state.load()
+        return continuefixup(ui, repo, fixup_state)
+    if abortopt:
+        if not fixup_state.exists():
+            raise error.Abort(_(b'no interrupted fixup to abort'))
+        fixup_state.load()
+        return abortfixup(ui, repo, fixup_state)
+
+    if node and opts.get('rev'):
+        raise error.Abort(_(b'please specify just one revision'))
+    if not node:
+        node = opts.get('rev')
+    if not node:
+        raise error.Abort(_(b'please specify a revision to fixup'))
+    target_ctx = scmutil.revsingle(repo, node)
+
+    fixup_state[b'startnode'] = repo[b'.'].node()
+
+    tr = repo.transaction(b'fixup')
+    with util.acceptintervention(tr):
+        overrides = {(b'ui', b'allowemptycommit'): False}
+        with repo.ui.configoverride(overrides, b'fixup'):
+            tempnode = repo.commit(
+                text=b'temporary fixup commit', user=opts.get(b'user'),
+                date=opts.get(b'date'))
+            if tempnode is None:
+                ui.status(_(b"nothing changed\n"))
+                return 1
+        fixup_state[b'tempnode'] = tempnode
+        # XXX: storing 'tempnode' should be enough, but 'current'
+        # is used by _relocate() logic
+        fixup_state[b'current'] = tempnode
+        fixup_state[b'target'] = target_ctx.node()
+        with state.saver(fixup_state):
+            # relocate temporary node to target revision
+            newnode = evolvecmd._relocate(
+                repo, repo[tempnode], target_ctx, fixup_state, update=False
+            )
+        # fold the two changesets
+        revs = (repo[newnode].rev(), target_ctx.rev())
+        root, head, p2 = rewriteutil.foldcheck(repo, revs)
+
+        allctx = [repo[r] for r in revs]
+        commitopts = {b'edit': False, b'message': target_ctx.description()}
+        newid, unusedvariable = rewriteutil.rewrite(
+            repo, root, head, [root.p1().node(), p2.node()],
+            commitopts=commitopts
+        )
+        phases.retractboundary(repo, tr, target_ctx.phase(), [newid])
+        replacements = {tuple(ctx.node() for ctx in allctx): [newid]}
+        compat.cleanupnodes(repo, replacements, operation=b'fixup')
+        fixup_state.delete()
+        compat.update(repo.unfiltered()[tempnode])
+        return 0
+
+def continuefixup(ui, repo, fixup_state):
+    """logic for handling of `hg fixup --continue`"""
+    target_node = fixup_state[b'target']
+    tempnode = fixup_state[b'tempnode']
+    target_ctx = repo[target_node]
+    tr = repo.transaction(b'fixup')
+    with util.acceptintervention(tr):
+        newnode = evolvecmd._completerelocation(ui, repo, fixup_state)
+        current = repo[fixup_state[b'current']]
+        obsolete.createmarkers(repo, [(current, (repo[newnode],))],
+                               operation=b'fixup')
+        # fold the two changesets
+        revs = (repo[newnode].rev(), target_ctx.rev())
+        root, head, p2 = rewriteutil.foldcheck(repo, revs)
+
+        allctx = [repo[r] for r in revs]
+        commitopts = {b'edit': False, b'message': target_ctx.description()}
+        newid, unusedvariable = rewriteutil.rewrite(
+            repo, root, head, [root.p1().node(), p2.node()],
+            commitopts=commitopts
+        )
+        phases.retractboundary(repo, tr, target_ctx.phase(), [newid])
+        replacements = {tuple(ctx.node() for ctx in allctx): [newid]}
+        compat.cleanupnodes(repo, replacements, operation=b'fixup')
+        fixup_state.delete()
+        compat.update(repo.unfiltered()[tempnode])
+        return 0
+
+def abortfixup(ui, repo, fixup_state):
+    """logic for handling of `hg fixup --abort`"""
+    with repo.wlock(), repo.lock():
+        startnode = fixup_state[b'startnode']
+        tempnode = fixup_state[b'tempnode']
+        tempctx = repo[tempnode]
+        compat.clean_update(repo[startnode])
+
+        stats = merge.graft(repo, tempctx, tempctx.p1(), [b'graft', b'fixup'])
+        # conflict is not possible, since grafting changes from descendant
+        assert not stats.unresolvedcount
+        repair.strip(ui, repo, [tempnode], backup=False)
+
+    pctx = repo[b'.']
+    ui.status(_(b'fixup aborted\n'))
+    ui.status(_(b'working directory is now at %s\n') % pctx)
+    fixup_state.delete()
+    return 0
--- a/hgext3rd/evolve/evolvecmd.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/evolve/evolvecmd.py	Tue Feb 22 08:19:44 2022 +0300
@@ -133,14 +133,14 @@
             ui.write_err(msg)
             return (False, b".")
         if exc.splitflag:
-            splitsucc = utility.picksplitsuccessor(ui, repo, obs, orig)
-            if not splitsucc[0]:
+            splitsucc = utility.select_split_successor(ui, repo, obs)
+            if not splitsucc:
                 msg = _(b"could not solve instability, "
                         b"ambiguous destination: "
                         b"parent split across two branches\n")
                 ui.write_err(msg)
                 return (False, b".")
-            newer = splitsucc[1]
+            newer = splitsucc
     target = repo[newer]
     if not ui.quiet or confirm:
         repo.ui.write(_(b'move:'), label=b'evolve.operation')
@@ -816,16 +816,18 @@
     there were conflicts or not while merging the messages"""
 
     merger = simplemerge.Merge3Text(basedesc, divdesc, othdesc)
-    mdesc = []
-    kwargs = {}
-    kwargs['name_base'] = b'base'
-    kwargs['base_marker'] = b'|||||||'
-    for line in merger.merge_lines(name_a=b'divergent', name_b=b'other',
-                                   **kwargs):
-        mdesc.append(line)
+    kwargs = {'name_a': b'divergent', 'name_b': b'other', 'name_base': b'base'}
 
-    desc = b''.join(mdesc)
-    if merger.conflicts:
+    if util.safehasattr(simplemerge, 'render_merge3'):
+        lines, conflicts = simplemerge.render_merge3(merger, **kwargs)
+        desc = b''.join(lines)
+    else:
+        # hg <= 6.0 (12ac4401ff7d)
+        kwargs['base_marker'] = b'|||||||'
+        desc = b''.join(merger.merge_lines(**kwargs))
+        conflicts = merger.conflicts
+
+    if conflicts:
 
         prefixes = (b"HG: Conflicts while merging changeset description of"
                     b" content-divergent changesets.\nHG: Resolve conflicts"
@@ -1138,18 +1140,14 @@
     Fall back to rev number, but in ascending order, for historical reasons.
     """
     ledmap = {
-        # For purpose of comparing, from `led` which is (unixtime, offset)
-        # we only need `unixtime`.
+        # for comparing, we only need unixtime from (unixtime, offset)
         rev: latest_evolution_date(repo, repo[rev])[0]
         for rev in divergent_revs
     }
-    # Sorting by negating the `rev` in key func, to fallback to the old way
-    # of selecting revision, in case when `led` is same while comparing.
-    # Old way: was to select the one with minimum revision number
     return sorted(divergent_revs, key=lambda rev: (-ledmap[rev], rev))
 
 def latest_evolution_date(repo, ctx):
-    """Return latest evolution date of `ctx`"""
+    """Return latest evolution date of `ctx` as (unixtime, offset)"""
     node = ctx.node()
     nodes = list(obsutil.closestpredecessors(repo, node))
     nodes.append(node)
@@ -1814,21 +1812,33 @@
     return ret
 
 def solveobswdp(ui, repo, opts):
-    """this function updates to the successor of obsolete wdir parent"""
+    """update to the successor of obsolete wdir parent"""
     oldid = repo[b'.'].node()
-    startctx = repo[b'.']
+    oldctx = repo[b'.']
     dryrunopt = opts.get('dry_run', False)
     display = compat.format_changeset_summary_fn(ui, repo, b'evolve',
                                                  shorttemplate)
     try:
-        ctx = repo[utility._singlesuccessor(repo, repo[b'.'])]
+        ctx = repo[utility._singlesuccessor(repo, oldctx)]
     except utility.MultipleSuccessorsError as exc:
-        repo.ui.write_err(_(b'parent is obsolete with multiple'
-                            b' successors:\n'))
-        for ln in exc.successorssets:
-            for n in ln:
-                display(repo[n])
-        return 2
+        if exc.splitflag:
+            splitsucc = utility.select_split_successor(ui, repo, oldctx)
+            if splitsucc:
+                ctx = repo[splitsucc]
+            else:
+                if util.safehasattr(error, 'CanceledError'):
+                    err = error.CanceledError
+                else:
+                    # hg <= 5.6 (ac362d5a7893)
+                    err = error.Abort
+                raise err(_(b'user quit'))
+        elif exc.divergenceflag:
+            repo.ui.write_err(_(b'parent is obsolete with multiple'
+                                b' content-divergent successors:\n'))
+            for sset in exc.successorssets:
+                for succ in sset:
+                    display(repo[succ])
+            return 2
 
     ui.status(_(b'update:'))
     if not ui.quiet:
@@ -1839,7 +1849,7 @@
     res = hg.update(repo, ctx.rev())
     newid = ctx.node()
 
-    if ctx != startctx:
+    if ctx != oldctx:
         with repo.wlock(), repo.lock(), repo.transaction(b'evolve') as tr:
             bmupdater = rewriteutil.bookmarksupdater(repo, oldid, tr)
             bmupdater(newid)
--- a/hgext3rd/evolve/headchecking.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/evolve/headchecking.py	Tue Feb 22 08:19:44 2022 +0300
@@ -25,7 +25,10 @@
 @eh.uisetup
 def uisetup(ui):
     extensions.wrapfunction(discovery, '_postprocessobsolete', _postprocessobsolete)
-    extensions.wrapfunction(scmutil, 'enforcesinglehead', enforcesinglehead)
+    code = scmutil.filteredhash.__code__
+    if r'needobsolete' not in code.co_varnames[:code.co_argcount]:
+        # hg <= 6.0 (053a5bf508da)
+        extensions.wrapfunction(scmutil, 'enforcesinglehead', enforcesinglehead)
 
 def branchinfo(pushop, repo, node):
     return repo[node].branch()
--- a/hgext3rd/evolve/metadata.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/evolve/metadata.py	Tue Feb 22 08:19:44 2022 +0300
@@ -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.4.1.dev'
-testedwith = b'4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9'
-minimumhgversion = b'4.7'
+__version__ = b'10.5.0.dev'
+testedwith = b'4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0'
+minimumhgversion = b'4.8'
 buglink = b'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obscache.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/evolve/obscache.py	Tue Feb 22 08:19:44 2022 +0300
@@ -432,9 +432,8 @@
 
 def _computeobsoleteset(orig, repo):
     """the set of obsolete revisions"""
-    obs = set()
     repo = repo.unfiltered()
-    notpublic = repo._phasecache.getrevset(repo, (phases.draft, phases.secret))
+    notpublic = repo._phasecache.getrevset(repo, phases.mutablephases)
     if notpublic:
         obscache = repo.obsstore.obscache
         # Since we warm the cache at the end of every transaction, the cache
@@ -461,10 +460,7 @@
                 # disk when the transaction close.
                 obscache.update(repo)
         isobs = obscache.get
-    for r in notpublic:
-        if isobs(r):
-            obs.add(r)
-    return obs
+    return frozenset(r for r in notpublic if isobs(r))
 
 @eh.uisetup
 def cachefuncs(ui):
--- a/hgext3rd/evolve/rewriteutil.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/evolve/rewriteutil.py	Tue Feb 22 08:19:44 2022 +0300
@@ -85,11 +85,14 @@
         msg %= (action, len(newunstable))
         hint = _(b"see 'hg help evolution.instability'")
         raise error.Abort(msg, hint=hint)
-    divrisk = revs_hascontentdivrisk(repo, revs)
     allowdivergence = compat.isenabled(repo, compat.allowdivergenceopt)
-    if divrisk and not allowdivergence:
-        localdiv = repo[divrisk[0]]
-        otherdiv, base = repo[divrisk[1][0]], repo[divrisk[1][1]]
+    if allowdivergence:
+        return
+    divergence = new_divergence(repo, revs)
+    if divergence:
+        local, other, common_prec = divergence
+        localdiv = repo[local]
+        otherdiv, base = repo[other], repo[common_prec]
         msg = _(b"%s of %s creates content-divergence "
                 b"with %s") % (action, localdiv, otherdiv)
         if localdiv.rev() != base.rev():
@@ -121,13 +124,16 @@
             repo._bookmarks.applychanges(repo, tr, bmchanges)
     return updatebookmarks
 
-def revs_hascontentdivrisk(repo, revs):
+def new_divergence(repo, revs):
+    """Return a tuple (rev, divergent, common_precursor) if rewriting one of
+    the revs will create content-divergence.
+    """
     obsrevs = repo.revs(b'%ld and obsolete()', revs)
     for r in obsrevs:
-        div = precheck_contentdiv(repo, repo[r])
-        if div:
-            return [r, div]
-    return []
+        div_prec = precheck_contentdiv(repo, repo[r])
+        if div_prec:
+            divergent, common_prec = div_prec
+            return (r, divergent, common_prec)
 
 def disallowednewunstable(repo, revs):
     """Check that editing <revs> will not create disallowed unstable
@@ -270,6 +276,7 @@
         if commitopts.get(b'edit'):
             editor = cmdutil.commitforceeditor
         new = wctx.tomemctx(text=message,
+                            branch=extra[b'branch'],
                             parents=newbases,
                             date=date,
                             extra=extra,
--- a/hgext3rd/evolve/utility.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/evolve/utility.py	Tue Feb 22 08:19:44 2022 +0300
@@ -128,32 +128,28 @@
 
     return repo[newer[0][0]].rev()
 
-def picksplitsuccessor(ui, repo, ctx, evolvecand):
-    """choose a successor of ctx from split targets
+def select_split_successor(ui, repo, ctx):
+    """Return most suitable split successor of <ctx> for evolution.
 
-    Choose highest one if all successors are in a topological branch. And if
-    they are split over multiple topological branches, we ask user to choose
-    an evolve destination.
+    If all successors are on one topological branch, we choose tipmost successor.
+    Otherwise, we ask user to choose an evolve destination.
 
-    Return (True, succ) unless split targets are split over multiple
-    topological branches and user didn't choose any evolve destination,
-    in which case return (False, '.')
+    Return ``None`` if no successor selected.
     """
     targets = obsutil.successorssets(repo, ctx.node())[0]
     assert targets
     targetrevs = [repo[r].rev() for r in targets]
     heads = repo.revs(b'heads(%ld::%ld)', targetrevs, targetrevs)
     if len(heads) > 1:
-        cheader = (_(b"ancestor of '%s' split over multiple topological"
-                     b" branches.\nchoose an evolve destination:") %
-                   evolvecand)
+        cheader = (_(b"changeset %s split over multiple topological"
+                     b" branches, choose an evolve destination:") % ctx)
         selectedrev = revselectionprompt(ui, repo, list(heads), cheader)
         if selectedrev is None:
-            return (False, '.')
+            return
         succ = repo[selectedrev]
     else:
         succ = repo[heads.first()]
-    return (True, repo[succ].rev())
+    return repo[succ].rev()
 
 def _successorrevs(repo, ctx):
     try:
--- a/hgext3rd/pullbundle.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/pullbundle.py	Tue Feb 22 08:19:44 2022 +0300
@@ -93,8 +93,8 @@
 from mercurial.i18n import _
 
 __version__ = b'0.1.3.dev'
-testedwith = b'4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9'
-minimumhgversion = b'4.7'
+testedwith = b'4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9'
+minimumhgversion = b'4.8'
 buglink = b'https://bz.mercurial-scm.org/'
 
 cmdtable = {}
--- a/hgext3rd/serverminitopic.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/serverminitopic.py	Tue Feb 22 08:19:44 2022 +0300
@@ -33,6 +33,14 @@
                default=False,
     )
 
+# nodemap.get and index.[has_node|rev|get_rev]
+# hg <= 5.2 (02802fa87b74)
+def getgetrev(cl):
+    """Returns index.get_rev or nodemap.get (for pre-5.3 Mercurial)."""
+    if util.safehasattr(cl.index, 'get_rev'):
+        return cl.index.get_rev
+    return cl.nodemap.get
+
 # hg <= 5.4 (e2d17974a869)
 def nonpublicphaseroots(repo):
     if util.safehasattr(repo._phasecache, 'nonpublicphaseroots'):
@@ -113,9 +121,9 @@
     revs = set()
     cl = repo.changelog
     fr = cl.filteredrevs
-    nm = cl.nodemap
+    getrev = getgetrev(cl)
     for n in nonpublicphaseroots(repo):
-        r = nm.get(n)
+        r = getrev(n)
         if r not in fr and r < maxrev:
             revs.add(r)
     key = node.nullid
--- a/hgext3rd/topic/__init__.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/topic/__init__.py	Tue Feb 22 08:19:44 2022 +0300
@@ -231,10 +231,10 @@
               b'log.topic': b'green_background',
               }
 
-__version__ = b'0.23.1.dev'
+__version__ = b'0.24.0.dev'
 
-testedwith = b'4.7 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9'
-minimumhgversion = b'4.7'
+testedwith = b'4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0'
+minimumhgversion = b'4.8'
 buglink = b'https://bz.mercurial-scm.org/'
 
 if util.safehasattr(registrar, 'configitem'):
@@ -440,6 +440,15 @@
         except (KeyError, AttributeError):
             pass
 
+    try:
+        histedit = extensions.find(b'histedit')
+    except KeyError:
+        pass
+    else:
+        # Make histedit preserve topics of edited commits
+        extensions.wrapfunction(histedit.histeditaction, 'applychange',
+                                applychangewrap)
+
     server.setupserver(ui)
 
 def reposetup(ui, repo):
@@ -732,6 +741,17 @@
     return orig(cl, manifest, files, desc, transaction, p1, p2, user,
                 date=date, extra=extra, **kwargs)
 
+def applychangewrap(orig, self):
+    orig(self)
+    repo = self.repo
+    rulectx = repo[self.node]
+
+    topic = None
+    if util.safehasattr(rulectx, 'topic'):
+        topic = rulectx.topic()
+    _changecurrenttopic(repo, topic)
+
+
 # revset predicates are automatically registered at loading via this symbol
 revsetpredicate = topicrevset.revsetpredicate
 
--- a/hgext3rd/topic/discovery.py	Thu Nov 18 19:26:14 2021 +0300
+++ b/hgext3rd/topic/discovery.py	Tue Feb 22 08:19:44 2022 +0300
@@ -217,12 +217,18 @@
 #
 # Handle this by doing an extra check for new head creation server side
 def _nbheads(repo):
+    code = scmutil.filteredhash.__code__
+    if r'needobsolete' not in code.co_varnames[:code.co_argcount]:
+        # hg <= 6.0 (053a5bf508da)
+        filterfn = _filter_obsolete_heads
+    else:
+        filterfn = lambda repo, heads: heads
     data = {}
     for b in repo.branchmap().iterbranches():
         if b':' in b[0]:
             continue
         oldheads = [repo[n].rev() for n in b[1]]
-        newheads = _filter_obsolete_heads(repo, oldheads)
+        newheads = filterfn(repo, oldheads)
         data[b[0]] = newheads
     return data
 
--- a/tests/test-check-sdist.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-check-sdist.t	Tue Feb 22 08:19:44 2022 +0300
@@ -35,7 +35,7 @@
 
   $ tar -tzf hg-evolve-*.tar.gz | sed 's|^hg-evolve-[^/]*/||' | sort > files
   $ wc -l files
-  352 files
+  356 files
   $ fgrep debian files
   tests/test-check-debian.t
   $ fgrep __init__.py files
--- a/tests/test-evolve-inmemory.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-evolve-inmemory.t	Tue Feb 22 08:19:44 2022 +0300
@@ -17,9 +17,9 @@
   $ 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
+  >     C  # C/C = C\n
+  > B2  |  # B2/B = B2\n
+  > |   B  # B/B = B\n
   >  \ /   # replace: B -> B2
   >   A
   > EOS
@@ -28,19 +28,19 @@
   move:[3] C
   atop:[2] B2
   $ hg glog
-  o  4:52da76e91abb draft tip
+  o  4:a2a0434af50b draft tip
   |  C
-  | x  3:bc77848cde3a draft C
+  | x  3:46f17045c5ee draft C
   | |  C
-  o |  2:377a194b9b8a draft B2
+  o |  2:3d6c495db414 draft B2
   | |  B2
-  | x  1:830b6315076c draft B
+  | x  1:caf23a7900cb draft B
   |/   B
   o  0:426bada5c675 draft A
      A
-  $ hg cat -r tip b c
-  b2
-  c
+  $ hg cat -r tip B C
+  B2
+  C
   $ cd ..
 
 Test that in-memory evolve works when there are conflicts
@@ -49,13 +49,13 @@
   $ hg init conflicts
   $ cd conflicts
   $ hg debugdrawdag <<'EOS'
-  >     E  # E/e = e\n
+  >     E  # E/E = E\n
   >     |
-  >     D  # D/b = d\n
+  >     D  # D/B = D\n
   >     |
-  >     C  # C/c = c\n
-  > B2  |  # B2/b = b2\n
-  > |   B  # B/b = b\n
+  >     C  # C/C = C\n
+  > B2  |  # B2/B = B2\n
+  > |   B  # B/B = B\n
   >  \ /   # replace: B -> B2
   >   A
   > EOS
@@ -64,67 +64,67 @@
   move:[3] C
   atop:[2] B2
   move:[4] D
-  merging b
+  merging B
   hit merge conflicts; retrying merge in working copy
-  merging b
-  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  merging B
+  warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
   unresolved merge conflicts
   (see 'hg help evolve.interrupted')
   [1]
   $ hg glog
-  @  6:52da76e91abb draft tip
+  @  6:a2a0434af50b draft tip
   |  C
-  | *  5:eae7899dd92b draft E
+  | *  5:844900596917 draft E
   | |  E
-  | %  4:57e51f6a6d36 draft D
+  | %  4:a75d38413966 draft D
   | |  D
-  | x  3:bc77848cde3a draft C
+  | x  3:46f17045c5ee draft C
   | |  C
-  o |  2:377a194b9b8a draft B2
+  o |  2:3d6c495db414 draft B2
   | |  B2
-  | x  1:830b6315076c draft B
+  | x  1:caf23a7900cb draft B
   |/   B
   o  0:426bada5c675 draft A
      A
-  $ cat c
-  c
-  $ cat b
-  <<<<<<< destination: 52da76e91abb - test: C
-  b2
+  $ cat C
+  C
+  $ cat B
+  <<<<<<< destination: a2a0434af50b - test: C
+  B2
   =======
-  d
-  >>>>>>> evolving:    57e51f6a6d36 D - test: D
-  $ echo d2 > b
+  D
+  >>>>>>> evolving:    a75d38413966 D - test: D
+  $ echo D2 > B
   $ hg resolve -m
   (no more unresolved files)
   continue: hg evolve --continue
   $ hg evolve --continue
-  evolving 4:57e51f6a6d36 "D"
+  evolving 4:a75d38413966 "D"
   move:[5] E
   atop:[7] D
   $ hg glog
-  o  8:3c658574f8ed draft tip
+  o  8:166afca01be8 draft tip
   |  E
-  o  7:16e609b952e8 draft
+  o  7:4f84a36487e4 draft
   |  D
-  o  6:52da76e91abb draft
+  o  6:a2a0434af50b draft
   |  C
-  | x  5:eae7899dd92b draft E
+  | x  5:844900596917 draft E
   | |  E
-  | x  4:57e51f6a6d36 draft D
+  | x  4:a75d38413966 draft D
   | |  D
-  | x  3:bc77848cde3a draft C
+  | x  3:46f17045c5ee draft C
   | |  C
-  o |  2:377a194b9b8a draft B2
+  o |  2:3d6c495db414 draft B2
   | |  B2
-  | x  1:830b6315076c draft B
+  | x  1:caf23a7900cb draft B
   |/   B
   o  0:426bada5c675 draft A
      A
-  $ hg cat -r tip b c e
-  d2
-  c
-  e
+  $ hg cat -r tip B C E
+  D2
+  C
+  E
   $ cd ..
 
 Test that in-memory merge is disabled if there's a precommit hook
@@ -132,16 +132,16 @@
   $ 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
+  >     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"
+  > precommit = sh -c "echo 'running precommit hook'"
   > EOF
 The hook is not run with in-memory=force
   $ hg co B2
@@ -149,20 +149,20 @@
   $ hg evolve --config experimental.evolution.in-memory=force --update
   move:[3] C
   atop:[2] B2
-  working directory is now at 52da76e91abb
+  working directory is now at a2a0434af50b
   $ hg glog
-  @  4:52da76e91abb draft tip
+  @  4:a2a0434af50b draft tip
   |  C
-  | x  3:bc77848cde3a draft C
+  | x  3:46f17045c5ee draft C
   | |  C
-  o |  2:377a194b9b8a draft B2
+  o |  2:3d6c495db414 draft B2
   | |  B2
-  | x  1:830b6315076c draft B
+  | x  1:caf23a7900cb draft B
   |/   B
   o  0:426bada5c675 draft A
      A
   $ hg co tip^
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg amend -m B3
   1 new orphan changesets
 The hook is run with in-memory=yes
@@ -170,17 +170,17 @@
   move:[4] C
   atop:[5] B3
   running precommit hook
-  working directory is now at aeee7323c054
+  working directory is now at 24f38f1ca271
   $ hg glog
-  @  6:aeee7323c054 draft tip
+  @  6:24f38f1ca271 draft tip
   |  C
-  o  5:908ce5f9d7eb draft
+  o  5:b43c30321752 draft
   |  B3
-  | x  3:bc77848cde3a draft C
+  | x  3:46f17045c5ee draft C
   | |  C
-  +---x  2:377a194b9b8a draft B2
+  +---x  2:3d6c495db414 draft B2
   | |    B2
-  | x  1:830b6315076c draft B
+  | x  1:caf23a7900cb draft B
   |/   B
   o  0:426bada5c675 draft A
      A
--- a/tests/test-evolve-issue5832.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-evolve-issue5832.t	Tue Feb 22 08:19:44 2022 +0300
@@ -117,8 +117,7 @@
   move:[2] added b
   atop:[5] added a
   move:[4] merge commit
-  ancestor of '7235ef625ea3' split over multiple topological branches.
-  choose an evolve destination:
+  changeset 9402371b436e split over multiple topological branches, choose an evolve destination:
   1: [62fb70414f99] added c
   2: [5841d7cf9893] added d
   q: quit the prompt
@@ -257,8 +256,7 @@
   move:[2] added b
   atop:[6] added a
   move:[4] merge commit
-  ancestor of 'cdf2ea1b9312' split over multiple topological branches.
-  choose an evolve destination:
+  changeset 9402371b436e split over multiple topological branches, choose an evolve destination:
   1: [62fb70414f99] added c
   2: [5841d7cf9893] added d
   q: quit the prompt
@@ -402,8 +400,7 @@
   > EOF
   move:[2] added b
   atop:[6] added a
-  ancestor of 'b9b387427a53' split over multiple topological branches.
-  choose an evolve destination:
+  changeset 9402371b436e split over multiple topological branches, choose an evolve destination:
   1: [62fb70414f99] added c
   2: [5841d7cf9893] added d
   q: quit the prompt
--- a/tests/test-evolve-orphan-split.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-evolve-orphan-split.t	Tue Feb 22 08:19:44 2022 +0300
@@ -193,8 +193,7 @@
   $ hg evolve --dry-run <<EOF
   > 1
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
-  choose an evolve destination:
+  changeset f89e4764f2ed split over multiple topological branches, choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
@@ -206,8 +205,7 @@
   $ hg evolve --dry-run <<EOF
   > 2
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
-  choose an evolve destination:
+  changeset f89e4764f2ed split over multiple topological branches, choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
@@ -222,8 +220,7 @@
   $ hg evolve --all <<EOF
   > foo
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
-  choose an evolve destination:
+  changeset f89e4764f2ed split over multiple topological branches, choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
@@ -234,8 +231,7 @@
   $ hg evolve --all <<EOF
   > 4
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
-  choose an evolve destination:
+  changeset f89e4764f2ed split over multiple topological branches, choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
@@ -246,8 +242,7 @@
   $ hg evolve --all <<EOF
   > -1
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
-  choose an evolve destination:
+  changeset f89e4764f2ed split over multiple topological branches, choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
@@ -258,8 +253,7 @@
   $ hg evolve --all <<EOF
   > q
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
-  choose an evolve destination:
+  changeset f89e4764f2ed split over multiple topological branches, choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
@@ -271,8 +265,7 @@
   $ hg evolve --all <<EOF
   > 1
   > EOF
-  ancestor of 'd48a30875f01' split over multiple topological branches.
-  choose an evolve destination:
+  changeset f89e4764f2ed split over multiple topological branches, choose an evolve destination:
   1: [f2632392aefe] added a b c
   2: [7f87764e5b64] added a b c
   q: quit the prompt
--- a/tests/test-evolve-phase-divergence.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-evolve-phase-divergence.t	Tue Feb 22 08:19:44 2022 +0300
@@ -17,11 +17,6 @@
   $ hg init public
   $ cd public
   $ echo a > a
-  $ mkcommit() {
-  >    echo "$1" > "$1"
-  >    hg add "$1"
-  >    hg ci -m "add $1"
-  > }
   $ hg commit -A -m init
   adding a
   $ cd ..
--- a/tests/test-evolve-progress.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-evolve-progress.t	Tue Feb 22 08:19:44 2022 +0300
@@ -122,8 +122,8 @@
   resolving manifests
    branchmerge: True, force: True, partial: False
    ancestor: 152c368c622b, local: f8d7d38c0a88+, remote: df5d742141b0
+  starting 4 threads for background file closing (?)
    preserving a for resolve of a
-  starting 4 threads for background file closing (?)
    a: versions differ -> m (premerge)
   updating: a 1/1 files (100.00%)
   picked tool ':merge' for a (binary False symlink False changedelete False)
--- a/tests/test-evolve-wdir.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-evolve-wdir.t	Tue Feb 22 08:19:44 2022 +0300
@@ -17,10 +17,12 @@
 ..     Resolution : update to a not-dead ancestor
 ..
 .. Case C: obsolete wdp with multiple successor (divergence rewriting)
-..     Resolution : #TODO: not handled yet
+..     Resolution : suggest to check out one of the divergent cset and
+..                  run `hg evolve --content-divergent`
 ..
 .. Case D: obsolete wdp with multiple successor (split rewriting)
-..     Resolution : #TODO: not handled yet
+..     Resolution : if split over a single topological branch, update to
+..                  tipmost, otherwise ask user to choose one
 
 A. Obsolete wdp with single successor
 -------------------------------------
@@ -101,22 +103,135 @@
   (707ee88b2870 has diverged, use 'hg evolve --list --content-divergent' to resolve the issue)
 
   $ hg evolve
-  parent is obsolete with multiple successors:
+  parent is obsolete with multiple content-divergent successors:
   [3] u_B
   [4] c_B
   [2]
 
+test that given hint works
+  $ hg up -r 'desc(u_B)'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg evolve --content-div
+  merge:[3] u_B
+  with: [4] c_B
+  base: [1] c_B
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 767c654afe84
   $ hg glog
-  *  4:39e54eb7aa3c (draft): c_B [content-divergent]
+  @  5:767c654afe84 (draft): u_B
   |
-  | *  3:90624b574289 (draft): u_B [content-divergent]
-  |/
-  | @  1:707ee88b2870 (draft): c_B
+  o  0:9f0188af4c58 (draft): c_A
+  
+
+D. Obsolete wdp with multiple successors (split rewriting)
+----------------------------------------------------------
+
+when split csets are on a single topological branch
+  $ hg up -r 'desc(c_A)'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo X > X; echo Y > Y; echo Z > Z;
+  $ hg ci -Am 'c_XYZ'
+  adding X
+  adding Y
+  adding Z
+  created new head
+  $ hg split -r "desc(c_XYZ)" -d "0 0" --config ui.interactive=True << EOF
+  > f
+  > d
+  > y
+  > f
+  > d
+  > c
+  > EOF
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  adding X
+  adding Y
+  adding Z
+  diff --git a/X b/X
+  new file mode 100644
+  examine changes to 'X'?
+  (enter ? for help) [Ynesfdaq?] f
+  
+  diff --git a/Y b/Y
+  new file mode 100644
+  examine changes to 'Y'?
+  (enter ? for help) [Ynesfdaq?] d
+  
+  created new head
+  continue splitting? [Ycdq?] y
+  diff --git a/Y b/Y
+  new file mode 100644
+  examine changes to 'Y'?
+  (enter ? for help) [Ynesfdaq?] f
+  
+  diff --git a/Z b/Z
+  new file mode 100644
+  examine changes to 'Z'?
+  (enter ? for help) [Ynesfdaq?] d
+  
+  continue splitting? [Ycdq?] c
+
+  $ hg up -r 'min(desc(c_XYZ))' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset c8b6cf6ce628
+  (hidden revision 'c8b6cf6ce628' was split as: 1eb7dbbcecbd, b99a391251cc and 1 more)
+  working directory parent is obsolete! (c8b6cf6ce628)
+  (use 'hg evolve' to update to its tipmost successor: 1eb7dbbcecbd, b99a391251cc and 1 more)
+
+  $ hg glog -l 3
+  o  9:b7ec9e61ccbf (draft): c_XYZ
+  |
+  o  8:b99a391251cc (draft): c_XYZ
+  |
+  o  7:1eb7dbbcecbd (draft): c_XYZ
+  |
+  ~
+test that given hint works
+  $ hg evolve
+  update:[9] c_XYZ
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at b7ec9e61ccbf
+
+when split csets are on multiple topological branches
+  $ hg rebase -r 'max(desc(c_XYZ))' -d 'desc(u_B)'
+  rebasing 9:b7ec9e61ccbf "c_XYZ" (tip)
+  $ hg glog
+  @  10:cadaa9246c55 (draft): c_XYZ
+  |
+  | o  8:b99a391251cc (draft): c_XYZ
+  | |
+  | o  7:1eb7dbbcecbd (draft): c_XYZ
+  | |
+  o |  5:767c654afe84 (draft): u_B
   |/
   o  0:9f0188af4c58 (draft): c_A
   
+  $ hg up -r 'min(desc(c_XYZ))' --hidden
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset c8b6cf6ce628
+  (hidden revision 'c8b6cf6ce628' was split as: 1eb7dbbcecbd, b99a391251cc and 1 more)
+  working directory parent is obsolete! (c8b6cf6ce628)
+  (use 'hg evolve' to update to its tipmost successor: 1eb7dbbcecbd, b99a391251cc and 1 more)
 
-D. Obsolete wdp with multiple successor (split rewriting)
-----------------------------------------------------------
+  $ hg evolve --config ui.interactive=True << EOF
+  > q
+  > EOF
+  changeset c8b6cf6ce628 split over multiple topological branches, choose an evolve destination:
+  1: [b99a391251cc] c_XYZ
+  2: [cadaa9246c55] c_XYZ
+  q: quit the prompt
+  enter the index of the revision you want to select: q
+  abort: user quit
+  [255]
 
-#TODO: yet to write tests for this case
+  $ hg evolve --config ui.interactive=True << EOF
+  > 1
+  > EOF
+  changeset c8b6cf6ce628 split over multiple topological branches, choose an evolve destination:
+  1: [b99a391251cc] c_XYZ
+  2: [cadaa9246c55] c_XYZ
+  q: quit the prompt
+  enter the index of the revision you want to select: 1
+  update:[8] c_XYZ
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory is now at b99a391251cc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fixup.t	Tue Feb 22 08:19:44 2022 +0300
@@ -0,0 +1,417 @@
+==========================
+Testing `hg fixup` command
+==========================
+
+  $ . $TESTDIR/testlib/common.sh
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase =
+  > evolve =
+  > [diff]
+  > git = 1
+  > EOF
+
+  $ hg help fixup
+  hg fixup [OPTION]... [-r] REV
+  
+  aliases: fix-up
+  
+  add working directory changes to an arbitrary revision
+  
+      A new changeset will be created, superseding the one specified. The new
+      changeset will combine working directory changes with the changes in the
+      target revision.
+  
+      This operation requires the working directory changes to be relocated onto
+      the target revision, which might result in merge conflicts.
+  
+      If fixup is interrupted to manually resolve a conflict, it can be
+      continued with --continue/-c, or aborted with --abort.
+  
+      Note that this command is fairly new and its behavior is still
+      experimental. For example, the working copy will be left on a temporary,
+      obsolete commit containing the fixed-up changes after the operation. This
+      might change in the future.
+  
+      Returns 0 on success, 1 if nothing changed.
+  
+  options:
+  
+   -r --rev REV  revision to amend
+   -c --continue continue an interrupted fixup
+      --abort    abort an interrupted fixup
+  
+  (some details hidden, use --verbose to show complete help)
+
+Simple cases
+------------
+
+  $ hg init simple
+  $ cd simple
+  $ mkcommit foo
+  $ mkcommit bar
+  $ mkcommit baz
+
+amending the middle of the stack
+--------------------------------
+
+  $ echo 'hookah bar' > bar
+  $ hg fixup -r 'desc(bar)'
+  1 new orphan changesets
+
+  $ hg diff -c tip
+  diff --git a/bar b/bar
+  new file mode 100644
+  --- /dev/null
+  +++ b/bar
+  @@ -0,0 +1,1 @@
+  +hookah bar
+
+  $ hg glog
+  o  5:2eec5320cfc7 bar
+  |   () draft
+  | @  3:fd2f632e47ab temporary fixup commit
+  | |   () draft
+  | *  2:a425495a8e64 baz
+  | |   () draft orphan
+  | x  1:c0c7cf58edc5 bar
+  |/    () draft
+  o  0:e63c23eaa88a foo
+      () draft
+
+  $ hg glog --hidden
+  o  5:2eec5320cfc7 bar
+  |   () draft
+  | x  4:4869c1db2884 temporary fixup commit
+  | |   () draft
+  | | @  3:fd2f632e47ab temporary fixup commit
+  | | |   () draft
+  | | *  2:a425495a8e64 baz
+  | |/    () draft orphan
+  | x  1:c0c7cf58edc5 bar
+  |/    () draft
+  o  0:e63c23eaa88a foo
+      () draft
+
+  $ hg evolve
+  update:[5] bar
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory is now at 2eec5320cfc7
+
+  $ hg evolve
+  move:[2] baz
+  atop:[5] bar
+
+  $ hg glog
+  o  6:eb1755d9f810 baz
+  |   () draft
+  @  5:2eec5320cfc7 bar
+  |   () draft
+  o  0:e63c23eaa88a foo
+      () draft
+
+amending working directory parent in secret phase
+-------------------------------------------------
+
+  $ hg up -r 'desc(baz)'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg phase --secret --force -r .
+  $ echo buzz >> baz
+  $ hg fix-up -r .
+
+  $ hg evolve
+  update:[9] baz
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 12b5e442244f
+  $ hg glog
+  @  9:12b5e442244f baz
+  |   () secret
+  o  5:2eec5320cfc7 bar
+  |   () draft
+  o  0:e63c23eaa88a foo
+      () draft
+
+testing --abort/--continue
+--------------------------
+
+  $ hg up -r 'desc(foo)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo 'update foo' > foo
+  $ hg ci -m 'update foo'
+  created new head
+  $ hg up -r 'desc(baz)'
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg glog
+  o  10:c90c517f86b3 update foo
+  |   () draft
+  | @  9:12b5e442244f baz
+  | |   () secret
+  | o  5:2eec5320cfc7 bar
+  |/    () draft
+  o  0:e63c23eaa88a foo
+      () draft
+
+testing --abort flag
+
+  $ echo 'update foo again' >> foo
+
+  $ hg fixup -r 'desc("update foo")'
+  merging foo
+  warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+
+  $ hg diff
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -1,1 +1,6 @@
+  +<<<<<<< destination: c90c517f86b3 - test: update foo
+   update foo
+  +=======
+  +foo
+  +update foo again
+  +>>>>>>> evolving:    1c9958e73c2d - test: temporary fixup commit
+
+  $ hg fixup --abort
+  fixup aborted
+  working directory is now at 12b5e442244f
+
+  $ hg diff
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -1,1 +1,2 @@
+   foo
+  +update foo again
+
+testing --continue flag
+
+  $ hg fixup -r 'desc("update foo")'
+  merging foo
+  warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+
+  $ hg status --verbose
+  M foo
+  ? foo.orig
+  # The repository is in an unfinished *fixup* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     foo
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:    hg fixup --continue
+  # To abort:       hg fixup --abort
+  
+  $ echo 'finalize foo' > foo
+
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg fixup --continue
+
+  $ hg fixup --continue
+  evolving 11:1c9958e73c2d "temporary fixup commit"
+
+  $ hg diff -c tip
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -1,1 +1,1 @@
+  -foo
+  +finalize foo
+
+  $ hg glog
+  o  13:fed7e534b3bb update foo
+  |   () draft
+  | @  11:1c9958e73c2d temporary fixup commit
+  | |   () secret
+  | o  9:12b5e442244f baz
+  | |   () secret
+  | o  5:2eec5320cfc7 bar
+  |/    () draft
+  o  0:e63c23eaa88a foo
+      () draft
+
+  $ hg evolve
+  update:[13] update foo
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  working directory is now at fed7e534b3bb
+
+amending a descendant of wdp
+
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo foobar > foobar
+  $ hg add foobar
+  $ hg fixup -r 'desc(baz)'
+  $ hg glog
+  o  16:b50fd0850076 baz
+  |   () secret
+  | @  14:4a9c4d14d447 temporary fixup commit
+  | |   () draft
+  | | o  13:fed7e534b3bb update foo
+  | |/    () draft
+  o |  5:2eec5320cfc7 bar
+  |/    () draft
+  o  0:e63c23eaa88a foo
+      () draft
+
+  $ hg evolve
+  update:[16] baz
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at b50fd0850076
+
+  $ hg glog
+  @  16:b50fd0850076 baz
+  |   () secret
+  | o  13:fed7e534b3bb update foo
+  | |   () draft
+  o |  5:2eec5320cfc7 bar
+  |/    () draft
+  o  0:e63c23eaa88a foo
+      () draft
+  $ hg diff -c .
+  diff --git a/baz b/baz
+  new file mode 100644
+  --- /dev/null
+  +++ b/baz
+  @@ -0,0 +1,2 @@
+  +baz
+  +buzz
+  diff --git a/foobar b/foobar
+  new file mode 100644
+  --- /dev/null
+  +++ b/foobar
+  @@ -0,0 +1,1 @@
+  +foobar
+
+no fixup in progress
+
+  $ hg fixup --continue
+  abort: no interrupted fixup to continue
+  [255]
+
+  $ hg fixup --abort
+  abort: no interrupted fixup to abort
+  [255]
+
+testing error cases
+
+  $ hg fixup tip --abort
+  abort: cannot specify a revision with --abort
+  [255]
+
+  $ hg fixup -r tip --continue
+  abort: cannot specify a revision with --continue
+  [255]
+
+  $ hg fixup
+  abort: please specify a revision to fixup
+  [255]
+
+  $ hg fixup tip
+  nothing changed
+  [1]
+
+  $ hg fixup -r tip
+  nothing changed
+  [1]
+
+  $ hg fixup 1 2 3
+  hg fixup: invalid arguments
+  hg fixup [OPTION]... [-r] REV
+  
+  add working directory changes to an arbitrary revision
+  
+  options:
+  
+   -r --rev REV  revision to amend
+   -c --continue continue an interrupted fixup
+      --abort    abort an interrupted fixup
+  
+  (use 'hg fixup -h' to show more help)
+  [255]
+
+  $ hg fixup :10 -r 5
+  abort: please specify just one revision
+  [255]
+
+  $ cd ..
+
+Multiple branches
+-----------------
+
+  $ hg init branches
+  $ cd branches
+
+  $ cat >> .hg/hgrc << EOF
+  > [extensions]
+  > topic =
+  > [alias]
+  > glog = log -GT '{rev}:{node|short} {desc}\n ({branch}) [{topic}]\n'
+  > EOF
+
+  $ mkcommit ROOT
+  $ hg topic topic-A -q
+  $ mkcommit A -q
+  $ hg topic topic-B -q
+  $ mkcommit B -q
+  $ hg up 'desc(ROOT)' -q
+  $ hg branch other-branch -q
+  $ hg topic topic-C -q
+  $ mkcommit C -q
+  $ hg topic topic-D -q
+  $ mkcommit D -q
+  $ hg up 'desc(A)' -q
+
+  $ hg glog
+  o  4:deb0223c611b D
+  |   (other-branch) [topic-D]
+  o  3:381934d792ab C
+  |   (other-branch) [topic-C]
+  | o  2:d2dfccd24f25 B
+  | |   (default) [topic-B]
+  | @  1:0a2783c5c927 A
+  |/    (default) [topic-A]
+  o  0:ea207398892e ROOT
+      (default) []
+
+  $ echo Z > Z
+  $ hg add Z
+  $ hg fix-up -r 'desc(C)'
+  switching to topic topic-C
+  1 new orphan changesets
+
+  $ hg evolve
+  update:[7] C
+  switching to topic topic-C
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  working directory is now at 57d19d0ff7ee
+  $ hg evolve --any
+  move:[4] D
+  atop:[7] C
+  switching to topic topic-C
+
+C and D keep their original branch and topics
+
+  $ hg glog
+  o  8:203e06b553f5 D
+  |   (other-branch) [topic-D]
+  @  7:57d19d0ff7ee C
+  |   (other-branch) [topic-C]
+  | o  2:d2dfccd24f25 B
+  | |   (default) [topic-B]
+  | o  1:0a2783c5c927 A
+  |/    (default) [topic-A]
+  o  0:ea207398892e ROOT
+      (default) []
+
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-issue6550.t	Tue Feb 22 08:19:44 2022 +0300
@@ -0,0 +1,124 @@
+histedit should preserve topics (issue6550)
+https://bz.mercurial-scm.org/show_bug.cgi?id=6550
+
+  $ . "$TESTDIR/testlib/topic_setup.sh"
+
+  $ cat << EOF >> "$HGRCPATH"
+  > [extensions]
+  > histedit =
+  > [alias]
+  > glog = log -G --template "{rev}:{node|short} [{topic}] {desc}\n"
+  > EOF
+
+Editing commits with one topic on top of a commit with a different topic:
+
+  $ hg init repo1
+  $ cd repo1
+  $ hg topic topic1
+  marked working directory as topic: topic1
+  $ echo 1 > A
+  $ hg ci -Aqm A
+  $ hg topic topic2
+  $ echo 1 > B
+  $ hg ci -Aqm B
+  $ echo 1 > C
+  $ hg ci -Aqm C
+  $ hg glog
+  @  2:392a64d00726 [topic2] C
+  |
+  o  1:8a25a1549e46 [topic2] B
+  |
+  o  0:c051488dac25 [topic1] A
+  
+Swap the order of commits B and C
+
+  $ hg histedit s1 -q --commands - 2>&1 << EOF
+  > pick 392a64d00726 C
+  > pick 8a25a1549e46 B
+  > EOF
+
+Topic of B and C is preserved
+
+  $ hg glog
+  @  4:065a99df807b [topic2] B
+  |
+  o  3:43dddca3e1d1 [topic2] C
+  |
+  o  0:c051488dac25 [topic1] A
+  
+  $ cd ..
+
+Editing commits without a topic on top of a commit with a topic:
+
+  $ hg init repo2
+  $ cd repo2
+  $ hg topic topic1
+  marked working directory as topic: topic1
+  $ echo 1 > A
+  $ hg ci -Aqm A
+  $ hg topic --clear
+  $ echo 1 > B
+  $ hg ci -Aqm B
+  $ echo 1 > C
+  $ hg ci -Aqm C
+  $ hg glog
+  @  2:c47acbb860b3 [] C
+  |
+  o  1:e2e2ca96a6bb [] B
+  |
+  o  0:c051488dac25 [topic1] A
+  
+Swap the order of commits B and C
+
+  $ hg histedit s1 -q --commands - 2>&1 << EOF
+  > pick c47acbb860b3 C
+  > pick e2e2ca96a6bb B
+  > EOF
+
+B and C still don't have a topic
+
+  $ hg glog
+  @  4:ff3439fe6f3d [] B
+  |
+  o  3:bb6fab1a29c6 [] C
+  |
+  o  0:c051488dac25 [topic1] A
+  
+  $ cd ..
+
+Editing commits with a topic on top of a commit without a topic:
+
+  $ hg init repo3
+  $ cd repo3
+  $ echo 1 > A
+  $ hg ci -Aqm A
+  $ hg topic topic1
+  marked working directory as topic: topic1
+  $ echo 1 > B
+  $ hg ci -Aqm B
+  $ echo 1 > C
+  $ hg ci -Aqm C
+  $ hg glog
+  @  2:c3dae6eda73b [topic1] C
+  |
+  o  1:db3a7c9052ac [topic1] B
+  |
+  o  0:a18fe624bf77 [] A
+  
+Swap the order of commits B and C
+
+  $ hg histedit s1 -q --commands - 2>&1 << EOF
+  > pick c3dae6eda73b C
+  > pick db3a7c9052ac B
+  > EOF
+
+Topic of B and C is preserved
+
+  $ hg glog
+  @  4:aa7af5cc1567 [topic1] B
+  |
+  o  3:4bf8cf7b2c73 [topic1] C
+  |
+  o  0:a18fe624bf77 [] A
+  
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-next-abort.t	Tue Feb 22 08:19:44 2022 +0300
@@ -0,0 +1,61 @@
+Testing hg next with --abort flag and hg abort command handling an interrupted hg next
+
+  $ . "$TESTDIR/testlib/common.sh"
+
+  $ cat >> "$HGRCPATH" << EOF
+  > [extensions]
+  > evolve =
+  > EOF
+
+  $ hg init next-abort
+  $ cd next-abort
+
+  $ echo apple > a
+  $ hg ci -qAm apple
+  $ echo banana > b
+  $ hg ci -qAm banana
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo blueberry > b
+  $ hg ci -qAm 'apple and blueberry' --amend
+  1 new orphan changesets
+
+  $ hg next
+  move:[1] banana
+  atop:[2] apple and blueberry
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+
+#testcases abortcommand abortflag
+#if abortflag
+  $ hg next --abort
+  next aborted
+  working directory is now at 1c7f51cf0ef0
+  $ hg next --abort
+  abort: no interrupted next to abort
+  [255]
+  $ hg evolve --abort
+  abort: no interrupted evolve to abort
+  [255]
+
+  $ hg next --abort --move-bookmark
+  abort: cannot specify both --abort and --move-bookmark
+  [255]
+  $ hg next --abort --merge
+  abort: cannot specify both --abort and --merge
+  [255]
+#else
+  $ hg abort --dry-run
+  evolve in progress, will be aborted
+  $ hg abort
+  evolve aborted
+  working directory is now at 1c7f51cf0ef0
+  $ hg abort
+  abort: no operation in progress
+  [255]
+#endif
+
+  $ cd ..
--- a/tests/test-pick.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-pick.t	Tue Feb 22 08:19:44 2022 +0300
@@ -269,7 +269,8 @@
   pick in progress, will be aborted
 #endif
   $ hg abort
-  aborting pick, updating to c437988de89f
+  pick aborted
+  working directory is now at c437988de89f
 
   $ hg glog
   @  10:c437988de89f foo to b
--- a/tests/test-single-head-obsolescence-named-branch-A1.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-named-branch-A1.t	Tue Feb 22 08:19:44 2022 +0300
@@ -14,13 +14,13 @@
 
 .. old-state:
 ..
-.. * 2 changeset changeset on branch default
-.. * 2 changeset changeset on branch Z on top of them.
+.. * 2 changesets on branch default
+.. * 2 changesets on branch Z on top of them.
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on branch Z at the same location
-.. * 2 changeset changeset on branch default superseding the other ones
+.. * 2 changesets on branch Z at the same location
+.. * 2 changesets on branch default superseding the other ones
 ..
 .. expected-result:
 ..
@@ -70,11 +70,11 @@
   created new head
   (consider using topic for lightweight branches. See 'hg help topic')
   $ mkcommit B1
-  $ hg debugobsolete `getid "desc(A0)" ` `getid "desc(A1)"`
+  $ hg debugobsolete `getid "desc(A0)"` `getid "desc(A1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   3 new orphan changesets
-  $ hg debugobsolete `getid "desc(B0)" ` `getid "desc(B1)"`
+  $ hg debugobsolete `getid "desc(B0)"` `getid "desc(B1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   $ hg log -G --hidden
--- a/tests/test-single-head-obsolescence-named-branch-A2.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-named-branch-A2.t	Tue Feb 22 08:19:44 2022 +0300
@@ -1,4 +1,3 @@
-
 =========================================
 Testing single head enforcement: Case A-2
 =========================================
@@ -15,14 +14,14 @@
 
 .. old-state:
 ..
-.. * 2 changeset changeset on branch default
-.. * 2 changeset changeset on branch Z on top of them.
+.. * 2 changesets on branch default
+.. * 2 changesets on branch Z on top of them.
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on branch Z at the same location
-.. * 1 changeset changeset on branch default unchanged
-.. * 1 changeset changeset on branch default superseding the other ones
+.. * 2 changesets on branch Z at the same location
+.. * 1 changeset on branch default unchanged
+.. * 1 changeset on branch default superseding the other ones
 ..
 .. expected-result:
 ..
@@ -71,7 +70,7 @@
   $ mkcommit B1
   created new head
   (consider using topic for lightweight branches. See 'hg help topic')
-  $ hg debugobsolete `getid "desc(B0)" ` `getid "desc(B1)"`
+  $ hg debugobsolete `getid "desc(B0)"` `getid "desc(B1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   2 new orphan changesets
--- a/tests/test-single-head-obsolescence-named-branch-A3.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-named-branch-A3.t	Tue Feb 22 08:19:44 2022 +0300
@@ -10,17 +10,17 @@
 This case is part of a series of tests checking this behavior.
 
 Category A: Involving obsolescence
-TestCase 3: Full supersedig of a branch interleaved with another
+TestCase 3: Full superseding of a branch interleaved with another
 
 .. old-state:
 ..
-.. * 2 changeset changeset on branch default
-.. * 2 changeset changeset on branch Z interleaved with the other
+.. * 2 changesets on branch default
+.. * 2 changesets on branch Z interleaved with the other
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on branch Z at the same location
-.. * 2 changeset changeset on branch default superseding the other ones
+.. * 2 changesets on branch Z at the same location
+.. * 2 changesets on branch default superseding the other ones
 ..
 .. expected-result:
 ..
@@ -78,11 +78,11 @@
   created new head
   (consider using topic for lightweight branches. See 'hg help topic')
   $ mkcommit C1
-  $ hg debugobsolete `getid "desc(A0)" ` `getid "desc(A1)"`
+  $ hg debugobsolete `getid "desc(A0)"` `getid "desc(A1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   3 new orphan changesets
-  $ hg debugobsolete `getid "desc(C0)" ` `getid "desc(C1)"`
+  $ hg debugobsolete `getid "desc(C0)"` `getid "desc(C1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   $ hg log -G --hidden
--- a/tests/test-single-head-obsolescence-named-branch-A4.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-named-branch-A4.t	Tue Feb 22 08:19:44 2022 +0300
@@ -14,12 +14,12 @@
 
 .. old-state:
 ..
-.. * 2 changeset changeset on branch default
-.. * 2 changeset changeset on branch Z interleaved with the other one
+.. * 2 changesets on branch default
+.. * 2 changesets on branch Z interleaved with the other one
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on branch Z at the same location
+.. * 2 changesets on branch Z at the same location
 .. * 1 changeset on default untouched (the lower one)
 .. * 1 changeset on default moved on the other one
 ..
@@ -78,7 +78,7 @@
   $ mkcommit C1
   created new head
   (consider using topic for lightweight branches. See 'hg help topic')
-  $ hg debugobsolete `getid "desc(C0)" ` `getid "desc(C1)"`
+  $ hg debugobsolete `getid "desc(C0)"` `getid "desc(C1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   1 new orphan changesets
--- a/tests/test-single-head-obsolescence-named-branch-A5.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-named-branch-A5.t	Tue Feb 22 08:19:44 2022 +0300
@@ -1,5 +1,5 @@
 =========================================
-Testing single head enforcement: Case A-1
+Testing single head enforcement: Case A-5
 =========================================
 
 A repository is set to only accept a single head per name (typically named
@@ -10,16 +10,16 @@
 This case is part of a series of tests checking this behavior.
 
 Category A: Involving obsolescence
-TestCase 1: obsoleting a merge reveal two heads
+TestCase 5: Obsoleting a merge reveals two heads
 
 .. old-state:
 ..
-.. * 3 changeset changeset on branch default (2 on their own branch + 1 merge)
+.. * 3 changesets on branch default (2 on their own branch + 1 merge)
 .. * 1 changeset on branch Z (children of the merge)
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on branch default (merge is obsolete) each a head
+.. * 2 changesets on branch default (merge is obsolete) each a head
 .. * 1 changeset on branch Z keeping the merge visible
 ..
 .. expected-result:
@@ -28,8 +28,6 @@
 ..
 .. graph-summary:
 ..
-..   D ●      (branch Z)
-..     |
 ..   C ●      (branch Z)
 ..     |
 ..   M ⊗
--- a/tests/test-single-head-obsolescence-topic-B1.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-topic-B1.t	Tue Feb 22 08:19:44 2022 +0300
@@ -1,5 +1,5 @@
 =========================================
-Testing single head enforcement: Case A-1
+Testing single head enforcement: Case B-1
 =========================================
 
 A repository is set to only accept a single head per name (typically named
@@ -9,18 +9,18 @@
 
 This case is part of a series of tests checking this behavior.
 
-Category B: Involving obsolescence and topic
+Category B: Involving obsolescence with topic
 TestCase 1: A fully obsolete topic kept visible by another one.
 
 .. old-state:
 ..
-.. * 2 changeset changeset on topic X
-.. * 2 changeset changeset on topic Y on top of them.
+.. * 2 changesets on topic X
+.. * 2 changesets on topic Y on top of them.
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on topic Y at the same location
-.. * 2 changeset changeset on topic X superseding the other ones
+.. * 2 changesets on topic Y at the same location
+.. * 2 changesets on topic X superseding the other ones
 ..
 .. expected-result:
 ..
@@ -76,11 +76,11 @@
   marked working directory as topic: topic-X
   $ mkcommit A1
   $ mkcommit B1
-  $ hg debugobsolete `getid "desc(A0)" ` `getid "desc(A1)"`
+  $ hg debugobsolete `getid "desc(A0)"` `getid "desc(A1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   3 new orphan changesets
-  $ hg debugobsolete `getid "desc(B0)" ` `getid "desc(B1)"`
+  $ hg debugobsolete `getid "desc(B0)"` `getid "desc(B1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   $ hg log -G --hidden
--- a/tests/test-single-head-obsolescence-topic-B2.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-topic-B2.t	Tue Feb 22 08:19:44 2022 +0300
@@ -1,6 +1,5 @@
-
 =========================================
-Testing single head enforcement: Case A-2
+Testing single head enforcement: Case B-2
 =========================================
 
 A repository is set to only accept a single head per name (typically named
@@ -15,14 +14,14 @@
 
 .. old-state:
 ..
-.. * 2 changeset changeset on topic X
-.. * 2 changeset changeset on topic Y on top of them.
+.. * 2 changesets on topic X
+.. * 2 changesets on topic Y on top of them.
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on topic Y at the same location
-.. * 1 changeset changeset on topic X unchanged
-.. * 1 changeset changeset on topic X superseding the other ones
+.. * 2 changesets on topic Y at the same location
+.. * 1 changeset on topic X unchanged
+.. * 1 changeset on topic X superseding the other ones
 ..
 .. expected-result:
 ..
@@ -79,7 +78,7 @@
   $ hg topic topic-X
   marked working directory as topic: topic-X
   $ mkcommit B1
-  $ hg debugobsolete `getid "desc(B0)" ` `getid "desc(B1)"`
+  $ hg debugobsolete `getid "desc(B0)"` `getid "desc(B1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   2 new orphan changesets
--- a/tests/test-single-head-obsolescence-topic-B3.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-topic-B3.t	Tue Feb 22 08:19:44 2022 +0300
@@ -1,5 +1,5 @@
 =========================================
-Testing single head enforcement: Case A-3
+Testing single head enforcement: Case B-3
 =========================================
 
 A repository is set to only accept a single head per name (typically named
@@ -10,17 +10,17 @@
 This case is part of a series of tests checking this behavior.
 
 Category B: Involving obsolescence with topic
-TestCase 3: Full supersedig of a branch interleaved with another
+TestCase 3: Full superseding of a branch interleaved with another
 
 .. old-state:
 ..
-.. * 2 changeset changeset on topic Y
-.. * 2 changeset changeset on topic X interleaved with the other
+.. * 2 changesets on topic Y
+.. * 2 changesets on topic X interleaved with the other
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on topic X at the same location
-.. * 2 changeset changeset on topic Y superseding the other ones
+.. * 2 changesets on topic X at the same location
+.. * 2 changesets on topic Y superseding the other ones
 ..
 .. expected-result:
 ..
@@ -78,11 +78,11 @@
   marked working directory as topic: topic-X
   $ mkcommit A1
   $ mkcommit C1
-  $ hg debugobsolete `getid "desc(A0)" ` `getid "desc(A1)"`
+  $ hg debugobsolete `getid "desc(A0)"` `getid "desc(A1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   3 new orphan changesets
-  $ hg debugobsolete `getid "desc(C0)" ` `getid "desc(C1)"`
+  $ hg debugobsolete `getid "desc(C0)"` `getid "desc(C1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   $ hg log -G --hidden
--- a/tests/test-single-head-obsolescence-topic-B4.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-topic-B4.t	Tue Feb 22 08:19:44 2022 +0300
@@ -1,5 +1,5 @@
 =========================================
-Testing single head enforcement: Case A-4
+Testing single head enforcement: Case B-4
 =========================================
 
 A repository is set to only accept a single head per name (typically named
@@ -9,17 +9,17 @@
 
 This case is part of a series of tests checking this behavior.
 
-Category A: Involving obsolescence
+Category B: Involving obsolescence with topic
 TestCase 4: Partial rewrite of a branch to dis-interleave it
 
 .. old-state:
 ..
-.. * 2 changeset changeset on topic X
-.. * 2 changeset changeset on topic Y interleaved with the other one
+.. * 2 changesets on topic X
+.. * 2 changesets on topic Y interleaved with the other one
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on topic Y at the same location
+.. * 2 changesets on topic Y at the same location
 .. * 1 changeset on topic X untouched (the lower one)
 .. * 1 changeset on topic X moved on the other one
 ..
@@ -78,7 +78,7 @@
   0 files updated, 0 files merged, 3 files removed, 0 files unresolved
   $ hg topic topic-X
   $ mkcommit C1
-  $ hg debugobsolete `getid "desc(C0)" ` `getid "desc(C1)"`
+  $ hg debugobsolete `getid "desc(C0)"` `getid "desc(C1)"`
   1 new obsolescence markers
   obsoleted 1 changesets
   1 new orphan changesets
--- a/tests/test-single-head-obsolescence-topic-B5.t	Thu Nov 18 19:26:14 2021 +0300
+++ b/tests/test-single-head-obsolescence-topic-B5.t	Tue Feb 22 08:19:44 2022 +0300
@@ -1,5 +1,5 @@
 =========================================
-Testing single head enforcement: Case A-1
+Testing single head enforcement: Case B-5
 =========================================
 
 A repository is set to only accept a single head per name (typically named
@@ -9,17 +9,17 @@
 
 This case is part of a series of tests checking this behavior.
 
-Category A: Involving obsolescence
-TestCase 1: obsoleting a merge reveal two heads
+Category B: Involving obsolescence with topic
+TestCase 5: Obsoleting a merge reveals two heads
 
 .. old-state:
 ..
-.. * 3 changeset changeset on topic X (2 on their own branch + 1 merge)
-.. * 1 changeset on topic Y (children of the merge)
+.. * 3 changesets on topic X (2 on their own branch + 1 merge)
+.. * 1 changeset on topic Y (child of the merge)
 ..
 .. new-state:
 ..
-.. * 2 changeset changeset on topic X (merge is obsolete) each a head
+.. * 2 changesets on topic X (merge is obsolete) each a head
 .. * 1 changeset on topic Y keeping the merge visible
 ..
 .. expected-result:
@@ -28,8 +28,6 @@
 ..
 .. graph-summary:
 ..
-..   D ●      (topic-Y)
-..     |
 ..   C ●      (topic-Y)
 ..     |
 ..   M ⊗      (topic-X)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/testlib/update-hg-repo.sh	Tue Feb 22 08:19:44 2022 +0300
@@ -0,0 +1,12 @@
+#!/bin/sh
+# Clone or update core Mercurial repo at the provided path. Useful for CI
+# runners that don't have a shared repo setup, e.g. the shell runner that is
+# currently used for Windows CI.
+
+URL=https://mirror.octobus.net/hg
+if hg root -R "$1"; then
+    hg pull -R "$1" "$URL"
+else
+    rm -rf "$1"
+    hg clone "$URL" "$1"
+fi