Patchwork [evolve-ext,v3] evolve--list: initial implementation with sample template

login
register
mail settings
Submitter Kostia Balytskyi
Date March 19, 2016, 8:09 p.m.
Message ID <f3f8ddd2738f8a036f5f.1458418174@dev1902.lla1.facebook.com>
Download mbox | patch
Permalink /patch/13961/
State Changes Requested
Headers show

Comments

Kostia Balytskyi - March 19, 2016, 8:09 p.m.
# HG changeset patch
# User Kostia Balytskyi <ikostia@fb.com>
# Date 1458417923 25200
#      Sat Mar 19 13:05:23 2016 -0700
# Node ID f3f8ddd2738f8a036f5f7dde4c4cae500575ce01
# Parent  081605c2e9b6bb4d917295aae06b8428f64d0df1
evolve--list: initial implementation with sample template

Please bear in mind that this was not adjusted to conform
with style requirements. This is just a sample implementation
to get some feeback, so the template constant will be removed.

This implementation does not work with JSON since it needs
multiple levels of depth.

Feedback is welcome and necessary :)


Sample output:
```
ikostia@dev1902.lla1:~/temprepos/supertroubledrepo$ hg evolve --list
01a3e66b: e (amended)
  unstable: unstable parent 1995fc65
  divergent with:
    (84e1c6ae, d3b90e9c); common precursor: 3efa43a7
    (add9a356); common precursor: 3efa43a7
add9a356: e (rebased)
  divergent with:
    (84e1c6ae, d3b90e9c); common precursor: 3efa43a7
    (01a3e66b); common precursor: 3efa43a7
84e1c6ae: e (e+f split)
  unstable: unstable parent 1995fc65
  divergent with:
    (01a3e66b); common precursor: 3efa43a7
    (add9a356); common precursor: 3efa43a7
d3b90e9c: f (e+f split)
  unstable: unstable parent 84e1c6ae
  divergent with:
    (01a3e66b); common precursor: 3efa43a7
    (add9a356); common precursor: 3efa43a7
8febfaee: c
  unstable: obsolete parent 43765473
  bumped: immutable precursor b36d99df
1995fc65: d: commit with a long happy message, ababagalamaga, ababagal...
  unstable: unstable parent 8febfaee
```
Pierre-Yves David - March 20, 2016, 4:26 p.m.
On 03/19/2016 01:09 PM, Kostia Balytskyi wrote:
> # HG changeset patch
> # User Kostia Balytskyi <ikostia@fb.com>
> # Date 1458417923 25200
> #      Sat Mar 19 13:05:23 2016 -0700
> # Node ID f3f8ddd2738f8a036f5f7dde4c4cae500575ce01
> # Parent  081605c2e9b6bb4d917295aae06b8428f64d0df1
> evolve--list: initial implementation with sample template
>
> Please bear in mind that this was not adjusted to conform
> with style requirements. This is just a sample implementation
> to get some feeback, so the template constant will be removed.



> This implementation does not work with JSON since it needs
> multiple levels of depth.
>
> Feedback is welcome and necessary :)
>
>
> Sample output:
> ```
> ikostia@dev1902.lla1:~/temprepos/supertroubledrepo$ hg evolve --list
> 01a3e66b: e (amended)
>    unstable: unstable parent 1995fc65
>    divergent with:
>      (84e1c6ae, d3b90e9c); common precursor: 3efa43a7
>      (add9a356); common precursor: 3efa43a7
> add9a356: e (rebased)
>    divergent with:
>      (84e1c6ae, d3b90e9c); common precursor: 3efa43a7
>      (01a3e66b); common precursor: 3efa43a7
> 84e1c6ae: e (e+f split)
>    unstable: unstable parent 1995fc65
>    divergent with:
>      (01a3e66b); common precursor: 3efa43a7
>      (add9a356); common precursor: 3efa43a7
> d3b90e9c: f (e+f split)
>    unstable: unstable parent 84e1c6ae
>    divergent with:
>      (01a3e66b); common precursor: 3efa43a7
>      (add9a356); common precursor: 3efa43a7
> 8febfaee: c
>    unstable: obsolete parent 43765473
>    bumped: immutable precursor b36d99df
> 1995fc65: d: commit with a long happy message, ababagalamaga, ababagal...
>    unstable: unstable parent 8febfaee
> ```

