comparison hgext3rd/pullbundle.py @ 4129:bc4e62a1cb82

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.
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Sun, 23 Sep 2018 23:41:08 +0200
parents 4e5ec9ae682e
children a1f6b8211016
comparison
equal deleted inserted replaced
4128:4e5ec9ae682e 4129:bc4e62a1cb82
3 # Copyright 2018 Pierre-Yves David <pierre-yves.david@ens-lyon.org> 3 # Copyright 2018 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 # 4 #
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 import errno
9 import os
10
8 from mercurial import ( 11 from mercurial import (
9 changegroup, 12 changegroup,
13 discovery,
10 exchange, 14 exchange,
11 narrowspec, 15 narrowspec,
16 node as nodemod,
17 util,
12 ) 18 )
13 19
14 from mercurial.i18n import _ 20 from mercurial.i18n import _
21
22 # generic wrapping
15 23
16 def uisetup(ui): 24 def uisetup(ui):
17 exchange.getbundle2partsmapping['changegroup'] = _getbundlechangegrouppart 25 exchange.getbundle2partsmapping['changegroup'] = _getbundlechangegrouppart
18 26
19 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None, 27 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
57 narrowspecpart.addparam( 65 narrowspecpart.addparam(
58 'exclude', '\n'.join(exclude), mandatory=True) 66 'exclude', '\n'.join(exclude), mandatory=True)
59 67
60 def makeallcgpart(newpart, repo, outgoing, version, source, 68 def makeallcgpart(newpart, repo, outgoing, version, source,
61 bundlecaps, filematcher, cgversions): 69 bundlecaps, filematcher, cgversions):
62 makeonecgpart(newpart, repo, outgoing, version, source, bundlecaps, 70
63 filematcher, cgversions) 71 pullbundle = not filematcher
72 if pullbundle and not util.safehasattr(repo, 'stablerange'):
73 repo.ui.warn('pullbundle: required extension "evolve" are missing, skipping pullbundle\n')
74 pullbundle = False
75 if filematcher:
76 makeonecgpart(newpart, repo, None, outgoing, version, source, bundlecaps,
77 filematcher, cgversions)
78 else:
79 for sliceid, sliceout in sliceoutgoing(repo, outgoing):
80 makeonecgpart(newpart, repo, sliceid, sliceout, version, source, bundlecaps,
81 filematcher, cgversions)
82
83 # stable range slicing
84
85 def sliceoutgoing(repo, outgoing):
86 cl = repo.changelog
87 rev = cl.nodemap.get
88 node = cl.node
89 revsort = repo.stablesort
90
91 missingrevs = set(rev(n) for n in outgoing.missing)
92 allslices = []
93 missingheads = [rev(n) for n in outgoing.missingheads]
94 for head in missingheads:
95 localslices = []
96 localmissing = set(repo.revs('%ld and ::%d', missingrevs, head))
97 while localmissing:
98 slicerevs = []
99 for r in revsort.walkfrom(repo, head):
100 if r not in missingrevs:
101 break
102 slicerevs.append(r)
103 slicenodes = [node(r) for r in slicerevs]
104 localslices.extend(canonicalslices(repo, slicenodes))
105 missingrevs.difference_update(slicerevs)
106 localmissing.difference_update(slicerevs)
107 if localmissing:
108 head = max(localmissing)
109
110 allslices.extend(localslices)
111 return [(rangeid, outgoingfromnodes(repo, nodes))
112 for rangeid, nodes in allslices]
113
114 def canonicalslices(repo, nodes):
115 depth = repo.depthcache.get
116 stablerange = repo.stablerange
117 rangelength = lambda x: stablerange.rangelength(repo, x)
118 headrev = repo.changelog.rev(nodes[0])
119 nbrevs = len(nodes)
120 headdepth = depth(headrev)
121 skipped = headdepth - nbrevs
122 rangeid = (headrev, skipped)
123
124 subranges = canonicalsubranges(repo, stablerange, rangeid)
125 idx = 0
126 slices =[]
127 nodes.reverse()
128 for rangeid in subranges:
129 size = rangelength(rangeid)
130 slices.append((rangeid, nodes[idx:idx + size]))
131 idx += size
132 return slices
133
134 def canonicalsubranges(repo, stablerange, rangeid):
135 """slice a size of nodes into most reusable subranges
136
137 We try to slice a range into a set of "largest" and "canonical" stable
138 range.
139
140 It might make sense to move this function as a 'stablerange' method.
141 """
142 headrev, skip = rangeid
143 rangedepth = stablerange.depthrev(repo, rangeid[0])
144 canonicals = []
145
146 # 0. find the first power of 2 higher than this range depth
147 cursor = 1
148 while cursor <= rangedepth:
149 cursor *= 2
150
151 # 1. find first cupt
152 precut = cut = 0
153 while True:
154 if skip <= cut:
155 break
156 if cut + cursor < rangedepth:
157 precut = cut
158 cut += cursor
159 if cursor == 1:
160 break
161 cursor //=2
162
163 # 2. optimise, bottom part
164 if skip != cut:
165 tailranges = []
166 tailsize = cut - skip
167 assert 0 < tailsize, tailsize
168 prerange = (headrev, precut)
169 size = stablerange.rangelength(repo, prerange)
170 sub = stablerange.subranges(repo, prerange)[:-1]
171 while not poweroftwo(tailsize):
172 for prerange in reversed(sub):
173 if tailsize <= 0:
174 break
175
176 assert stablerange.depthrev(repo, prerange[0]) != prerange[1], prerange
177 tailrev, tailskip = prerange
178 size = stablerange.rangelength(repo, (tailrev, tailskip))
179 if tailsize < size:
180 tailskip += size - tailsize
181 size = tailsize
182 tailranges.append((tailrev, tailskip))
183 tailsize -= size
184 else:
185 # size of the last block
186 tailsize = stablerange.rangelength(repo, tailranges[-1])
187 if poweroftwo(tailsize):
188 continue # exit the loop
189 prerange = tailranges.pop()
190 sub = stablerange.subranges(repo, prerange)
191
192 tailranges.reverse()
193 canonicals.extend(tailranges)
194
195 # 3. take recursive subrange until we get to a power of two size?
196 current = (headrev, cut)
197 while not poweroftwo(stablerange.rangelength(repo, current)):
198 sub = stablerange.subranges(repo, current)
199 canonicals.extend(sub[:-1])
200 current = sub[-1]
201 canonicals.append(current)
202
203 return canonicals
204
205 def poweroftwo(num):
206 return num and not num & (num - 1)
207
208 def outgoingfromnodes(repo, nodes):
209 return discovery.outgoing(repo,
210 missingroots=nodes,
211 missingheads=nodes)
212
213 # changegroup part construction
64 214
65 def _changegroupinfo(repo, nodes, source): 215 def _changegroupinfo(repo, nodes, source):
66 if repo.ui.verbose or source == 'bundle': 216 if repo.ui.verbose or source == 'bundle':
67 repo.ui.status(_("%d changesets found\n") % len(nodes)) 217 repo.ui.status(_("%d changesets found\n") % len(nodes))
68 218
69 def makeonecgpart(newpart, repo, outgoing, version, source, 219 def makeonecgpart(newpart, repo, rangeid, outgoing, version, source,
70 bundlecaps, filematcher, cgversions): 220 bundlecaps, filematcher, cgversions):
71 # same as upstream code 221 # same as upstream code
72 222
73 old = changegroup._changegroupinfo 223 old = changegroup._changegroupinfo
74 try: 224 try: