hgext/evolve.py
changeset 1357 3bb7a080da4d
parent 1354 b4a62d6f0353
child 1361 043e5ca9322f
--- a/hgext/evolve.py	Thu Jun 04 13:26:58 2015 -0700
+++ b/hgext/evolve.py	Thu Jun 04 13:35:12 2015 -0700
@@ -28,6 +28,7 @@
 from StringIO import StringIO
 import struct
 import re
+import collections
 import socket
 import errno
 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
@@ -1232,6 +1233,76 @@
     if repo['.'] != startnode:
         ui.status(_('working directory is now at %s\n') % repo['.'])
 
+def _singlesuccessor(repo, p):
+    """returns p if not obsolete or its unique latest successors
+
+    fail if there are no such successor"""
+
+    if not p.obsolete():
+        return p
+    obs = repo[p]
+    ui = repo.ui
+    newer = obsolete.successorssets(repo, obs.node())
+    # search of a parent which is not killed
+    while not newer or newer == [()]:
+        ui.debug("stabilize target %s is plain dead,"
+                 " trying to stabilize on its parent\n" %
+                 obs)
+        obs = obs.parents()[0]
+        newer = obsolete.successorssets(repo, obs.node())
+    if len(newer) > 1:
+        raise util.Abort(_("conflict rewriting. can't choose destination\n"))
+    return repo[newer[0][0]].rev()
+
+def orderrevs(repo, revs):
+    """ Compute an ordering to solve instability for the given revs
+
+    - Takes revs a list of instable revisions
+
+    - Returns the same revisions ordered to solve their instability from the
+    bottom to the top of the stack that the stabilization process will produce
+    eventually.
+
+    This ensure the minimal number of stabilization as we can stabilize each
+    revision on its final, stabilized, destination.
+    """
+
+    # Step 1: Build the dependency graph
+    # For each troubled revision we keep track of what instability if any should
+    # be resolved in order to resolve it. Example:
+    # dependencies = {3: [6], 6:[]}
+    # Means that: 6 has no dependency, 3 depends on 6 to be solved
+    dependencies = {}
+    # rdependencies is the inverted dict of dependencies
+    rdependencies = collections.defaultdict(set)
+
+    for r in revs:
+        dependencies[r] = set()
+        for p in repo[r].parents():
+            succ = _singlesuccessor(repo, p)
+            if succ in revs:
+                dependencies[r].add(succ)
+                rdependencies[succ].add(r)
+
+    # Step 2: Build the ordering
+    # Remove the revisions with no dependency(A) and add them to the ordering.
+    # Removing these revisions leads to new revisions with no dependency (the
+    # one depending on A) that we can remove from the dependency graph and add
+    # to the ordering. We progress in a similar fashion until the ordering is
+    # built
+    solvablerevs = collections.deque([r for r in sorted(dependencies.keys())
+                                      if not dependencies[r]])
+    ordering = []
+    while solvablerevs:
+        rev = solvablerevs.popleft()
+        for dependent in rdependencies[rev]:
+            dependencies[dependent].remove(rev)
+            if not dependencies[dependent]:
+                solvablerevs.append(dependent)
+        ordering.append(rev)
+
+    return ordering
+
 @command('^evolve|stabilize|solve',
     [('n', 'dry-run', False,
         'do not perform actions, just print what would be done'),
@@ -1307,6 +1378,8 @@
         else:
             # For the progress bar to show
             count = len(_revs)
+            # Order the revisions
+            _revs = orderrevs(repo, _revs)
             for rev in _revs:
                 progresscb()
                 _solveone(ui, repo, repo[rev], dryrunopt, confirmopt,