##// END OF EJS Templates
widening: duplicate generateellipsesbundle2() for widening...
Martin von Zweigbergk -
r43536:561f9bc4 default
parent child Browse files
Show More
@@ -1,360 +1,459 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 (
15 15 bin,
16 16 nullid,
17 17 )
18 18 from mercurial import (
19 19 bundle2,
20 20 changegroup,
21 21 error,
22 22 exchange,
23 23 localrepo,
24 24 narrowspec,
25 25 repair,
26 26 util,
27 27 wireprototypes,
28 28 )
29 29 from mercurial.interfaces import repository
30 30 from mercurial.utils import stringutil
31 31
32 32 _NARROWACL_SECTION = b'narrowacl'
33 33 _CHANGESPECPART = b'narrow:changespec'
34 34 _RESSPECS = b'narrow:responsespec'
35 35 _SPECPART = b'narrow:spec'
36 36 _SPECPART_INCLUDE = b'include'
37 37 _SPECPART_EXCLUDE = b'exclude'
38 38 _KILLNODESIGNAL = b'KILL'
39 39 _DONESIGNAL = b'DONE'
40 40 _ELIDEDCSHEADER = b'>20s20s20sl' # cset id, p1, p2, len(text)
41 41 _ELIDEDMFHEADER = b'>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
42 42 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
43 43 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
44 44
45 45 # Serve a changegroup for a client with a narrow clone.
46 46 def getbundlechangegrouppart_narrow(
47 47 bundler,
48 48 repo,
49 49 source,
50 50 bundlecaps=None,
51 51 b2caps=None,
52 52 heads=None,
53 53 common=None,
54 54 **kwargs
55 55 ):
56 56 assert repo.ui.configbool(b'experimental', b'narrowservebrokenellipses')
57 57
58 58 cgversions = b2caps.get(b'changegroup')
59 59 cgversions = [
60 60 v
61 61 for v in cgversions
62 62 if v in changegroup.supportedoutgoingversions(repo)
63 63 ]
64 64 if not cgversions:
65 65 raise ValueError(_(b'no common changegroup version'))
66 66 version = max(cgversions)
67 67
68 68 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
69 69 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
70 70 newinclude = sorted(filter(bool, kwargs.get(r'includepats', [])))
71 71 newexclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
72 72 known = {bin(n) for n in kwargs.get(r'known', [])}
73 73 generateellipsesbundle2(
74 74 bundler,
75 75 repo,
76 76 oldinclude,
77 77 oldexclude,
78 78 newinclude,
79 79 newexclude,
80 80 version,
81 81 common,
82 82 heads,
83 83 known,
84 84 kwargs.get(r'depth', None),
85 85 )
86 86
87 87
88 88 def generateellipsesbundle2(
89 89 bundler,
90 90 repo,
91 91 oldinclude,
92 92 oldexclude,
93 93 newinclude,
94 94 newexclude,
95 95 version,
96 96 common,
97 97 heads,
98 98 known,
99 99 depth,
100 100 ):
101 101 newmatch = narrowspec.match(
102 102 repo.root, include=newinclude, exclude=newexclude
103 103 )
104 104 if depth is not None:
105 105 depth = int(depth)
106 106 if depth < 1:
107 107 raise error.Abort(_(b'depth must be positive, got %d') % depth)
108 108
109 109 heads = set(heads or repo.heads())
110 110 common = set(common or [nullid])
111 111 if known and (oldinclude != newinclude or oldexclude != newexclude):
112 112 # Steps:
113 113 # 1. Send kill for "$known & ::common"
114 114 #
115 115 # 2. Send changegroup for ::common
116 116 #
117 117 # 3. Proceed.
118 118 #
119 119 # In the future, we can send kills for only the specific
120 120 # nodes we know should go away or change shape, and then
121 121 # send a data stream that tells the client something like this:
122 122 #
123 123 # a) apply this changegroup
124 124 # b) apply nodes XXX, YYY, ZZZ that you already have
125 125 # c) goto a
126 126 #
127 127 # until they've built up the full new state.
128 128 # Convert to revnums and intersect with "common". The client should
129 129 # have made it a subset of "common" already, but let's be safe.
130 130 known = set(repo.revs(b"%ln & ::%ln", known, common))
131 131 # TODO: we could send only roots() of this set, and the
132 132 # list of nodes in common, and the client could work out
133 133 # what to strip, instead of us explicitly sending every
134 134 # single node.
135 135 deadrevs = known
136 136
137 137 def genkills():
138 138 for r in deadrevs:
139 139 yield _KILLNODESIGNAL
140 140 yield repo.changelog.node(r)
141 141 yield _DONESIGNAL
142 142
143 143 bundler.newpart(_CHANGESPECPART, data=genkills())
144 144 newvisit, newfull, newellipsis = exchange._computeellipsis(
145 145 repo, set(), common, known, newmatch
146 146 )
147 147 if newvisit:
148 148 packer = changegroup.getbundler(
149 149 version,
150 150 repo,
151 151 matcher=newmatch,
152 152 ellipses=True,
153 153 shallow=depth is not None,
154 154 ellipsisroots=newellipsis,
155 155 fullnodes=newfull,
156 156 )
157 157 cgdata = packer.generate(common, newvisit, False, b'narrow_widen')
158 158
159 159 part = bundler.newpart(b'changegroup', data=cgdata)
160 160 part.addparam(b'version', version)
161 161 if b'treemanifest' in repo.requirements:
162 162 part.addparam(b'treemanifest', b'1')
163 163
164 164 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
165 165 repo, common, heads, set(), newmatch, depth=depth
166 166 )
167 167
168 168 repo.ui.debug(b'Found %d relevant revs\n' % len(relevant_nodes))
169 169 if visitnodes:
170 170 packer = changegroup.getbundler(
171 171 version,
172 172 repo,
173 173 matcher=newmatch,
174 174 ellipses=True,
175 175 shallow=depth is not None,
176 176 ellipsisroots=ellipsisroots,
177 177 fullnodes=relevant_nodes,
178 178 )
179 179 cgdata = packer.generate(common, visitnodes, False, b'narrow_widen')
180 180
181 181 part = bundler.newpart(b'changegroup', data=cgdata)
182 182 part.addparam(b'version', version)
183 183 if b'treemanifest' in repo.requirements:
184 184 part.addparam(b'treemanifest', b'1')
185 185
186 186
187 def generate_ellipses_bundle2_for_widening(
188 bundler,
189 repo,
190 oldinclude,
191 oldexclude,
192 newinclude,
193 newexclude,
194 version,
195 common,
196 heads,
197 known,
198 depth,
199 ):
200 newmatch = narrowspec.match(
201 repo.root, include=newinclude, exclude=newexclude
202 )
203 if depth is not None:
204 depth = int(depth)
205 if depth < 1:
206 raise error.Abort(_(b'depth must be positive, got %d') % depth)
207
208 heads = set(heads or repo.heads())
209 common = set(common or [nullid])
210 if known and (oldinclude != newinclude or oldexclude != newexclude):
211 # Steps:
212 # 1. Send kill for "$known & ::common"
213 #
214 # 2. Send changegroup for ::common
215 #
216 # 3. Proceed.
217 #
218 # In the future, we can send kills for only the specific
219 # nodes we know should go away or change shape, and then
220 # send a data stream that tells the client something like this:
221 #
222 # a) apply this changegroup
223 # b) apply nodes XXX, YYY, ZZZ that you already have
224 # c) goto a
225 #
226 # until they've built up the full new state.
227 # Convert to revnums and intersect with "common". The client should
228 # have made it a subset of "common" already, but let's be safe.
229 known = set(repo.revs(b"%ln & ::%ln", known, common))
230 # TODO: we could send only roots() of this set, and the
231 # list of nodes in common, and the client could work out
232 # what to strip, instead of us explicitly sending every
233 # single node.
234 deadrevs = known
235
236 def genkills():
237 for r in deadrevs:
238 yield _KILLNODESIGNAL
239 yield repo.changelog.node(r)
240 yield _DONESIGNAL
241
242 bundler.newpart(_CHANGESPECPART, data=genkills())
243 newvisit, newfull, newellipsis = exchange._computeellipsis(
244 repo, set(), common, known, newmatch
245 )
246 if newvisit:
247 packer = changegroup.getbundler(
248 version,
249 repo,
250 matcher=newmatch,
251 ellipses=True,
252 shallow=depth is not None,
253 ellipsisroots=newellipsis,
254 fullnodes=newfull,
255 )
256 cgdata = packer.generate(common, newvisit, False, b'narrow_widen')
257
258 part = bundler.newpart(b'changegroup', data=cgdata)
259 part.addparam(b'version', version)
260 if b'treemanifest' in repo.requirements:
261 part.addparam(b'treemanifest', b'1')
262
263 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
264 repo, common, heads, set(), newmatch, depth=depth
265 )
266
267 repo.ui.debug(b'Found %d relevant revs\n' % len(relevant_nodes))
268 if visitnodes:
269 packer = changegroup.getbundler(
270 version,
271 repo,
272 matcher=newmatch,
273 ellipses=True,
274 shallow=depth is not None,
275 ellipsisroots=ellipsisroots,
276 fullnodes=relevant_nodes,
277 )
278 cgdata = packer.generate(common, visitnodes, False, b'narrow_widen')
279
280 part = bundler.newpart(b'changegroup', data=cgdata)
281 part.addparam(b'version', version)
282 if b'treemanifest' in repo.requirements:
283 part.addparam(b'treemanifest', b'1')
284
285
187 286 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
188 287 def _handlechangespec_2(op, inpart):
189 288 # XXX: This bundle2 handling is buggy and should be removed after hg5.2 is
190 289 # released. New servers will send a mandatory bundle2 part named
191 290 # 'Narrowspec' and will send specs as data instead of params.
192 291 # Refer to issue5952 and 6019
193 292 includepats = set(inpart.params.get(_SPECPART_INCLUDE, b'').splitlines())
194 293 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, b'').splitlines())
195 294 narrowspec.validatepatterns(includepats)
196 295 narrowspec.validatepatterns(excludepats)
197 296
198 297 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
199 298 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
200 299 op.repo._writerequirements()
201 300 op.repo.setnarrowpats(includepats, excludepats)
202 301 narrowspec.copytoworkingcopy(op.repo)
203 302
204 303
205 304 @bundle2.parthandler(_RESSPECS)
206 305 def _handlenarrowspecs(op, inpart):
207 306 data = inpart.read()
208 307 inc, exc = data.split(b'\0')
209 308 includepats = set(inc.splitlines())
210 309 excludepats = set(exc.splitlines())
211 310 narrowspec.validatepatterns(includepats)
212 311 narrowspec.validatepatterns(excludepats)
213 312
214 313 if repository.NARROW_REQUIREMENT not in op.repo.requirements:
215 314 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
216 315 op.repo._writerequirements()
217 316 op.repo.setnarrowpats(includepats, excludepats)
218 317 narrowspec.copytoworkingcopy(op.repo)
219 318
220 319
221 320 @bundle2.parthandler(_CHANGESPECPART)
222 321 def _handlechangespec(op, inpart):
223 322 repo = op.repo
224 323 cl = repo.changelog
225 324
226 325 # changesets which need to be stripped entirely. either they're no longer
227 326 # needed in the new narrow spec, or the server is sending a replacement
228 327 # in the changegroup part.
229 328 clkills = set()
230 329
231 330 # A changespec part contains all the updates to ellipsis nodes
232 331 # that will happen as a result of widening or narrowing a
233 332 # repo. All the changes that this block encounters are ellipsis
234 333 # nodes or flags to kill an existing ellipsis.
235 334 chunksignal = changegroup.readexactly(inpart, 4)
236 335 while chunksignal != _DONESIGNAL:
237 336 if chunksignal == _KILLNODESIGNAL:
238 337 # a node used to be an ellipsis but isn't anymore
239 338 ck = changegroup.readexactly(inpart, 20)
240 339 if cl.hasnode(ck):
241 340 clkills.add(ck)
242 341 else:
243 342 raise error.Abort(
244 343 _(b'unexpected changespec node chunk type: %s') % chunksignal
245 344 )
246 345 chunksignal = changegroup.readexactly(inpart, 4)
247 346
248 347 if clkills:
249 348 # preserve bookmarks that repair.strip() would otherwise strip
250 349 op._bookmarksbackup = repo._bookmarks
251 350
252 351 class dummybmstore(dict):
253 352 def applychanges(self, repo, tr, changes):
254 353 pass
255 354
256 355 localrepo.localrepository._bookmarks.set(repo, dummybmstore())
257 356 chgrpfile = repair.strip(
258 357 op.ui, repo, list(clkills), backup=True, topic=b'widen'
259 358 )
260 359 if chgrpfile:
261 360 op._widen_uninterr = repo.ui.uninterruptible()
262 361 op._widen_uninterr.__enter__()
263 362 # presence of _widen_bundle attribute activates widen handler later
264 363 op._widen_bundle = chgrpfile
265 364 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
266 365 # will currently always be there when using the core+narrowhg server, but
267 366 # other servers may include a changespec part even when not widening (e.g.
268 367 # because we're deepening a shallow repo).
269 368 if util.safehasattr(repo, 'setnewnarrowpats'):
270 369 repo.setnewnarrowpats()
271 370
272 371
273 372 def handlechangegroup_widen(op, inpart):
274 373 """Changegroup exchange handler which restores temporarily-stripped nodes"""
275 374 # We saved a bundle with stripped node data we must now restore.
276 375 # This approach is based on mercurial/repair.py@6ee26a53c111.
277 376 repo = op.repo
278 377 ui = op.ui
279 378
280 379 chgrpfile = op._widen_bundle
281 380 del op._widen_bundle
282 381 vfs = repo.vfs
283 382
284 383 ui.note(_(b"adding branch\n"))
285 384 f = vfs.open(chgrpfile, b"rb")
286 385 try:
287 386 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
288 387 # silence internal shuffling chatter
289 388 override = {(b'ui', b'quiet'): True}
290 389 if ui.verbose:
291 390 override = {}
292 391 with ui.configoverride(override):
293 392 if isinstance(gen, bundle2.unbundle20):
294 393 with repo.transaction(b'strip') as tr:
295 394 bundle2.processbundle(repo, gen, lambda: tr)
296 395 else:
297 396 gen.apply(
298 397 repo, b'strip', b'bundle:' + vfs.join(chgrpfile), True
299 398 )
300 399 finally:
301 400 f.close()
302 401
303 402 # remove undo files
304 403 for undovfs, undofile in repo.undofiles():
305 404 try:
306 405 undovfs.unlink(undofile)
307 406 except OSError as e:
308 407 if e.errno != errno.ENOENT:
309 408 ui.warn(
310 409 _(b'error removing %s: %s\n')
311 410 % (undovfs.join(undofile), stringutil.forcebytestr(e))
312 411 )
313 412
314 413 # Remove partial backup only if there were no exceptions
315 414 op._widen_uninterr.__exit__(None, None, None)
316 415 vfs.unlink(chgrpfile)
317 416
318 417
319 418 def setup():
320 419 """Enable narrow repo support in bundle2-related extension points."""
321 420 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
322 421
323 422 getbundleargs[b'narrow'] = b'boolean'
324 423 getbundleargs[b'depth'] = b'plain'
325 424 getbundleargs[b'oldincludepats'] = b'csv'
326 425 getbundleargs[b'oldexcludepats'] = b'csv'
327 426 getbundleargs[b'known'] = b'csv'
328 427
329 428 # Extend changegroup serving to handle requests from narrow clients.
330 429 origcgfn = exchange.getbundle2partsmapping[b'changegroup']
331 430
332 431 def wrappedcgfn(*args, **kwargs):
333 432 repo = args[1]
334 433 if repo.ui.has_section(_NARROWACL_SECTION):
335 434 kwargs = exchange.applynarrowacl(repo, kwargs)
336 435
337 436 if kwargs.get(r'narrow', False) and repo.ui.configbool(
338 437 b'experimental', b'narrowservebrokenellipses'
339 438 ):
340 439 getbundlechangegrouppart_narrow(*args, **kwargs)
341 440 else:
342 441 origcgfn(*args, **kwargs)
343 442
344 443 exchange.getbundle2partsmapping[b'changegroup'] = wrappedcgfn
345 444
346 445 # Extend changegroup receiver so client can fixup after widen requests.
347 446 origcghandler = bundle2.parthandlermapping[b'changegroup']
348 447
349 448 def wrappedcghandler(op, inpart):
350 449 origcghandler(op, inpart)
351 450 if util.safehasattr(op, '_widen_bundle'):
352 451 handlechangegroup_widen(op, inpart)
353 452 if util.safehasattr(op, '_bookmarksbackup'):
354 453 localrepo.localrepository._bookmarks.set(
355 454 op.repo, op._bookmarksbackup
356 455 )
357 456 del op._bookmarksbackup
358 457
359 458 wrappedcghandler.params = origcghandler.params
360 459 bundle2.parthandlermapping[b'changegroup'] = wrappedcghandler
@@ -1,160 +1,160 b''
1 1 # narrowwirepeer.py - passes narrow spec with unbundle command
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 from mercurial import (
11 11 bundle2,
12 12 error,
13 13 extensions,
14 14 hg,
15 15 narrowspec,
16 16 pycompat,
17 17 wireprototypes,
18 18 wireprotov1peer,
19 19 wireprotov1server,
20 20 )
21 21
22 22 from . import narrowbundle2
23 23
24 24
25 25 def uisetup():
26 26 wireprotov1peer.wirepeer.narrow_widen = peernarrowwiden
27 27
28 28
29 29 def reposetup(repo):
30 30 def wirereposetup(ui, peer):
31 31 def wrapped(orig, cmd, *args, **kwargs):
32 32 if cmd == b'unbundle':
33 33 # TODO: don't blindly add include/exclude wireproto
34 34 # arguments to unbundle.
35 35 include, exclude = repo.narrowpats
36 36 kwargs[r"includepats"] = b','.join(include)
37 37 kwargs[r"excludepats"] = b','.join(exclude)
38 38 return orig(cmd, *args, **kwargs)
39 39
40 40 extensions.wrapfunction(peer, b'_calltwowaystream', wrapped)
41 41
42 42 hg.wirepeersetupfuncs.append(wirereposetup)
43 43
44 44
45 45 @wireprotov1server.wireprotocommand(
46 46 b'narrow_widen',
47 47 b'oldincludes oldexcludes'
48 48 b' newincludes newexcludes'
49 49 b' commonheads cgversion'
50 50 b' known ellipses',
51 51 permission=b'pull',
52 52 )
53 53 def narrow_widen(
54 54 repo,
55 55 proto,
56 56 oldincludes,
57 57 oldexcludes,
58 58 newincludes,
59 59 newexcludes,
60 60 commonheads,
61 61 cgversion,
62 62 known,
63 63 ellipses,
64 64 ):
65 65 """wireprotocol command to send data when a narrow clone is widen. We will
66 66 be sending a changegroup here.
67 67
68 68 The current set of arguments which are required:
69 69 oldincludes: the old includes of the narrow copy
70 70 oldexcludes: the old excludes of the narrow copy
71 71 newincludes: the new includes of the narrow copy
72 72 newexcludes: the new excludes of the narrow copy
73 73 commonheads: list of heads which are common between the server and client
74 74 cgversion(maybe): the changegroup version to produce
75 75 known: list of nodes which are known on the client (used in ellipses cases)
76 76 ellipses: whether to send ellipses data or not
77 77 """
78 78
79 79 preferuncompressed = False
80 80 try:
81 81
82 82 def splitpaths(data):
83 83 # work around ''.split(',') => ['']
84 84 return data.split(b',') if data else []
85 85
86 86 oldincludes = splitpaths(oldincludes)
87 87 newincludes = splitpaths(newincludes)
88 88 oldexcludes = splitpaths(oldexcludes)
89 89 newexcludes = splitpaths(newexcludes)
90 90 # validate the patterns
91 91 narrowspec.validatepatterns(set(oldincludes))
92 92 narrowspec.validatepatterns(set(newincludes))
93 93 narrowspec.validatepatterns(set(oldexcludes))
94 94 narrowspec.validatepatterns(set(newexcludes))
95 95
96 96 common = wireprototypes.decodelist(commonheads)
97 97 known = wireprototypes.decodelist(known)
98 98 if ellipses == b'0':
99 99 ellipses = False
100 100 else:
101 101 ellipses = bool(ellipses)
102 102 cgversion = cgversion
103 103
104 104 bundler = bundle2.bundle20(repo.ui)
105 105 if not ellipses:
106 106 newmatch = narrowspec.match(
107 107 repo.root, include=newincludes, exclude=newexcludes
108 108 )
109 109 oldmatch = narrowspec.match(
110 110 repo.root, include=oldincludes, exclude=oldexcludes
111 111 )
112 112 bundle2.widen_bundle(
113 113 bundler,
114 114 repo,
115 115 oldmatch,
116 116 newmatch,
117 117 common,
118 118 known,
119 119 cgversion,
120 120 ellipses,
121 121 )
122 122 else:
123 narrowbundle2.generateellipsesbundle2(
123 narrowbundle2.generate_ellipses_bundle2_for_widening(
124 124 bundler,
125 125 repo,
126 126 oldincludes,
127 127 oldexcludes,
128 128 newincludes,
129 129 newexcludes,
130 130 cgversion,
131 131 common,
132 132 list(common),
133 133 known,
134 134 None,
135 135 )
136 136 except error.Abort as exc:
137 137 bundler = bundle2.bundle20(repo.ui)
138 138 manargs = [(b'message', pycompat.bytestr(exc))]
139 139 advargs = []
140 140 if exc.hint is not None:
141 141 advargs.append((b'hint', exc.hint))
142 142 bundler.addpart(bundle2.bundlepart(b'error:abort', manargs, advargs))
143 143 preferuncompressed = True
144 144
145 145 chunks = bundler.getchunks()
146 146 return wireprototypes.streamres(
147 147 gen=chunks, prefer_uncompressed=preferuncompressed
148 148 )
149 149
150 150
151 151 def peernarrowwiden(remote, **kwargs):
152 152 for ch in (r'commonheads', r'known'):
153 153 kwargs[ch] = wireprototypes.encodelist(kwargs[ch])
154 154
155 155 for ch in (r'oldincludes', r'newincludes', r'oldexcludes', r'newexcludes'):
156 156 kwargs[ch] = b','.join(kwargs[ch])
157 157
158 158 kwargs[r'ellipses'] = b'%i' % bool(kwargs[r'ellipses'])
159 159 f = remote._callcompressable(b'narrow_widen', **kwargs)
160 160 return bundle2.getunbundler(remote.ui, f)
General Comments 0
You need to be logged in to leave comments. Login now