##// END OF EJS Templates
narrow: drop support for remote expansion (BC)...
Gregory Szorc -
r39581:10a8472f default
parent child Browse files
Show More
@@ -1,37 +1,30 b''
1 Integration with the share extension needs improvement. Right now
1 Integration with the share extension needs improvement. Right now
2 we've seen some odd bugs, and the way we modify the contents of the
2 we've seen some odd bugs, and the way we modify the contents of the
3 .hg/shared file is unfortunate. See wrappostshare() and unsharenarrowspec().
3 .hg/shared file is unfortunate. See wrappostshare() and unsharenarrowspec().
4
4
5 Resolve commentary on narrowrepo.wraprepo.narrowrepository.status
5 Resolve commentary on narrowrepo.wraprepo.narrowrepository.status
6 about the filtering of status being done at an awkward layer. This
6 about the filtering of status being done at an awkward layer. This
7 came up the import to hgext, but nobody's got concrete improvement
7 came up the import to hgext, but nobody's got concrete improvement
8 ideas as of then.
8 ideas as of then.
9
9
10 Fold most (or preferably all) of narrowrevlog.py into core.
10 Fold most (or preferably all) of narrowrevlog.py into core.
11
11
12 Address commentary in narrowrevlog.excludedmanifestrevlog.add -
12 Address commentary in narrowrevlog.excludedmanifestrevlog.add -
13 specifically we should improve the collaboration with core so that
13 specifically we should improve the collaboration with core so that
14 add() never gets called on an excluded directory and we can improve
14 add() never gets called on an excluded directory and we can improve
15 the stand-in to raise a ProgrammingError.
15 the stand-in to raise a ProgrammingError.
16
16
17 Figure out how to correctly produce narrowmanifestrevlog and
17 Figure out how to correctly produce narrowmanifestrevlog and
18 narrowfilelog instances instead of monkeypatching regular revlogs at
18 narrowfilelog instances instead of monkeypatching regular revlogs at
19 runtime to our subclass. Even better, merge the narrowing logic
19 runtime to our subclass. Even better, merge the narrowing logic
20 directly into core.
20 directly into core.
21
21
22 Reason more completely about rename-filtering logic in
22 Reason more completely about rename-filtering logic in
23 narrowfilelog. There could be some surprises lurking there.
23 narrowfilelog. There could be some surprises lurking there.
24
24
25 Formally document the narrowspec format. Unify with sparse, if at all
25 Formally document the narrowspec format. Unify with sparse, if at all
26 possible. For bonus points, unify with the server-specified narrowspec
26 possible. For bonus points, unify with the server-specified narrowspec
27 format.
27 format.
28
28
29 narrowrepo.setnarrowpats() or narrowspec.save() need to make sure
29 narrowrepo.setnarrowpats() or narrowspec.save() need to make sure
30 they're holding the wlock.
30 they're holding the wlock.
31
32 Implement a simple version of the expandnarrow wireproto command for
33 core. Having configurable shorthands for narrowspecs has been useful
34 at Google (and sparse has a similar feature from Facebook), so it
35 probably makes sense to implement the feature in core. (Google's
36 handler is entirely custom to Google, with a custom format related to
37 bazel's build language, so it's not in the narrowhg distribution.)
@@ -1,463 +1,433 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 narrowwirepeer,
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):
66 if not narrowspec.needsexpansion(includepats):
67 return includepats, excludepats
68
69 heads = pullop.heads or pullop.rheads
70 includepats, excludepats = pullop.remote.expandnarrow(
71 includepats, excludepats, heads)
72 pullop.repo.ui.debug('Expanded narrowspec to inc=%s, exc=%s\n' % (
73 includepats, excludepats))
74
75 includepats = set(includepats)
76 excludepats = set(excludepats)
77
78 # Nefarious remote could supply unsafe patterns. Validate them.
79 narrowspec.validatepatterns(includepats)
80 narrowspec.validatepatterns(excludepats)
81
82 return includepats, excludepats
83
84 def clonenarrowcmd(orig, ui, repo, *args, **opts):
65 def clonenarrowcmd(orig, ui, repo, *args, **opts):
85 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
66 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
86 opts = pycompat.byteskwargs(opts)
67 opts = pycompat.byteskwargs(opts)
87 wrappedextraprepare = util.nullcontextmanager()
68 wrappedextraprepare = util.nullcontextmanager()
88 opts_narrow = opts['narrow']
69 opts_narrow = opts['narrow']
89 narrowspecfile = opts['narrowspec']
70 narrowspecfile = opts['narrowspec']
90
71
91 if narrowspecfile:
72 if narrowspecfile:
92 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
73 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
93 ui.status(_("reading narrowspec from '%s'\n") % filepath)
74 ui.status(_("reading narrowspec from '%s'\n") % filepath)
94 try:
75 try:
95 fdata = util.readfile(filepath)
76 fdata = util.readfile(filepath)
96 except IOError as inst:
77 except IOError as inst:
97 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
78 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
98 (filepath, encoding.strtolocal(inst.strerror)))
79 (filepath, encoding.strtolocal(inst.strerror)))
99
80
100 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
81 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
101 if profiles:
82 if profiles:
102 raise error.Abort(_("cannot specify other files using '%include' in"
83 raise error.Abort(_("cannot specify other files using '%include' in"
103 " narrowspec"))
84 " narrowspec"))
104
85
105 narrowspec.validatepatterns(includes)
86 narrowspec.validatepatterns(includes)
106 narrowspec.validatepatterns(excludes)
87 narrowspec.validatepatterns(excludes)
107
88
108 # narrowspec is passed so we should assume that user wants narrow clone
89 # narrowspec is passed so we should assume that user wants narrow clone
109 opts_narrow = True
90 opts_narrow = True
110 opts['include'].extend(includes)
91 opts['include'].extend(includes)
111 opts['exclude'].extend(excludes)
92 opts['exclude'].extend(excludes)
112
93
113 if opts_narrow:
94 if opts_narrow:
114 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
95 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
115 # Create narrow spec patterns from clone flags
96 # Create narrow spec patterns from clone flags
116 includepats = narrowspec.parsepatterns(opts['include'])
97 includepats = narrowspec.parsepatterns(opts['include'])
117 excludepats = narrowspec.parsepatterns(opts['exclude'])
98 excludepats = narrowspec.parsepatterns(opts['exclude'])
118
99
119 # If necessary, ask the server to expand the narrowspec.
120 includepats, excludepats = expandpull(
121 pullop, includepats, excludepats)
122
123 if not includepats and excludepats:
100 if not includepats and excludepats:
124 # If nothing was included, we assume the user meant to include
101 # If nothing was included, we assume the user meant to include
125 # everything, except what they asked to exclude.
102 # everything, except what they asked to exclude.
126 includepats = {'path:.'}
103 includepats = {'path:.'}
127
104
128 pullop.repo.setnarrowpats(includepats, excludepats)
105 pullop.repo.setnarrowpats(includepats, excludepats)
129
106
130 # This will populate 'includepats' etc with the values from the
107 # This will populate 'includepats' etc with the values from the
131 # narrowspec we just saved.
108 # narrowspec we just saved.
132 orig(pullop, kwargs)
109 orig(pullop, kwargs)
133
110
134 if opts.get('depth'):
111 if opts.get('depth'):
135 kwargs['depth'] = opts['depth']
112 kwargs['depth'] = opts['depth']
136 wrappedextraprepare = extensions.wrappedfunction(exchange,
113 wrappedextraprepare = extensions.wrappedfunction(exchange,
137 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
114 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
138
115
139 def pullnarrow(orig, repo, *args, **kwargs):
116 def pullnarrow(orig, repo, *args, **kwargs):
140 if opts_narrow:
117 if opts_narrow:
141 repo.requirements.add(repository.NARROW_REQUIREMENT)
118 repo.requirements.add(repository.NARROW_REQUIREMENT)
142 repo._writerequirements()
119 repo._writerequirements()
143
120
144 return orig(repo, *args, **kwargs)
121 return orig(repo, *args, **kwargs)
145
122
146 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
123 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
147
124
148 with wrappedextraprepare, wrappedpull:
125 with wrappedextraprepare, wrappedpull:
149 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
126 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
150
127
151 def pullnarrowcmd(orig, ui, repo, *args, **opts):
128 def pullnarrowcmd(orig, ui, repo, *args, **opts):
152 """Wraps pull command to allow modifying narrow spec."""
129 """Wraps pull command to allow modifying narrow spec."""
153 wrappedextraprepare = util.nullcontextmanager()
130 wrappedextraprepare = util.nullcontextmanager()
154 if repository.NARROW_REQUIREMENT in repo.requirements:
131 if repository.NARROW_REQUIREMENT in repo.requirements:
155
132
156 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
133 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
157 orig(pullop, kwargs)
134 orig(pullop, kwargs)
158 if opts.get(r'depth'):
135 if opts.get(r'depth'):
159 kwargs['depth'] = opts[r'depth']
136 kwargs['depth'] = opts[r'depth']
160 wrappedextraprepare = extensions.wrappedfunction(exchange,
137 wrappedextraprepare = extensions.wrappedfunction(exchange,
161 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
138 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
162
139
163 with wrappedextraprepare:
140 with wrappedextraprepare:
164 return orig(ui, repo, *args, **opts)
141 return orig(ui, repo, *args, **opts)
165
142
166 def archivenarrowcmd(orig, ui, repo, *args, **opts):
143 def archivenarrowcmd(orig, ui, repo, *args, **opts):
167 """Wraps archive command to narrow the default includes."""
144 """Wraps archive command to narrow the default includes."""
168 if repository.NARROW_REQUIREMENT in repo.requirements:
145 if repository.NARROW_REQUIREMENT in repo.requirements:
169 repo_includes, repo_excludes = repo.narrowpats
146 repo_includes, repo_excludes = repo.narrowpats
170 includes = set(opts.get(r'include', []))
147 includes = set(opts.get(r'include', []))
171 excludes = set(opts.get(r'exclude', []))
148 excludes = set(opts.get(r'exclude', []))
172 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
149 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
173 includes, excludes, repo_includes, repo_excludes)
150 includes, excludes, repo_includes, repo_excludes)
174 if includes:
151 if includes:
175 opts[r'include'] = includes
152 opts[r'include'] = includes
176 if excludes:
153 if excludes:
177 opts[r'exclude'] = excludes
154 opts[r'exclude'] = excludes
178 return orig(ui, repo, *args, **opts)
155 return orig(ui, repo, *args, **opts)
179
156
180 def pullbundle2extraprepare(orig, pullop, kwargs):
157 def pullbundle2extraprepare(orig, pullop, kwargs):
181 repo = pullop.repo
158 repo = pullop.repo
182 if repository.NARROW_REQUIREMENT not in repo.requirements:
159 if repository.NARROW_REQUIREMENT not in repo.requirements:
183 return orig(pullop, kwargs)
160 return orig(pullop, kwargs)
184
161
185 if narrowwirepeer.NARROWCAP not in pullop.remote.capabilities():
162 if narrowwirepeer.NARROWCAP not in pullop.remote.capabilities():
186 raise error.Abort(_("server doesn't support narrow clones"))
163 raise error.Abort(_("server doesn't support narrow clones"))
187 orig(pullop, kwargs)
164 orig(pullop, kwargs)
188 kwargs['narrow'] = True
165 kwargs['narrow'] = True
189 include, exclude = repo.narrowpats
166 include, exclude = repo.narrowpats
190 kwargs['oldincludepats'] = include
167 kwargs['oldincludepats'] = include
191 kwargs['oldexcludepats'] = exclude
168 kwargs['oldexcludepats'] = exclude
192 kwargs['includepats'] = include
169 kwargs['includepats'] = include
193 kwargs['excludepats'] = exclude
170 kwargs['excludepats'] = exclude
194 # calculate known nodes only in ellipses cases because in non-ellipses cases
171 # calculate known nodes only in ellipses cases because in non-ellipses cases
195 # we have all the nodes
172 # we have all the nodes
196 if narrowwirepeer.ELLIPSESCAP in pullop.remote.capabilities():
173 if narrowwirepeer.ELLIPSESCAP in pullop.remote.capabilities():
197 kwargs['known'] = [node.hex(ctx.node()) for ctx in
174 kwargs['known'] = [node.hex(ctx.node()) for ctx in
198 repo.set('::%ln', pullop.common)
175 repo.set('::%ln', pullop.common)
199 if ctx.node() != node.nullid]
176 if ctx.node() != node.nullid]
200 if not kwargs['known']:
177 if not kwargs['known']:
201 # Mercurial serializes an empty list as '' and deserializes it as
178 # Mercurial serializes an empty list as '' and deserializes it as
202 # [''], so delete it instead to avoid handling the empty string on
179 # [''], so delete it instead to avoid handling the empty string on
203 # the server.
180 # the server.
204 del kwargs['known']
181 del kwargs['known']
205
182
206 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
183 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
207 pullbundle2extraprepare)
184 pullbundle2extraprepare)
208
185
209 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
186 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
210 newincludes, newexcludes, force):
187 newincludes, newexcludes, force):
211 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
188 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
212 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
189 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
213
190
214 # This is essentially doing "hg outgoing" to find all local-only
191 # This is essentially doing "hg outgoing" to find all local-only
215 # commits. We will then check that the local-only commits don't
192 # commits. We will then check that the local-only commits don't
216 # have any changes to files that will be untracked.
193 # have any changes to files that will be untracked.
217 unfi = repo.unfiltered()
194 unfi = repo.unfiltered()
218 outgoing = discovery.findcommonoutgoing(unfi, remote,
195 outgoing = discovery.findcommonoutgoing(unfi, remote,
219 commoninc=commoninc)
196 commoninc=commoninc)
220 ui.status(_('looking for local changes to affected paths\n'))
197 ui.status(_('looking for local changes to affected paths\n'))
221 localnodes = []
198 localnodes = []
222 for n in itertools.chain(outgoing.missing, outgoing.excluded):
199 for n in itertools.chain(outgoing.missing, outgoing.excluded):
223 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
200 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
224 localnodes.append(n)
201 localnodes.append(n)
225 revstostrip = unfi.revs('descendants(%ln)', localnodes)
202 revstostrip = unfi.revs('descendants(%ln)', localnodes)
226 hiddenrevs = repoview.filterrevs(repo, 'visible')
203 hiddenrevs = repoview.filterrevs(repo, 'visible')
227 visibletostrip = list(repo.changelog.node(r)
204 visibletostrip = list(repo.changelog.node(r)
228 for r in (revstostrip - hiddenrevs))
205 for r in (revstostrip - hiddenrevs))
229 if visibletostrip:
206 if visibletostrip:
230 ui.status(_('The following changeset(s) or their ancestors have '
207 ui.status(_('The following changeset(s) or their ancestors have '
231 'local changes not on the remote:\n'))
208 'local changes not on the remote:\n'))
232 maxnodes = 10
209 maxnodes = 10
233 if ui.verbose or len(visibletostrip) <= maxnodes:
210 if ui.verbose or len(visibletostrip) <= maxnodes:
234 for n in visibletostrip:
211 for n in visibletostrip:
235 ui.status('%s\n' % node.short(n))
212 ui.status('%s\n' % node.short(n))
236 else:
213 else:
237 for n in visibletostrip[:maxnodes]:
214 for n in visibletostrip[:maxnodes]:
238 ui.status('%s\n' % node.short(n))
215 ui.status('%s\n' % node.short(n))
239 ui.status(_('...and %d more, use --verbose to list all\n') %
216 ui.status(_('...and %d more, use --verbose to list all\n') %
240 (len(visibletostrip) - maxnodes))
217 (len(visibletostrip) - maxnodes))
241 if not force:
218 if not force:
242 raise error.Abort(_('local changes found'),
219 raise error.Abort(_('local changes found'),
243 hint=_('use --force-delete-local-changes to '
220 hint=_('use --force-delete-local-changes to '
244 'ignore'))
221 'ignore'))
245
222
246 with ui.uninterruptable():
223 with ui.uninterruptable():
247 if revstostrip:
224 if revstostrip:
248 tostrip = [unfi.changelog.node(r) for r in revstostrip]
225 tostrip = [unfi.changelog.node(r) for r in revstostrip]
249 if repo['.'].node() in tostrip:
226 if repo['.'].node() in tostrip:
250 # stripping working copy, so move to a different commit first
227 # stripping working copy, so move to a different commit first
251 urev = max(repo.revs('(::%n) - %ln + null',
228 urev = max(repo.revs('(::%n) - %ln + null',
252 repo['.'].node(), visibletostrip))
229 repo['.'].node(), visibletostrip))
253 hg.clean(repo, urev)
230 hg.clean(repo, urev)
254 repair.strip(ui, unfi, tostrip, topic='narrow')
231 repair.strip(ui, unfi, tostrip, topic='narrow')
255
232
256 todelete = []
233 todelete = []
257 for f, f2, size in repo.store.datafiles():
234 for f, f2, size in repo.store.datafiles():
258 if f.startswith('data/'):
235 if f.startswith('data/'):
259 file = f[5:-2]
236 file = f[5:-2]
260 if not newmatch(file):
237 if not newmatch(file):
261 todelete.append(f)
238 todelete.append(f)
262 elif f.startswith('meta/'):
239 elif f.startswith('meta/'):
263 dir = f[5:-13]
240 dir = f[5:-13]
264 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
241 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
265 include = True
242 include = True
266 for d in dirs:
243 for d in dirs:
267 visit = newmatch.visitdir(d)
244 visit = newmatch.visitdir(d)
268 if not visit:
245 if not visit:
269 include = False
246 include = False
270 break
247 break
271 if visit == 'all':
248 if visit == 'all':
272 break
249 break
273 if not include:
250 if not include:
274 todelete.append(f)
251 todelete.append(f)
275
252
276 repo.destroying()
253 repo.destroying()
277
254
278 with repo.transaction("narrowing"):
255 with repo.transaction("narrowing"):
279 for f in todelete:
256 for f in todelete:
280 ui.status(_('deleting %s\n') % f)
257 ui.status(_('deleting %s\n') % f)
281 util.unlinkpath(repo.svfs.join(f))
258 util.unlinkpath(repo.svfs.join(f))
282 repo.store.markremoved(f)
259 repo.store.markremoved(f)
283
260
284 for f in repo.dirstate:
261 for f in repo.dirstate:
285 if not newmatch(f):
262 if not newmatch(f):
286 repo.dirstate.drop(f)
263 repo.dirstate.drop(f)
287 repo.wvfs.unlinkpath(f)
264 repo.wvfs.unlinkpath(f)
288 repo.setnarrowpats(newincludes, newexcludes)
265 repo.setnarrowpats(newincludes, newexcludes)
289
266
290 repo.destroyed()
267 repo.destroyed()
291
268
292 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
269 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
293 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
270 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
294
271
295 # TODO(martinvonz): Get expansion working with widening/narrowing.
296 if narrowspec.needsexpansion(newincludes):
297 raise error.Abort('Expansion not yet supported on pull')
298
299 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
272 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
300 orig(pullop, kwargs)
273 orig(pullop, kwargs)
301 # The old{in,ex}cludepats have already been set by orig()
274 # The old{in,ex}cludepats have already been set by orig()
302 kwargs['includepats'] = newincludes
275 kwargs['includepats'] = newincludes
303 kwargs['excludepats'] = newexcludes
276 kwargs['excludepats'] = newexcludes
304 kwargs['widen'] = True
277 kwargs['widen'] = True
305 wrappedextraprepare = extensions.wrappedfunction(exchange,
278 wrappedextraprepare = extensions.wrappedfunction(exchange,
306 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
279 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
307
280
308 # define a function that narrowbundle2 can call after creating the
281 # define a function that narrowbundle2 can call after creating the
309 # backup bundle, but before applying the bundle from the server
282 # backup bundle, but before applying the bundle from the server
310 def setnewnarrowpats():
283 def setnewnarrowpats():
311 repo.setnarrowpats(newincludes, newexcludes)
284 repo.setnarrowpats(newincludes, newexcludes)
312 repo.setnewnarrowpats = setnewnarrowpats
285 repo.setnewnarrowpats = setnewnarrowpats
313
286
314 with ui.uninterruptable():
287 with ui.uninterruptable():
315 ds = repo.dirstate
288 ds = repo.dirstate
316 p1, p2 = ds.p1(), ds.p2()
289 p1, p2 = ds.p1(), ds.p2()
317 with ds.parentchange():
290 with ds.parentchange():
318 ds.setparents(node.nullid, node.nullid)
291 ds.setparents(node.nullid, node.nullid)
319 common = commoninc[0]
292 common = commoninc[0]
320 with wrappedextraprepare:
293 with wrappedextraprepare:
321 exchange.pull(repo, remote, heads=common)
294 exchange.pull(repo, remote, heads=common)
322 with ds.parentchange():
295 with ds.parentchange():
323 ds.setparents(p1, p2)
296 ds.setparents(p1, p2)
324
297
325 repo.setnewnarrowpats()
298 repo.setnewnarrowpats()
326 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
299 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
327 addgaction = actions['g'].append
300 addgaction = actions['g'].append
328
301
329 mf = repo['.'].manifest().matches(newmatch)
302 mf = repo['.'].manifest().matches(newmatch)
330 for f, fn in mf.iteritems():
303 for f, fn in mf.iteritems():
331 if f not in repo.dirstate:
304 if f not in repo.dirstate:
332 addgaction((f, (mf.flags(f), False),
305 addgaction((f, (mf.flags(f), False),
333 "add from widened narrow clone"))
306 "add from widened narrow clone"))
334
307
335 merge.applyupdates(repo, actions, wctx=repo[None],
308 merge.applyupdates(repo, actions, wctx=repo[None],
336 mctx=repo['.'], overwrite=False)
309 mctx=repo['.'], overwrite=False)
337 merge.recordupdates(repo, actions, branchmerge=False)
310 merge.recordupdates(repo, actions, branchmerge=False)
338
311
339 # TODO(rdamazio): Make new matcher format and update description
312 # TODO(rdamazio): Make new matcher format and update description
340 @command('tracked',
313 @command('tracked',
341 [('', 'addinclude', [], _('new paths to include')),
314 [('', 'addinclude', [], _('new paths to include')),
342 ('', 'removeinclude', [], _('old paths to no longer include')),
315 ('', 'removeinclude', [], _('old paths to no longer include')),
343 ('', 'addexclude', [], _('new paths to exclude')),
316 ('', 'addexclude', [], _('new paths to exclude')),
344 ('', 'import-rules', '', _('import narrowspecs from a file')),
317 ('', 'import-rules', '', _('import narrowspecs from a file')),
345 ('', 'removeexclude', [], _('old paths to no longer exclude')),
318 ('', 'removeexclude', [], _('old paths to no longer exclude')),
346 ('', 'clear', False, _('whether to replace the existing narrowspec')),
319 ('', 'clear', False, _('whether to replace the existing narrowspec')),
347 ('', 'force-delete-local-changes', False,
320 ('', 'force-delete-local-changes', False,
348 _('forces deletion of local changes when narrowing')),
321 _('forces deletion of local changes when narrowing')),
349 ] + commands.remoteopts,
322 ] + commands.remoteopts,
350 _('[OPTIONS]... [REMOTE]'),
323 _('[OPTIONS]... [REMOTE]'),
351 inferrepo=True)
324 inferrepo=True)
352 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
325 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
353 """show or change the current narrowspec
326 """show or change the current narrowspec
354
327
355 With no argument, shows the current narrowspec entries, one per line. Each
328 With no argument, shows the current narrowspec entries, one per line. Each
356 line will be prefixed with 'I' or 'X' for included or excluded patterns,
329 line will be prefixed with 'I' or 'X' for included or excluded patterns,
357 respectively.
330 respectively.
358
331
359 The narrowspec is comprised of expressions to match remote files and/or
332 The narrowspec is comprised of expressions to match remote files and/or
360 directories that should be pulled into your client.
333 directories that should be pulled into your client.
361 The narrowspec has *include* and *exclude* expressions, with excludes always
334 The narrowspec has *include* and *exclude* expressions, with excludes always
362 trumping includes: that is, if a file matches an exclude expression, it will
335 trumping includes: that is, if a file matches an exclude expression, it will
363 be excluded even if it also matches an include expression.
336 be excluded even if it also matches an include expression.
364 Excluding files that were never included has no effect.
337 Excluding files that were never included has no effect.
365
338
366 Each included or excluded entry is in the format described by
339 Each included or excluded entry is in the format described by
367 'hg help patterns'.
340 'hg help patterns'.
368
341
369 The options allow you to add or remove included and excluded expressions.
342 The options allow you to add or remove included and excluded expressions.
370
343
371 If --clear is specified, then all previous includes and excludes are DROPPED
344 If --clear is specified, then all previous includes and excludes are DROPPED
372 and replaced by the new ones specified to --addinclude and --addexclude.
345 and replaced by the new ones specified to --addinclude and --addexclude.
373 If --clear is specified without any further options, the narrowspec will be
346 If --clear is specified without any further options, the narrowspec will be
374 empty and will not match any files.
347 empty and will not match any files.
375 """
348 """
376 opts = pycompat.byteskwargs(opts)
349 opts = pycompat.byteskwargs(opts)
377 if repository.NARROW_REQUIREMENT not in repo.requirements:
350 if repository.NARROW_REQUIREMENT not in repo.requirements:
378 ui.warn(_('The narrow command is only supported on respositories cloned'
351 ui.warn(_('The narrow command is only supported on respositories cloned'
379 ' with --narrow.\n'))
352 ' with --narrow.\n'))
380 return 1
353 return 1
381
354
382 # Before supporting, decide whether it "hg tracked --clear" should mean
355 # Before supporting, decide whether it "hg tracked --clear" should mean
383 # tracking no paths or all paths.
356 # tracking no paths or all paths.
384 if opts['clear']:
357 if opts['clear']:
385 ui.warn(_('The --clear option is not yet supported.\n'))
358 ui.warn(_('The --clear option is not yet supported.\n'))
386 return 1
359 return 1
387
360
388 # import rules from a file
361 # import rules from a file
389 newrules = opts.get('import_rules')
362 newrules = opts.get('import_rules')
390 if newrules:
363 if newrules:
391 try:
364 try:
392 filepath = os.path.join(pycompat.getcwd(), newrules)
365 filepath = os.path.join(pycompat.getcwd(), newrules)
393 fdata = util.readfile(filepath)
366 fdata = util.readfile(filepath)
394 except IOError as inst:
367 except IOError as inst:
395 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
368 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
396 (filepath, encoding.strtolocal(inst.strerror)))
369 (filepath, encoding.strtolocal(inst.strerror)))
397 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
370 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
398 'narrow')
371 'narrow')
399 if profiles:
372 if profiles:
400 raise error.Abort(_("including other spec files using '%include' "
373 raise error.Abort(_("including other spec files using '%include' "
401 "is not supported in narrowspec"))
374 "is not supported in narrowspec"))
402 opts['addinclude'].extend(includepats)
375 opts['addinclude'].extend(includepats)
403 opts['addexclude'].extend(excludepats)
376 opts['addexclude'].extend(excludepats)
404
377
405 if narrowspec.needsexpansion(opts['addinclude'] + opts['addexclude']):
406 raise error.Abort('Expansion not yet supported on widen/narrow')
407
408 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
378 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
409 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
379 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
410 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
380 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
411 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
381 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
412 widening = addedincludes or removedexcludes
382 widening = addedincludes or removedexcludes
413 narrowing = removedincludes or addedexcludes
383 narrowing = removedincludes or addedexcludes
414 only_show = not widening and not narrowing
384 only_show = not widening and not narrowing
415
385
416 # Only print the current narrowspec.
386 # Only print the current narrowspec.
417 if only_show:
387 if only_show:
418 include, exclude = repo.narrowpats
388 include, exclude = repo.narrowpats
419
389
420 ui.pager('tracked')
390 ui.pager('tracked')
421 fm = ui.formatter('narrow', opts)
391 fm = ui.formatter('narrow', opts)
422 for i in sorted(include):
392 for i in sorted(include):
423 fm.startitem()
393 fm.startitem()
424 fm.write('status', '%s ', 'I', label='narrow.included')
394 fm.write('status', '%s ', 'I', label='narrow.included')
425 fm.write('pat', '%s\n', i, label='narrow.included')
395 fm.write('pat', '%s\n', i, label='narrow.included')
426 for i in sorted(exclude):
396 for i in sorted(exclude):
427 fm.startitem()
397 fm.startitem()
428 fm.write('status', '%s ', 'X', label='narrow.excluded')
398 fm.write('status', '%s ', 'X', label='narrow.excluded')
429 fm.write('pat', '%s\n', i, label='narrow.excluded')
399 fm.write('pat', '%s\n', i, label='narrow.excluded')
430 fm.end()
400 fm.end()
431 return 0
401 return 0
432
402
433 with repo.wlock(), repo.lock():
403 with repo.wlock(), repo.lock():
434 cmdutil.bailifchanged(repo)
404 cmdutil.bailifchanged(repo)
435
405
436 # Find the revisions we have in common with the remote. These will
406 # Find the revisions we have in common with the remote. These will
437 # be used for finding local-only changes for narrowing. They will
407 # be used for finding local-only changes for narrowing. They will
438 # also define the set of revisions to update for widening.
408 # also define the set of revisions to update for widening.
439 remotepath = ui.expandpath(remotepath or 'default')
409 remotepath = ui.expandpath(remotepath or 'default')
440 url, branches = hg.parseurl(remotepath)
410 url, branches = hg.parseurl(remotepath)
441 ui.status(_('comparing with %s\n') % util.hidepassword(url))
411 ui.status(_('comparing with %s\n') % util.hidepassword(url))
442 remote = hg.peer(repo, opts, url)
412 remote = hg.peer(repo, opts, url)
443 commoninc = discovery.findcommonincoming(repo, remote)
413 commoninc = discovery.findcommonincoming(repo, remote)
444
414
445 oldincludes, oldexcludes = repo.narrowpats
415 oldincludes, oldexcludes = repo.narrowpats
446 if narrowing:
416 if narrowing:
447 newincludes = oldincludes - removedincludes
417 newincludes = oldincludes - removedincludes
448 newexcludes = oldexcludes | addedexcludes
418 newexcludes = oldexcludes | addedexcludes
449 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
419 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
450 newincludes, newexcludes,
420 newincludes, newexcludes,
451 opts['force_delete_local_changes'])
421 opts['force_delete_local_changes'])
452 # _narrow() updated the narrowspec and _widen() below needs to
422 # _narrow() updated the narrowspec and _widen() below needs to
453 # use the updated values as its base (otherwise removed includes
423 # use the updated values as its base (otherwise removed includes
454 # and addedexcludes will be lost in the resulting narrowspec)
424 # and addedexcludes will be lost in the resulting narrowspec)
455 oldincludes = newincludes
425 oldincludes = newincludes
456 oldexcludes = newexcludes
426 oldexcludes = newexcludes
457
427
458 if widening:
428 if widening:
459 newincludes = oldincludes | addedincludes
429 newincludes = oldincludes | addedincludes
460 newexcludes = oldexcludes - removedexcludes
430 newexcludes = oldexcludes - removedexcludes
461 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
431 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
462
432
463 return 0
433 return 0
@@ -1,66 +1,41 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 _
11 from mercurial import (
10 from mercurial import (
12 error,
13 extensions,
11 extensions,
14 hg,
12 hg,
15 narrowspec,
16 node,
17 wireprotov1server,
13 wireprotov1server,
18 )
14 )
19
15
20 NARROWCAP = 'exp-narrow-1'
16 NARROWCAP = 'exp-narrow-1'
21 ELLIPSESCAP = 'exp-ellipses-1'
17 ELLIPSESCAP = 'exp-ellipses-1'
22
18
23 def uisetup():
19 def uisetup():
24 def peersetup(ui, peer):
25 # We must set up the expansion before reposetup below, since it's used
26 # at clone time before we have a repo.
27 class expandingpeer(peer.__class__):
28 def expandnarrow(self, narrow_include, narrow_exclude, nodes):
29 ui.status(_("expanding narrowspec\n"))
30 if not self.capable('exp-expandnarrow'):
31 raise error.Abort(
32 'peer does not support expanding narrowspecs')
33
34 hex_nodes = (node.hex(n) for n in nodes)
35 new_narrowspec = self._call(
36 'expandnarrow',
37 includepats=','.join(narrow_include),
38 excludepats=','.join(narrow_exclude),
39 nodes=','.join(hex_nodes))
40
41 return narrowspec.parseserverpatterns(new_narrowspec)
42 peer.__class__ = expandingpeer
43 hg.wirepeersetupfuncs.append(peersetup)
44
45 extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
20 extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
46
21
47 def addnarrowcap(orig, repo, proto):
22 def addnarrowcap(orig, repo, proto):
48 """add the narrow capability to the server"""
23 """add the narrow capability to the server"""
49 caps = orig(repo, proto)
24 caps = orig(repo, proto)
50 caps.append(NARROWCAP)
25 caps.append(NARROWCAP)
51 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
26 if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
52 caps.append(ELLIPSESCAP)
27 caps.append(ELLIPSESCAP)
53 return caps
28 return caps
54
29
55 def reposetup(repo):
30 def reposetup(repo):
56 def wirereposetup(ui, peer):
31 def wirereposetup(ui, peer):
57 def wrapped(orig, cmd, *args, **kwargs):
32 def wrapped(orig, cmd, *args, **kwargs):
58 if cmd == 'unbundle':
33 if cmd == 'unbundle':
59 # TODO: don't blindly add include/exclude wireproto
34 # TODO: don't blindly add include/exclude wireproto
60 # arguments to unbundle.
35 # arguments to unbundle.
61 include, exclude = repo.narrowpats
36 include, exclude = repo.narrowpats
62 kwargs[r"includepats"] = ','.join(include)
37 kwargs[r"includepats"] = ','.join(include)
63 kwargs[r"excludepats"] = ','.join(exclude)
38 kwargs[r"excludepats"] = ','.join(exclude)
64 return orig(cmd, *args, **kwargs)
39 return orig(cmd, *args, **kwargs)
65 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
40 extensions.wrapfunction(peer, '_calltwowaystream', wrapped)
66 hg.wirepeersetupfuncs.append(wirereposetup)
41 hg.wirepeersetupfuncs.append(wirereposetup)
@@ -1,247 +1,244 b''
1 # narrowspec.py - methods for working with a narrow view of a repository
1 # narrowspec.py - methods for working with a narrow view of a repository
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
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 match as matchmod,
15 match as matchmod,
16 repository,
16 repository,
17 sparse,
17 sparse,
18 util,
18 util,
19 )
19 )
20
20
21 FILENAME = 'narrowspec'
21 FILENAME = 'narrowspec'
22
22
23 # Pattern prefixes that are allowed in narrow patterns. This list MUST
23 # Pattern prefixes that are allowed in narrow patterns. This list MUST
24 # only contain patterns that are fast and safe to evaluate. Keep in mind
24 # only contain patterns that are fast and safe to evaluate. Keep in mind
25 # that patterns are supplied by clients and executed on remote servers
25 # that patterns are supplied by clients and executed on remote servers
26 # as part of wire protocol commands.
26 # as part of wire protocol commands.
27 VALID_PREFIXES = (
27 VALID_PREFIXES = (
28 b'path:',
28 b'path:',
29 b'rootfilesin:',
29 b'rootfilesin:',
30 )
30 )
31
31
32 def parseserverpatterns(text):
32 def parseserverpatterns(text):
33 """Parses the narrowspec format that's returned by the server."""
33 """Parses the narrowspec format that's returned by the server."""
34 includepats = set()
34 includepats = set()
35 excludepats = set()
35 excludepats = set()
36
36
37 # We get one entry per line, in the format "<key> <value>".
37 # We get one entry per line, in the format "<key> <value>".
38 # It's OK for value to contain other spaces.
38 # It's OK for value to contain other spaces.
39 for kp in (l.split(' ', 1) for l in text.splitlines()):
39 for kp in (l.split(' ', 1) for l in text.splitlines()):
40 if len(kp) != 2:
40 if len(kp) != 2:
41 raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
41 raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
42 key = kp[0]
42 key = kp[0]
43 pat = kp[1]
43 pat = kp[1]
44 if key == 'include':
44 if key == 'include':
45 includepats.add(pat)
45 includepats.add(pat)
46 elif key == 'exclude':
46 elif key == 'exclude':
47 excludepats.add(pat)
47 excludepats.add(pat)
48 else:
48 else:
49 raise error.Abort(_('Invalid key "%s" in server response') % key)
49 raise error.Abort(_('Invalid key "%s" in server response') % key)
50
50
51 return includepats, excludepats
51 return includepats, excludepats
52
52
53 def normalizesplitpattern(kind, pat):
53 def normalizesplitpattern(kind, pat):
54 """Returns the normalized version of a pattern and kind.
54 """Returns the normalized version of a pattern and kind.
55
55
56 Returns a tuple with the normalized kind and normalized pattern.
56 Returns a tuple with the normalized kind and normalized pattern.
57 """
57 """
58 pat = pat.rstrip('/')
58 pat = pat.rstrip('/')
59 _validatepattern(pat)
59 _validatepattern(pat)
60 return kind, pat
60 return kind, pat
61
61
62 def _numlines(s):
62 def _numlines(s):
63 """Returns the number of lines in s, including ending empty lines."""
63 """Returns the number of lines in s, including ending empty lines."""
64 # We use splitlines because it is Unicode-friendly and thus Python 3
64 # We use splitlines because it is Unicode-friendly and thus Python 3
65 # compatible. However, it does not count empty lines at the end, so trick
65 # compatible. However, it does not count empty lines at the end, so trick
66 # it by adding a character at the end.
66 # it by adding a character at the end.
67 return len((s + 'x').splitlines())
67 return len((s + 'x').splitlines())
68
68
69 def _validatepattern(pat):
69 def _validatepattern(pat):
70 """Validates the pattern and aborts if it is invalid.
70 """Validates the pattern and aborts if it is invalid.
71
71
72 Patterns are stored in the narrowspec as newline-separated
72 Patterns are stored in the narrowspec as newline-separated
73 POSIX-style bytestring paths. There's no escaping.
73 POSIX-style bytestring paths. There's no escaping.
74 """
74 """
75
75
76 # We use newlines as separators in the narrowspec file, so don't allow them
76 # We use newlines as separators in the narrowspec file, so don't allow them
77 # in patterns.
77 # in patterns.
78 if _numlines(pat) > 1:
78 if _numlines(pat) > 1:
79 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
79 raise error.Abort(_('newlines are not allowed in narrowspec paths'))
80
80
81 components = pat.split('/')
81 components = pat.split('/')
82 if '.' in components or '..' in components:
82 if '.' in components or '..' in components:
83 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
83 raise error.Abort(_('"." and ".." are not allowed in narrowspec paths'))
84
84
85 def normalizepattern(pattern, defaultkind='path'):
85 def normalizepattern(pattern, defaultkind='path'):
86 """Returns the normalized version of a text-format pattern.
86 """Returns the normalized version of a text-format pattern.
87
87
88 If the pattern has no kind, the default will be added.
88 If the pattern has no kind, the default will be added.
89 """
89 """
90 kind, pat = matchmod._patsplit(pattern, defaultkind)
90 kind, pat = matchmod._patsplit(pattern, defaultkind)
91 return '%s:%s' % normalizesplitpattern(kind, pat)
91 return '%s:%s' % normalizesplitpattern(kind, pat)
92
92
93 def parsepatterns(pats):
93 def parsepatterns(pats):
94 """Parses an iterable of patterns into a typed pattern set.
94 """Parses an iterable of patterns into a typed pattern set.
95
95
96 Patterns are assumed to be ``path:`` if no prefix is present.
96 Patterns are assumed to be ``path:`` if no prefix is present.
97 For safety and performance reasons, only some prefixes are allowed.
97 For safety and performance reasons, only some prefixes are allowed.
98 See ``validatepatterns()``.
98 See ``validatepatterns()``.
99
99
100 This function should be used on patterns that come from the user to
100 This function should be used on patterns that come from the user to
101 normalize and validate them to the internal data structure used for
101 normalize and validate them to the internal data structure used for
102 representing patterns.
102 representing patterns.
103 """
103 """
104 res = {normalizepattern(orig) for orig in pats}
104 res = {normalizepattern(orig) for orig in pats}
105 validatepatterns(res)
105 validatepatterns(res)
106 return res
106 return res
107
107
108 def validatepatterns(pats):
108 def validatepatterns(pats):
109 """Validate that patterns are in the expected data structure and format.
109 """Validate that patterns are in the expected data structure and format.
110
110
111 And that is a set of normalized patterns beginning with ``path:`` or
111 And that is a set of normalized patterns beginning with ``path:`` or
112 ``rootfilesin:``.
112 ``rootfilesin:``.
113
113
114 This function should be used to validate internal data structures
114 This function should be used to validate internal data structures
115 and patterns that are loaded from sources that use the internal,
115 and patterns that are loaded from sources that use the internal,
116 prefixed pattern representation (but can't necessarily be fully trusted).
116 prefixed pattern representation (but can't necessarily be fully trusted).
117 """
117 """
118 if not isinstance(pats, set):
118 if not isinstance(pats, set):
119 raise error.ProgrammingError('narrow patterns should be a set; '
119 raise error.ProgrammingError('narrow patterns should be a set; '
120 'got %r' % pats)
120 'got %r' % pats)
121
121
122 for pat in pats:
122 for pat in pats:
123 if not pat.startswith(VALID_PREFIXES):
123 if not pat.startswith(VALID_PREFIXES):
124 # Use a Mercurial exception because this can happen due to user
124 # Use a Mercurial exception because this can happen due to user
125 # bugs (e.g. manually updating spec file).
125 # bugs (e.g. manually updating spec file).
126 raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
126 raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
127 hint=_('narrow patterns must begin with one of '
127 hint=_('narrow patterns must begin with one of '
128 'the following: %s') %
128 'the following: %s') %
129 ', '.join(VALID_PREFIXES))
129 ', '.join(VALID_PREFIXES))
130
130
131 def format(includes, excludes):
131 def format(includes, excludes):
132 output = '[include]\n'
132 output = '[include]\n'
133 for i in sorted(includes - excludes):
133 for i in sorted(includes - excludes):
134 output += i + '\n'
134 output += i + '\n'
135 output += '[exclude]\n'
135 output += '[exclude]\n'
136 for e in sorted(excludes):
136 for e in sorted(excludes):
137 output += e + '\n'
137 output += e + '\n'
138 return output
138 return output
139
139
140 def match(root, include=None, exclude=None):
140 def match(root, include=None, exclude=None):
141 if not include:
141 if not include:
142 # Passing empty include and empty exclude to matchmod.match()
142 # Passing empty include and empty exclude to matchmod.match()
143 # gives a matcher that matches everything, so explicitly use
143 # gives a matcher that matches everything, so explicitly use
144 # the nevermatcher.
144 # the nevermatcher.
145 return matchmod.never(root, '')
145 return matchmod.never(root, '')
146 return matchmod.match(root, '', [], include=include or [],
146 return matchmod.match(root, '', [], include=include or [],
147 exclude=exclude or [])
147 exclude=exclude or [])
148
148
149 def needsexpansion(includes):
150 return [i for i in includes if i.startswith('include:')]
151
152 def load(repo):
149 def load(repo):
153 try:
150 try:
154 spec = repo.svfs.read(FILENAME)
151 spec = repo.svfs.read(FILENAME)
155 except IOError as e:
152 except IOError as e:
156 # Treat "narrowspec does not exist" the same as "narrowspec file exists
153 # Treat "narrowspec does not exist" the same as "narrowspec file exists
157 # and is empty".
154 # and is empty".
158 if e.errno == errno.ENOENT:
155 if e.errno == errno.ENOENT:
159 return set(), set()
156 return set(), set()
160 raise
157 raise
161 # maybe we should care about the profiles returned too
158 # maybe we should care about the profiles returned too
162 includepats, excludepats, profiles = sparse.parseconfig(repo.ui, spec,
159 includepats, excludepats, profiles = sparse.parseconfig(repo.ui, spec,
163 'narrow')
160 'narrow')
164 if profiles:
161 if profiles:
165 raise error.Abort(_("including other spec files using '%include' is not"
162 raise error.Abort(_("including other spec files using '%include' is not"
166 " supported in narrowspec"))
163 " supported in narrowspec"))
167
164
168 validatepatterns(includepats)
165 validatepatterns(includepats)
169 validatepatterns(excludepats)
166 validatepatterns(excludepats)
170
167
171 return includepats, excludepats
168 return includepats, excludepats
172
169
173 def save(repo, includepats, excludepats):
170 def save(repo, includepats, excludepats):
174 validatepatterns(includepats)
171 validatepatterns(includepats)
175 validatepatterns(excludepats)
172 validatepatterns(excludepats)
176 spec = format(includepats, excludepats)
173 spec = format(includepats, excludepats)
177 repo.svfs.write(FILENAME, spec)
174 repo.svfs.write(FILENAME, spec)
178
175
179 def savebackup(repo, backupname):
176 def savebackup(repo, backupname):
180 if repository.NARROW_REQUIREMENT not in repo.requirements:
177 if repository.NARROW_REQUIREMENT not in repo.requirements:
181 return
178 return
182 vfs = repo.vfs
179 vfs = repo.vfs
183 vfs.tryunlink(backupname)
180 vfs.tryunlink(backupname)
184 util.copyfile(repo.svfs.join(FILENAME), vfs.join(backupname), hardlink=True)
181 util.copyfile(repo.svfs.join(FILENAME), vfs.join(backupname), hardlink=True)
185
182
186 def restorebackup(repo, backupname):
183 def restorebackup(repo, backupname):
187 if repository.NARROW_REQUIREMENT not in repo.requirements:
184 if repository.NARROW_REQUIREMENT not in repo.requirements:
188 return
185 return
189 util.rename(repo.vfs.join(backupname), repo.svfs.join(FILENAME))
186 util.rename(repo.vfs.join(backupname), repo.svfs.join(FILENAME))
190
187
191 def clearbackup(repo, backupname):
188 def clearbackup(repo, backupname):
192 if repository.NARROW_REQUIREMENT not in repo.requirements:
189 if repository.NARROW_REQUIREMENT not in repo.requirements:
193 return
190 return
194 repo.vfs.unlink(backupname)
191 repo.vfs.unlink(backupname)
195
192
196 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
193 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
197 r""" Restricts the patterns according to repo settings,
194 r""" Restricts the patterns according to repo settings,
198 results in a logical AND operation
195 results in a logical AND operation
199
196
200 :param req_includes: requested includes
197 :param req_includes: requested includes
201 :param req_excludes: requested excludes
198 :param req_excludes: requested excludes
202 :param repo_includes: repo includes
199 :param repo_includes: repo includes
203 :param repo_excludes: repo excludes
200 :param repo_excludes: repo excludes
204 :return: include patterns, exclude patterns, and invalid include patterns.
201 :return: include patterns, exclude patterns, and invalid include patterns.
205
202
206 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
203 >>> restrictpatterns({'f1','f2'}, {}, ['f1'], [])
207 (set(['f1']), {}, [])
204 (set(['f1']), {}, [])
208 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
205 >>> restrictpatterns({'f1'}, {}, ['f1','f2'], [])
209 (set(['f1']), {}, [])
206 (set(['f1']), {}, [])
210 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
207 >>> restrictpatterns({'f1/fc1', 'f3/fc3'}, {}, ['f1','f2'], [])
211 (set(['f1/fc1']), {}, [])
208 (set(['f1/fc1']), {}, [])
212 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
209 >>> restrictpatterns({'f1_fc1'}, {}, ['f1','f2'], [])
213 ([], set(['path:.']), [])
210 ([], set(['path:.']), [])
214 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
211 >>> restrictpatterns({'f1/../f2/fc2'}, {}, ['f1','f2'], [])
215 (set(['f2/fc2']), {}, [])
212 (set(['f2/fc2']), {}, [])
216 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
213 >>> restrictpatterns({'f1/../f3/fc3'}, {}, ['f1','f2'], [])
217 ([], set(['path:.']), [])
214 ([], set(['path:.']), [])
218 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
215 >>> restrictpatterns({'f1/$non_exitent_var'}, {}, ['f1','f2'], [])
219 (set(['f1/$non_exitent_var']), {}, [])
216 (set(['f1/$non_exitent_var']), {}, [])
220 """
217 """
221 res_excludes = set(req_excludes)
218 res_excludes = set(req_excludes)
222 res_excludes.update(repo_excludes)
219 res_excludes.update(repo_excludes)
223 invalid_includes = []
220 invalid_includes = []
224 if not req_includes:
221 if not req_includes:
225 res_includes = set(repo_includes)
222 res_includes = set(repo_includes)
226 elif 'path:.' not in repo_includes:
223 elif 'path:.' not in repo_includes:
227 res_includes = []
224 res_includes = []
228 for req_include in req_includes:
225 for req_include in req_includes:
229 req_include = util.expandpath(util.normpath(req_include))
226 req_include = util.expandpath(util.normpath(req_include))
230 if req_include in repo_includes:
227 if req_include in repo_includes:
231 res_includes.append(req_include)
228 res_includes.append(req_include)
232 continue
229 continue
233 valid = False
230 valid = False
234 for repo_include in repo_includes:
231 for repo_include in repo_includes:
235 if req_include.startswith(repo_include + '/'):
232 if req_include.startswith(repo_include + '/'):
236 valid = True
233 valid = True
237 res_includes.append(req_include)
234 res_includes.append(req_include)
238 break
235 break
239 if not valid:
236 if not valid:
240 invalid_includes.append(req_include)
237 invalid_includes.append(req_include)
241 if len(res_includes) == 0:
238 if len(res_includes) == 0:
242 res_excludes = {'path:.'}
239 res_excludes = {'path:.'}
243 else:
240 else:
244 res_includes = set(res_includes)
241 res_includes = set(res_includes)
245 else:
242 else:
246 res_includes = set(req_includes)
243 res_includes = set(req_includes)
247 return res_includes, res_excludes, invalid_includes
244 return res_includes, res_excludes, invalid_includes
General Comments 0
You need to be logged in to leave comments. Login now