Mercurial > evolve
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: |