##// END OF EJS Templates
narrow: add narrow and ellipses as server capabilities...
Pulkit Goyal -
r39559:c9051404 default
parent child Browse files
Show More
@@ -1,332 +1,333 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 ELLIPSESCAP = 'ellipses'
35 _NARROWACL_SECTION = 'narrowhgacl'
36 _NARROWACL_SECTION = 'narrowhgacl'
36 _CHANGESPECPART = NARROWCAP + ':changespec'
37 _CHANGESPECPART = NARROWCAP + ':changespec'
37 _SPECPART = NARROWCAP + ':spec'
38 _SPECPART = NARROWCAP + ':spec'
38 _SPECPART_INCLUDE = 'include'
39 _SPECPART_INCLUDE = 'include'
39 _SPECPART_EXCLUDE = 'exclude'
40 _SPECPART_EXCLUDE = 'exclude'
40 _KILLNODESIGNAL = 'KILL'
41 _KILLNODESIGNAL = 'KILL'
41 _DONESIGNAL = 'DONE'
42 _DONESIGNAL = 'DONE'
42 _ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text)
43 _ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text)
43 _ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
44 _ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
44 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
45 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
45 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
46 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
46
47
47 # When advertising capabilities, always include narrow clone support.
48 # When advertising capabilities, always include narrow clone support.
48 def getrepocaps_narrow(orig, repo, **kwargs):
49 def getrepocaps_narrow(orig, repo, **kwargs):
49 caps = orig(repo, **kwargs)
50 caps = orig(repo, **kwargs)
50 caps[NARROWCAP] = ['v0']
51 caps[NARROWCAP] = ['v0']
51 return caps
52 return caps
52
53
53 def getbundlechangegrouppart_widen(bundler, repo, source, bundlecaps=None,
54 def getbundlechangegrouppart_widen(bundler, repo, source, bundlecaps=None,
54 b2caps=None, heads=None, common=None,
55 b2caps=None, heads=None, common=None,
55 **kwargs):
56 **kwargs):
56 """Handling changegroup changegroup generation on the server when user
57 """Handling changegroup changegroup generation on the server when user
57 is widening their narrowspec"""
58 is widening their narrowspec"""
58
59
59 cgversions = b2caps.get('changegroup')
60 cgversions = b2caps.get('changegroup')
60 if cgversions: # 3.1 and 3.2 ship with an empty value
61 if cgversions: # 3.1 and 3.2 ship with an empty value
61 cgversions = [v for v in cgversions
62 cgversions = [v for v in cgversions
62 if v in changegroup.supportedoutgoingversions(repo)]
63 if v in changegroup.supportedoutgoingversions(repo)]
63 if not cgversions:
64 if not cgversions:
64 raise ValueError(_('no common changegroup version'))
65 raise ValueError(_('no common changegroup version'))
65 version = max(cgversions)
66 version = max(cgversions)
66 else:
67 else:
67 raise ValueError(_("server does not advertise changegroup version,"
68 raise ValueError(_("server does not advertise changegroup version,"
68 " can't negotiate support for ellipsis nodes"))
69 " can't negotiate support for ellipsis nodes"))
69
70
70 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
71 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
71 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
72 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
72 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
73 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
73 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
74 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
74 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
75 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
75 common = set(common or [nullid])
76 common = set(common or [nullid])
76
77
77 if (oldinclude != include or oldexclude != exclude):
78 if (oldinclude != include or oldexclude != exclude):
78 common = repo.revs("::%ln", common)
79 common = repo.revs("::%ln", common)
79 commonnodes = set()
80 commonnodes = set()
80 cl = repo.changelog
81 cl = repo.changelog
81 for c in common:
82 for c in common:
82 commonnodes.add(cl.node(c))
83 commonnodes.add(cl.node(c))
83 if commonnodes:
84 if commonnodes:
84 # XXX: we should only send the filelogs (and treemanifest). user
85 # XXX: we should only send the filelogs (and treemanifest). user
85 # already has the changelog and manifest
86 # already has the changelog and manifest
86 packer = changegroup.getbundler(version, repo,
87 packer = changegroup.getbundler(version, repo,
87 filematcher=newmatch,
88 filematcher=newmatch,
88 fullnodes=commonnodes)
89 fullnodes=commonnodes)
89 cgdata = packer.generate(set([nullid]), list(commonnodes), False,
90 cgdata = packer.generate(set([nullid]), list(commonnodes), False,
90 source)
91 source)
91
92
92 part = bundler.newpart('changegroup', data=cgdata)
93 part = bundler.newpart('changegroup', data=cgdata)
93 part.addparam('version', version)
94 part.addparam('version', version)
94 if 'treemanifest' in repo.requirements:
95 if 'treemanifest' in repo.requirements:
95 part.addparam('treemanifest', '1')
96 part.addparam('treemanifest', '1')
96
97
97 # Serve a changegroup for a client with a narrow clone.
98 # Serve a changegroup for a client with a narrow clone.
98 def getbundlechangegrouppart_narrow(bundler, repo, source,
99 def getbundlechangegrouppart_narrow(bundler, repo, source,
99 bundlecaps=None, b2caps=None, heads=None,
100 bundlecaps=None, b2caps=None, heads=None,
100 common=None, **kwargs):
101 common=None, **kwargs):
101 assert repo.ui.configbool('experimental', 'narrowservebrokenellipses')
102 assert repo.ui.configbool('experimental', 'narrowservebrokenellipses')
102
103
103 cgversions = b2caps.get('changegroup')
104 cgversions = b2caps.get('changegroup')
104 if cgversions: # 3.1 and 3.2 ship with an empty value
105 if cgversions: # 3.1 and 3.2 ship with an empty value
105 cgversions = [v for v in cgversions
106 cgversions = [v for v in cgversions
106 if v in changegroup.supportedoutgoingversions(repo)]
107 if v in changegroup.supportedoutgoingversions(repo)]
107 if not cgversions:
108 if not cgversions:
108 raise ValueError(_('no common changegroup version'))
109 raise ValueError(_('no common changegroup version'))
109 version = max(cgversions)
110 version = max(cgversions)
110 else:
111 else:
111 raise ValueError(_("server does not advertise changegroup version,"
112 raise ValueError(_("server does not advertise changegroup version,"
112 " can't negotiate support for ellipsis nodes"))
113 " can't negotiate support for ellipsis nodes"))
113
114
114 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
115 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
115 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
116 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
116 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
117 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
117
118
118 depth = kwargs.get(r'depth', None)
119 depth = kwargs.get(r'depth', None)
119 if depth is not None:
120 if depth is not None:
120 depth = int(depth)
121 depth = int(depth)
121 if depth < 1:
122 if depth < 1:
122 raise error.Abort(_('depth must be positive, got %d') % depth)
123 raise error.Abort(_('depth must be positive, got %d') % depth)
123
124
124 heads = set(heads or repo.heads())
125 heads = set(heads or repo.heads())
125 common = set(common or [nullid])
126 common = set(common or [nullid])
126 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
127 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
127 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
128 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
128 known = {bin(n) for n in kwargs.get(r'known', [])}
129 known = {bin(n) for n in kwargs.get(r'known', [])}
129 if known and (oldinclude != include or oldexclude != exclude):
130 if known and (oldinclude != include or oldexclude != exclude):
130 # Steps:
131 # Steps:
131 # 1. Send kill for "$known & ::common"
132 # 1. Send kill for "$known & ::common"
132 #
133 #
133 # 2. Send changegroup for ::common
134 # 2. Send changegroup for ::common
134 #
135 #
135 # 3. Proceed.
136 # 3. Proceed.
136 #
137 #
137 # In the future, we can send kills for only the specific
138 # In the future, we can send kills for only the specific
138 # nodes we know should go away or change shape, and then
139 # nodes we know should go away or change shape, and then
139 # send a data stream that tells the client something like this:
140 # send a data stream that tells the client something like this:
140 #
141 #
141 # a) apply this changegroup
142 # a) apply this changegroup
142 # b) apply nodes XXX, YYY, ZZZ that you already have
143 # b) apply nodes XXX, YYY, ZZZ that you already have
143 # c) goto a
144 # c) goto a
144 #
145 #
145 # until they've built up the full new state.
146 # until they've built up the full new state.
146 # Convert to revnums and intersect with "common". The client should
147 # 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 # have made it a subset of "common" already, but let's be safe.
148 known = set(repo.revs("%ln & ::%ln", known, common))
149 known = set(repo.revs("%ln & ::%ln", known, common))
149 # TODO: we could send only roots() of this set, and the
150 # TODO: we could send only roots() of this set, and the
150 # list of nodes in common, and the client could work out
151 # list of nodes in common, and the client could work out
151 # what to strip, instead of us explicitly sending every
152 # what to strip, instead of us explicitly sending every
152 # single node.
153 # single node.
153 deadrevs = known
154 deadrevs = known
154 def genkills():
155 def genkills():
155 for r in deadrevs:
156 for r in deadrevs:
156 yield _KILLNODESIGNAL
157 yield _KILLNODESIGNAL
157 yield repo.changelog.node(r)
158 yield repo.changelog.node(r)
158 yield _DONESIGNAL
159 yield _DONESIGNAL
159 bundler.newpart(_CHANGESPECPART, data=genkills())
160 bundler.newpart(_CHANGESPECPART, data=genkills())
160 newvisit, newfull, newellipsis = exchange._computeellipsis(
161 newvisit, newfull, newellipsis = exchange._computeellipsis(
161 repo, set(), common, known, newmatch)
162 repo, set(), common, known, newmatch)
162 if newvisit:
163 if newvisit:
163 packer = changegroup.getbundler(version, repo,
164 packer = changegroup.getbundler(version, repo,
164 filematcher=newmatch,
165 filematcher=newmatch,
165 ellipses=True,
166 ellipses=True,
166 shallow=depth is not None,
167 shallow=depth is not None,
167 ellipsisroots=newellipsis,
168 ellipsisroots=newellipsis,
168 fullnodes=newfull)
169 fullnodes=newfull)
169 cgdata = packer.generate(common, newvisit, False, source)
170 cgdata = packer.generate(common, newvisit, False, source)
170
171
171 part = bundler.newpart('changegroup', data=cgdata)
172 part = bundler.newpart('changegroup', data=cgdata)
172 part.addparam('version', version)
173 part.addparam('version', version)
173 if 'treemanifest' in repo.requirements:
174 if 'treemanifest' in repo.requirements:
174 part.addparam('treemanifest', '1')
175 part.addparam('treemanifest', '1')
175
176
176 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
177 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
177 repo, common, heads, set(), newmatch, depth=depth)
178 repo, common, heads, set(), newmatch, depth=depth)
178
179
179 repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
180 repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
180 if visitnodes:
181 if visitnodes:
181 packer = changegroup.getbundler(version, repo,
182 packer = changegroup.getbundler(version, repo,
182 filematcher=newmatch,
183 filematcher=newmatch,
183 ellipses=True,
184 ellipses=True,
184 shallow=depth is not None,
185 shallow=depth is not None,
185 ellipsisroots=ellipsisroots,
186 ellipsisroots=ellipsisroots,
186 fullnodes=relevant_nodes)
187 fullnodes=relevant_nodes)
187 cgdata = packer.generate(common, visitnodes, False, source)
188 cgdata = packer.generate(common, visitnodes, False, source)
188
189
189 part = bundler.newpart('changegroup', data=cgdata)
190 part = bundler.newpart('changegroup', data=cgdata)
190 part.addparam('version', version)
191 part.addparam('version', version)
191 if 'treemanifest' in repo.requirements:
192 if 'treemanifest' in repo.requirements:
192 part.addparam('treemanifest', '1')
193 part.addparam('treemanifest', '1')
193
194
194 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
195 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
195 def _handlechangespec_2(op, inpart):
196 def _handlechangespec_2(op, inpart):
196 includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
197 includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
197 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
198 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
198 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
199 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
199 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
200 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
200 op.repo._writerequirements()
201 op.repo._writerequirements()
201 op.repo.setnarrowpats(includepats, excludepats)
202 op.repo.setnarrowpats(includepats, excludepats)
202
203
203 @bundle2.parthandler(_CHANGESPECPART)
204 @bundle2.parthandler(_CHANGESPECPART)
204 def _handlechangespec(op, inpart):
205 def _handlechangespec(op, inpart):
205 repo = op.repo
206 repo = op.repo
206 cl = repo.changelog
207 cl = repo.changelog
207
208
208 # changesets which need to be stripped entirely. either they're no longer
209 # changesets which need to be stripped entirely. either they're no longer
209 # needed in the new narrow spec, or the server is sending a replacement
210 # needed in the new narrow spec, or the server is sending a replacement
210 # in the changegroup part.
211 # in the changegroup part.
211 clkills = set()
212 clkills = set()
212
213
213 # A changespec part contains all the updates to ellipsis nodes
214 # A changespec part contains all the updates to ellipsis nodes
214 # that will happen as a result of widening or narrowing a
215 # that will happen as a result of widening or narrowing a
215 # repo. All the changes that this block encounters are ellipsis
216 # repo. All the changes that this block encounters are ellipsis
216 # nodes or flags to kill an existing ellipsis.
217 # nodes or flags to kill an existing ellipsis.
217 chunksignal = changegroup.readexactly(inpart, 4)
218 chunksignal = changegroup.readexactly(inpart, 4)
218 while chunksignal != _DONESIGNAL:
219 while chunksignal != _DONESIGNAL:
219 if chunksignal == _KILLNODESIGNAL:
220 if chunksignal == _KILLNODESIGNAL:
220 # a node used to be an ellipsis but isn't anymore
221 # a node used to be an ellipsis but isn't anymore
221 ck = changegroup.readexactly(inpart, 20)
222 ck = changegroup.readexactly(inpart, 20)
222 if cl.hasnode(ck):
223 if cl.hasnode(ck):
223 clkills.add(ck)
224 clkills.add(ck)
224 else:
225 else:
225 raise error.Abort(
226 raise error.Abort(
226 _('unexpected changespec node chunk type: %s') % chunksignal)
227 _('unexpected changespec node chunk type: %s') % chunksignal)
227 chunksignal = changegroup.readexactly(inpart, 4)
228 chunksignal = changegroup.readexactly(inpart, 4)
228
229
229 if clkills:
230 if clkills:
230 # preserve bookmarks that repair.strip() would otherwise strip
231 # preserve bookmarks that repair.strip() would otherwise strip
231 bmstore = repo._bookmarks
232 bmstore = repo._bookmarks
232 class dummybmstore(dict):
233 class dummybmstore(dict):
233 def applychanges(self, repo, tr, changes):
234 def applychanges(self, repo, tr, changes):
234 pass
235 pass
235 def recordchange(self, tr): # legacy version
236 def recordchange(self, tr): # legacy version
236 pass
237 pass
237 repo._bookmarks = dummybmstore()
238 repo._bookmarks = dummybmstore()
238 chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
239 chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
239 topic='widen')
240 topic='widen')
240 repo._bookmarks = bmstore
241 repo._bookmarks = bmstore
241 if chgrpfile:
242 if chgrpfile:
242 op._widen_uninterr = repo.ui.uninterruptable()
243 op._widen_uninterr = repo.ui.uninterruptable()
243 op._widen_uninterr.__enter__()
244 op._widen_uninterr.__enter__()
244 # presence of _widen_bundle attribute activates widen handler later
245 # presence of _widen_bundle attribute activates widen handler later
245 op._widen_bundle = chgrpfile
246 op._widen_bundle = chgrpfile
246 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
247 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
247 # will currently always be there when using the core+narrowhg server, but
248 # will currently always be there when using the core+narrowhg server, but
248 # other servers may include a changespec part even when not widening (e.g.
249 # other servers may include a changespec part even when not widening (e.g.
249 # because we're deepening a shallow repo).
250 # because we're deepening a shallow repo).
250 if util.safehasattr(repo, 'setnewnarrowpats'):
251 if util.safehasattr(repo, 'setnewnarrowpats'):
251 repo.setnewnarrowpats()
252 repo.setnewnarrowpats()
252
253
253 def handlechangegroup_widen(op, inpart):
254 def handlechangegroup_widen(op, inpart):
254 """Changegroup exchange handler which restores temporarily-stripped nodes"""
255 """Changegroup exchange handler which restores temporarily-stripped nodes"""
255 # We saved a bundle with stripped node data we must now restore.
256 # We saved a bundle with stripped node data we must now restore.
256 # This approach is based on mercurial/repair.py@6ee26a53c111.
257 # This approach is based on mercurial/repair.py@6ee26a53c111.
257 repo = op.repo
258 repo = op.repo
258 ui = op.ui
259 ui = op.ui
259
260
260 chgrpfile = op._widen_bundle
261 chgrpfile = op._widen_bundle
261 del op._widen_bundle
262 del op._widen_bundle
262 vfs = repo.vfs
263 vfs = repo.vfs
263
264
264 ui.note(_("adding branch\n"))
265 ui.note(_("adding branch\n"))
265 f = vfs.open(chgrpfile, "rb")
266 f = vfs.open(chgrpfile, "rb")
266 try:
267 try:
267 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
268 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
268 if not ui.verbose:
269 if not ui.verbose:
269 # silence internal shuffling chatter
270 # silence internal shuffling chatter
270 ui.pushbuffer()
271 ui.pushbuffer()
271 if isinstance(gen, bundle2.unbundle20):
272 if isinstance(gen, bundle2.unbundle20):
272 with repo.transaction('strip') as tr:
273 with repo.transaction('strip') as tr:
273 bundle2.processbundle(repo, gen, lambda: tr)
274 bundle2.processbundle(repo, gen, lambda: tr)
274 else:
275 else:
275 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
276 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
276 if not ui.verbose:
277 if not ui.verbose:
277 ui.popbuffer()
278 ui.popbuffer()
278 finally:
279 finally:
279 f.close()
280 f.close()
280
281
281 # remove undo files
282 # remove undo files
282 for undovfs, undofile in repo.undofiles():
283 for undovfs, undofile in repo.undofiles():
283 try:
284 try:
284 undovfs.unlink(undofile)
285 undovfs.unlink(undofile)
285 except OSError as e:
286 except OSError as e:
286 if e.errno != errno.ENOENT:
287 if e.errno != errno.ENOENT:
287 ui.warn(_('error removing %s: %s\n') %
288 ui.warn(_('error removing %s: %s\n') %
288 (undovfs.join(undofile), stringutil.forcebytestr(e)))
289 (undovfs.join(undofile), stringutil.forcebytestr(e)))
289
290
290 # Remove partial backup only if there were no exceptions
291 # Remove partial backup only if there were no exceptions
291 op._widen_uninterr.__exit__(None, None, None)
292 op._widen_uninterr.__exit__(None, None, None)
292 vfs.unlink(chgrpfile)
293 vfs.unlink(chgrpfile)
293
294
294 def setup():
295 def setup():
295 """Enable narrow repo support in bundle2-related extension points."""
296 """Enable narrow repo support in bundle2-related extension points."""
296 extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
297 extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
297
298
298 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
299 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
299
300
300 getbundleargs['narrow'] = 'boolean'
301 getbundleargs['narrow'] = 'boolean'
301 getbundleargs['widen'] = 'boolean'
302 getbundleargs['widen'] = 'boolean'
302 getbundleargs['depth'] = 'plain'
303 getbundleargs['depth'] = 'plain'
303 getbundleargs['oldincludepats'] = 'csv'
304 getbundleargs['oldincludepats'] = 'csv'
304 getbundleargs['oldexcludepats'] = 'csv'
305 getbundleargs['oldexcludepats'] = 'csv'
305 getbundleargs['includepats'] = 'csv'
306 getbundleargs['includepats'] = 'csv'
306 getbundleargs['excludepats'] = 'csv'
307 getbundleargs['excludepats'] = 'csv'
307 getbundleargs['known'] = 'csv'
308 getbundleargs['known'] = 'csv'
308
309
309 # Extend changegroup serving to handle requests from narrow clients.
310 # Extend changegroup serving to handle requests from narrow clients.
310 origcgfn = exchange.getbundle2partsmapping['changegroup']
311 origcgfn = exchange.getbundle2partsmapping['changegroup']
311 def wrappedcgfn(*args, **kwargs):
312 def wrappedcgfn(*args, **kwargs):
312 repo = args[1]
313 repo = args[1]
313 if repo.ui.has_section(_NARROWACL_SECTION):
314 if repo.ui.has_section(_NARROWACL_SECTION):
314 kwargs = exchange.applynarrowacl(repo, kwargs)
315 kwargs = exchange.applynarrowacl(repo, kwargs)
315
316
316 if (kwargs.get(r'narrow', False) and
317 if (kwargs.get(r'narrow', False) and
317 repo.ui.configbool('experimental', 'narrowservebrokenellipses')):
318 repo.ui.configbool('experimental', 'narrowservebrokenellipses')):
318 getbundlechangegrouppart_narrow(*args, **kwargs)
319 getbundlechangegrouppart_narrow(*args, **kwargs)
319 elif kwargs.get(r'widen', False) and kwargs.get(r'narrow', False):
320 elif kwargs.get(r'widen', False) and kwargs.get(r'narrow', False):
320 getbundlechangegrouppart_widen(*args, **kwargs)
321 getbundlechangegrouppart_widen(*args, **kwargs)
321 else:
322 else:
322 origcgfn(*args, **kwargs)
323 origcgfn(*args, **kwargs)
323 exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
324 exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
324
325
325 # Extend changegroup receiver so client can fixup after widen requests.
326 # Extend changegroup receiver so client can fixup after widen requests.
326 origcghandler = bundle2.parthandlermapping['changegroup']
327 origcghandler = bundle2.parthandlermapping['changegroup']
327 def wrappedcghandler(op, inpart):
328 def wrappedcghandler(op, inpart):
328 origcghandler(op, inpart)
329 origcghandler(op, inpart)
329 if util.safehasattr(op, '_widen_bundle'):
330 if util.safehasattr(op, '_widen_bundle'):
330 handlechangegroup_widen(op, inpart)
331 handlechangegroup_widen(op, inpart)
331 wrappedcghandler.params = origcghandler.params
332 wrappedcghandler.params = origcghandler.params
332 bundle2.parthandlermapping['changegroup'] = wrappedcghandler
333 bundle2.parthandlermapping['changegroup'] = wrappedcghandler
@@ -1,52 +1,65 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.i18n import _
10 from mercurial.i18n import _
11 from mercurial import (
11 from mercurial import (
12 error,
12 error,
13 extensions,
13 extensions,
14 hg,
14 hg,
15 narrowspec,
15 narrowspec,
16 node,
16 node,
17 wireprotov1server,
17 )
18 )
18
19
20 from . import narrowbundle2
21
19 def uisetup():
22 def uisetup():
20 def peersetup(ui, peer):
23 def peersetup(ui, peer):
21 # We must set up the expansion before reposetup below, since it's used
24 # We must set up the expansion before reposetup below, since it's used
22 # at clone time before we have a repo.
25 # at clone time before we have a repo.
23 class expandingpeer(peer.__class__):
26 class expandingpeer(peer.__class__):
24 def expandnarrow(self, narrow_include, narrow_exclude, nodes):
27 def expandnarrow(self, narrow_include, narrow_exclude, nodes):
25 ui.status(_("expanding narrowspec\n"))
28 ui.status(_("expanding narrowspec\n"))
26 if not self.capable('exp-expandnarrow'):
29 if not self.capable('exp-expandnarrow'):
27 raise error.Abort(
30 raise error.Abort(
28 'peer does not support expanding narrowspecs')
31 'peer does not support expanding narrowspecs')
29
32
30 hex_nodes = (node.hex(n) for n in nodes)
33 hex_nodes = (node.hex(n) for n in nodes)
31 new_narrowspec = self._call(
34 new_narrowspec = self._call(
32 'expandnarrow',
35 'expandnarrow',
33 includepats=','.join(narrow_include),
36 includepats=','.join(narrow_include),
34 excludepats=','.join(narrow_exclude),
37 excludepats=','.join(narrow_exclude),
35 nodes=','.join(hex_nodes))
38 nodes=','.join(hex_nodes))
36
39
37 return narrowspec.parseserverpatterns(new_narrowspec)
40 return narrowspec.parseserverpatterns(new_narrowspec)
38 peer.__class__ = expandingpeer
41 peer.__class__ = expandingpeer
39 hg.wirepeersetupfuncs.append(peersetup)
42 hg.wirepeersetupfuncs.append(peersetup)
40
43
44 extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
45
46 def addnarrowcap(orig, repo, proto):
47 """add the narrow capability to the server"""
48 caps = orig(repo, proto)
49 caps.append(narrowbundle2.NARROWCAP)
50 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
51 caps.append(narrowbundle2.ELLIPSESCAP)
52 return caps
53
41 def reposetup(repo):
54 def reposetup(repo):
42 def wirereposetup(ui, peer):
55 def wirereposetup(ui, peer):
43 def wrapped(orig, cmd, *args, **kwargs):
56 def wrapped(orig, cmd, *args, **kwargs):
44 if cmd == 'unbundle':
57 if cmd == 'unbundle':
45 # TODO: don't blindly add include/exclude wireproto
58 # TODO: don't blindly add include/exclude wireproto
46 # arguments to unbundle.
59 # arguments to unbundle.
47 include, exclude = repo.narrowpats
60 include, exclude = repo.narrowpats
48 kwargs[r"includepats"] = ','.join(include)
61 kwargs[r"includepats"] = ','.join(include)
49 kwargs[r"excludepats"] = ','.join(exclude)
62 kwargs[r"excludepats"] = ','.join(exclude)
50 return orig(cmd, *args, **kwargs)
63 return orig(cmd, *args, **kwargs)
51 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
64 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
52 hg.wirepeersetupfuncs.append(wirereposetup)
65 hg.wirepeersetupfuncs.append(wirereposetup)
@@ -1,65 +1,66 b''
1 Test attempting a narrow clone against a server that doesn't support narrowhg.
1 Test attempting a narrow clone against a server that doesn't support narrowhg.
2
2
3 $ . "$TESTDIR/narrow-library.sh"
3 $ . "$TESTDIR/narrow-library.sh"
4
4
5 $ hg init master
5 $ hg init master
6 $ cd master
6 $ cd master
7
7
8 $ for x in `$TESTDIR/seq.py 10`; do
8 $ for x in `$TESTDIR/seq.py 10`; do
9 > echo $x > "f$x"
9 > echo $x > "f$x"
10 > hg add "f$x"
10 > hg add "f$x"
11 > hg commit -m "Add $x"
11 > hg commit -m "Add $x"
12 > done
12 > done
13
13
14 $ hg serve -a localhost -p $HGPORT1 --config extensions.narrow=! -d \
14 $ hg serve -a localhost -p $HGPORT1 --config extensions.narrow=! -d \
15 > --pid-file=hg.pid
15 > --pid-file=hg.pid
16 $ cat hg.pid >> "$DAEMON_PIDS"
16 $ cat hg.pid >> "$DAEMON_PIDS"
17 $ hg serve -a localhost -p $HGPORT2 -d --pid-file=hg.pid
17 $ hg serve -a localhost -p $HGPORT2 -d --pid-file=hg.pid
18 $ cat hg.pid >> "$DAEMON_PIDS"
18 $ cat hg.pid >> "$DAEMON_PIDS"
19
19
20 Verify that narrow is advertised in the bundle2 capabilities:
20 Verify that narrow is advertised in the bundle2 capabilities:
21
21
22 $ cat >> unquote.py <<EOF
22 $ cat >> unquote.py <<EOF
23 > from __future__ import print_function
23 > from __future__ import print_function
24 > import sys
24 > import sys
25 > if sys.version[0] == '3':
25 > if sys.version[0] == '3':
26 > import urllib.parse as up
26 > import urllib.parse as up
27 > unquote = up.unquote_plus
27 > unquote = up.unquote_plus
28 > else:
28 > else:
29 > import urllib
29 > import urllib
30 > unquote = urllib.unquote_plus
30 > unquote = urllib.unquote_plus
31 > print(unquote(list(sys.stdin)[1]))
31 > print(unquote(list(sys.stdin)[1]))
32 > EOF
32 > EOF
33 $ echo hello | hg -R . serve --stdio | \
33 $ echo hello | hg -R . serve --stdio | \
34 > $PYTHON unquote.py | grep narrow
34 > $PYTHON unquote.py | grep narrow
35 narrow=v0
35 narrow=v0
36 rev-branch-cache changegroupsubset getbundle known lookup narrow protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
36
37
37 $ cd ..
38 $ cd ..
38
39
39 $ hg clone --narrow --include f1 http://localhost:$HGPORT1/ narrowclone
40 $ hg clone --narrow --include f1 http://localhost:$HGPORT1/ narrowclone
40 requesting all changes
41 requesting all changes
41 abort: server doesn't support narrow clones
42 abort: server doesn't support narrow clones
42 [255]
43 [255]
43
44
44 Make a narrow clone (via HGPORT2), then try to narrow and widen
45 Make a narrow clone (via HGPORT2), then try to narrow and widen
45 into it (from HGPORT1) to prove that narrowing is fine and widening fails
46 into it (from HGPORT1) to prove that narrowing is fine and widening fails
46 gracefully:
47 gracefully:
47 $ hg clone -r 0 --narrow --include f1 http://localhost:$HGPORT2/ narrowclone
48 $ hg clone -r 0 --narrow --include f1 http://localhost:$HGPORT2/ narrowclone
48 adding changesets
49 adding changesets
49 adding manifests
50 adding manifests
50 adding file changes
51 adding file changes
51 added 1 changesets with 1 changes to 1 files
52 added 1 changesets with 1 changes to 1 files
52 new changesets * (glob)
53 new changesets * (glob)
53 updating to branch default
54 updating to branch default
54 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
55 $ cd narrowclone
56 $ cd narrowclone
56 $ hg tracked --addexclude f2 http://localhost:$HGPORT1/
57 $ hg tracked --addexclude f2 http://localhost:$HGPORT1/
57 comparing with http://localhost:$HGPORT1/
58 comparing with http://localhost:$HGPORT1/
58 searching for changes
59 searching for changes
59 looking for local changes to affected paths
60 looking for local changes to affected paths
60 $ hg tracked --addinclude f1 http://localhost:$HGPORT1/
61 $ hg tracked --addinclude f1 http://localhost:$HGPORT1/
61 comparing with http://localhost:$HGPORT1/
62 comparing with http://localhost:$HGPORT1/
62 searching for changes
63 searching for changes
63 no changes found
64 no changes found
64 abort: server doesn't support narrow clones
65 abort: server doesn't support narrow clones
65 [255]
66 [255]
General Comments 0
You need to be logged in to leave comments. Login now