For other people reading this, more discussion around the output format 
have been happening here:

   http://bz.mercurial-scm.org/show_bug.cgi?id=4845

> diff --git a/hgext/evolve.py b/hgext/evolve.py
> --- a/hgext/evolve.py
> +++ b/hgext/evolve.py
> @@ -1509,6 +1509,117 @@ def _orderrevs(repo, revs):
>       ordering.extend(sorted(dependencies))
>       return ordering
>
> +def divergentsets(repo, ctx):

Can we get some documentation for this function?

(Also, how hard would it be the similar bits in mercurial.obsolete to 
yield the same kind of information)

> +    cache = {}
> +    succsets = {}
> +    base = {}
> +    for n in obsolete.allprecursors(repo.obsstore, [ctx.node()]):
> +        if n == ctx.node():
> +            # a node can't be a base for divergencies with itself
> +            continue
> +        nsuccsets = obsolete.successorssets(repo, n, cache)
> +        for nsuccset in nsuccsets:
> +            if ctx.node() in nsuccset:
> +                # we are only interested in *other* successor sets
> +                continue
> +            if tuple(nsuccset) in base:
> +                # we already know the latest base for this divergency
> +                continue
> +            base[tuple(nsuccset)] = n
> +    divergencies = []

divergence?

> +    for divset, b in base.iteritems():
> +        divergencies.append({
> +            'divergentnodes': map(lambda n: node.hex(n), divset),

Turning things to hex (and dict) now seems premature. This function 
seems to be computing the raw data of the divergence. We should keep itr 
as a "logic" only function. The formatting should happen outside of it.

> +            'commonprecursor': node.hex(b)
> +        })
> +
> +    return divergencies
> +
> +def _preparelistctxs(items, condition):
> +    return [item.hex() for item in items if condition(item)]
> +
> +
> +# to be removed, for templater demonstration purposes only
> +troublelisttemplate = "{short(node)}: {desc}\n{troubles % '" \
> +    "{ifeq(troubletype, \'unstable\', \'  unstable: {ifeq(sourcetype, \\'unstableparent\\', \\'unstable\\', \\'obsolete\\')} parent {short(sourcenode)}\n\')}" \
> +    "{ifeq(troubletype, \'bumped\', \'  bumped: immutable precursor {short(sourcenode)}\n\')}" \
> +    "{ifeq(troubletype, \'divergent\', \'  divergent with: ({divergentnodes % \\'{short(node)}, \\'}); common precursor {short(commonprecursor)}\n\')}" \
> +    "'}"

I think we agreed on that IRL, but we should probably drop all 
templating support for now and focus on written output. We'll think 
about templating (probably reusing a lot of this) as a second step when 
we are sure about the data we display.

> +def listtroubles(ui, repo, troublecategories, **opts):
> +    """Print all the troubles for the repo (or given revset)"""
> +    # to be removed; uncomment this is you want plain output
> +    #opts.setdefault('template', troublelisttemplate)
> +
> +    troublecategories = troublecategories or ['divergent', 'unstable', 'bumped']
> +    showunstable = 'unstable' in troublecategories
> +    showbumped = 'bumped' in troublecategories
> +    showdivergent = 'divergent' in troublecategories
> +
> +    revs = scmutil.revrange(repo, map(lambda c: c+'()', troublecategories))

1) you should use repo.revs here. to bypass any user alias.

2) map is usually unnecessary in python.

   rpeo.revs('+'.join("%s()" t for t in troublescategories))

> +    if opts.get('rev'):
> +        revs = revs & scmutil.revrange(repo, opts.get('rev'))
> +
> +    fm = ui.formatter('evolvelist', opts)
> +    for rev in revs:
> +        ctx = repo[rev]
> +        unpars = _preparelistctxs(ctx.parents(), lambda p: p.unstable())
> +        obspars = _preparelistctxs(ctx.parents(), lambda p: p.obsolete())
> +        imprecs = _preparelistctxs(repo.set("allprecursors(%s)" % ctx.hex()),
> +                                   lambda p: not p.mutable())

1) What is the "imprecs" list about? it does not seems to exist anywhere.

2) You should use the simpler form '%n' % ctx instead of '%s' % ctx.hex().

> +        dsets = divergentsets(repo, ctx)
> +
> +        fm.startitem()
> +        # plain formatter section
> +        hashlen, desclen = 8, 60
> +        desc = ctx.description()
> +        desc = desc[:desclen] + '...' if len(desc) > desclen else desc
> +        fm.plain('%s: ' % ctx.hex()[:hashlen])
> +        fm.plain('%s\n' % desc)
> +
> +        for unpar in unpars if showunstable else []:
> +            fm.plain('  unstable: unstable parent %s\n' % unpar[:hashlen])
> +        for obspar in obspars if showunstable else []:
> +            fm.plain('  unstable: obsolete parent %s\n' % obspar[:hashlen])
> +        for imprec in imprecs if showbumped else []:
> +            fm.plain('  bumped: immutable precursor %s\n' % imprec[:hashlen])
> +
> +        if dsets and showdivergent:
> +            fm.plain('  divergent with:\n')
> +            for dset in dsets:
> +                fm.plain("    (")
> +                first = True
> +                for n in dset['divergentnodes']:
> +                    t = "%s" if first else ", %s"
> +                    first = False
> +                    fm.plain(t % n[:hashlen])
> +                comprec = dset['commonprecursor'][:hashlen]
> +                fm.plain("); common precursor: %s\n" % comprec)
> +
> +        # templater-friendly section
> +        fm.data(node=ctx.hex())
> +        fm.data(desc=ctx.description())
> +        fm.data(date=ctx.date())
> +        fm.data(user=ctx.user())

