evolve--list: initial implementation
authorKostia Balytskyi <ikostia@fb.com>
Tue, 22 Mar 2016 14:08:16 -0700
changeset 1634 9ae4e79a28f3
parent 1633 9bcb24c3ba8d
child 1635 91ba7e0daff6
evolve--list: initial implementation This implementation does not work with '-T json' since it needs multiple levels of depth. There is various small issue with the current data, the test coverage is a bit low and the output is likely to change, but this is a good step forward. Sample output: 01a3e66ba030: e (amended) unstable: 1995fc658ad6 (unstable parent) divergent: 84e1c6ae319d d3b90e9c84ab (precursor 3efa43a7427b) divergent: add9a356b8cf (precursor 3efa43a7427b) add9a356b8cf: e (rebased) divergent: 84e1c6ae319d d3b90e9c84ab (precursor 3efa43a7427b) divergent: 01a3e66ba030 (precursor 3efa43a7427b) 84e1c6ae319d: e (e+f split) unstable: 1995fc658ad6 (unstable parent) divergent: 01a3e66ba030 (precursor 3efa43a7427b) divergent: add9a356b8cf (precursor 3efa43a7427b) d3b90e9c84ab: f (e+f split) unstable: 84e1c6ae319d (unstable parent) divergent: 01a3e66ba030 (precursor 3efa43a7427b) divergent: add9a356b8cf (precursor 3efa43a7427b) 8febfaee0dd1: c unstable: 43765473b851 (obsolete parent) bumped: b36d99df9f41 (immutable precursor) 1995fc658ad6: d: commit with a long happy message, ababagalamaga, ababagal... unstable: 8febfaee0dd1 (unstable parent) fa8498ad030f: aa unstable: d3b90e9c84ab (unstable parent)
hgext/evolve.py
tests/test-evolve-list.t
--- a/hgext/evolve.py	Fri Mar 18 23:49:32 2016 -0700
+++ b/hgext/evolve.py	Tue Mar 22 14:08:16 2016 -0700
@@ -1514,6 +1514,110 @@
     ordering.extend(sorted(dependencies))
     return ordering
 
+def divergentsets(repo, ctx):
+    """Compute sets of commits divergent with a given one"""
+    cache = {}
+    succsets = {}
+    base = {}
+    for n in obsolete.allprecursors(repo.obsstore, [ctx.node()]):
+        if n == ctx.node():
+            # a node can't be a base for divergence 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
+    divergence = []
+    for divset, b in base.iteritems():
+        divergence.append({
+            'divergentnodes': divset,
+            'commonprecursor': b
+        })
+
+    return divergence
+
+def _preparelistctxs(items, condition):
+    return [item.hex() for item in items if condition(item)]
+
+def _formatctx(fm, ctx):
+    fm.data(node=ctx.hex())
+    fm.data(desc=ctx.description())
+    fm.data(date=ctx.date())
+    fm.data(user=ctx.user())
+
+def listtroubles(ui, repo, troublecategories, **opts):
+    """Print all the troubles for the repo (or given revset)"""
+    troublecategories = troublecategories or ['divergent', 'unstable', 'bumped']
+    showunstable = 'unstable' in troublecategories
+    showbumped = 'bumped' in troublecategories
+    showdivergent = 'divergent' in troublecategories
+
+    revs = repo.revs('+'.join("%s()" % t for t in troublecategories))
+    if opts.get('rev'):
+        revs = revs & repo.revs(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(%n)", ctx.node()),
+                                   lambda p: not p.mutable())
+        dsets = divergentsets(repo, ctx)
+
+        fm.startitem()
+        # plain formatter section
+        hashlen, desclen = 12, 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: %s (unstable parent)\n' % unpar[:hashlen])
+        for obspar in obspars if showunstable else []:
+            fm.plain('  unstable: %s (obsolete parent)\n' % obspar[:hashlen])
+        for imprec in imprecs if showbumped else []:
+            fm.plain('  bumped: %s (immutable precursor)\n' % imprec[:hashlen])
+
+        if dsets and showdivergent:
+            for dset in dsets:
+                fm.plain('  divergent: ')
+                first = True
+                for n in dset['divergentnodes']:
+                    t = "%s" if first else " %s"
+                    first = False
+                    fm.plain(t % node.hex(n)[:hashlen])
+                comprec = node.hex(dset['commonprecursor'])[:hashlen]
+                fm.plain(" (precursor %s)\n" % comprec)
+        fm.plain("\n")
+
+        # templater-friendly section
+        _formatctx(fm, ctx)
+        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')),
@@ -1529,6 +1633,7 @@
     ('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, 'provide details on troubled changesets in the repo'),
     ] + mergetoolopts,
     _('[OPTIONS]...'))
 def evolve(ui, repo, **opts):
@@ -1596,9 +1701,13 @@
     (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']
@@ -1608,6 +1717,10 @@
     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)')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-evolve-list.t	Tue Mar 22 14:08:16 2016 -0700
@@ -0,0 +1,75 @@
+Set up some configs
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > EOF
+  $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext/evolve.py" >> $HGRCPATH
+
+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
+  d2ae7f538514: b
+    unstable: cb9a9f314b8b (obsolete parent)
+  
+  177f92b77385: c
+    unstable: d2ae7f538514 (unstable parent)
+  
+  $ 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
+  88cc282e27fc: ab
+    bumped: cb9a9f314b8b (immutable precursor)
+  
+  $ cd ..
+
+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 --config experimental.allowdivergence=True
+  rebasing 1:d2ae7f538514 "b"
+  2 new divergent changesets
+  $ hg evolve --list
+  c882616e9d84: b
+    divergent: a922b3733e98 (precursor d2ae7f538514)
+  
+  a922b3733e98: b
+    divergent: c882616e9d84 (precursor d2ae7f538514)
+  
+  $ cd ..