# HG changeset patch # User Boris Feld # Date 1534530952 -7200 # Node ID f736fdbe546af28040f53b3bbed40af02fdf7a65 # Parent c89e2fb207a15fd9922d803aa855d1807a830cf0 remotephase: avoid full changelog iteration (issue5964) Changeset 88efb7d6bcb6 introduced a performance regression by triggering a full ancestors walk. This changeset reworks this logic so that we no longer walk down the full changelog. The motivation for 88efb7d6bcb6, issue5939, is still fixed. mercurial compared to a draft repository ---------------------------------------- 8eeed92475d5: 0.012637 seconds 88efb7d6bcb6: 0.202699 seconds (x16) 46da52f4b820: 0.215551 seconds (+6%) this code: 0.008397 seconds (-33% from base) The payload size reduction we see in `test-bookmarks-pushpull.t` comes from a more aggressive filter of nullid and is harmless. 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 diff -r c89e2fb207a1 -r f736fdbe546a tests/test-bookmarks-pushpull.t --- a/tests/test-bookmarks-pushpull.t Fri Aug 17 16:00:32 2018 -0700 +++ b/tests/test-bookmarks-pushpull.t Fri Aug 17 20:35:52 2018 +0200 @@ -141,10 +141,10 @@ bundle2-output: payload chunk size: 23 bundle2-output: closing payload chunk bundle2-output: bundle part: "check:phases" - bundle2-output-part: "check:phases" 48 bytes payload + bundle2-output-part: "check:phases" 24 bytes payload bundle2-output: part 2: "CHECK:PHASES" bundle2-output: header chunk size: 19 - bundle2-output: payload chunk size: 48 + bundle2-output: payload chunk size: 24 bundle2-output: closing payload chunk bundle2-output: bundle part: "pushkey" bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload @@ -180,9 +180,9 @@ bundle2-input: part parameters: 0 bundle2-input: found a handler for part check:phases bundle2-input-part: "check:phases" supported - bundle2-input: payload chunk size: 48 + bundle2-input: payload chunk size: 24 bundle2-input: payload chunk size: 0 - bundle2-input-part: total payload size 48 + bundle2-input-part: total payload size 24 bundle2-input: part header size: 90 bundle2-input: part type: "PUSHKEY" bundle2-input: part id: "3" @@ -253,10 +253,10 @@ bundle2-output: payload chunk size: 23 bundle2-output: closing payload chunk bundle2-output: bundle part: "check:phases" - bundle2-output-part: "check:phases" 48 bytes payload + bundle2-output-part: "check:phases" 24 bytes payload bundle2-output: part 2: "CHECK:PHASES" bundle2-output: header chunk size: 19 - bundle2-output: payload chunk size: 48 + bundle2-output: payload chunk size: 24 bundle2-output: closing payload chunk bundle2-output: bundle part: "bookmarks" bundle2-output-part: "bookmarks" 23 bytes payload @@ -293,9 +293,9 @@ bundle2-input: part parameters: 0 bundle2-input: found a handler for part check:phases bundle2-input-part: "check:phases" supported - bundle2-input: payload chunk size: 48 + bundle2-input: payload chunk size: 24 bundle2-input: payload chunk size: 0 - bundle2-input-part: total payload size 48 + bundle2-input-part: total payload size 24 bundle2-input: part header size: 16 bundle2-input: part type: "BOOKMARKS" bundle2-input: part id: "3"