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.
--- 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
--- 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"