merge with stable
authorGregory Szorc <gregory.szorc@gmail.com>
Fri, 17 Aug 2018 16:11:35 -0700
changeset 39147 b95b48a55c36
parent 39145 b623c7b23695 (current diff)
parent 39146 f736fdbe546a (diff)
child 39148 1464183343b3
merge with stable
contrib/perf.py
mercurial/phases.py
mercurial/revlog.py
tests/test-contrib-perf.t
--- a/contrib/perf.py	Fri Aug 17 15:32:38 2018 -0700
+++ b/contrib/perf.py	Fri Aug 17 16:11:35 2018 -0700
@@ -790,6 +790,63 @@
     timer(d)
     fm.end()
 
+@command('perfphasesremote',
+         [], "[DEST]")
+def perfphasesremote(ui, repo, dest=None, **opts):
+    """benchmark time needed to analyse phases of the remote server"""
+    from mercurial.node import (
+        bin,
+    )
+    from mercurial import (
+        exchange,
+        hg,
+        phases,
+    )
+    timer, fm = gettimer(ui, opts)
+
+    path = ui.paths.getpath(dest, default=('default-push', 'default'))
+    if not path:
+        raise error.abort(('default repository not configured!'),
+                         hint=("see 'hg help config.paths'"))
+    dest = path.pushloc or path.loc
+    branches = (path.branch, opts.get('branch') or [])
+    ui.status(('analysing phase of %s\n') % util.hidepassword(dest))
+    revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
+    other = hg.peer(repo, opts, dest)
+
+    # easier to perform discovery through the operation
+    op = exchange.pushoperation(repo, other)
+    exchange._pushdiscoverychangeset(op)
+
+    remotesubset = op.fallbackheads
+
+    with other.commandexecutor() as e:
+        remotephases = e.callcommand('listkeys',
+                       {'namespace': 'phases'}).result()
+    del other
+    publishing = remotephases.get('publishing', False)
+    if publishing:
+        ui.status(('publishing: yes\n'))
+    else:
+        ui.status(('publishing: no\n'))
+
+    nodemap = repo.changelog.nodemap
+    nonpublishroots = 0
+    for nhex, phase in remotephases.iteritems():
+        if nhex == 'publishing': # ignore data related to publish option
+            continue
+        node = bin(nhex)
+        if node in nodemap and int(phase):
+            nonpublishroots += 1
+    ui.status(('number of roots: %d\n') % len(remotephases))
+    ui.status(('number of known non public roots: %d\n') % nonpublishroots)
+    def d():
+        phases.remotephasessummary(repo,
+                                   remotesubset,
+                                   remotephases)
+    timer(d)
+    fm.end()
+
 @command('perfmanifest',[
             ('m', 'manifest-rev', False, 'Look up a manifest node revision'),
             ('', 'clear-disk', False, 'clear on-disk caches too'),
--- a/mercurial/phases.py	Fri Aug 17 15:32:38 2018 -0700
+++ b/mercurial/phases.py	Fri Aug 17 16:11:35 2018 -0700
@@ -664,9 +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()
-    revs = repo.revs('heads(::%ln - (%ln::%ln))', heads, roots, heads)
-    return pycompat.maplist(repo.changelog.node, revs)
+    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
+    # 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/mercurial/revlog.py	Fri Aug 17 15:32:38 2018 -0700
+++ b/mercurial/revlog.py	Fri Aug 17 16:11:35 2018 -0700
@@ -263,13 +263,17 @@
     if endidx is None:
         endidx = len(revs)
 
-    # Trim empty revs at the end, but never the very first revision of a chain
-    while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
-        endidx -= 1
+    # If we have a non-emtpy delta candidate, there are nothing to trim
+    if revs[endidx - 1] < len(revlog):
+        # Trim empty revs at the end, except the very first revision of a chain
+        while (endidx > 1
+                and endidx > startidx
+                and length(revs[endidx - 1]) == 0):
+            endidx -= 1
 
     return revs[startidx:endidx]
 
-def _segmentspan(revlog, revs):
+def _segmentspan(revlog, revs, deltainfo=None):
     """Get the byte span of a segment of revisions
 
     revs is a sorted array of revision numbers
@@ -295,7 +299,14 @@
     """
     if not revs:
         return 0
-    return revlog.end(revs[-1]) - revlog.start(revs[0])
+    if deltainfo is not None and len(revlog) <= revs[-1]:
+        if len(revs) == 1:
+            return deltainfo.deltalen
+        offset = revlog.end(len(revlog) - 1)
+        end = deltainfo.deltalen + offset
+    else:
+        end = revlog.end(revs[-1])
+    return end - revlog.start(revs[0])
 
 def _slicechunk(revlog, revs, deltainfo=None, targetsize=None):
     """slice revs to reduce the amount of unrelated data to be read from disk.
@@ -527,7 +538,7 @@
         yield revs
         return
 
-    if deltainfo is not None:
+    if deltainfo is not None and deltainfo.deltalen:
         revs = list(revs)
         revs.append(nextrev)
 
@@ -2471,7 +2482,8 @@
                 deltachain = []
 
             chunks = _slicechunk(self, deltachain, deltainfo)
-            distance = max(map(lambda revs:_segmentspan(self, revs), chunks))
+            all_span = [_segmentspan(self, revs, deltainfo) for revs in chunks]
+            distance = max(all_span)
         else:
             distance = deltainfo.distance
 
--- a/tests/test-bookmarks-pushpull.t	Fri Aug 17 15:32:38 2018 -0700
+++ b/tests/test-bookmarks-pushpull.t	Fri Aug 17 16:11:35 2018 -0700
@@ -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"
--- a/tests/test-contrib-perf.t	Fri Aug 17 15:32:38 2018 -0700
+++ b/tests/test-contrib-perf.t	Fri Aug 17 16:11:35 2018 -0700
@@ -103,6 +103,8 @@
    perfpathcopies
                  (no help text available)
    perfphases    benchmark phasesets computation
+   perfphasesremote
+                 benchmark time needed to analyse phases of the remote server
    perfrawfiles  (no help text available)
    perfrevlogchunks
                  Benchmark operations on revlog chunks.
@@ -212,4 +214,7 @@
   contrib/perf.py:\d+: (re)
    >     from mercurial import (
    import newer module separately in try clause for early Mercurial
+  contrib/perf.py:\d+: (re)
+   >     from mercurial import (
+   import newer module separately in try clause for early Mercurial
   [1]