Skip to content
Snippets Groups Projects
Commit bc4e62a1 authored by Pierre-Yves David's avatar Pierre-Yves David :octopus:
Browse files

pullbundle: slice pull into multiple ranges

We subdivide the set of missing revisions into multiple "range" using the "stable
range" data structure. This slicing aims at maximizing the capability of the
resulting ranges.
parent 4e5ec9ae
No related branches found
No related tags found
No related merge requests found
......@@ -5,5 +5,8 @@
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
import errno
import os
from mercurial import (
changegroup,
......@@ -8,4 +11,5 @@
from mercurial import (
changegroup,
discovery,
exchange,
narrowspec,
......@@ -10,6 +14,8 @@
exchange,
narrowspec,
node as nodemod,
util,
)
from mercurial.i18n import _
......@@ -12,7 +18,9 @@
)
from mercurial.i18n import _
# generic wrapping
def uisetup(ui):
exchange.getbundle2partsmapping['changegroup'] = _getbundlechangegrouppart
......@@ -59,10 +67,152 @@
def makeallcgpart(newpart, repo, outgoing, version, source,
bundlecaps, filematcher, cgversions):
makeonecgpart(newpart, repo, outgoing, version, source, bundlecaps,
filematcher, cgversions)
pullbundle = not filematcher
if pullbundle and not util.safehasattr(repo, 'stablerange'):
repo.ui.warn('pullbundle: required extension "evolve" are missing, skipping pullbundle\n')
pullbundle = False
if filematcher:
makeonecgpart(newpart, repo, None, outgoing, version, source, bundlecaps,
filematcher, cgversions)
else:
for sliceid, sliceout in sliceoutgoing(repo, outgoing):
makeonecgpart(newpart, repo, sliceid, sliceout, version, source, bundlecaps,
filematcher, cgversions)
# stable range slicing
def sliceoutgoing(repo, outgoing):
cl = repo.changelog
rev = cl.nodemap.get
node = cl.node
revsort = repo.stablesort
missingrevs = set(rev(n) for n in outgoing.missing)
allslices = []
missingheads = [rev(n) for n in outgoing.missingheads]
for head in missingheads:
localslices = []
localmissing = set(repo.revs('%ld and ::%d', missingrevs, head))
while localmissing:
slicerevs = []
for r in revsort.walkfrom(repo, head):
if r not in missingrevs:
break
slicerevs.append(r)
slicenodes = [node(r) for r in slicerevs]
localslices.extend(canonicalslices(repo, slicenodes))
missingrevs.difference_update(slicerevs)
localmissing.difference_update(slicerevs)
if localmissing:
head = max(localmissing)
allslices.extend(localslices)
return [(rangeid, outgoingfromnodes(repo, nodes))
for rangeid, nodes in allslices]
def canonicalslices(repo, nodes):
depth = repo.depthcache.get
stablerange = repo.stablerange
rangelength = lambda x: stablerange.rangelength(repo, x)
headrev = repo.changelog.rev(nodes[0])
nbrevs = len(nodes)
headdepth = depth(headrev)
skipped = headdepth - nbrevs
rangeid = (headrev, skipped)
subranges = canonicalsubranges(repo, stablerange, rangeid)
idx = 0
slices =[]
nodes.reverse()
for rangeid in subranges:
size = rangelength(rangeid)
slices.append((rangeid, nodes[idx:idx + size]))
idx += size
return slices
def canonicalsubranges(repo, stablerange, rangeid):
"""slice a size of nodes into most reusable subranges
We try to slice a range into a set of "largest" and "canonical" stable
range.
It might make sense to move this function as a 'stablerange' method.
"""
headrev, skip = rangeid
rangedepth = stablerange.depthrev(repo, rangeid[0])
canonicals = []
# 0. find the first power of 2 higher than this range depth
cursor = 1
while cursor <= rangedepth:
cursor *= 2
# 1. find first cupt
precut = cut = 0
while True:
if skip <= cut:
break
if cut + cursor < rangedepth:
precut = cut
cut += cursor
if cursor == 1:
break
cursor //=2
# 2. optimise, bottom part
if skip != cut:
tailranges = []
tailsize = cut - skip
assert 0 < tailsize, tailsize
prerange = (headrev, precut)
size = stablerange.rangelength(repo, prerange)
sub = stablerange.subranges(repo, prerange)[:-1]
while not poweroftwo(tailsize):
for prerange in reversed(sub):
if tailsize <= 0:
break
assert stablerange.depthrev(repo, prerange[0]) != prerange[1], prerange
tailrev, tailskip = prerange
size = stablerange.rangelength(repo, (tailrev, tailskip))
if tailsize < size:
tailskip += size - tailsize
size = tailsize
tailranges.append((tailrev, tailskip))
tailsize -= size
else:
# size of the last block
tailsize = stablerange.rangelength(repo, tailranges[-1])
if poweroftwo(tailsize):
continue # exit the loop
prerange = tailranges.pop()
sub = stablerange.subranges(repo, prerange)
tailranges.reverse()
canonicals.extend(tailranges)
# 3. take recursive subrange until we get to a power of two size?
current = (headrev, cut)
while not poweroftwo(stablerange.rangelength(repo, current)):
sub = stablerange.subranges(repo, current)
canonicals.extend(sub[:-1])
current = sub[-1]
canonicals.append(current)
return canonicals
def poweroftwo(num):
return num and not num & (num - 1)
def outgoingfromnodes(repo, nodes):
return discovery.outgoing(repo,
missingroots=nodes,
missingheads=nodes)
# changegroup part construction
def _changegroupinfo(repo, nodes, source):
if repo.ui.verbose or source == 'bundle':
repo.ui.status(_("%d changesets found\n") % len(nodes))
......@@ -64,9 +214,9 @@
def _changegroupinfo(repo, nodes, source):
if repo.ui.verbose or source == 'bundle':
repo.ui.status(_("%d changesets found\n") % len(nodes))
def makeonecgpart(newpart, repo, outgoing, version, source,
def makeonecgpart(newpart, repo, rangeid, outgoing, version, source,
bundlecaps, filematcher, cgversions):
# same as upstream code
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment