##// END OF EJS Templates
narrow: move wire proto capabilities to narrowwirepeer...
Martin von Zweigbergk -
r39565:e82da0fc default
parent child Browse files
Show More
@@ -1,333 +1,332 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'
36 _NARROWACL_SECTION = 'narrowhgacl'
35 _NARROWACL_SECTION = 'narrowhgacl'
37 _CHANGESPECPART = NARROWCAP + ':changespec'
36 _CHANGESPECPART = NARROWCAP + ':changespec'
38 _SPECPART = NARROWCAP + ':spec'
37 _SPECPART = NARROWCAP + ':spec'
39 _SPECPART_INCLUDE = 'include'
38 _SPECPART_INCLUDE = 'include'
40 _SPECPART_EXCLUDE = 'exclude'
39 _SPECPART_EXCLUDE = 'exclude'
41 _KILLNODESIGNAL = 'KILL'
40 _KILLNODESIGNAL = 'KILL'
42 _DONESIGNAL = 'DONE'
41 _DONESIGNAL = 'DONE'
43 _ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text)
42 _ELIDEDCSHEADER = '>20s20s20sl' # cset id, p1, p2, len(text)
44 _ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
43 _ELIDEDMFHEADER = '>20s20s20s20sl' # manifest id, p1, p2, link id, len(text)
45 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
44 _CSHEADERSIZE = struct.calcsize(_ELIDEDCSHEADER)
46 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
45 _MFHEADERSIZE = struct.calcsize(_ELIDEDMFHEADER)
47
46
48 # When advertising capabilities, always include narrow clone support.
47 # When advertising capabilities, always include narrow clone support.
49 def getrepocaps_narrow(orig, repo, **kwargs):
48 def getrepocaps_narrow(orig, repo, **kwargs):
50 caps = orig(repo, **kwargs)
49 caps = orig(repo, **kwargs)
51 caps[NARROWCAP] = ['v0']
50 caps[NARROWCAP] = ['v0']
52 return caps
51 return caps
53
52
54 def getbundlechangegrouppart_widen(bundler, repo, source, bundlecaps=None,
53 def getbundlechangegrouppart_widen(bundler, repo, source, bundlecaps=None,
55 b2caps=None, heads=None, common=None,
54 b2caps=None, heads=None, common=None,
56 **kwargs):
55 **kwargs):
57 """Handling changegroup changegroup generation on the server when user
56 """Handling changegroup changegroup generation on the server when user
58 is widening their narrowspec"""
57 is widening their narrowspec"""
59
58
60 cgversions = b2caps.get('changegroup')
59 cgversions = b2caps.get('changegroup')
61 if cgversions: # 3.1 and 3.2 ship with an empty value
60 if cgversions: # 3.1 and 3.2 ship with an empty value
62 cgversions = [v for v in cgversions
61 cgversions = [v for v in cgversions
63 if v in changegroup.supportedoutgoingversions(repo)]
62 if v in changegroup.supportedoutgoingversions(repo)]
64 if not cgversions:
63 if not cgversions:
65 raise ValueError(_('no common changegroup version'))
64 raise ValueError(_('no common changegroup version'))
66 version = max(cgversions)
65 version = max(cgversions)
67 else:
66 else:
68 raise ValueError(_("server does not advertise changegroup version,"
67 raise ValueError(_("server does not advertise changegroup version,"
69 " can't negotiate support for ellipsis nodes"))
68 " can't negotiate support for ellipsis nodes"))
70
69
71 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
70 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
72 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
71 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
73 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
72 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
74 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
73 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
75 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
74 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
76 common = set(common or [nullid])
75 common = set(common or [nullid])
77
76
78 if (oldinclude != include or oldexclude != exclude):
77 if (oldinclude != include or oldexclude != exclude):
79 common = repo.revs("::%ln", common)
78 common = repo.revs("::%ln", common)
80 commonnodes = set()
79 commonnodes = set()
81 cl = repo.changelog
80 cl = repo.changelog
82 for c in common:
81 for c in common:
83 commonnodes.add(cl.node(c))
82 commonnodes.add(cl.node(c))
84 if commonnodes:
83 if commonnodes:
85 # XXX: we should only send the filelogs (and treemanifest). user
84 # XXX: we should only send the filelogs (and treemanifest). user
86 # already has the changelog and manifest
85 # already has the changelog and manifest
87 packer = changegroup.getbundler(version, repo,
86 packer = changegroup.getbundler(version, repo,
88 filematcher=newmatch,
87 filematcher=newmatch,
89 fullnodes=commonnodes)
88 fullnodes=commonnodes)
90 cgdata = packer.generate(set([nullid]), list(commonnodes), False,
89 cgdata = packer.generate(set([nullid]), list(commonnodes), False,
91 source)
90 source)
92
91
93 part = bundler.newpart('changegroup', data=cgdata)
92 part = bundler.newpart('changegroup', data=cgdata)
94 part.addparam('version', version)
93 part.addparam('version', version)
95 if 'treemanifest' in repo.requirements:
94 if 'treemanifest' in repo.requirements:
96 part.addparam('treemanifest', '1')
95 part.addparam('treemanifest', '1')
97
96
98 # Serve a changegroup for a client with a narrow clone.
97 # Serve a changegroup for a client with a narrow clone.
99 def getbundlechangegrouppart_narrow(bundler, repo, source,
98 def getbundlechangegrouppart_narrow(bundler, repo, source,
100 bundlecaps=None, b2caps=None, heads=None,
99 bundlecaps=None, b2caps=None, heads=None,
101 common=None, **kwargs):
100 common=None, **kwargs):
102 assert repo.ui.configbool('experimental', 'narrowservebrokenellipses')
101 assert repo.ui.configbool('experimental', 'narrowservebrokenellipses')
103
102
104 cgversions = b2caps.get('changegroup')
103 cgversions = b2caps.get('changegroup')
105 if cgversions: # 3.1 and 3.2 ship with an empty value
104 if cgversions: # 3.1 and 3.2 ship with an empty value
106 cgversions = [v for v in cgversions
105 cgversions = [v for v in cgversions
107 if v in changegroup.supportedoutgoingversions(repo)]
106 if v in changegroup.supportedoutgoingversions(repo)]
108 if not cgversions:
107 if not cgversions:
109 raise ValueError(_('no common changegroup version'))
108 raise ValueError(_('no common changegroup version'))
110 version = max(cgversions)
109 version = max(cgversions)
111 else:
110 else:
112 raise ValueError(_("server does not advertise changegroup version,"
111 raise ValueError(_("server does not advertise changegroup version,"
113 " can't negotiate support for ellipsis nodes"))
112 " can't negotiate support for ellipsis nodes"))
114
113
115 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
114 include = sorted(filter(bool, kwargs.get(r'includepats', [])))
116 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
115 exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
117 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
116 newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
118
117
119 depth = kwargs.get(r'depth', None)
118 depth = kwargs.get(r'depth', None)
120 if depth is not None:
119 if depth is not None:
121 depth = int(depth)
120 depth = int(depth)
122 if depth < 1:
121 if depth < 1:
123 raise error.Abort(_('depth must be positive, got %d') % depth)
122 raise error.Abort(_('depth must be positive, got %d') % depth)
124
123
125 heads = set(heads or repo.heads())
124 heads = set(heads or repo.heads())
126 common = set(common or [nullid])
125 common = set(common or [nullid])
127 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
126 oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
128 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
127 oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
129 known = {bin(n) for n in kwargs.get(r'known', [])}
128 known = {bin(n) for n in kwargs.get(r'known', [])}
130 if known and (oldinclude != include or oldexclude != exclude):
129 if known and (oldinclude != include or oldexclude != exclude):
131 # Steps:
130 # Steps:
132 # 1. Send kill for "$known & ::common"
131 # 1. Send kill for "$known & ::common"
133 #
132 #
134 # 2. Send changegroup for ::common
133 # 2. Send changegroup for ::common
135 #
134 #
136 # 3. Proceed.
135 # 3. Proceed.
137 #
136 #
138 # In the future, we can send kills for only the specific
137 # In the future, we can send kills for only the specific
139 # nodes we know should go away or change shape, and then
138 # nodes we know should go away or change shape, and then
140 # send a data stream that tells the client something like this:
139 # send a data stream that tells the client something like this:
141 #
140 #
142 # a) apply this changegroup
141 # a) apply this changegroup
143 # b) apply nodes XXX, YYY, ZZZ that you already have
142 # b) apply nodes XXX, YYY, ZZZ that you already have
144 # c) goto a
143 # c) goto a
145 #
144 #
146 # until they've built up the full new state.
145 # until they've built up the full new state.
147 # Convert to revnums and intersect with "common". The client should
146 # Convert to revnums and intersect with "common". The client should
148 # have made it a subset of "common" already, but let's be safe.
147 # have made it a subset of "common" already, but let's be safe.
149 known = set(repo.revs("%ln & ::%ln", known, common))
148 known = set(repo.revs("%ln & ::%ln", known, common))
150 # TODO: we could send only roots() of this set, and the
149 # TODO: we could send only roots() of this set, and the
151 # list of nodes in common, and the client could work out
150 # list of nodes in common, and the client could work out
152 # what to strip, instead of us explicitly sending every
151 # what to strip, instead of us explicitly sending every
153 # single node.
152 # single node.
154 deadrevs = known
153 deadrevs = known
155 def genkills():
154 def genkills():
156 for r in deadrevs:
155 for r in deadrevs:
157 yield _KILLNODESIGNAL
156 yield _KILLNODESIGNAL
158 yield repo.changelog.node(r)
157 yield repo.changelog.node(r)
159 yield _DONESIGNAL
158 yield _DONESIGNAL
160 bundler.newpart(_CHANGESPECPART, data=genkills())
159 bundler.newpart(_CHANGESPECPART, data=genkills())
161 newvisit, newfull, newellipsis = exchange._computeellipsis(
160 newvisit, newfull, newellipsis = exchange._computeellipsis(
162 repo, set(), common, known, newmatch)
161 repo, set(), common, known, newmatch)
163 if newvisit:
162 if newvisit:
164 packer = changegroup.getbundler(version, repo,
163 packer = changegroup.getbundler(version, repo,
165 filematcher=newmatch,
164 filematcher=newmatch,
166 ellipses=True,
165 ellipses=True,
167 shallow=depth is not None,
166 shallow=depth is not None,
168 ellipsisroots=newellipsis,
167 ellipsisroots=newellipsis,
169 fullnodes=newfull)
168 fullnodes=newfull)
170 cgdata = packer.generate(common, newvisit, False, source)
169 cgdata = packer.generate(common, newvisit, False, source)
171
170
172 part = bundler.newpart('changegroup', data=cgdata)
171 part = bundler.newpart('changegroup', data=cgdata)
173 part.addparam('version', version)
172 part.addparam('version', version)
174 if 'treemanifest' in repo.requirements:
173 if 'treemanifest' in repo.requirements:
175 part.addparam('treemanifest', '1')
174 part.addparam('treemanifest', '1')
176
175
177 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
176 visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
178 repo, common, heads, set(), newmatch, depth=depth)
177 repo, common, heads, set(), newmatch, depth=depth)
179
178
180 repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
179 repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
181 if visitnodes:
180 if visitnodes:
182 packer = changegroup.getbundler(version, repo,
181 packer = changegroup.getbundler(version, repo,
183 filematcher=newmatch,
182 filematcher=newmatch,
184 ellipses=True,
183 ellipses=True,
185 shallow=depth is not None,
184 shallow=depth is not None,
186 ellipsisroots=ellipsisroots,
185 ellipsisroots=ellipsisroots,
187 fullnodes=relevant_nodes)
186 fullnodes=relevant_nodes)
188 cgdata = packer.generate(common, visitnodes, False, source)
187 cgdata = packer.generate(common, visitnodes, False, source)
189
188
190 part = bundler.newpart('changegroup', data=cgdata)
189 part = bundler.newpart('changegroup', data=cgdata)
191 part.addparam('version', version)
190 part.addparam('version', version)
192 if 'treemanifest' in repo.requirements:
191 if 'treemanifest' in repo.requirements:
193 part.addparam('treemanifest', '1')
192 part.addparam('treemanifest', '1')
194
193
195 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
194 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
196 def _handlechangespec_2(op, inpart):
195 def _handlechangespec_2(op, inpart):
197 includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
196 includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
198 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
197 excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
199 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
198 if not repository.NARROW_REQUIREMENT in op.repo.requirements:
200 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
199 op.repo.requirements.add(repository.NARROW_REQUIREMENT)
201 op.repo._writerequirements()
200 op.repo._writerequirements()
202 op.repo.setnarrowpats(includepats, excludepats)
201 op.repo.setnarrowpats(includepats, excludepats)
203
202
204 @bundle2.parthandler(_CHANGESPECPART)
203 @bundle2.parthandler(_CHANGESPECPART)
205 def _handlechangespec(op, inpart):
204 def _handlechangespec(op, inpart):
206 repo = op.repo
205 repo = op.repo
207 cl = repo.changelog
206 cl = repo.changelog
208
207
209 # changesets which need to be stripped entirely. either they're no longer
208 # changesets which need to be stripped entirely. either they're no longer
210 # needed in the new narrow spec, or the server is sending a replacement
209 # needed in the new narrow spec, or the server is sending a replacement
211 # in the changegroup part.
210 # in the changegroup part.
212 clkills = set()
211 clkills = set()
213
212
214 # A changespec part contains all the updates to ellipsis nodes
213 # A changespec part contains all the updates to ellipsis nodes
215 # that will happen as a result of widening or narrowing a
214 # that will happen as a result of widening or narrowing a
216 # repo. All the changes that this block encounters are ellipsis
215 # repo. All the changes that this block encounters are ellipsis
217 # nodes or flags to kill an existing ellipsis.
216 # nodes or flags to kill an existing ellipsis.
218 chunksignal = changegroup.readexactly(inpart, 4)
217 chunksignal = changegroup.readexactly(inpart, 4)
219 while chunksignal != _DONESIGNAL:
218 while chunksignal != _DONESIGNAL:
220 if chunksignal == _KILLNODESIGNAL:
219 if chunksignal == _KILLNODESIGNAL:
221 # a node used to be an ellipsis but isn't anymore
220 # a node used to be an ellipsis but isn't anymore
222 ck = changegroup.readexactly(inpart, 20)
221 ck = changegroup.readexactly(inpart, 20)
223 if cl.hasnode(ck):
222 if cl.hasnode(ck):
224 clkills.add(ck)
223 clkills.add(ck)
225 else:
224 else:
226 raise error.Abort(
225 raise error.Abort(
227 _('unexpected changespec node chunk type: %s') % chunksignal)
226 _('unexpected changespec node chunk type: %s') % chunksignal)
228 chunksignal = changegroup.readexactly(inpart, 4)
227 chunksignal = changegroup.readexactly(inpart, 4)
229
228
230 if clkills:
229 if clkills:
231 # preserve bookmarks that repair.strip() would otherwise strip
230 # preserve bookmarks that repair.strip() would otherwise strip
232 bmstore = repo._bookmarks
231 bmstore = repo._bookmarks
233 class dummybmstore(dict):
232 class dummybmstore(dict):
234 def applychanges(self, repo, tr, changes):
233 def applychanges(self, repo, tr, changes):
235 pass
234 pass
236 def recordchange(self, tr): # legacy version
235 def recordchange(self, tr): # legacy version
237 pass
236 pass
238 repo._bookmarks = dummybmstore()
237 repo._bookmarks = dummybmstore()
239 chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
238 chgrpfile = repair.strip(op.ui, repo, list(clkills), backup=True,
240 topic='widen')
239 topic='widen')
241 repo._bookmarks = bmstore
240 repo._bookmarks = bmstore
242 if chgrpfile:
241 if chgrpfile:
243 op._widen_uninterr = repo.ui.uninterruptable()
242 op._widen_uninterr = repo.ui.uninterruptable()
244 op._widen_uninterr.__enter__()
243 op._widen_uninterr.__enter__()
245 # presence of _widen_bundle attribute activates widen handler later
244 # presence of _widen_bundle attribute activates widen handler later
246 op._widen_bundle = chgrpfile
245 op._widen_bundle = chgrpfile
247 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
246 # Set the new narrowspec if we're widening. The setnewnarrowpats() method
248 # will currently always be there when using the core+narrowhg server, but
247 # will currently always be there when using the core+narrowhg server, but
249 # other servers may include a changespec part even when not widening (e.g.
248 # other servers may include a changespec part even when not widening (e.g.
250 # because we're deepening a shallow repo).
249 # because we're deepening a shallow repo).
251 if util.safehasattr(repo, 'setnewnarrowpats'):
250 if util.safehasattr(repo, 'setnewnarrowpats'):
252 repo.setnewnarrowpats()
251 repo.setnewnarrowpats()
253
252
254 def handlechangegroup_widen(op, inpart):
253 def handlechangegroup_widen(op, inpart):
255 """Changegroup exchange handler which restores temporarily-stripped nodes"""
254 """Changegroup exchange handler which restores temporarily-stripped nodes"""
256 # We saved a bundle with stripped node data we must now restore.
255 # We saved a bundle with stripped node data we must now restore.
257 # This approach is based on mercurial/repair.py@6ee26a53c111.
256 # This approach is based on mercurial/repair.py@6ee26a53c111.
258 repo = op.repo
257 repo = op.repo
259 ui = op.ui
258 ui = op.ui
260
259
261 chgrpfile = op._widen_bundle
260 chgrpfile = op._widen_bundle
262 del op._widen_bundle
261 del op._widen_bundle
263 vfs = repo.vfs
262 vfs = repo.vfs
264
263
265 ui.note(_("adding branch\n"))
264 ui.note(_("adding branch\n"))
266 f = vfs.open(chgrpfile, "rb")
265 f = vfs.open(chgrpfile, "rb")
267 try:
266 try:
268 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
267 gen = exchange.readbundle(ui, f, chgrpfile, vfs)
269 if not ui.verbose:
268 if not ui.verbose:
270 # silence internal shuffling chatter
269 # silence internal shuffling chatter
271 ui.pushbuffer()
270 ui.pushbuffer()
272 if isinstance(gen, bundle2.unbundle20):
271 if isinstance(gen, bundle2.unbundle20):
273 with repo.transaction('strip') as tr:
272 with repo.transaction('strip') as tr:
274 bundle2.processbundle(repo, gen, lambda: tr)
273 bundle2.processbundle(repo, gen, lambda: tr)
275 else:
274 else:
276 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
275 gen.apply(repo, 'strip', 'bundle:' + vfs.join(chgrpfile), True)
277 if not ui.verbose:
276 if not ui.verbose:
278 ui.popbuffer()
277 ui.popbuffer()
279 finally:
278 finally:
280 f.close()
279 f.close()
281
280
282 # remove undo files
281 # remove undo files
283 for undovfs, undofile in repo.undofiles():
282 for undovfs, undofile in repo.undofiles():
284 try:
283 try:
285 undovfs.unlink(undofile)
284 undovfs.unlink(undofile)
286 except OSError as e:
285 except OSError as e:
287 if e.errno != errno.ENOENT:
286 if e.errno != errno.ENOENT:
288 ui.warn(_('error removing %s: %s\n') %
287 ui.warn(_('error removing %s: %s\n') %
289 (undovfs.join(undofile), stringutil.forcebytestr(e)))
288 (undovfs.join(undofile), stringutil.forcebytestr(e)))
290
289
291 # Remove partial backup only if there were no exceptions
290 # Remove partial backup only if there were no exceptions
292 op._widen_uninterr.__exit__(None, None, None)
291 op._widen_uninterr.__exit__(None, None, None)
293 vfs.unlink(chgrpfile)
292 vfs.unlink(chgrpfile)
294
293
295 def setup():
294 def setup():
296 """Enable narrow repo support in bundle2-related extension points."""
295 """Enable narrow repo support in bundle2-related extension points."""
297 extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
296 extensions.wrapfunction(bundle2, 'getrepocaps', getrepocaps_narrow)
298
297
299 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
298 getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
300
299
301 getbundleargs['narrow'] = 'boolean'
300 getbundleargs['narrow'] = 'boolean'
302 getbundleargs['widen'] = 'boolean'
301 getbundleargs['widen'] = 'boolean'
303 getbundleargs['depth'] = 'plain'
302 getbundleargs['depth'] = 'plain'
304 getbundleargs['oldincludepats'] = 'csv'
303 getbundleargs['oldincludepats'] = 'csv'
305 getbundleargs['oldexcludepats'] = 'csv'
304 getbundleargs['oldexcludepats'] = 'csv'
306 getbundleargs['includepats'] = 'csv'
305 getbundleargs['includepats'] = 'csv'
307 getbundleargs['excludepats'] = 'csv'
306 getbundleargs['excludepats'] = 'csv'
308 getbundleargs['known'] = 'csv'
307 getbundleargs['known'] = 'csv'
309
308
310 # Extend changegroup serving to handle requests from narrow clients.
309 # Extend changegroup serving to handle requests from narrow clients.
311 origcgfn = exchange.getbundle2partsmapping['changegroup']
310 origcgfn = exchange.getbundle2partsmapping['changegroup']
312 def wrappedcgfn(*args, **kwargs):
311 def wrappedcgfn(*args, **kwargs):
313 repo = args[1]
312 repo = args[1]
314 if repo.ui.has_section(_NARROWACL_SECTION):
313 if repo.ui.has_section(_NARROWACL_SECTION):
315 kwargs = exchange.applynarrowacl(repo, kwargs)
314 kwargs = exchange.applynarrowacl(repo, kwargs)
316
315
317 if (kwargs.get(r'narrow', False) and
316 if (kwargs.get(r'narrow', False) and
318 repo.ui.configbool('experimental', 'narrowservebrokenellipses')):
317 repo.ui.configbool('experimental', 'narrowservebrokenellipses')):
319 getbundlechangegrouppart_narrow(*args, **kwargs)
318 getbundlechangegrouppart_narrow(*args, **kwargs)
320 elif kwargs.get(r'widen', False) and kwargs.get(r'narrow', False):
319 elif kwargs.get(r'widen', False) and kwargs.get(r'narrow', False):
321 getbundlechangegrouppart_widen(*args, **kwargs)
320 getbundlechangegrouppart_widen(*args, **kwargs)
322 else:
321 else:
323 origcgfn(*args, **kwargs)
322 origcgfn(*args, **kwargs)
324 exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
323 exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
325
324
326 # Extend changegroup receiver so client can fixup after widen requests.
325 # Extend changegroup receiver so client can fixup after widen requests.
327 origcghandler = bundle2.parthandlermapping['changegroup']
326 origcghandler = bundle2.parthandlermapping['changegroup']
328 def wrappedcghandler(op, inpart):
327 def wrappedcghandler(op, inpart):
329 origcghandler(op, inpart)
328 origcghandler(op, inpart)
330 if util.safehasattr(op, '_widen_bundle'):
329 if util.safehasattr(op, '_widen_bundle'):
331 handlechangegroup_widen(op, inpart)
330 handlechangegroup_widen(op, inpart)
332 wrappedcghandler.params = origcghandler.params
331 wrappedcghandler.params = origcghandler.params
333 bundle2.parthandlermapping['changegroup'] = wrappedcghandler
332 bundle2.parthandlermapping['changegroup'] = wrappedcghandler
@@ -1,452 +1,452 b''
1 # narrowcommands.py - command modifications for narrowhg extension
1 # narrowcommands.py - command modifications for narrowhg extension
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 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import itertools
9 import itertools
10 import os
10 import os
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial import (
13 from mercurial import (
14 cmdutil,
14 cmdutil,
15 commands,
15 commands,
16 discovery,
16 discovery,
17 encoding,
17 encoding,
18 error,
18 error,
19 exchange,
19 exchange,
20 extensions,
20 extensions,
21 hg,
21 hg,
22 merge,
22 merge,
23 narrowspec,
23 narrowspec,
24 node,
24 node,
25 pycompat,
25 pycompat,
26 registrar,
26 registrar,
27 repair,
27 repair,
28 repository,
28 repository,
29 repoview,
29 repoview,
30 sparse,
30 sparse,
31 util,
31 util,
32 )
32 )
33
33
34 from . import (
34 from . import (
35 narrowbundle2,
35 narrowwirepeer,
36 )
36 )
37
37
38 table = {}
38 table = {}
39 command = registrar.command(table)
39 command = registrar.command(table)
40
40
41 def setup():
41 def setup():
42 """Wraps user-facing mercurial commands with narrow-aware versions."""
42 """Wraps user-facing mercurial commands with narrow-aware versions."""
43
43
44 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
44 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
45 entry[1].append(('', 'narrow', None,
45 entry[1].append(('', 'narrow', None,
46 _("create a narrow clone of select files")))
46 _("create a narrow clone of select files")))
47 entry[1].append(('', 'depth', '',
47 entry[1].append(('', 'depth', '',
48 _("limit the history fetched by distance from heads")))
48 _("limit the history fetched by distance from heads")))
49 entry[1].append(('', 'narrowspec', '',
49 entry[1].append(('', 'narrowspec', '',
50 _("read narrowspecs from file")))
50 _("read narrowspecs from file")))
51 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
51 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
52 if 'sparse' not in extensions.enabled():
52 if 'sparse' not in extensions.enabled():
53 entry[1].append(('', 'include', [],
53 entry[1].append(('', 'include', [],
54 _("specifically fetch this file/directory")))
54 _("specifically fetch this file/directory")))
55 entry[1].append(
55 entry[1].append(
56 ('', 'exclude', [],
56 ('', 'exclude', [],
57 _("do not fetch this file/directory, even if included")))
57 _("do not fetch this file/directory, even if included")))
58
58
59 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
59 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
60 entry[1].append(('', 'depth', '',
60 entry[1].append(('', 'depth', '',
61 _("limit the history fetched by distance from heads")))
61 _("limit the history fetched by distance from heads")))
62
62
63 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
63 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
64
64
65 def expandpull(pullop, includepats, excludepats):
65 def expandpull(pullop, includepats, excludepats):
66 if not narrowspec.needsexpansion(includepats):
66 if not narrowspec.needsexpansion(includepats):
67 return includepats, excludepats
67 return includepats, excludepats
68
68
69 heads = pullop.heads or pullop.rheads
69 heads = pullop.heads or pullop.rheads
70 includepats, excludepats = pullop.remote.expandnarrow(
70 includepats, excludepats = pullop.remote.expandnarrow(
71 includepats, excludepats, heads)
71 includepats, excludepats, heads)
72 pullop.repo.ui.debug('Expanded narrowspec to inc=%s, exc=%s\n' % (
72 pullop.repo.ui.debug('Expanded narrowspec to inc=%s, exc=%s\n' % (
73 includepats, excludepats))
73 includepats, excludepats))
74 return set(includepats), set(excludepats)
74 return set(includepats), set(excludepats)
75
75
76 def clonenarrowcmd(orig, ui, repo, *args, **opts):
76 def clonenarrowcmd(orig, ui, repo, *args, **opts):
77 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
77 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
78 opts = pycompat.byteskwargs(opts)
78 opts = pycompat.byteskwargs(opts)
79 wrappedextraprepare = util.nullcontextmanager()
79 wrappedextraprepare = util.nullcontextmanager()
80 opts_narrow = opts['narrow']
80 opts_narrow = opts['narrow']
81 narrowspecfile = opts['narrowspec']
81 narrowspecfile = opts['narrowspec']
82
82
83 if narrowspecfile:
83 if narrowspecfile:
84 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
84 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
85 ui.status(_("reading narrowspec from '%s'\n") % filepath)
85 ui.status(_("reading narrowspec from '%s'\n") % filepath)
86 try:
86 try:
87 fdata = util.readfile(filepath)
87 fdata = util.readfile(filepath)
88 except IOError as inst:
88 except IOError as inst:
89 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
89 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
90 (filepath, encoding.strtolocal(inst.strerror)))
90 (filepath, encoding.strtolocal(inst.strerror)))
91
91
92 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
92 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
93 if profiles:
93 if profiles:
94 raise error.Abort(_("cannot specify other files using '%include' in"
94 raise error.Abort(_("cannot specify other files using '%include' in"
95 " narrowspec"))
95 " narrowspec"))
96
96
97 # narrowspec is passed so we should assume that user wants narrow clone
97 # narrowspec is passed so we should assume that user wants narrow clone
98 opts_narrow = True
98 opts_narrow = True
99 opts['include'].extend(includes)
99 opts['include'].extend(includes)
100 opts['exclude'].extend(excludes)
100 opts['exclude'].extend(excludes)
101
101
102 if opts_narrow:
102 if opts_narrow:
103 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
103 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
104 # Create narrow spec patterns from clone flags
104 # Create narrow spec patterns from clone flags
105 includepats = narrowspec.parsepatterns(opts['include'])
105 includepats = narrowspec.parsepatterns(opts['include'])
106 excludepats = narrowspec.parsepatterns(opts['exclude'])
106 excludepats = narrowspec.parsepatterns(opts['exclude'])
107
107
108 # If necessary, ask the server to expand the narrowspec.
108 # If necessary, ask the server to expand the narrowspec.
109 includepats, excludepats = expandpull(
109 includepats, excludepats = expandpull(
110 pullop, includepats, excludepats)
110 pullop, includepats, excludepats)
111
111
112 if not includepats and excludepats:
112 if not includepats and excludepats:
113 # If nothing was included, we assume the user meant to include
113 # If nothing was included, we assume the user meant to include
114 # everything, except what they asked to exclude.
114 # everything, except what they asked to exclude.
115 includepats = {'path:.'}
115 includepats = {'path:.'}
116
116
117 pullop.repo.setnarrowpats(includepats, excludepats)
117 pullop.repo.setnarrowpats(includepats, excludepats)
118
118
119 # This will populate 'includepats' etc with the values from the
119 # This will populate 'includepats' etc with the values from the
120 # narrowspec we just saved.
120 # narrowspec we just saved.
121 orig(pullop, kwargs)
121 orig(pullop, kwargs)
122
122
123 if opts.get('depth'):
123 if opts.get('depth'):
124 kwargs['depth'] = opts['depth']
124 kwargs['depth'] = opts['depth']
125 wrappedextraprepare = extensions.wrappedfunction(exchange,
125 wrappedextraprepare = extensions.wrappedfunction(exchange,
126 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
126 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
127
127
128 def pullnarrow(orig, repo, *args, **kwargs):
128 def pullnarrow(orig, repo, *args, **kwargs):
129 if opts_narrow:
129 if opts_narrow:
130 repo.requirements.add(repository.NARROW_REQUIREMENT)
130 repo.requirements.add(repository.NARROW_REQUIREMENT)
131 repo._writerequirements()
131 repo._writerequirements()
132
132
133 return orig(repo, *args, **kwargs)
133 return orig(repo, *args, **kwargs)
134
134
135 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
135 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
136
136
137 with wrappedextraprepare, wrappedpull:
137 with wrappedextraprepare, wrappedpull:
138 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
138 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
139
139
140 def pullnarrowcmd(orig, ui, repo, *args, **opts):
140 def pullnarrowcmd(orig, ui, repo, *args, **opts):
141 """Wraps pull command to allow modifying narrow spec."""
141 """Wraps pull command to allow modifying narrow spec."""
142 wrappedextraprepare = util.nullcontextmanager()
142 wrappedextraprepare = util.nullcontextmanager()
143 if repository.NARROW_REQUIREMENT in repo.requirements:
143 if repository.NARROW_REQUIREMENT in repo.requirements:
144
144
145 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
145 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
146 orig(pullop, kwargs)
146 orig(pullop, kwargs)
147 if opts.get(r'depth'):
147 if opts.get(r'depth'):
148 kwargs['depth'] = opts[r'depth']
148 kwargs['depth'] = opts[r'depth']
149 wrappedextraprepare = extensions.wrappedfunction(exchange,
149 wrappedextraprepare = extensions.wrappedfunction(exchange,
150 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
150 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
151
151
152 with wrappedextraprepare:
152 with wrappedextraprepare:
153 return orig(ui, repo, *args, **opts)
153 return orig(ui, repo, *args, **opts)
154
154
155 def archivenarrowcmd(orig, ui, repo, *args, **opts):
155 def archivenarrowcmd(orig, ui, repo, *args, **opts):
156 """Wraps archive command to narrow the default includes."""
156 """Wraps archive command to narrow the default includes."""
157 if repository.NARROW_REQUIREMENT in repo.requirements:
157 if repository.NARROW_REQUIREMENT in repo.requirements:
158 repo_includes, repo_excludes = repo.narrowpats
158 repo_includes, repo_excludes = repo.narrowpats
159 includes = set(opts.get(r'include', []))
159 includes = set(opts.get(r'include', []))
160 excludes = set(opts.get(r'exclude', []))
160 excludes = set(opts.get(r'exclude', []))
161 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
161 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
162 includes, excludes, repo_includes, repo_excludes)
162 includes, excludes, repo_includes, repo_excludes)
163 if includes:
163 if includes:
164 opts[r'include'] = includes
164 opts[r'include'] = includes
165 if excludes:
165 if excludes:
166 opts[r'exclude'] = excludes
166 opts[r'exclude'] = excludes
167 return orig(ui, repo, *args, **opts)
167 return orig(ui, repo, *args, **opts)
168
168
169 def pullbundle2extraprepare(orig, pullop, kwargs):
169 def pullbundle2extraprepare(orig, pullop, kwargs):
170 repo = pullop.repo
170 repo = pullop.repo
171 if repository.NARROW_REQUIREMENT not in repo.requirements:
171 if repository.NARROW_REQUIREMENT not in repo.requirements:
172 return orig(pullop, kwargs)
172 return orig(pullop, kwargs)
173
173
174 if narrowbundle2.NARROWCAP not in pullop.remote.capabilities():
174 if narrowwirepeer.NARROWCAP not in pullop.remote.capabilities():
175 raise error.Abort(_("server doesn't support narrow clones"))
175 raise error.Abort(_("server doesn't support narrow clones"))
176 orig(pullop, kwargs)
176 orig(pullop, kwargs)
177 kwargs['narrow'] = True
177 kwargs['narrow'] = True
178 include, exclude = repo.narrowpats
178 include, exclude = repo.narrowpats
179 kwargs['oldincludepats'] = include
179 kwargs['oldincludepats'] = include
180 kwargs['oldexcludepats'] = exclude
180 kwargs['oldexcludepats'] = exclude
181 kwargs['includepats'] = include
181 kwargs['includepats'] = include
182 kwargs['excludepats'] = exclude
182 kwargs['excludepats'] = exclude
183 # calculate known nodes only in ellipses cases because in non-ellipses cases
183 # calculate known nodes only in ellipses cases because in non-ellipses cases
184 # we have all the nodes
184 # we have all the nodes
185 if narrowbundle2.ELLIPSESCAP in pullop.remote.capabilities():
185 if narrowwirepeer.ELLIPSESCAP in pullop.remote.capabilities():
186 kwargs['known'] = [node.hex(ctx.node()) for ctx in
186 kwargs['known'] = [node.hex(ctx.node()) for ctx in
187 repo.set('::%ln', pullop.common)
187 repo.set('::%ln', pullop.common)
188 if ctx.node() != node.nullid]
188 if ctx.node() != node.nullid]
189 if not kwargs['known']:
189 if not kwargs['known']:
190 # Mercurial serializes an empty list as '' and deserializes it as
190 # Mercurial serializes an empty list as '' and deserializes it as
191 # [''], so delete it instead to avoid handling the empty string on
191 # [''], so delete it instead to avoid handling the empty string on
192 # the server.
192 # the server.
193 del kwargs['known']
193 del kwargs['known']
194
194
195 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
195 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
196 pullbundle2extraprepare)
196 pullbundle2extraprepare)
197
197
198 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
198 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
199 newincludes, newexcludes, force):
199 newincludes, newexcludes, force):
200 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
200 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
201 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
201 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
202
202
203 # This is essentially doing "hg outgoing" to find all local-only
203 # This is essentially doing "hg outgoing" to find all local-only
204 # commits. We will then check that the local-only commits don't
204 # commits. We will then check that the local-only commits don't
205 # have any changes to files that will be untracked.
205 # have any changes to files that will be untracked.
206 unfi = repo.unfiltered()
206 unfi = repo.unfiltered()
207 outgoing = discovery.findcommonoutgoing(unfi, remote,
207 outgoing = discovery.findcommonoutgoing(unfi, remote,
208 commoninc=commoninc)
208 commoninc=commoninc)
209 ui.status(_('looking for local changes to affected paths\n'))
209 ui.status(_('looking for local changes to affected paths\n'))
210 localnodes = []
210 localnodes = []
211 for n in itertools.chain(outgoing.missing, outgoing.excluded):
211 for n in itertools.chain(outgoing.missing, outgoing.excluded):
212 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
212 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
213 localnodes.append(n)
213 localnodes.append(n)
214 revstostrip = unfi.revs('descendants(%ln)', localnodes)
214 revstostrip = unfi.revs('descendants(%ln)', localnodes)
215 hiddenrevs = repoview.filterrevs(repo, 'visible')
215 hiddenrevs = repoview.filterrevs(repo, 'visible')
216 visibletostrip = list(repo.changelog.node(r)
216 visibletostrip = list(repo.changelog.node(r)
217 for r in (revstostrip - hiddenrevs))
217 for r in (revstostrip - hiddenrevs))
218 if visibletostrip:
218 if visibletostrip:
219 ui.status(_('The following changeset(s) or their ancestors have '
219 ui.status(_('The following changeset(s) or their ancestors have '
220 'local changes not on the remote:\n'))
220 'local changes not on the remote:\n'))
221 maxnodes = 10
221 maxnodes = 10
222 if ui.verbose or len(visibletostrip) <= maxnodes:
222 if ui.verbose or len(visibletostrip) <= maxnodes:
223 for n in visibletostrip:
223 for n in visibletostrip:
224 ui.status('%s\n' % node.short(n))
224 ui.status('%s\n' % node.short(n))
225 else:
225 else:
226 for n in visibletostrip[:maxnodes]:
226 for n in visibletostrip[:maxnodes]:
227 ui.status('%s\n' % node.short(n))
227 ui.status('%s\n' % node.short(n))
228 ui.status(_('...and %d more, use --verbose to list all\n') %
228 ui.status(_('...and %d more, use --verbose to list all\n') %
229 (len(visibletostrip) - maxnodes))
229 (len(visibletostrip) - maxnodes))
230 if not force:
230 if not force:
231 raise error.Abort(_('local changes found'),
231 raise error.Abort(_('local changes found'),
232 hint=_('use --force-delete-local-changes to '
232 hint=_('use --force-delete-local-changes to '
233 'ignore'))
233 'ignore'))
234
234
235 with ui.uninterruptable():
235 with ui.uninterruptable():
236 if revstostrip:
236 if revstostrip:
237 tostrip = [unfi.changelog.node(r) for r in revstostrip]
237 tostrip = [unfi.changelog.node(r) for r in revstostrip]
238 if repo['.'].node() in tostrip:
238 if repo['.'].node() in tostrip:
239 # stripping working copy, so move to a different commit first
239 # stripping working copy, so move to a different commit first
240 urev = max(repo.revs('(::%n) - %ln + null',
240 urev = max(repo.revs('(::%n) - %ln + null',
241 repo['.'].node(), visibletostrip))
241 repo['.'].node(), visibletostrip))
242 hg.clean(repo, urev)
242 hg.clean(repo, urev)
243 repair.strip(ui, unfi, tostrip, topic='narrow')
243 repair.strip(ui, unfi, tostrip, topic='narrow')
244
244
245 todelete = []
245 todelete = []
246 for f, f2, size in repo.store.datafiles():
246 for f, f2, size in repo.store.datafiles():
247 if f.startswith('data/'):
247 if f.startswith('data/'):
248 file = f[5:-2]
248 file = f[5:-2]
249 if not newmatch(file):
249 if not newmatch(file):
250 todelete.append(f)
250 todelete.append(f)
251 elif f.startswith('meta/'):
251 elif f.startswith('meta/'):
252 dir = f[5:-13]
252 dir = f[5:-13]
253 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
253 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
254 include = True
254 include = True
255 for d in dirs:
255 for d in dirs:
256 visit = newmatch.visitdir(d)
256 visit = newmatch.visitdir(d)
257 if not visit:
257 if not visit:
258 include = False
258 include = False
259 break
259 break
260 if visit == 'all':
260 if visit == 'all':
261 break
261 break
262 if not include:
262 if not include:
263 todelete.append(f)
263 todelete.append(f)
264
264
265 repo.destroying()
265 repo.destroying()
266
266
267 with repo.transaction("narrowing"):
267 with repo.transaction("narrowing"):
268 for f in todelete:
268 for f in todelete:
269 ui.status(_('deleting %s\n') % f)
269 ui.status(_('deleting %s\n') % f)
270 util.unlinkpath(repo.svfs.join(f))
270 util.unlinkpath(repo.svfs.join(f))
271 repo.store.markremoved(f)
271 repo.store.markremoved(f)
272
272
273 for f in repo.dirstate:
273 for f in repo.dirstate:
274 if not newmatch(f):
274 if not newmatch(f):
275 repo.dirstate.drop(f)
275 repo.dirstate.drop(f)
276 repo.wvfs.unlinkpath(f)
276 repo.wvfs.unlinkpath(f)
277 repo.setnarrowpats(newincludes, newexcludes)
277 repo.setnarrowpats(newincludes, newexcludes)
278
278
279 repo.destroyed()
279 repo.destroyed()
280
280
281 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
281 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
282 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
282 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
283
283
284 # TODO(martinvonz): Get expansion working with widening/narrowing.
284 # TODO(martinvonz): Get expansion working with widening/narrowing.
285 if narrowspec.needsexpansion(newincludes):
285 if narrowspec.needsexpansion(newincludes):
286 raise error.Abort('Expansion not yet supported on pull')
286 raise error.Abort('Expansion not yet supported on pull')
287
287
288 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
288 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
289 orig(pullop, kwargs)
289 orig(pullop, kwargs)
290 # The old{in,ex}cludepats have already been set by orig()
290 # The old{in,ex}cludepats have already been set by orig()
291 kwargs['includepats'] = newincludes
291 kwargs['includepats'] = newincludes
292 kwargs['excludepats'] = newexcludes
292 kwargs['excludepats'] = newexcludes
293 kwargs['widen'] = True
293 kwargs['widen'] = True
294 wrappedextraprepare = extensions.wrappedfunction(exchange,
294 wrappedextraprepare = extensions.wrappedfunction(exchange,
295 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
295 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
296
296
297 # define a function that narrowbundle2 can call after creating the
297 # define a function that narrowbundle2 can call after creating the
298 # backup bundle, but before applying the bundle from the server
298 # backup bundle, but before applying the bundle from the server
299 def setnewnarrowpats():
299 def setnewnarrowpats():
300 repo.setnarrowpats(newincludes, newexcludes)
300 repo.setnarrowpats(newincludes, newexcludes)
301 repo.setnewnarrowpats = setnewnarrowpats
301 repo.setnewnarrowpats = setnewnarrowpats
302
302
303 with ui.uninterruptable():
303 with ui.uninterruptable():
304 ds = repo.dirstate
304 ds = repo.dirstate
305 p1, p2 = ds.p1(), ds.p2()
305 p1, p2 = ds.p1(), ds.p2()
306 with ds.parentchange():
306 with ds.parentchange():
307 ds.setparents(node.nullid, node.nullid)
307 ds.setparents(node.nullid, node.nullid)
308 common = commoninc[0]
308 common = commoninc[0]
309 with wrappedextraprepare:
309 with wrappedextraprepare:
310 exchange.pull(repo, remote, heads=common)
310 exchange.pull(repo, remote, heads=common)
311 with ds.parentchange():
311 with ds.parentchange():
312 ds.setparents(p1, p2)
312 ds.setparents(p1, p2)
313
313
314 repo.setnewnarrowpats()
314 repo.setnewnarrowpats()
315 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
315 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
316 addgaction = actions['g'].append
316 addgaction = actions['g'].append
317
317
318 mf = repo['.'].manifest().matches(newmatch)
318 mf = repo['.'].manifest().matches(newmatch)
319 for f, fn in mf.iteritems():
319 for f, fn in mf.iteritems():
320 if f not in repo.dirstate:
320 if f not in repo.dirstate:
321 addgaction((f, (mf.flags(f), False),
321 addgaction((f, (mf.flags(f), False),
322 "add from widened narrow clone"))
322 "add from widened narrow clone"))
323
323
324 merge.applyupdates(repo, actions, wctx=repo[None],
324 merge.applyupdates(repo, actions, wctx=repo[None],
325 mctx=repo['.'], overwrite=False)
325 mctx=repo['.'], overwrite=False)
326 merge.recordupdates(repo, actions, branchmerge=False)
326 merge.recordupdates(repo, actions, branchmerge=False)
327
327
328 # TODO(rdamazio): Make new matcher format and update description
328 # TODO(rdamazio): Make new matcher format and update description
329 @command('tracked',
329 @command('tracked',
330 [('', 'addinclude', [], _('new paths to include')),
330 [('', 'addinclude', [], _('new paths to include')),
331 ('', 'removeinclude', [], _('old paths to no longer include')),
331 ('', 'removeinclude', [], _('old paths to no longer include')),
332 ('', 'addexclude', [], _('new paths to exclude')),
332 ('', 'addexclude', [], _('new paths to exclude')),
333 ('', 'import-rules', '', _('import narrowspecs from a file')),
333 ('', 'import-rules', '', _('import narrowspecs from a file')),
334 ('', 'removeexclude', [], _('old paths to no longer exclude')),
334 ('', 'removeexclude', [], _('old paths to no longer exclude')),
335 ('', 'clear', False, _('whether to replace the existing narrowspec')),
335 ('', 'clear', False, _('whether to replace the existing narrowspec')),
336 ('', 'force-delete-local-changes', False,
336 ('', 'force-delete-local-changes', False,
337 _('forces deletion of local changes when narrowing')),
337 _('forces deletion of local changes when narrowing')),
338 ] + commands.remoteopts,
338 ] + commands.remoteopts,
339 _('[OPTIONS]... [REMOTE]'),
339 _('[OPTIONS]... [REMOTE]'),
340 inferrepo=True)
340 inferrepo=True)
341 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
341 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
342 """show or change the current narrowspec
342 """show or change the current narrowspec
343
343
344 With no argument, shows the current narrowspec entries, one per line. Each
344 With no argument, shows the current narrowspec entries, one per line. Each
345 line will be prefixed with 'I' or 'X' for included or excluded patterns,
345 line will be prefixed with 'I' or 'X' for included or excluded patterns,
346 respectively.
346 respectively.
347
347
348 The narrowspec is comprised of expressions to match remote files and/or
348 The narrowspec is comprised of expressions to match remote files and/or
349 directories that should be pulled into your client.
349 directories that should be pulled into your client.
350 The narrowspec has *include* and *exclude* expressions, with excludes always
350 The narrowspec has *include* and *exclude* expressions, with excludes always
351 trumping includes: that is, if a file matches an exclude expression, it will
351 trumping includes: that is, if a file matches an exclude expression, it will
352 be excluded even if it also matches an include expression.
352 be excluded even if it also matches an include expression.
353 Excluding files that were never included has no effect.
353 Excluding files that were never included has no effect.
354
354
355 Each included or excluded entry is in the format described by
355 Each included or excluded entry is in the format described by
356 'hg help patterns'.
356 'hg help patterns'.
357
357
358 The options allow you to add or remove included and excluded expressions.
358 The options allow you to add or remove included and excluded expressions.
359
359
360 If --clear is specified, then all previous includes and excludes are DROPPED
360 If --clear is specified, then all previous includes and excludes are DROPPED
361 and replaced by the new ones specified to --addinclude and --addexclude.
361 and replaced by the new ones specified to --addinclude and --addexclude.
362 If --clear is specified without any further options, the narrowspec will be
362 If --clear is specified without any further options, the narrowspec will be
363 empty and will not match any files.
363 empty and will not match any files.
364 """
364 """
365 opts = pycompat.byteskwargs(opts)
365 opts = pycompat.byteskwargs(opts)
366 if repository.NARROW_REQUIREMENT not in repo.requirements:
366 if repository.NARROW_REQUIREMENT not in repo.requirements:
367 ui.warn(_('The narrow command is only supported on respositories cloned'
367 ui.warn(_('The narrow command is only supported on respositories cloned'
368 ' with --narrow.\n'))
368 ' with --narrow.\n'))
369 return 1
369 return 1
370
370
371 # Before supporting, decide whether it "hg tracked --clear" should mean
371 # Before supporting, decide whether it "hg tracked --clear" should mean
372 # tracking no paths or all paths.
372 # tracking no paths or all paths.
373 if opts['clear']:
373 if opts['clear']:
374 ui.warn(_('The --clear option is not yet supported.\n'))
374 ui.warn(_('The --clear option is not yet supported.\n'))
375 return 1
375 return 1
376
376
377 # import rules from a file
377 # import rules from a file
378 newrules = opts.get('import_rules')
378 newrules = opts.get('import_rules')
379 if newrules:
379 if newrules:
380 try:
380 try:
381 filepath = os.path.join(pycompat.getcwd(), newrules)
381 filepath = os.path.join(pycompat.getcwd(), newrules)
382 fdata = util.readfile(filepath)
382 fdata = util.readfile(filepath)
383 except IOError as inst:
383 except IOError as inst:
384 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
384 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
385 (filepath, encoding.strtolocal(inst.strerror)))
385 (filepath, encoding.strtolocal(inst.strerror)))
386 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
386 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
387 'narrow')
387 'narrow')
388 if profiles:
388 if profiles:
389 raise error.Abort(_("including other spec files using '%include' "
389 raise error.Abort(_("including other spec files using '%include' "
390 "is not supported in narrowspec"))
390 "is not supported in narrowspec"))
391 opts['addinclude'].extend(includepats)
391 opts['addinclude'].extend(includepats)
392 opts['addexclude'].extend(excludepats)
392 opts['addexclude'].extend(excludepats)
393
393
394 if narrowspec.needsexpansion(opts['addinclude'] + opts['addexclude']):
394 if narrowspec.needsexpansion(opts['addinclude'] + opts['addexclude']):
395 raise error.Abort('Expansion not yet supported on widen/narrow')
395 raise error.Abort('Expansion not yet supported on widen/narrow')
396
396
397 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
397 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
398 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
398 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
399 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
399 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
400 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
400 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
401 widening = addedincludes or removedexcludes
401 widening = addedincludes or removedexcludes
402 narrowing = removedincludes or addedexcludes
402 narrowing = removedincludes or addedexcludes
403 only_show = not widening and not narrowing
403 only_show = not widening and not narrowing
404
404
405 # Only print the current narrowspec.
405 # Only print the current narrowspec.
406 if only_show:
406 if only_show:
407 include, exclude = repo.narrowpats
407 include, exclude = repo.narrowpats
408
408
409 ui.pager('tracked')
409 ui.pager('tracked')
410 fm = ui.formatter('narrow', opts)
410 fm = ui.formatter('narrow', opts)
411 for i in sorted(include):
411 for i in sorted(include):
412 fm.startitem()
412 fm.startitem()
413 fm.write('status', '%s ', 'I', label='narrow.included')
413 fm.write('status', '%s ', 'I', label='narrow.included')
414 fm.write('pat', '%s\n', i, label='narrow.included')
414 fm.write('pat', '%s\n', i, label='narrow.included')
415 for i in sorted(exclude):
415 for i in sorted(exclude):
416 fm.startitem()
416 fm.startitem()
417 fm.write('status', '%s ', 'X', label='narrow.excluded')
417 fm.write('status', '%s ', 'X', label='narrow.excluded')
418 fm.write('pat', '%s\n', i, label='narrow.excluded')
418 fm.write('pat', '%s\n', i, label='narrow.excluded')
419 fm.end()
419 fm.end()
420 return 0
420 return 0
421
421
422 with repo.wlock(), repo.lock():
422 with repo.wlock(), repo.lock():
423 cmdutil.bailifchanged(repo)
423 cmdutil.bailifchanged(repo)
424
424
425 # Find the revisions we have in common with the remote. These will
425 # Find the revisions we have in common with the remote. These will
426 # be used for finding local-only changes for narrowing. They will
426 # be used for finding local-only changes for narrowing. They will
427 # also define the set of revisions to update for widening.
427 # also define the set of revisions to update for widening.
428 remotepath = ui.expandpath(remotepath or 'default')
428 remotepath = ui.expandpath(remotepath or 'default')
429 url, branches = hg.parseurl(remotepath)
429 url, branches = hg.parseurl(remotepath)
430 ui.status(_('comparing with %s\n') % util.hidepassword(url))
430 ui.status(_('comparing with %s\n') % util.hidepassword(url))
431 remote = hg.peer(repo, opts, url)
431 remote = hg.peer(repo, opts, url)
432 commoninc = discovery.findcommonincoming(repo, remote)
432 commoninc = discovery.findcommonincoming(repo, remote)
433
433
434 oldincludes, oldexcludes = repo.narrowpats
434 oldincludes, oldexcludes = repo.narrowpats
435 if narrowing:
435 if narrowing:
436 newincludes = oldincludes - removedincludes
436 newincludes = oldincludes - removedincludes
437 newexcludes = oldexcludes | addedexcludes
437 newexcludes = oldexcludes | addedexcludes
438 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
438 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
439 newincludes, newexcludes,
439 newincludes, newexcludes,
440 opts['force_delete_local_changes'])
440 opts['force_delete_local_changes'])
441 # _narrow() updated the narrowspec and _widen() below needs to
441 # _narrow() updated the narrowspec and _widen() below needs to
442 # use the updated values as its base (otherwise removed includes
442 # use the updated values as its base (otherwise removed includes
443 # and addedexcludes will be lost in the resulting narrowspec)
443 # and addedexcludes will be lost in the resulting narrowspec)
444 oldincludes = newincludes
444 oldincludes = newincludes
445 oldexcludes = newexcludes
445 oldexcludes = newexcludes
446
446
447 if widening:
447 if widening:
448 newincludes = oldincludes | addedincludes
448 newincludes = oldincludes | addedincludes
449 newexcludes = oldexcludes - removedexcludes
449 newexcludes = oldexcludes - removedexcludes
450 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
450 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
451
451
452 return 0
452 return 0
@@ -1,36 +1,36 b''
1 # narrowrepo.py - repository which supports narrow revlogs, lazy loading
1 # narrowrepo.py - repository which supports narrow revlogs, lazy loading
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 . import (
10 from . import (
11 narrowbundle2,
12 narrowdirstate,
11 narrowdirstate,
13 narrowrevlog,
12 narrowrevlog,
13 narrowwirepeer,
14 )
14 )
15
15
16 def wraprepo(repo):
16 def wraprepo(repo):
17 """Enables narrow clone functionality on a single local repository."""
17 """Enables narrow clone functionality on a single local repository."""
18
18
19 class narrowrepository(repo.__class__):
19 class narrowrepository(repo.__class__):
20
20
21 def file(self, f):
21 def file(self, f):
22 fl = super(narrowrepository, self).file(f)
22 fl = super(narrowrepository, self).file(f)
23 narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
23 narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
24 return fl
24 return fl
25
25
26 def _makedirstate(self):
26 def _makedirstate(self):
27 dirstate = super(narrowrepository, self)._makedirstate()
27 dirstate = super(narrowrepository, self)._makedirstate()
28 return narrowdirstate.wrapdirstate(self, dirstate)
28 return narrowdirstate.wrapdirstate(self, dirstate)
29
29
30 def peer(self):
30 def peer(self):
31 peer = super(narrowrepository, self).peer()
31 peer = super(narrowrepository, self).peer()
32 peer._caps.add(narrowbundle2.NARROWCAP)
32 peer._caps.add(narrowwirepeer.NARROWCAP)
33 peer._caps.add(narrowbundle2.ELLIPSESCAP)
33 peer._caps.add(narrowwirepeer.ELLIPSESCAP)
34 return peer
34 return peer
35
35
36 repo.__class__ = narrowrepository
36 repo.__class__ = narrowrepository
@@ -1,65 +1,66 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 wireprotov1server,
18 )
18 )
19
19
20 from . import narrowbundle2
20 NARROWCAP = 'narrow'
21 ELLIPSESCAP = 'ellipses'
21
22
22 def uisetup():
23 def uisetup():
23 def peersetup(ui, peer):
24 def peersetup(ui, peer):
24 # We must set up the expansion before reposetup below, since it's used
25 # We must set up the expansion before reposetup below, since it's used
25 # at clone time before we have a repo.
26 # at clone time before we have a repo.
26 class expandingpeer(peer.__class__):
27 class expandingpeer(peer.__class__):
27 def expandnarrow(self, narrow_include, narrow_exclude, nodes):
28 def expandnarrow(self, narrow_include, narrow_exclude, nodes):
28 ui.status(_("expanding narrowspec\n"))
29 ui.status(_("expanding narrowspec\n"))
29 if not self.capable('exp-expandnarrow'):
30 if not self.capable('exp-expandnarrow'):
30 raise error.Abort(
31 raise error.Abort(
31 'peer does not support expanding narrowspecs')
32 'peer does not support expanding narrowspecs')
32
33
33 hex_nodes = (node.hex(n) for n in nodes)
34 hex_nodes = (node.hex(n) for n in nodes)
34 new_narrowspec = self._call(
35 new_narrowspec = self._call(
35 'expandnarrow',
36 'expandnarrow',
36 includepats=','.join(narrow_include),
37 includepats=','.join(narrow_include),
37 excludepats=','.join(narrow_exclude),
38 excludepats=','.join(narrow_exclude),
38 nodes=','.join(hex_nodes))
39 nodes=','.join(hex_nodes))
39
40
40 return narrowspec.parseserverpatterns(new_narrowspec)
41 return narrowspec.parseserverpatterns(new_narrowspec)
41 peer.__class__ = expandingpeer
42 peer.__class__ = expandingpeer
42 hg.wirepeersetupfuncs.append(peersetup)
43 hg.wirepeersetupfuncs.append(peersetup)
43
44
44 extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
45 extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
45
46
46 def addnarrowcap(orig, repo, proto):
47 def addnarrowcap(orig, repo, proto):
47 """add the narrow capability to the server"""
48 """add the narrow capability to the server"""
48 caps = orig(repo, proto)
49 caps = orig(repo, proto)
49 caps.append(narrowbundle2.NARROWCAP)
50 caps.append(NARROWCAP)
50 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
51 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
51 caps.append(narrowbundle2.ELLIPSESCAP)
52 caps.append(ELLIPSESCAP)
52 return caps
53 return caps
53
54
54 def reposetup(repo):
55 def reposetup(repo):
55 def wirereposetup(ui, peer):
56 def wirereposetup(ui, peer):
56 def wrapped(orig, cmd, *args, **kwargs):
57 def wrapped(orig, cmd, *args, **kwargs):
57 if cmd == 'unbundle':
58 if cmd == 'unbundle':
58 # TODO: don't blindly add include/exclude wireproto
59 # TODO: don't blindly add include/exclude wireproto
59 # arguments to unbundle.
60 # arguments to unbundle.
60 include, exclude = repo.narrowpats
61 include, exclude = repo.narrowpats
61 kwargs[r"includepats"] = ','.join(include)
62 kwargs[r"includepats"] = ','.join(include)
62 kwargs[r"excludepats"] = ','.join(exclude)
63 kwargs[r"excludepats"] = ','.join(exclude)
63 return orig(cmd, *args, **kwargs)
64 return orig(cmd, *args, **kwargs)
64 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
65 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
65 hg.wirepeersetupfuncs.append(wirereposetup)
66 hg.wirepeersetupfuncs.append(wirereposetup)
General Comments 0
You need to be logged in to leave comments. Login now