Could we move the building of this into some kind of function to be able 
to reuse it for the other nodes we include in various place in the 
output. (don't have to be done for the first version)

> +        troubles = []
> +        for unpar in unpars:
> +            troubles.append({'troubletype': 'unstable', 'sourcenode': unpar,
> +                             'sourcetype': 'unstableparent'})
> +        for obspar in obspars:
> +            troubles.append({'troubletype': 'unstable', 'sourcenode': obspar,
> +                             'sourcetype': 'obsoleteparent'})
> +        for imprec in imprecs:
> +            troubles.append({'troubletype': 'bumped', 'sourcenode': imprec,
> +                             'sourcetype': 'immutableprecursor'})
> +        for dset in dsets:
> +            divnodes = [{'node': n} for n in dset['divergentnodes']]
> +            troubles.append({'troubletype': 'divergent',
> +                             'commonprecursor': dset['commonprecursor'],
> +                             'divergentnodes': divnodes})
> +        fm.data(troubles=troubles)
> +
> +    fm.end()
> +
>   @command('^evolve|stabilize|solve',
>       [('n', 'dry-run', False,
>           _('do not perform actions, just print what would be done')),
> @@ -1524,6 +1635,7 @@ def _orderrevs(repo, revs):
>       ('a', 'all', False, _('evolve all troubled changesets related to the '
>                             'current  working directory and its descendants')),
>       ('c', 'continue', False, _('continue an interrupted evolution')),
> +    ('l', 'list', False, 'list all the troubled changesets in the repository'),

We should probably have 'details' here somewhere. something like

'provide details on troubled changesets in the repository'


>       ] + mergetoolopts,
>       _('[OPTIONS]...'))
>   def evolve(ui, repo, **opts):
> @@ -1591,9 +1703,13 @@ def evolve(ui, repo, **opts):
>       (the default). You can combine ``--bumped`` or ``--divergent`` with
>       ``--rev``, ``--all``, or ``--any``.
>
> +    You can also use the evolve command to list the troubles affecting your
> +    repository by using the --list flag. You can choose to display only some
> +    categories of troubles with the --unstable, --divergent or --bumped flags.

We should probably run our help through timeless he probably have better 
forms in mind (because timeless).

>       """
>
>       # Options
> +    listopt = opts['list']
>       contopt = opts['continue']
>       anyopt = opts['any']
>       allopt = opts['all']
> @@ -1603,6 +1719,10 @@ def evolve(ui, repo, **opts):
>       revopt = opts['rev']
>       troublecategories = ['bumped', 'divergent', 'unstable']
>       specifiedcategories = [t for t in troublecategories if opts[t]]
> +    if listopt:
> +        listtroubles(ui, repo, specifiedcategories, **opts)
> +        return
> +
>       targetcat = 'unstable'
>       if 1 < len(specifiedcategories):
>           msg = _('cannot specify more than one trouble category to solve (yet)')
> diff --git a/tests/test-evolve-list.t b/tests/test-evolve-list.t
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-evolve-list.t
> @@ -0,0 +1,72 @@
> +Set up some configs
> +  $ cat >> $HGRCPATH <<EOF
> +  > [extensions]
> +  > rebase=
> +  > EOF
> +  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH

On the wiki page, there is list of all cases we have to account for. We 
should probably turn each of them into each small sub test case.

I also go per complexity order (unstable, bumped, divergence).

On that topic, I'm also okay with a more iterative approach where patch 
1 only support unstable, patch 2 bumping, patch 3 divergence (or more 
slicing if appropriate). That would make the patch smaller and easier to 
review.

> +Test the divergence listing
> +  $ hg init r1
> +  $ cd r1
> +  $ echo a > a && hg ci -Am a
> +  adding a
> +  $ hg up 0
> +  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +  $ echo b > b && hg ci -Am b
> +  adding b
> +  $ hg up 0
> +  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
> +  $ echo c > c && hg ci -Am c
> +  adding c
> +  created new head
> +  $ hg up 0
> +  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
> +  $ echo d > d && hg ci -Am d
> +  adding d
> +  created new head
> +  $ hg rebase -s 1 -d 2
> +  rebasing 1:d2ae7f538514 "b"
> +  $ hg rebase -s 1 -d 3 --hidden
> +  rebasing 1:d2ae7f538514 "b"
> +  2 new divergent changesets
> +  $ hg evolve --list
> +  c882616e: b
> +    divergent with:
> +      (a922b373); common precursor: d2ae7f53
> +  a922b373: b
> +    divergent with:
> +      (c882616e); common precursor: d2ae7f53
> +  $ cd ..
> +
> +Test the instability listing
> +  $ hg init r2
> +  $ cd r2
> +  $ echo a > a && hg ci -Am a
> +  adding a
> +  $ echo b > b && hg ci -Am b
> +  adding b
> +  $ echo c > c && hg ci -Am c
> +  adding c
> +  $ hg up 0
> +  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
> +  $ echo a >> a && hg ci --amend -m a
> +  2 new unstable changesets
> +  $ hg evolve --list
> +  d2ae7f53: b
> +    unstable: obsolete parent cb9a9f31
> +  177f92b7: c
> +    unstable: unstable parent d2ae7f53
> +  $ cd ..
> +
> +Test the bumpedness listing
> +  $ hg init r3
> +  $ cd r3
> +  $ echo a > a && hg ci -Am a
> +  adding a
> +  $ echo b > b && hg ci --amend -m ab
> +  $ hg phase --public --rev 0 --hidden
> +  1 new bumped changesets
> +  $ hg evolve --list
> +  88cc282e: ab
> +    bumped: immutable precursor cb9a9f31
> +  $ cd ..
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
>

Patch

diff --git a/hgext/evolve.py b/hgext/evolve.py
--- a/hgext/evolve.py
+++ b/hgext/evolve.py
@@ -1509,6 +1509,117 @@  def _orderrevs(repo, revs):
     ordering.extend(sorted(dependencies))
     return ordering
 
+def divergentsets(repo, ctx):
+    cache = {}
+    succsets = {}
+    base = {}
+    for n in obsolete.allprecursors(repo.obsstore, [ctx.node()]):
+        if n == ctx.node():
+            # a node can't be a base for divergencies with itself
+            continue
+        nsuccsets = obsolete.successorssets(repo, n, cache)
+        for nsuccset in nsuccsets:
+            if ctx.node() in nsuccset:
+                # we are only interested in *other* successor sets
+                continue
+            if tuple(nsuccset) in base:
+                # we already know the latest base for this divergency
+                continue
+            base[tuple(nsuccset)] = n
+    divergencies = []
+    for divset, b in base.iteritems():
+        divergencies.append({
+            'divergentnodes': map(lambda n: node.hex(n), divset),
+            'commonprecursor': node.hex(b)
+        })
+
+    return divergencies
+
+def _preparelistctxs(items, condition):
+    return [item.hex() for item in items if condition(item)]
+
+
+# to be removed, for templater demonstration purposes only
+troublelisttemplate = "{short(node)}: {desc}\n{troubles % '" \
+    "{ifeq(troubletype, \'unstable\', \'  unstable: {ifeq(sourcetype, \\'unstableparent\\', \\'unstable\\', \\'obsolete\\')} parent {short(sourcenode)}\n\')}" \
+    "{ifeq(troubletype, \'bumped\', \'  bumped: immutable precursor {short(sourcenode)}\n\')}" \
+    "{ifeq(troubletype, \'divergent\', \'  divergent with: ({divergentnodes % \\'{short(node)}, \\'}); common precursor {short(commonprecursor)}\n\')}" \
+    "'}"
+
+def listtroubles(ui, repo, troublecategories, **opts):
+    """Print all the troubles for the repo (or given revset)"""
+    # to be removed; uncomment this is you want plain output
+    #opts.setdefault('template', troublelisttemplate)
+
+    troublecategories = troublecategories or ['divergent', 'unstable', 'bumped']
+    showunstable = 'unstable' in troublecategories
+    showbumped = 'bumped' in troublecategories
+    showdivergent = 'divergent' in troublecategories
+
+    revs = scmutil.revrange(repo, map(lambda c: c+'()', troublecategories))
+    if opts.get('rev'):
+        revs = revs & scmutil.revrange(repo, opts.get('rev'))
+
+    fm = ui.formatter('evolvelist', opts)
+    for rev in revs:
+        ctx = repo[rev]
+        unpars = _preparelistctxs(ctx.parents(), lambda p: p.unstable())
+        obspars = _preparelistctxs(ctx.parents(), lambda p: p.obsolete())
+        imprecs = _preparelistctxs(repo.set("allprecursors(%s)" % ctx.hex()),
+                                   lambda p: not p.mutable())
+        dsets = divergentsets(repo, ctx)
+
+        fm.startitem()
+        # plain formatter section
+        hashlen, desclen = 8, 60
+        desc = ctx.description()
+        desc = desc[:desclen] + '...' if len(desc) > desclen else desc
+        fm.plain('%s: ' % ctx.hex()[:hashlen])
+        fm.plain('%s\n' % desc)
+
+        for unpar in unpars if showunstable else []:
+            fm.plain('  unstable: unstable parent %s\n' % unpar[:hashlen])
+        for obspar in obspars if showunstable else []:
+            fm.plain('  unstable: obsolete parent %s\n' % obspar[:hashlen])
+        for imprec in imprecs if showbumped else []:
+            fm.plain('  bumped: immutable precursor %s\n' % imprec[:hashlen])
+
+        if dsets and showdivergent:
+            fm.plain('  divergent with:\n')
+            for dset in dsets:
+                fm.plain("    (")
+                first = True
+                for n in dset['divergentnodes']:
+                    t = "%s" if first else ", %s"
+                    first = False
+                    fm.plain(t % n[:hashlen])
+                comprec = dset['commonprecursor'][:hashlen]
+                fm.plain("); common precursor: %s\n" % comprec)
+
+        # templater-friendly section
+        fm.data(node=ctx.hex())
+        fm.data(desc=ctx.description())
+        fm.data(date=ctx.date())
+        fm.data(user=ctx.user())
+        troubles = []
+        for unpar in unpars:
+            troubles.append({'troubletype': 'unstable', 'sourcenode': unpar,
+                             'sourcetype': 'unstableparent'})
+        for obspar in obspars:
+            troubles.append({'troubletype': 'unstable', 'sourcenode': obspar,
+                             'sourcetype': 'obsoleteparent'})
+        for imprec in imprecs:
+            troubles.append({'troubletype': 'bumped', 'sourcenode': imprec,
+                             'sourcetype': 'immutableprecursor'})
+        for dset in dsets:
+            divnodes = [{'node': n} for n in dset['divergentnodes']]
+            troubles.append({'troubletype': 'divergent',
+                             'commonprecursor': dset['commonprecursor'],
+                             'divergentnodes': divnodes})
+        fm.data(troubles=troubles)
+
+    fm.end()
+
 @command('^evolve|stabilize|solve',
     [('n', 'dry-run', False,
         _('do not perform actions, just print what would be done')),
@@ -1524,6 +1635,7 @@  def _orderrevs(repo, revs):
     ('a', 'all', False, _('evolve all troubled changesets related to the '
                           'current  working directory and its descendants')),
     ('c', 'continue', False, _('continue an interrupted evolution')),
+    ('l', 'list', False, 'list all the troubled changesets in the repository'),
     ] + mergetoolopts,
     _('[OPTIONS]...'))
 def evolve(ui, repo, **opts):
@@ -1591,9 +1703,13 @@  def evolve(ui, repo, **opts):
     (the default). You can combine ``--bumped`` or ``--divergent`` with
     ``--rev``, ``--all``, or ``--any``.
 
+    You can also use the evolve command to list the troubles affecting your
+    repository by using the --list flag. You can choose to display only some
+    categories of troubles with the --unstable, --divergent or --bumped flags.
     """
 
     # Options
+    listopt = opts['list']
     contopt = opts['continue']
     anyopt = opts['any']
     allopt = opts['all']
@@ -1603,6 +1719,10 @@  def evolve(ui, repo, **opts):
     revopt = opts['rev']
     troublecategories = ['bumped', 'divergent', 'unstable']
     specifiedcategories = [t for t in troublecategories if opts[t]]
+    if listopt:
+        listtroubles(ui, repo, specifiedcategories, **opts)
+        return
+
     targetcat = 'unstable'
     if 1 < len(specifiedcategories):
         msg = _('cannot specify more than one trouble category to solve (yet)')
diff --git a/tests/test-evolve-list.t b/tests/test-evolve-list.t
new file mode 100644
--- /dev/null
+++ b/tests/test-evolve-list.t
@@ -0,0 +1,72 @@ 
+Set up some configs
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+Test the divergence listing
+  $ hg init r1
+  $ cd r1
+  $ echo a > a && hg ci -Am a
+  adding a
+  $ hg up 0
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo b > b && hg ci -Am b
+  adding b
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo c > c && hg ci -Am c
+  adding c
+  created new head
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo d > d && hg ci -Am d
+  adding d
+  created new head
+  $ hg rebase -s 1 -d 2
+  rebasing 1:d2ae7f538514 "b"
+  $ hg rebase -s 1 -d 3 --hidden
+  rebasing 1:d2ae7f538514 "b"
+  2 new divergent changesets
+  $ hg evolve --list
+  c882616e: b
+    divergent with:
+      (a922b373); common precursor: d2ae7f53
+  a922b373: b
+    divergent with:
+      (c882616e); common precursor: d2ae7f53
+  $ cd ..
+
+Test the instability listing
+  $ hg init r2
+  $ cd r2
+  $ echo a > a && hg ci -Am a
+  adding a
+  $ echo b > b && hg ci -Am b
+  adding b
+  $ echo c > c && hg ci -Am c
+  adding c
+  $ hg up 0
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo a >> a && hg ci --amend -m a
+  2 new unstable changesets
+  $ hg evolve --list
+  d2ae7f53: b
+    unstable: obsolete parent cb9a9f31
+  177f92b7: c
+    unstable: unstable parent d2ae7f53
+  $ cd ..
+
+Test the bumpedness listing
+  $ hg init r3
+  $ cd r3
+  $ echo a > a && hg ci -Am a
+  adding a
+  $ echo b > b && hg ci --amend -m ab
+  $ hg phase --public --rev 0 --hidden
+  1 new bumped changesets
+  $ hg evolve --list
+  88cc282e: ab
+    bumped: immutable precursor cb9a9f31
+  $ cd ..