##// END OF EJS Templates
widening: pass in matchers instead of patterns...
Martin von Zweigbergk -
r43543:63d440be default
parent child Browse files
Show More
@@ -1,354 +1,342 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 bundler,
116 repo,
117 oldinclude,
118 oldexclude,
119 newinclude,
120 newexclude,
121 version,
122 common,
123 known,
115 bundler, repo, oldmatch, newmatch, version, common, known,
124 116 ):
125 newmatch = narrowspec.match(
126 repo.root, include=newinclude, exclude=newexclude
127 )
128
129 117 common = set(common or [nullid])
130 118 # Steps:
131 119 # 1. Send kill for "$known & ::common"
132 120 #
133 121 # 2. Send changegroup for ::common
134 122 #
135 123 # 3. Proceed.
136 124 #
137 125 # In the future, we can send kills for only the specific
138 126 # nodes we know should go away or change shape, and then
139 127 # send a data stream that tells the client something like this:
140 128 #
141 129 # a) apply this changegroup
142 130 # b) apply nodes XXX, YYY, ZZZ that you already have
143 131 # c) goto a
144 132 #
145 133 # until they've built up the full new state.
146 134 knownrevs = {repo.changelog.rev(n) for n in known}
147 135 # TODO: we could send only roots() of this set, and the
148 136 # list of nodes in common, and the client could work out
149 137 # what to strip, instead of us explicitly sending every
150 138 # single node.
151 139 deadrevs = knownrevs
152 140
153 141 def genkills():
154 142 for r in deadrevs:
155 143 yield _KILLNODESIGNAL
156 144 yield repo.changelog.node(r)
157 145 yield _DONESIGNAL
158 146
159 147 bundler.newpart(_CHANGESPECPART, data=genkills())
160 148 newvisit, newfull, newellipsis = exchange._computeellipsis(
161 149 repo, set(), common, knownrevs, newmatch
162 150 )
163 151 if newvisit:
164 152 packer = changegroup.getbundler(
165 153 version,
166 154 repo,
167 155 matcher=newmatch,
168 156 ellipses=True,
169 157 shallow=False,
170 158 ellipsisroots=newellipsis,
171 159 fullnodes=newfull,
172 160 )
173 161 cgdata = packer.generate(common, newvisit, False, b'narrow_widen')
174 162
175 163 part = bundler.newpart(b'changegroup', data=cgdata)
176 164 part.addparam(b'version', version)
177 165 if b'treemanifest' in repo.requirements:
178 166 part.addparam(b'treemanifest', b'1')
179 167
180 168
181 169 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
182 170 def _handlechangespec_2(op, inpart):
183 171 # XXX: This bundle2 handling is buggy and should be removed after hg5.2 is
184 172 # released. New servers will send a mandatory bundle2 part named
185 173 # 'Narrowspec' and will send specs as data instead of params.
186 174 # Refer to issue5952 and 6019
187 175 includepats = set(inpart.params.get(_SPECPART_INCLUDE, b'').splitlines())
188 176 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, b'').splitlines())
189 177 narrowspec.validatepatterns(includepats)
190 178 narrowspec.validatepatterns(excludepats)
191 179
192 180 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
193 181 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
194 182 op.repo._writerequirements()
195 183 op.repo.setnarrowpats(includepats, excludepats)
196 184 narrowspec.copytoworkingcopy(op.repo)
197 185
198 186
199 187 @bundle2.parthandler(_RESSPECS)
200 188 def _handlenarrowspecs(op, inpart):
201 189 data = inpart.read()
202 190 inc, exc = data.split(b'\0')
203 191 includepats = set(inc.splitlines())
204 192 excludepats = set(exc.splitlines())
205 193 narrowspec.validatepatterns(includepats)
206 194 narrowspec.validatepatterns(excludepats)
207 195
208 196 if repository.NARROW_REQUIREMENT not in op.repo.requirements:
209 197 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
210 198 op.repo._writerequirements()
211 199 op.repo.setnarrowpats(includepats, excludepats)
212 200 narrowspec.copytoworkingcopy(op.repo)
213 201
214 202
215 203 @bundle2.parthandler(_CHANGESPECPART)
216 204 def _handlechangespec(op, inpart):
217 205 repo = op.repo
218 206 cl = repo.changelog
219 207
220 208 # changesets which need to be stripped entirely. either they're no longer
221 209 # needed in the new narrow spec, or the server is sending a replacement
222 210 # in the changegroup part.
223 211 clkills = set()
224 212
225 213 # A changespec part contains all the updates to ellipsis nodes
226 214 # that will happen as a result of widening or narrowing a
227 215 # repo. All the changes that this block encounters are ellipsis
228 216 # nodes or flags to kill an existing ellipsis.
229 217 chunksignal = changegroup.readexactly(inpart, 4)
230 218 while chunksignal != _DONESIGNAL:
231 219 if chunksignal == _KILLNODESIGNAL:
232 220 # a node used to be an ellipsis but isn't anymore
233 221 ck = changegroup.readexactly(inpart, 20)
234 222 if cl.hasnode(ck):
235 223 clkills.add(ck)
236 224 else:
237 225 raise error.Abort(
238 226 _(b'unexpected changespec node chunk type: %s') % chunksignal
239 227 )
240 228 chunksignal = changegroup.readexactly(inpart, 4)
241 229
242 230 if clkills:
243 231 # preserve bookmarks that repair.strip() would otherwise strip
244 232 op._bookmarksbackup = repo._bookmarks
245 233
246 234 class dummybmstore(dict):
247 235 def applychanges(self, repo, tr, changes):
248 236 pass
249 237
250 238 localrepo.localrepository._bookmarks.set(repo, dummybmstore())
251 239 chgrpfile = repair.strip(
252 240 op.ui, repo, list(clkills), backup=True, topic=b'widen'
253 241 )
254 242 if chgrpfile:
255 243 op._widen_uninterr = repo.ui.uninterruptible()
256 244 op._widen_uninterr.__enter__()
257 245 # presence of _widen_bundle attribute activates widen handler later
258 246 op._widen_bundle = chgrpfile
259 247 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
260 248 # will currently always be there when using the core+narrowhg server, but
261 249 # other servers may include a changespec part even when not widening (e.g.
262 250 # because we're deepening a shallow repo).
263 251 if util.safehasattr(repo, 'setnewnarrowpats'):
264 252 repo.setnewnarrowpats()
265 253
266 254
267 255 def handlechangegroup_widen(op, inpart):
268 256 """Changegroup exchange handler which restores temporarily-stripped nodes"""
269 257 # We saved a bundle with stripped node data we must now restore.
270 258 # This approach is based on mercurial/repair.py@6ee26a53c111.
271 259 repo = op.repo
272 260 ui = op.ui
273 261
274 262 chgrpfile = op._widen_bundle
275 263 del op._widen_bundle
276 264 vfs = repo.vfs
277 265
278 266 ui.note(_(b"adding branch\n"))
279 267 f = vfs.open(chgrpfile, b"rb")
280 268 try:
281 269 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
282 270 # silence internal shuffling chatter
283 271 override = {(b'ui', b'quiet'): True}
284 272 if ui.verbose:
285 273 override = {}
286 274 with ui.configoverride(override):
287 275 if isinstance(gen, bundle2.unbundle20):
288 276 with repo.transaction(b'strip') as tr:
289 277 bundle2.processbundle(repo, gen, lambda: tr)
290 278 else:
291 279 gen.apply(
292 280 repo, b'strip', b'bundle:' + vfs.join(chgrpfile), True
293 281 )
294 282 finally:
295 283 f.close()
296 284
297 285 # remove undo files
298 286 for undovfs, undofile in repo.undofiles():
299 287 try:
300 288 undovfs.unlink(undofile)
301 289 except OSError as e:
302 290 if e.errno != errno.ENOENT:
303 291 ui.warn(
304 292 _(b'error removing %s: %s\n')
305 293 % (undovfs.join(undofile), stringutil.forcebytestr(e))
306 294 )
307 295
308 296 # Remove partial backup only if there were no exceptions
309 297 op._widen_uninterr.__exit__(None, None, None)
310 298 vfs.unlink(chgrpfile)
311 299
312 300
313 301 def setup():
314 302 """Enable narrow repo support in bundle2-related extension points."""
315 303 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
316 304
317 305 getbundleargs[b'narrow'] = b'boolean'
318 306 getbundleargs[b'depth'] = b'plain'
319 307 getbundleargs[b'oldincludepats'] = b'csv'
320 308 getbundleargs[b'oldexcludepats'] = b'csv'
321 309 getbundleargs[b'known'] = b'csv'
322 310
323 311 # Extend changegroup serving to handle requests from narrow clients.
324 312 origcgfn = exchange.getbundle2partsmapping[b'changegroup']
325 313
326 314 def wrappedcgfn(*args, **kwargs):
327 315 repo = args[1]
328 316 if repo.ui.has_section(_NARROWACL_SECTION):
329 317 kwargs = exchange.applynarrowacl(repo, kwargs)
330 318
331 319 if kwargs.get(r'narrow', False) and repo.ui.configbool(
332 320 b'experimental', b'narrowservebrokenellipses'
333 321 ):
334 322 getbundlechangegrouppart_narrow(*args, **kwargs)
335 323 else:
336 324 origcgfn(*args, **kwargs)
337 325
338 326 exchange.getbundle2partsmapping[b'changegroup'] = wrappedcgfn
339 327
340 328 # Extend changegroup receiver so client can fixup after widen requests.
341 329 origcghandler = bundle2.parthandlermapping[b'changegroup']
342 330
343 331 def wrappedcghandler(op, inpart):
344 332 origcghandler(op, inpart)
345 333 if util.safehasattr(op, '_widen_bundle'):
346 334 handlechangegroup_widen(op, inpart)
347 335 if util.safehasattr(op, '_bookmarksbackup'):
348 336 localrepo.localrepository._bookmarks.set(
349 337 op.repo, op._bookmarksbackup
350 338 )
351 339 del op._bookmarksbackup
352 340
353 341 wrappedcghandler.params = origcghandler.params
354 342 bundle2.parthandlermapping[b'changegroup'] = wrappedcghandler
@@ -1,158 +1,150 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 if not ellipses:
106 105 newmatch = narrowspec.match(
107 106 repo.root, include=newincludes, exclude=newexcludes
108 107 )
109 108 oldmatch = narrowspec.match(
110 109 repo.root, include=oldincludes, exclude=oldexcludes
111 110 )
111 if not ellipses:
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 123 narrowbundle2.generate_ellipses_bundle2_for_widening(
124 bundler,
125 repo,
126 oldincludes,
127 oldexcludes,
128 newincludes,
129 newexcludes,
130 cgversion,
131 common,
132 known,
124 bundler, repo, oldmatch, newmatch, cgversion, common, known,
133 125 )
134 126 except error.Abort as exc:
135 127 bundler = bundle2.bundle20(repo.ui)
136 128 manargs = [(b'message', pycompat.bytestr(exc))]
137 129 advargs = []
138 130 if exc.hint is not None:
139 131 advargs.append((b'hint', exc.hint))
140 132 bundler.addpart(bundle2.bundlepart(b'error:abort', manargs, advargs))
141 133 preferuncompressed = True
142 134
143 135 chunks = bundler.getchunks()
144 136 return wireprototypes.streamres(
145 137 gen=chunks, prefer_uncompressed=preferuncompressed
146 138 )
147 139
148 140
149 141 def peernarrowwiden(remote, **kwargs):
150 142 for ch in (r'commonheads', r'known'):
151 143 kwargs[ch] = wireprototypes.encodelist(kwargs[ch])
152 144
153 145 for ch in (r'oldincludes', r'newincludes', r'oldexcludes', r'newexcludes'):
154 146 kwargs[ch] = b','.join(kwargs[ch])
155 147
156 148 kwargs[r'ellipses'] = b'%i' % bool(kwargs[r'ellipses'])
157 149 f = remote._callcompressable(b'narrow_widen', **kwargs)
158 150 return bundle2.getunbundler(remote.ui, f)
General Comments 0
You need to be logged in to leave comments. Login now