##// END OF EJS Templates
narrow: start returning bundle2 from widen_bundle()...
Pulkit Goyal -
r40107:e8132a88 default
parent child Browse files
Show More
@@ -1,321 +1,322 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 extensions,
24 24 narrowspec,
25 25 repair,
26 26 repository,
27 27 util,
28 28 wireprototypes,
29 29 )
30 30 from mercurial.utils import (
31 31 stringutil,
32 32 )
33 33
34 34 NARROWCAP = 'narrow'
35 35 _NARROWACL_SECTION = 'narrowhgacl'
36 36 _CHANGESPECPART = NARROWCAP + ':changespec'
37 37 _SPECPART = NARROWCAP + ':spec'
38 38 _SPECPART_INCLUDE = 'include'
39 39 _SPECPART_EXCLUDE = 'exclude'
40 40 _KILLNODESIGNAL = 'KILL'
41 41 _DONESIGNAL = 'DONE'
42 42 _ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text)
43 43 _ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
44 44 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
45 45 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
46 46
47 47 # When advertising capabilities, always include narrow clone support.
48 48 def getrepocaps_narrow(orig, repo, **kwargs):
49 49 caps = orig(repo, **kwargs)
50 50 caps[NARROWCAP] = ['v0']
51 51 return caps
52 52
53 53 def widen_bundle(repo, diffmatcher, common, known, cgversion, ellipses):
54 """generates changegroup for widening a narrow clone
54 """generates bundle2 for widening a narrow clone
55 55
56 56 repo is the localrepository instance
57 57 diffmatcher is a differencemacther of '(newincludes, newexcludes) -
58 58 (oldincludes, oldexcludes)'
59 59 common is set of common heads between server and client
60 60 known is a set of revs known on the client side (used in ellipses)
61 61 cgversion is the changegroup version to send
62 62 ellipses is boolean value telling whether to send ellipses data or not
63 63
64 returns changegroup data of the changegroup built or return None if there
65 are no common revs
64 returns bundle2 of the data required for extending
66 65 """
67 # XXX: This patch will start sending bundle2 after couple of patches when
68 # called from the wireprotocol command
66 bundler = bundle2.bundle20(repo.ui)
69 67 commonnodes = set()
70 68 cl = repo.changelog
71 69 for r in repo.revs("::%ln", common):
72 70 commonnodes.add(cl.node(r))
73 71 if commonnodes:
74 72 # XXX: we should only send the filelogs (and treemanifest). user
75 73 # already has the changelog and manifest
76 74 packer = changegroup.getbundler(cgversion, repo,
77 75 filematcher=diffmatcher,
78 76 fullnodes=commonnodes)
79 77 cgdata = packer.generate(set([nullid]), list(commonnodes), False,
80 78 'narrow_widen', changelog=False)
81 79
82 return cgdata
80 part = bundler.newpart('changegroup', data=cgdata)
81 part.addparam('version', cgversion)
82 if 'treemanifest' in repo.requirements:
83 part.addparam('treemanifest', '1')
83 84
84 return None
85 return bundler
85 86
86 87 # Serve a changegroup for a client with a narrow clone.
87 88 def getbundlechangegrouppart_narrow(bundler, repo, source,
88 89 bundlecaps=None, b2caps=None, heads=None,
89 90 common=None, **kwargs):
90 91 assert repo.ui.configbool('experimental', 'narrowservebrokenellipses')
91 92
92 93 cgversions = b2caps.get('changegroup')
93 94 if cgversions: # 3.1 and 3.2 ship with an empty value
94 95 cgversions = [v for v in cgversions
95 96 if v in changegroup.supportedoutgoingversions(repo)]
96 97 if not cgversions:
97 98 raise ValueError(_('no common changegroup version'))
98 99 version = max(cgversions)
99 100 else:
100 101 raise ValueError(_("server does not advertise changegroup version,"
101 102 " can't negotiate support for ellipsis nodes"))
102 103
103 104 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
104 105 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
105 106 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
106 107
107 108 depth = kwargs.get(r'depth', None)
108 109 if depth is not None:
109 110 depth = int(depth)
110 111 if depth < 1:
111 112 raise error.Abort(_('depth must be positive, got %d') % depth)
112 113
113 114 heads = set(heads or repo.heads())
114 115 common = set(common or [nullid])
115 116 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
116 117 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
117 118 known = {bin(n) for n in kwargs.get(r'known', [])}
118 119 if known and (oldinclude != include or oldexclude != exclude):
119 120 # Steps:
120 121 # 1. Send kill for "$known & ::common"
121 122 #
122 123 # 2. Send changegroup for ::common
123 124 #
124 125 # 3. Proceed.
125 126 #
126 127 # In the future, we can send kills for only the specific
127 128 # nodes we know should go away or change shape, and then
128 129 # send a data stream that tells the client something like this:
129 130 #
130 131 # a) apply this changegroup
131 132 # b) apply nodes XXX, YYY, ZZZ that you already have
132 133 # c) goto a
133 134 #
134 135 # until they've built up the full new state.
135 136 # Convert to revnums and intersect with "common". The client should
136 137 # have made it a subset of "common" already, but let's be safe.
137 138 known = set(repo.revs("%ln & ::%ln", known, common))
138 139 # TODO: we could send only roots() of this set, and the
139 140 # list of nodes in common, and the client could work out
140 141 # what to strip, instead of us explicitly sending every
141 142 # single node.
142 143 deadrevs = known
143 144 def genkills():
144 145 for r in deadrevs:
145 146 yield _KILLNODESIGNAL
146 147 yield repo.changelog.node(r)
147 148 yield _DONESIGNAL
148 149 bundler.newpart(_CHANGESPECPART, data=genkills())
149 150 newvisit, newfull, newellipsis = exchange._computeellipsis(
150 151 repo, set(), common, known, newmatch)
151 152 if newvisit:
152 153 packer = changegroup.getbundler(version, repo,
153 154 filematcher=newmatch,
154 155 ellipses=True,
155 156 shallow=depth is not None,
156 157 ellipsisroots=newellipsis,
157 158 fullnodes=newfull)
158 159 cgdata = packer.generate(common, newvisit, False, 'narrow_widen')
159 160
160 161 part = bundler.newpart('changegroup', data=cgdata)
161 162 part.addparam('version', version)
162 163 if 'treemanifest' in repo.requirements:
163 164 part.addparam('treemanifest', '1')
164 165
165 166 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
166 167 repo, common, heads, set(), newmatch, depth=depth)
167 168
168 169 repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
169 170 if visitnodes:
170 171 packer = changegroup.getbundler(version, repo,
171 172 filematcher=newmatch,
172 173 ellipses=True,
173 174 shallow=depth is not None,
174 175 ellipsisroots=ellipsisroots,
175 176 fullnodes=relevant_nodes)
176 177 cgdata = packer.generate(common, visitnodes, False, 'narrow_widen')
177 178
178 179 part = bundler.newpart('changegroup', data=cgdata)
179 180 part.addparam('version', version)
180 181 if 'treemanifest' in repo.requirements:
181 182 part.addparam('treemanifest', '1')
182 183
183 184 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
184 185 def _handlechangespec_2(op, inpart):
185 186 includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
186 187 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
187 188 narrowspec.validatepatterns(includepats)
188 189 narrowspec.validatepatterns(excludepats)
189 190
190 191 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
191 192 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
192 193 op.repo._writerequirements()
193 194 op.repo.setnarrowpats(includepats, excludepats)
194 195
195 196 @bundle2.parthandler(_CHANGESPECPART)
196 197 def _handlechangespec(op, inpart):
197 198 repo = op.repo
198 199 cl = repo.changelog
199 200
200 201 # changesets which need to be stripped entirely. either they're no longer
201 202 # needed in the new narrow spec, or the server is sending a replacement
202 203 # in the changegroup part.
203 204 clkills = set()
204 205
205 206 # A changespec part contains all the updates to ellipsis nodes
206 207 # that will happen as a result of widening or narrowing a
207 208 # repo. All the changes that this block encounters are ellipsis
208 209 # nodes or flags to kill an existing ellipsis.
209 210 chunksignal = changegroup.readexactly(inpart, 4)
210 211 while chunksignal != _DONESIGNAL:
211 212 if chunksignal == _KILLNODESIGNAL:
212 213 # a node used to be an ellipsis but isn't anymore
213 214 ck = changegroup.readexactly(inpart, 20)
214 215 if cl.hasnode(ck):
215 216 clkills.add(ck)
216 217 else:
217 218 raise error.Abort(
218 219 _('unexpected changespec node chunk type: %s') % chunksignal)
219 220 chunksignal = changegroup.readexactly(inpart, 4)
220 221
221 222 if clkills:
222 223 # preserve bookmarks that repair.strip() would otherwise strip
223 224 bmstore = repo._bookmarks
224 225 class dummybmstore(dict):
225 226 def applychanges(self, repo, tr, changes):
226 227 pass
227 228 def recordchange(self, tr): # legacy version
228 229 pass
229 230 repo._bookmarks = dummybmstore()
230 231 chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
231 232 topic='widen')
232 233 repo._bookmarks = bmstore
233 234 if chgrpfile:
234 235 op._widen_uninterr = repo.ui.uninterruptable()
235 236 op._widen_uninterr.__enter__()
236 237 # presence of _widen_bundle attribute activates widen handler later
237 238 op._widen_bundle = chgrpfile
238 239 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
239 240 # will currently always be there when using the core+narrowhg server, but
240 241 # other servers may include a changespec part even when not widening (e.g.
241 242 # because we're deepening a shallow repo).
242 243 if util.safehasattr(repo, 'setnewnarrowpats'):
243 244 repo.setnewnarrowpats()
244 245
245 246 def handlechangegroup_widen(op, inpart):
246 247 """Changegroup exchange handler which restores temporarily-stripped nodes"""
247 248 # We saved a bundle with stripped node data we must now restore.
248 249 # This approach is based on mercurial/repair.py@6ee26a53c111.
249 250 repo = op.repo
250 251 ui = op.ui
251 252
252 253 chgrpfile = op._widen_bundle
253 254 del op._widen_bundle
254 255 vfs = repo.vfs
255 256
256 257 ui.note(_("adding branch\n"))
257 258 f = vfs.open(chgrpfile, "rb")
258 259 try:
259 260 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
260 261 if not ui.verbose:
261 262 # silence internal shuffling chatter
262 263 ui.pushbuffer()
263 264 if isinstance(gen, bundle2.unbundle20):
264 265 with repo.transaction('strip') as tr:
265 266 bundle2.processbundle(repo, gen, lambda: tr)
266 267 else:
267 268 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
268 269 if not ui.verbose:
269 270 ui.popbuffer()
270 271 finally:
271 272 f.close()
272 273
273 274 # remove undo files
274 275 for undovfs, undofile in repo.undofiles():
275 276 try:
276 277 undovfs.unlink(undofile)
277 278 except OSError as e:
278 279 if e.errno != errno.ENOENT:
279 280 ui.warn(_('error removing %s: %s\n') %
280 281 (undovfs.join(undofile), stringutil.forcebytestr(e)))
281 282
282 283 # Remove partial backup only if there were no exceptions
283 284 op._widen_uninterr.__exit__(None, None, None)
284 285 vfs.unlink(chgrpfile)
285 286
286 287 def setup():
287 288 """Enable narrow repo support in bundle2-related extension points."""
288 289 extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
289 290
290 291 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
291 292
292 293 getbundleargs['narrow'] = 'boolean'
293 294 getbundleargs['depth'] = 'plain'
294 295 getbundleargs['oldincludepats'] = 'csv'
295 296 getbundleargs['oldexcludepats'] = 'csv'
296 297 getbundleargs['includepats'] = 'csv'
297 298 getbundleargs['excludepats'] = 'csv'
298 299 getbundleargs['known'] = 'csv'
299 300
300 301 # Extend changegroup serving to handle requests from narrow clients.
301 302 origcgfn = exchange.getbundle2partsmapping['changegroup']
302 303 def wrappedcgfn(*args, **kwargs):
303 304 repo = args[1]
304 305 if repo.ui.has_section(_NARROWACL_SECTION):
305 306 kwargs = exchange.applynarrowacl(repo, kwargs)
306 307
307 308 if (kwargs.get(r'narrow', False) and
308 309 repo.ui.configbool('experimental', 'narrowservebrokenellipses')):
309 310 getbundlechangegrouppart_narrow(*args, **kwargs)
310 311 else:
311 312 origcgfn(*args, **kwargs)
312 313 exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
313 314
314 315 # Extend changegroup receiver so client can fixup after widen requests.
315 316 origcghandler = bundle2.parthandlermapping['changegroup']
316 317 def wrappedcghandler(op, inpart):
317 318 origcghandler(op, inpart)
318 319 if util.safehasattr(op, '_widen_bundle'):
319 320 handlechangegroup_widen(op, inpart)
320 321 wrappedcghandler.params = origcghandler.params
321 322 bundle2.parthandlermapping['changegroup'] = wrappedcghandler
@@ -1,124 +1,118 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 match as matchmod,
16 16 narrowspec,
17 17 pycompat,
18 18 wireprotoserver,
19 19 wireprototypes,
20 20 wireprotov1peer,
21 21 wireprotov1server,
22 22 )
23 23
24 24 from . import narrowbundle2
25 25
26 26 def uisetup():
27 27 extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
28 28 wireprotov1peer.wirepeer.narrow_widen = peernarrowwiden
29 29
30 30 def addnarrowcap(orig, repo, proto):
31 31 """add the narrow capability to the server"""
32 32 caps = orig(repo, proto)
33 33 caps.append(wireprotoserver.NARROWCAP)
34 34 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
35 35 caps.append(wireprotoserver.ELLIPSESCAP)
36 36 return caps
37 37
38 38 def reposetup(repo):
39 39 def wirereposetup(ui, peer):
40 40 def wrapped(orig, cmd, *args, **kwargs):
41 41 if cmd == 'unbundle':
42 42 # TODO: don't blindly add include/exclude wireproto
43 43 # arguments to unbundle.
44 44 include, exclude = repo.narrowpats
45 45 kwargs[r"includepats"] = ','.join(include)
46 46 kwargs[r"excludepats"] = ','.join(exclude)
47 47 return orig(cmd, *args, **kwargs)
48 48 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
49 49 hg.wirepeersetupfuncs.append(wirereposetup)
50 50
51 51 @wireprotov1server.wireprotocommand('narrow_widen', 'oldincludes oldexcludes'
52 52 ' newincludes newexcludes'
53 53 ' commonheads cgversion'
54 54 ' known ellipses',
55 55 permission='pull')
56 56 def narrow_widen(repo, proto, oldincludes, oldexcludes, newincludes,
57 57 newexcludes, commonheads, cgversion, known, ellipses):
58 58 """wireprotocol command to send data when a narrow clone is widen. We will
59 59 be sending a changegroup here.
60 60
61 61 The current set of arguments which are required:
62 62 oldincludes: the old includes of the narrow copy
63 63 oldexcludes: the old excludes of the narrow copy
64 64 newincludes: the new includes of the narrow copy
65 65 newexcludes: the new excludes of the narrow copy
66 66 commonheads: list of heads which are common between the server and client
67 67 cgversion(maybe): the changegroup version to produce
68 68 known: list of nodes which are known on the client (used in ellipses cases)
69 69 ellipses: whether to send ellipses data or not
70 70 """
71 71
72 bundler = bundle2.bundle20(repo.ui)
73 72 try:
74 73 oldincludes = wireprototypes.decodelist(oldincludes)
75 74 newincludes = wireprototypes.decodelist(newincludes)
76 75 oldexcludes = wireprototypes.decodelist(oldexcludes)
77 76 newexcludes = wireprototypes.decodelist(newexcludes)
78 77 # validate the patterns
79 78 narrowspec.validatepatterns(set(oldincludes))
80 79 narrowspec.validatepatterns(set(newincludes))
81 80 narrowspec.validatepatterns(set(oldexcludes))
82 81 narrowspec.validatepatterns(set(newexcludes))
83 82
84 83 common = wireprototypes.decodelist(commonheads)
85 84 known = None
86 85 if known:
87 86 known = wireprototypes.decodelist(known)
88 87 if ellipses == '0':
89 88 ellipses = False
90 89 else:
91 90 ellipses = bool(ellipses)
92 91 cgversion = cgversion
93 92 newmatch = narrowspec.match(repo.root, include=newincludes,
94 93 exclude=newexcludes)
95 94 oldmatch = narrowspec.match(repo.root, include=oldincludes,
96 95 exclude=oldexcludes)
97 96 diffmatch = matchmod.differencematcher(newmatch, oldmatch)
98 97
99 # get changegroup data
100 cg = narrowbundle2.widen_bundle(repo, diffmatch, common, known,
101 cgversion, ellipses)
102 if cg is not None:
103 part = bundler.newpart('changegroup', data=cg)
104 part.addparam('version', cgversion)
105 if 'treemanifest' in repo.requirements:
106 part.addparam('treemanifest', '1')
98 bundler = narrowbundle2.widen_bundle(repo, diffmatch, common, known,
99 cgversion, ellipses)
107 100 except error.Abort as exc:
101 bundler = bundle2.bundle20(repo.ui)
108 102 manargs = [('message', pycompat.bytestr(exc))]
109 103 advargs = []
110 104 if exc.hint is not None:
111 105 advargs.append(('hint', exc.hint))
112 106 bundler.addpart(bundle2.bundlepart('error:abort', manargs, advargs))
113 107
114 108 chunks = bundler.getchunks()
115 109 return wireprototypes.streamres(gen=chunks)
116 110
117 111 def peernarrowwiden(remote, **kwargs):
118 112 for ch in ('oldincludes', 'newincludes', 'oldexcludes', 'newexcludes',
119 113 'commonheads', 'known'):
120 114 kwargs[ch] = wireprototypes.encodelist(kwargs[ch])
121 115
122 116 kwargs['ellipses'] = '%i' % bool(kwargs['ellipses'])
123 117 f = remote._callcompressable('narrow_widen', **kwargs)
124 118 return bundle2.getunbundler(remote.ui, f)
General Comments 0
You need to be logged in to leave comments. Login now