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