##// END OF EJS Templates
widening: trust user to give full "known" set...
Martin von Zweigbergk -
r43523:a4d4e3d4 default draft
parent child Browse files
Show More
@@ -1,356 +1,354 b''
1 1 # narrowbundle2.py - bundle2 extensions for narrow repository support
2 2 #
3 3 # Copyright 2017 Google, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import struct
12 12
13 13 from mercurial.i18n import _
14 14 from mercurial.node import nullid
15 15 from mercurial import (
16 16 bundle2,
17 17 changegroup,
18 18 error,
19 19 exchange,
20 20 localrepo,
21 21 narrowspec,
22 22 repair,
23 23 util,
24 24 wireprototypes,
25 25 )
26 26 from mercurial.interfaces import repository
27 27 from mercurial.utils import stringutil
28 28
29 29 _NARROWACL_SECTION = b'narrowacl'
30 30 _CHANGESPECPART = b'narrow:changespec'
31 31 _RESSPECS = b'narrow:responsespec'
32 32 _SPECPART = b'narrow:spec'
33 33 _SPECPART_INCLUDE = b'include'
34 34 _SPECPART_EXCLUDE = b'exclude'
35 35 _KILLNODESIGNAL = b'KILL'
36 36 _DONESIGNAL = b'DONE'
37 37 _ELIDEDCSHEADER = b'>20s20s20sl' # cset id, p1, p2, len(text)
38 38 _ELIDEDMFHEADER = b'>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
39 39 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
40 40 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
41 41
42 42 # Serve a changegroup for a client with a narrow clone.
43 43 def getbundlechangegrouppart_narrow(
44 44 bundler,
45 45 repo,
46 46 source,
47 47 bundlecaps=None,
48 48 b2caps=None,
49 49 heads=None,
50 50 common=None,
51 51 **kwargs
52 52 ):
53 53 assert repo.ui.configbool(b'experimental', b'narrowservebrokenellipses')
54 54
55 55 cgversions = b2caps.get(b'changegroup')
56 56 cgversions = [
57 57 v
58 58 for v in cgversions
59 59 if v in changegroup.supportedoutgoingversions(repo)
60 60 ]
61 61 if not cgversions:
62 62 raise ValueError(_(b'no common changegroup version'))
63 63 version = max(cgversions)
64 64
65 65 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
66 66 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
67 67 generateellipsesbundle2(
68 68 bundler,
69 69 repo,
70 70 include,
71 71 exclude,
72 72 version,
73 73 common,
74 74 heads,
75 75 kwargs.get(r'depth', None),
76 76 )
77 77
78 78
79 79 def generateellipsesbundle2(
80 80 bundler, repo, include, exclude, version, common, heads, depth,
81 81 ):
82 82 match = narrowspec.match(repo.root, include=include, exclude=exclude)
83 83 if depth is not None:
84 84 depth = int(depth)
85 85 if depth < 1:
86 86 raise error.Abort(_(b'depth must be positive, got %d') % depth)
87 87
88 88 heads = set(heads or repo.heads())
89 89 common = set(common or [nullid])
90 90
91 91 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
92 92 repo, common, heads, set(), match, depth=depth
93 93 )
94 94
95 95 repo.ui.debug(b'Found %d relevant revs\n' % len(relevant_nodes))
96 96 if visitnodes:
97 97 packer = changegroup.getbundler(
98 98 version,
99 99 repo,
100 100 matcher=match,
101 101 ellipses=True,
102 102 shallow=depth is not None,
103 103 ellipsisroots=ellipsisroots,
104 104 fullnodes=relevant_nodes,
105 105 )
106 106 cgdata = packer.generate(common, visitnodes, False, b'narrow_widen')
107 107
108 108 part = bundler.newpart(b'changegroup', data=cgdata)
109 109 part.addparam(b'version', version)
110 110 if b'treemanifest' in repo.requirements:
111 111 part.addparam(b'treemanifest', b'1')
112 112
113 113
114 114 def generate_ellipses_bundle2_for_widening(
115 115 bundler,
116 116 repo,
117 117 oldinclude,
118 118 oldexclude,
119 119 newinclude,
120 120 newexclude,
121 121 version,
122 122 common,
123 123 known,
124 124 ):
125 125 newmatch = narrowspec.match(
126 126 repo.root, include=newinclude, exclude=newexclude
127 127 )
128 128
129 129 common = set(common or [nullid])
130 130 # Steps:
131 131 # 1. Send kill for "$known & ::common"
132 132 #
133 133 # 2. Send changegroup for ::common
134 134 #
135 135 # 3. Proceed.
136 136 #
137 137 # In the future, we can send kills for only the specific
138 138 # nodes we know should go away or change shape, and then
139 139 # send a data stream that tells the client something like this:
140 140 #
141 141 # a) apply this changegroup
142 142 # b) apply nodes XXX, YYY, ZZZ that you already have
143 143 # c) goto a
144 144 #
145 145 # until they've built up the full new state.
146 # Convert to revnums and intersect with "common". The client should
147 # have made it a subset of "common" already, but let's be safe.
148 known = set(repo.revs(b"%ln & ::%ln", known, common))
146 knownrevs = {repo.changelog.rev(n) for n in known}
149 147 # TODO: we could send only roots() of this set, and the
150 148 # list of nodes in common, and the client could work out
151 149 # what to strip, instead of us explicitly sending every
152 150 # single node.
153 deadrevs = known
151 deadrevs = knownrevs
154 152
155 153 def genkills():
156 154 for r in deadrevs:
157 155 yield _KILLNODESIGNAL
158 156 yield repo.changelog.node(r)
159 157 yield _DONESIGNAL
160 158
161 159 bundler.newpart(_CHANGESPECPART, data=genkills())
162 160 newvisit, newfull, newellipsis = exchange._computeellipsis(
163 repo, set(), common, known, newmatch
161 repo, set(), common, knownrevs, newmatch
164 162 )
165 163 if newvisit:
166 164 packer = changegroup.getbundler(
167 165 version,
168 166 repo,
169 167 matcher=newmatch,
170 168 ellipses=True,
171 169 shallow=False,
172 170 ellipsisroots=newellipsis,
173 171 fullnodes=newfull,
174 172 )
175 173 cgdata = packer.generate(common, newvisit, False, b'narrow_widen')
176 174
177 175 part = bundler.newpart(b'changegroup', data=cgdata)
178 176 part.addparam(b'version', version)
179 177 if b'treemanifest' in repo.requirements:
180 178 part.addparam(b'treemanifest', b'1')
181 179
182 180
183 181 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
184 182 def _handlechangespec_2(op, inpart):
185 183 # XXX: This bundle2 handling is buggy and should be removed after hg5.2 is
186 184 # released. New servers will send a mandatory bundle2 part named
187 185 # 'Narrowspec' and will send specs as data instead of params.
188 186 # Refer to issue5952 and 6019
189 187 includepats = set(inpart.params.get(_SPECPART_INCLUDE, b'').splitlines())
190 188 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, b'').splitlines())
191 189 narrowspec.validatepatterns(includepats)
192 190 narrowspec.validatepatterns(excludepats)
193 191
194 192 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
195 193 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
196 194 op.repo._writerequirements()
197 195 op.repo.setnarrowpats(includepats, excludepats)
198 196 narrowspec.copytoworkingcopy(op.repo)
199 197
200 198
201 199 @bundle2.parthandler(_RESSPECS)
202 200 def _handlenarrowspecs(op, inpart):
203 201 data = inpart.read()
204 202 inc, exc = data.split(b'\0')
205 203 includepats = set(inc.splitlines())
206 204 excludepats = set(exc.splitlines())
207 205 narrowspec.validatepatterns(includepats)
208 206 narrowspec.validatepatterns(excludepats)
209 207
210 208 if repository.NARROW_REQUIREMENT not in op.repo.requirements:
211 209 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
212 210 op.repo._writerequirements()
213 211 op.repo.setnarrowpats(includepats, excludepats)
214 212 narrowspec.copytoworkingcopy(op.repo)
215 213
216 214
217 215 @bundle2.parthandler(_CHANGESPECPART)
218 216 def _handlechangespec(op, inpart):
219 217 repo = op.repo
220 218 cl = repo.changelog
221 219
222 220 # changesets which need to be stripped entirely. either they're no longer
223 221 # needed in the new narrow spec, or the server is sending a replacement
224 222 # in the changegroup part.
225 223 clkills = set()
226 224
227 225 # A changespec part contains all the updates to ellipsis nodes
228 226 # that will happen as a result of widening or narrowing a
229 227 # repo. All the changes that this block encounters are ellipsis
230 228 # nodes or flags to kill an existing ellipsis.
231 229 chunksignal = changegroup.readexactly(inpart, 4)
232 230 while chunksignal != _DONESIGNAL:
233 231 if chunksignal == _KILLNODESIGNAL:
234 232 # a node used to be an ellipsis but isn't anymore
235 233 ck = changegroup.readexactly(inpart, 20)
236 234 if cl.hasnode(ck):
237 235 clkills.add(ck)
238 236 else:
239 237 raise error.Abort(
240 238 _(b'unexpected changespec node chunk type: %s') % chunksignal
241 239 )
242 240 chunksignal = changegroup.readexactly(inpart, 4)
243 241
244 242 if clkills:
245 243 # preserve bookmarks that repair.strip() would otherwise strip
246 244 op._bookmarksbackup = repo._bookmarks
247 245
248 246 class dummybmstore(dict):
249 247 def applychanges(self, repo, tr, changes):
250 248 pass
251 249
252 250 localrepo.localrepository._bookmarks.set(repo, dummybmstore())
253 251 chgrpfile = repair.strip(
254 252 op.ui, repo, list(clkills), backup=True, topic=b'widen'
255 253 )
256 254 if chgrpfile:
257 255 op._widen_uninterr = repo.ui.uninterruptible()
258 256 op._widen_uninterr.__enter__()
259 257 # presence of _widen_bundle attribute activates widen handler later
260 258 op._widen_bundle = chgrpfile
261 259 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
262 260 # will currently always be there when using the core+narrowhg server, but
263 261 # other servers may include a changespec part even when not widening (e.g.
264 262 # because we're deepening a shallow repo).
265 263 if util.safehasattr(repo, 'setnewnarrowpats'):
266 264 repo.setnewnarrowpats()
267 265
268 266
269 267 def handlechangegroup_widen(op, inpart):
270 268 """Changegroup exchange handler which restores temporarily-stripped nodes"""
271 269 # We saved a bundle with stripped node data we must now restore.
272 270 # This approach is based on mercurial/repair.py@6ee26a53c111.
273 271 repo = op.repo
274 272 ui = op.ui
275 273
276 274 chgrpfile = op._widen_bundle
277 275 del op._widen_bundle
278 276 vfs = repo.vfs
279 277
280 278 ui.note(_(b"adding branch\n"))
281 279 f = vfs.open(chgrpfile, b"rb")
282 280 try:
283 281 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
284 282 # silence internal shuffling chatter
285 283 override = {(b'ui', b'quiet'): True}
286 284 if ui.verbose:
287 285 override = {}
288 286 with ui.configoverride(override):
289 287 if isinstance(gen, bundle2.unbundle20):
290 288 with repo.transaction(b'strip') as tr:
291 289 bundle2.processbundle(repo, gen, lambda: tr)
292 290 else:
293 291 gen.apply(
294 292 repo, b'strip', b'bundle:' + vfs.join(chgrpfile), True
295 293 )
296 294 finally:
297 295 f.close()
298 296
299 297 # remove undo files
300 298 for undovfs, undofile in repo.undofiles():
301 299 try:
302 300 undovfs.unlink(undofile)
303 301 except OSError as e:
304 302 if e.errno != errno.ENOENT:
305 303 ui.warn(
306 304 _(b'error removing %s: %s\n')
307 305 % (undovfs.join(undofile), stringutil.forcebytestr(e))
308 306 )
309 307
310 308 # Remove partial backup only if there were no exceptions
311 309 op._widen_uninterr.__exit__(None, None, None)
312 310 vfs.unlink(chgrpfile)
313 311
314 312
315 313 def setup():
316 314 """Enable narrow repo support in bundle2-related extension points."""
317 315 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
318 316
319 317 getbundleargs[b'narrow'] = b'boolean'
320 318 getbundleargs[b'depth'] = b'plain'
321 319 getbundleargs[b'oldincludepats'] = b'csv'
322 320 getbundleargs[b'oldexcludepats'] = b'csv'
323 321 getbundleargs[b'known'] = b'csv'
324 322
325 323 # Extend changegroup serving to handle requests from narrow clients.
326 324 origcgfn = exchange.getbundle2partsmapping[b'changegroup']
327 325
328 326 def wrappedcgfn(*args, **kwargs):
329 327 repo = args[1]
330 328 if repo.ui.has_section(_NARROWACL_SECTION):
331 329 kwargs = exchange.applynarrowacl(repo, kwargs)
332 330
333 331 if kwargs.get(r'narrow', False) and repo.ui.configbool(
334 332 b'experimental', b'narrowservebrokenellipses'
335 333 ):
336 334 getbundlechangegrouppart_narrow(*args, **kwargs)
337 335 else:
338 336 origcgfn(*args, **kwargs)
339 337
340 338 exchange.getbundle2partsmapping[b'changegroup'] = wrappedcgfn
341 339
342 340 # Extend changegroup receiver so client can fixup after widen requests.
343 341 origcghandler = bundle2.parthandlermapping[b'changegroup']
344 342
345 343 def wrappedcghandler(op, inpart):
346 344 origcghandler(op, inpart)
347 345 if util.safehasattr(op, '_widen_bundle'):
348 346 handlechangegroup_widen(op, inpart)
349 347 if util.safehasattr(op, '_bookmarksbackup'):
350 348 localrepo.localrepository._bookmarks.set(
351 349 op.repo, op._bookmarksbackup
352 350 )
353 351 del op._bookmarksbackup
354 352
355 353 wrappedcghandler.params = origcghandler.params
356 354 bundle2.parthandlermapping[b'changegroup'] = wrappedcghandler
General Comments 0
You need to be logged in to leave comments. Login now