mercurial/phases.py
branchstable
changeset 39146 f736fdbe546a
parent 39141 c89e2fb207a1
child 39147 b95b48a55c36
child 39199 d8ac2ad55d9c
--- a/mercurial/phases.py	Fri Aug 17 16:00:32 2018 -0700
+++ b/mercurial/phases.py	Fri Aug 17 20:35:52 2018 +0200
@@ -664,11 +664,39 @@
 
     * `heads`: define the first subset
     * `roots`: define the second we subtract from the first"""
+    # prevent an import cycle
+    # phases > dagop > patch > copies > scmutil > obsolete > obsutil > phases
+    from . import dagop
+
+    repo = repo.unfiltered()
+    cl = repo.changelog
+    rev = cl.nodemap.get
+    if not roots:
+        return heads
+    if not heads or heads == [nullrev]:
+        return []
+    # The logic operated on revisions, convert arguments early for convenience
+    new_heads = set(rev(n) for n in heads if n != nullid)
+    roots = [rev(n) for n in roots]
     if not heads or not roots:
         return heads
-    repo = repo.unfiltered()
-    revs = repo.revs('heads(::%ln - (%ln::%ln))', heads, roots, heads)
-    return pycompat.maplist(repo.changelog.node, revs)
+    # compute the area we need to remove
+    affected_zone = repo.revs("(%ld::%ld)", roots, new_heads)
+    # heads in the area are no longer heads
+    new_heads.difference_update(affected_zone)
+    # revisions in the area have children outside of it,
+    # They might be new heads
+    candidates = repo.revs("parents(%ld + (%ld and merge())) and not null",
+                           roots, affected_zone)
+    candidates -= affected_zone
+    if new_heads or candidates:
+        # remove candidate that are ancestors of other heads
+        new_heads.update(candidates)
+        prunestart = repo.revs("parents(%ld) and not null", new_heads)
+        pruned = dagop.reachableroots(repo, candidates, prunestart)
+        new_heads.difference_update(pruned)
+
+    return pycompat.maplist(cl.node, sorted(new_heads))
 
 def newcommitphase(ui):
     """helper to get the target phase of new commit