diff -r c89e2fb207a1 -r f736fdbe546a mercurial/phases.py --- 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