Patchwork [1,of,2,V3] debug: add debugwhyunstable that explains instabilities

login
register
mail settings
Submitter Anton Shestakov
Date March 14, 2018, 10:56 a.m.
Message ID <3372fb5de4a6f5a6e327.1521024985@neuro>
Download mbox | patch
Permalink /patch/29486/
State Superseded
Headers show

Comments

Anton Shestakov - March 14, 2018, 10:56 a.m.
# HG changeset patch
# User Anton Shestakov <av6@dwimlabs.net>
# Date 1520944478 -28800
#      Tue Mar 13 20:34:38 2018 +0800
# Node ID 3372fb5de4a6f5a6e327d4d98d57f4e94ae64b60
# Parent  31581528f2421dc5d8a567125b8ecc0367b2b906
# EXP-Topic whyunstable-obsolete
debug: add debugwhyunstable that explains instabilities

This is a port of evolve's feature of listing all unstable changesets in detail
(`hg evolve --list`).

Patch

diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py
--- a/mercurial/debugcommands.py
+++ b/mercurial/debugcommands.py
@@ -2526,6 +2526,17 @@  def debugwalk(ui, repo, *pats, **opts):
         line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
         ui.write("%s\n" % line.rstrip())
 
+@command('debugwhyunstable', [], _('REV'))
+def debugwhyunstable(ui, repo, rev):
+    """explain instabilities of a changeset"""
+    for entry in obsolete.whyunstable(repo, repo[rev]):
+        dnodes = ''
+        if entry.get('divergentnodes'):
+            dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
+                              for ctx in entry['divergentnodes']) + ' '
+        ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
+                                    entry['reason'], entry['node']))
+
 @command('debugwireargs',
     [('', 'three', '', 'three'),
     ('', 'four', '', 'four'),
diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py
--- a/mercurial/obsolete.py
+++ b/mercurial/obsolete.py
@@ -1040,3 +1040,35 @@  def createmarkers(repo, relations, flag=
                                  date=date, metadata=localmetadata,
                                  ui=repo.ui)
             repo.filteredrevcache.clear()
+
+def whyunstable(repo, ctx):
+    result = []
+    if ctx.orphan():
+        for parent in ctx.parents():
+            kind = None
+            if parent.orphan():
+                kind = 'orphan'
+            elif parent.obsolete():
+                kind = 'obsolete'
+            if kind is not None:
+                result.append({'instability': 'orphan',
+                               'reason': '%s parent' % kind,
+                               'node': parent.hex()})
+    if ctx.phasedivergent():
+        predecessors = obsutil.allpredecessors(repo.obsstore, [ctx.node()],
+                                               ignoreflags=bumpedfix)
+        immutable = [repo[p] for p in predecessors
+                     if p in repo and not repo[p].mutable()]
+        for predecessor in immutable:
+            result.append({'instability': 'phase-divergent',
+                           'reason': 'immutable predecessor',
+                           'node': predecessor.hex()})
+    if ctx.contentdivergent():
+        dsets = obsutil.divergentsets(repo, ctx)
+        for dset in dsets:
+            divnodes = [repo[n] for n in dset['divergentnodes']]
+            result.append({'instability': 'content-divergent',
+                           'divergentnodes': divnodes,
+                           'reason': 'predecessor',
+                           'node': node.hex(dset['commonpredecessor'])})
+    return result
diff --git a/mercurial/obsutil.py b/mercurial/obsutil.py
--- a/mercurial/obsutil.py
+++ b/mercurial/obsutil.py
@@ -890,3 +890,23 @@  def _getfilteredreason(repo, changeid, c
 
             args = (changeid, firstsuccessors, remainingnumber)
             return filteredmsgtable['superseded_split_several'] % args
+
+def divergentsets(repo, ctx):
+    """Compute sets of commits divergent with a given one"""
+    cache = {}
+    base = {}
+    for n in allpredecessors(repo.obsstore, [ctx.node()]):
+        if n == ctx.node():
+            # a node can't be a base for divergence with itself
+            continue
+        nsuccsets = 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
+    return [{'divergentnodes': divset, 'commonpredecessor': b}
+            for divset, b in base.iteritems()]
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -122,6 +122,7 @@  Show debug commands if there are no othe
   debugupdatecaches
   debugupgraderepo
   debugwalk
+  debugwhyunstable
   debugwireargs
   debugwireproto
 
@@ -306,6 +307,7 @@  Show all commands + options
   debugupdatecaches: 
   debugupgraderepo: optimize, run
   debugwalk: include, exclude
+  debugwhyunstable: 
   debugwireargs: three, four, five, ssh, remotecmd, insecure
   debugwireproto: localssh, peer, noreadstderr, ssh, remotecmd, insecure
   files: rev, print0, include, exclude, template, subrepos
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -985,6 +985,8 @@  Test list of internal help commands
    debugupgraderepo
                  upgrade a repository to use different features
    debugwalk     show how files match on given patterns
+   debugwhyunstable
+                 explain instabilities of a changeset
    debugwireargs
                  (no help text available)
    debugwireproto
diff --git a/tests/test-obsolete-divergent.t b/tests/test-obsolete-divergent.t
--- a/tests/test-obsolete-divergent.t
+++ b/tests/test-obsolete-divergent.t
@@ -717,3 +717,6 @@  Use scmutil.cleanupnodes API to create d
   a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
   a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'test', 'user': 'test'}
   ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'test', 'user': 'test'}
+
+  $ hg debugwhyunstable 1a2a9b5b0030
+  content-divergent: 70d5a63ca112acb3764bc1d7320ca90ea688d671 (draft) predecessor a178212c3433c4e77b573f6011e29affb8aefa33
diff --git a/tests/test-obsolete.t b/tests/test-obsolete.t
--- a/tests/test-obsolete.t
+++ b/tests/test-obsolete.t
@@ -1033,6 +1033,12 @@  test summary output
   orphan: 2 changesets
   phase-divergent: 1 changesets
 
+test debugwhyunstable output
+
+  $ hg debugwhyunstable 50c51b361e60
+  orphan: obsolete parent 3de5eca88c00aa039da7399a220f4a5221faa585
+  phase-divergent: immutable predecessor 245bde4270cd1072a27757984f9cda8ba26f08ca
+
 #if serve
 
   $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log