##// END OF EJS Templates
narrow: set opts['narrow'] instead of local variable...
Gregory Szorc -
r39582:4afd2af3 default
parent child Browse files
Show More
@@ -1,433 +1,432 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 clonenarrowcmd(orig, ui, repo, *args, **opts):
65 def clonenarrowcmd(orig, ui, repo, *args, **opts):
66 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
66 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
67 opts = pycompat.byteskwargs(opts)
67 opts = pycompat.byteskwargs(opts)
68 wrappedextraprepare = util.nullcontextmanager()
68 wrappedextraprepare = util.nullcontextmanager()
69 opts_narrow = opts['narrow']
70 narrowspecfile = opts['narrowspec']
69 narrowspecfile = opts['narrowspec']
71
70
72 if narrowspecfile:
71 if narrowspecfile:
73 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
72 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
74 ui.status(_("reading narrowspec from '%s'\n") % filepath)
73 ui.status(_("reading narrowspec from '%s'\n") % filepath)
75 try:
74 try:
76 fdata = util.readfile(filepath)
75 fdata = util.readfile(filepath)
77 except IOError as inst:
76 except IOError as inst:
78 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
77 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
79 (filepath, encoding.strtolocal(inst.strerror)))
78 (filepath, encoding.strtolocal(inst.strerror)))
80
79
81 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
80 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
82 if profiles:
81 if profiles:
83 raise error.Abort(_("cannot specify other files using '%include' in"
82 raise error.Abort(_("cannot specify other files using '%include' in"
84 " narrowspec"))
83 " narrowspec"))
85
84
86 narrowspec.validatepatterns(includes)
85 narrowspec.validatepatterns(includes)
87 narrowspec.validatepatterns(excludes)
86 narrowspec.validatepatterns(excludes)
88
87
89 # narrowspec is passed so we should assume that user wants narrow clone
88 # narrowspec is passed so we should assume that user wants narrow clone
90 opts_narrow = True
89 opts['narrow'] = True
91 opts['include'].extend(includes)
90 opts['include'].extend(includes)
92 opts['exclude'].extend(excludes)
91 opts['exclude'].extend(excludes)
93
92
94 if opts_narrow:
93 if opts['narrow']:
95 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
94 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
96 # Create narrow spec patterns from clone flags
95 # Create narrow spec patterns from clone flags
97 includepats = narrowspec.parsepatterns(opts['include'])
96 includepats = narrowspec.parsepatterns(opts['include'])
98 excludepats = narrowspec.parsepatterns(opts['exclude'])
97 excludepats = narrowspec.parsepatterns(opts['exclude'])
99
98
100 if not includepats and excludepats:
99 if not includepats and excludepats:
101 # If nothing was included, we assume the user meant to include
100 # If nothing was included, we assume the user meant to include
102 # everything, except what they asked to exclude.
101 # everything, except what they asked to exclude.
103 includepats = {'path:.'}
102 includepats = {'path:.'}
104
103
105 pullop.repo.setnarrowpats(includepats, excludepats)
104 pullop.repo.setnarrowpats(includepats, excludepats)
106
105
107 # This will populate 'includepats' etc with the values from the
106 # This will populate 'includepats' etc with the values from the
108 # narrowspec we just saved.
107 # narrowspec we just saved.
109 orig(pullop, kwargs)
108 orig(pullop, kwargs)
110
109
111 if opts.get('depth'):
110 if opts.get('depth'):
112 kwargs['depth'] = opts['depth']
111 kwargs['depth'] = opts['depth']
113 wrappedextraprepare = extensions.wrappedfunction(exchange,
112 wrappedextraprepare = extensions.wrappedfunction(exchange,
114 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
113 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
115
114
116 def pullnarrow(orig, repo, *args, **kwargs):
115 def pullnarrow(orig, repo, *args, **kwargs):
117 if opts_narrow:
116 if opts['narrow']:
118 repo.requirements.add(repository.NARROW_REQUIREMENT)
117 repo.requirements.add(repository.NARROW_REQUIREMENT)
119 repo._writerequirements()
118 repo._writerequirements()
120
119
121 return orig(repo, *args, **kwargs)
120 return orig(repo, *args, **kwargs)
122
121
123 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
122 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
124
123
125 with wrappedextraprepare, wrappedpull:
124 with wrappedextraprepare, wrappedpull:
126 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
125 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
127
126
128 def pullnarrowcmd(orig, ui, repo, *args, **opts):
127 def pullnarrowcmd(orig, ui, repo, *args, **opts):
129 """Wraps pull command to allow modifying narrow spec."""
128 """Wraps pull command to allow modifying narrow spec."""
130 wrappedextraprepare = util.nullcontextmanager()
129 wrappedextraprepare = util.nullcontextmanager()
131 if repository.NARROW_REQUIREMENT in repo.requirements:
130 if repository.NARROW_REQUIREMENT in repo.requirements:
132
131
133 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
132 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
134 orig(pullop, kwargs)
133 orig(pullop, kwargs)
135 if opts.get(r'depth'):
134 if opts.get(r'depth'):
136 kwargs['depth'] = opts[r'depth']
135 kwargs['depth'] = opts[r'depth']
137 wrappedextraprepare = extensions.wrappedfunction(exchange,
136 wrappedextraprepare = extensions.wrappedfunction(exchange,
138 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
137 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
139
138
140 with wrappedextraprepare:
139 with wrappedextraprepare:
141 return orig(ui, repo, *args, **opts)
140 return orig(ui, repo, *args, **opts)
142
141
143 def archivenarrowcmd(orig, ui, repo, *args, **opts):
142 def archivenarrowcmd(orig, ui, repo, *args, **opts):
144 """Wraps archive command to narrow the default includes."""
143 """Wraps archive command to narrow the default includes."""
145 if repository.NARROW_REQUIREMENT in repo.requirements:
144 if repository.NARROW_REQUIREMENT in repo.requirements:
146 repo_includes, repo_excludes = repo.narrowpats
145 repo_includes, repo_excludes = repo.narrowpats
147 includes = set(opts.get(r'include', []))
146 includes = set(opts.get(r'include', []))
148 excludes = set(opts.get(r'exclude', []))
147 excludes = set(opts.get(r'exclude', []))
149 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
148 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
150 includes, excludes, repo_includes, repo_excludes)
149 includes, excludes, repo_includes, repo_excludes)
151 if includes:
150 if includes:
152 opts[r'include'] = includes
151 opts[r'include'] = includes
153 if excludes:
152 if excludes:
154 opts[r'exclude'] = excludes
153 opts[r'exclude'] = excludes
155 return orig(ui, repo, *args, **opts)
154 return orig(ui, repo, *args, **opts)
156
155
157 def pullbundle2extraprepare(orig, pullop, kwargs):
156 def pullbundle2extraprepare(orig, pullop, kwargs):
158 repo = pullop.repo
157 repo = pullop.repo
159 if repository.NARROW_REQUIREMENT not in repo.requirements:
158 if repository.NARROW_REQUIREMENT not in repo.requirements:
160 return orig(pullop, kwargs)
159 return orig(pullop, kwargs)
161
160
162 if narrowwirepeer.NARROWCAP not in pullop.remote.capabilities():
161 if narrowwirepeer.NARROWCAP not in pullop.remote.capabilities():
163 raise error.Abort(_("server doesn't support narrow clones"))
162 raise error.Abort(_("server doesn't support narrow clones"))
164 orig(pullop, kwargs)
163 orig(pullop, kwargs)
165 kwargs['narrow'] = True
164 kwargs['narrow'] = True
166 include, exclude = repo.narrowpats
165 include, exclude = repo.narrowpats
167 kwargs['oldincludepats'] = include
166 kwargs['oldincludepats'] = include
168 kwargs['oldexcludepats'] = exclude
167 kwargs['oldexcludepats'] = exclude
169 kwargs['includepats'] = include
168 kwargs['includepats'] = include
170 kwargs['excludepats'] = exclude
169 kwargs['excludepats'] = exclude
171 # calculate known nodes only in ellipses cases because in non-ellipses cases
170 # calculate known nodes only in ellipses cases because in non-ellipses cases
172 # we have all the nodes
171 # we have all the nodes
173 if narrowwirepeer.ELLIPSESCAP in pullop.remote.capabilities():
172 if narrowwirepeer.ELLIPSESCAP in pullop.remote.capabilities():
174 kwargs['known'] = [node.hex(ctx.node()) for ctx in
173 kwargs['known'] = [node.hex(ctx.node()) for ctx in
175 repo.set('::%ln', pullop.common)
174 repo.set('::%ln', pullop.common)
176 if ctx.node() != node.nullid]
175 if ctx.node() != node.nullid]
177 if not kwargs['known']:
176 if not kwargs['known']:
178 # Mercurial serializes an empty list as '' and deserializes it as
177 # Mercurial serializes an empty list as '' and deserializes it as
179 # [''], so delete it instead to avoid handling the empty string on
178 # [''], so delete it instead to avoid handling the empty string on
180 # the server.
179 # the server.
181 del kwargs['known']
180 del kwargs['known']
182
181
183 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
182 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
184 pullbundle2extraprepare)
183 pullbundle2extraprepare)
185
184
186 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
185 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
187 newincludes, newexcludes, force):
186 newincludes, newexcludes, force):
188 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
187 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
189 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
188 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
190
189
191 # This is essentially doing "hg outgoing" to find all local-only
190 # This is essentially doing "hg outgoing" to find all local-only
192 # commits. We will then check that the local-only commits don't
191 # commits. We will then check that the local-only commits don't
193 # have any changes to files that will be untracked.
192 # have any changes to files that will be untracked.
194 unfi = repo.unfiltered()
193 unfi = repo.unfiltered()
195 outgoing = discovery.findcommonoutgoing(unfi, remote,
194 outgoing = discovery.findcommonoutgoing(unfi, remote,
196 commoninc=commoninc)
195 commoninc=commoninc)
197 ui.status(_('looking for local changes to affected paths\n'))
196 ui.status(_('looking for local changes to affected paths\n'))
198 localnodes = []
197 localnodes = []
199 for n in itertools.chain(outgoing.missing, outgoing.excluded):
198 for n in itertools.chain(outgoing.missing, outgoing.excluded):
200 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
199 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
201 localnodes.append(n)
200 localnodes.append(n)
202 revstostrip = unfi.revs('descendants(%ln)', localnodes)
201 revstostrip = unfi.revs('descendants(%ln)', localnodes)
203 hiddenrevs = repoview.filterrevs(repo, 'visible')
202 hiddenrevs = repoview.filterrevs(repo, 'visible')
204 visibletostrip = list(repo.changelog.node(r)
203 visibletostrip = list(repo.changelog.node(r)
205 for r in (revstostrip - hiddenrevs))
204 for r in (revstostrip - hiddenrevs))
206 if visibletostrip:
205 if visibletostrip:
207 ui.status(_('The following changeset(s) or their ancestors have '
206 ui.status(_('The following changeset(s) or their ancestors have '
208 'local changes not on the remote:\n'))
207 'local changes not on the remote:\n'))
209 maxnodes = 10
208 maxnodes = 10
210 if ui.verbose or len(visibletostrip) <= maxnodes:
209 if ui.verbose or len(visibletostrip) <= maxnodes:
211 for n in visibletostrip:
210 for n in visibletostrip:
212 ui.status('%s\n' % node.short(n))
211 ui.status('%s\n' % node.short(n))
213 else:
212 else:
214 for n in visibletostrip[:maxnodes]:
213 for n in visibletostrip[:maxnodes]:
215 ui.status('%s\n' % node.short(n))
214 ui.status('%s\n' % node.short(n))
216 ui.status(_('...and %d more, use --verbose to list all\n') %
215 ui.status(_('...and %d more, use --verbose to list all\n') %
217 (len(visibletostrip) - maxnodes))
216 (len(visibletostrip) - maxnodes))
218 if not force:
217 if not force:
219 raise error.Abort(_('local changes found'),
218 raise error.Abort(_('local changes found'),
220 hint=_('use --force-delete-local-changes to '
219 hint=_('use --force-delete-local-changes to '
221 'ignore'))
220 'ignore'))
222
221
223 with ui.uninterruptable():
222 with ui.uninterruptable():
224 if revstostrip:
223 if revstostrip:
225 tostrip = [unfi.changelog.node(r) for r in revstostrip]
224 tostrip = [unfi.changelog.node(r) for r in revstostrip]
226 if repo['.'].node() in tostrip:
225 if repo['.'].node() in tostrip:
227 # stripping working copy, so move to a different commit first
226 # stripping working copy, so move to a different commit first
228 urev = max(repo.revs('(::%n) - %ln + null',
227 urev = max(repo.revs('(::%n) - %ln + null',
229 repo['.'].node(), visibletostrip))
228 repo['.'].node(), visibletostrip))
230 hg.clean(repo, urev)
229 hg.clean(repo, urev)
231 repair.strip(ui, unfi, tostrip, topic='narrow')
230 repair.strip(ui, unfi, tostrip, topic='narrow')
232
231
233 todelete = []
232 todelete = []
234 for f, f2, size in repo.store.datafiles():
233 for f, f2, size in repo.store.datafiles():
235 if f.startswith('data/'):
234 if f.startswith('data/'):
236 file = f[5:-2]
235 file = f[5:-2]
237 if not newmatch(file):
236 if not newmatch(file):
238 todelete.append(f)
237 todelete.append(f)
239 elif f.startswith('meta/'):
238 elif f.startswith('meta/'):
240 dir = f[5:-13]
239 dir = f[5:-13]
241 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
240 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
242 include = True
241 include = True
243 for d in dirs:
242 for d in dirs:
244 visit = newmatch.visitdir(d)
243 visit = newmatch.visitdir(d)
245 if not visit:
244 if not visit:
246 include = False
245 include = False
247 break
246 break
248 if visit == 'all':
247 if visit == 'all':
249 break
248 break
250 if not include:
249 if not include:
251 todelete.append(f)
250 todelete.append(f)
252
251
253 repo.destroying()
252 repo.destroying()
254
253
255 with repo.transaction("narrowing"):
254 with repo.transaction("narrowing"):
256 for f in todelete:
255 for f in todelete:
257 ui.status(_('deleting %s\n') % f)
256 ui.status(_('deleting %s\n') % f)
258 util.unlinkpath(repo.svfs.join(f))
257 util.unlinkpath(repo.svfs.join(f))
259 repo.store.markremoved(f)
258 repo.store.markremoved(f)
260
259
261 for f in repo.dirstate:
260 for f in repo.dirstate:
262 if not newmatch(f):
261 if not newmatch(f):
263 repo.dirstate.drop(f)
262 repo.dirstate.drop(f)
264 repo.wvfs.unlinkpath(f)
263 repo.wvfs.unlinkpath(f)
265 repo.setnarrowpats(newincludes, newexcludes)
264 repo.setnarrowpats(newincludes, newexcludes)
266
265
267 repo.destroyed()
266 repo.destroyed()
268
267
269 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
268 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
270 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
269 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
271
270
272 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
271 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
273 orig(pullop, kwargs)
272 orig(pullop, kwargs)
274 # The old{in,ex}cludepats have already been set by orig()
273 # The old{in,ex}cludepats have already been set by orig()
275 kwargs['includepats'] = newincludes
274 kwargs['includepats'] = newincludes
276 kwargs['excludepats'] = newexcludes
275 kwargs['excludepats'] = newexcludes
277 kwargs['widen'] = True
276 kwargs['widen'] = True
278 wrappedextraprepare = extensions.wrappedfunction(exchange,
277 wrappedextraprepare = extensions.wrappedfunction(exchange,
279 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
278 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
280
279
281 # define a function that narrowbundle2 can call after creating the
280 # define a function that narrowbundle2 can call after creating the
282 # backup bundle, but before applying the bundle from the server
281 # backup bundle, but before applying the bundle from the server
283 def setnewnarrowpats():
282 def setnewnarrowpats():
284 repo.setnarrowpats(newincludes, newexcludes)
283 repo.setnarrowpats(newincludes, newexcludes)
285 repo.setnewnarrowpats = setnewnarrowpats
284 repo.setnewnarrowpats = setnewnarrowpats
286
285
287 with ui.uninterruptable():
286 with ui.uninterruptable():
288 ds = repo.dirstate
287 ds = repo.dirstate
289 p1, p2 = ds.p1(), ds.p2()
288 p1, p2 = ds.p1(), ds.p2()
290 with ds.parentchange():
289 with ds.parentchange():
291 ds.setparents(node.nullid, node.nullid)
290 ds.setparents(node.nullid, node.nullid)
292 common = commoninc[0]
291 common = commoninc[0]
293 with wrappedextraprepare:
292 with wrappedextraprepare:
294 exchange.pull(repo, remote, heads=common)
293 exchange.pull(repo, remote, heads=common)
295 with ds.parentchange():
294 with ds.parentchange():
296 ds.setparents(p1, p2)
295 ds.setparents(p1, p2)
297
296
298 repo.setnewnarrowpats()
297 repo.setnewnarrowpats()
299 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
298 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
300 addgaction = actions['g'].append
299 addgaction = actions['g'].append
301
300
302 mf = repo['.'].manifest().matches(newmatch)
301 mf = repo['.'].manifest().matches(newmatch)
303 for f, fn in mf.iteritems():
302 for f, fn in mf.iteritems():
304 if f not in repo.dirstate:
303 if f not in repo.dirstate:
305 addgaction((f, (mf.flags(f), False),
304 addgaction((f, (mf.flags(f), False),
306 "add from widened narrow clone"))
305 "add from widened narrow clone"))
307
306
308 merge.applyupdates(repo, actions, wctx=repo[None],
307 merge.applyupdates(repo, actions, wctx=repo[None],
309 mctx=repo['.'], overwrite=False)
308 mctx=repo['.'], overwrite=False)
310 merge.recordupdates(repo, actions, branchmerge=False)
309 merge.recordupdates(repo, actions, branchmerge=False)
311
310
312 # TODO(rdamazio): Make new matcher format and update description
311 # TODO(rdamazio): Make new matcher format and update description
313 @command('tracked',
312 @command('tracked',
314 [('', 'addinclude', [], _('new paths to include')),
313 [('', 'addinclude', [], _('new paths to include')),
315 ('', 'removeinclude', [], _('old paths to no longer include')),
314 ('', 'removeinclude', [], _('old paths to no longer include')),
316 ('', 'addexclude', [], _('new paths to exclude')),
315 ('', 'addexclude', [], _('new paths to exclude')),
317 ('', 'import-rules', '', _('import narrowspecs from a file')),
316 ('', 'import-rules', '', _('import narrowspecs from a file')),
318 ('', 'removeexclude', [], _('old paths to no longer exclude')),
317 ('', 'removeexclude', [], _('old paths to no longer exclude')),
319 ('', 'clear', False, _('whether to replace the existing narrowspec')),
318 ('', 'clear', False, _('whether to replace the existing narrowspec')),
320 ('', 'force-delete-local-changes', False,
319 ('', 'force-delete-local-changes', False,
321 _('forces deletion of local changes when narrowing')),
320 _('forces deletion of local changes when narrowing')),
322 ] + commands.remoteopts,
321 ] + commands.remoteopts,
323 _('[OPTIONS]... [REMOTE]'),
322 _('[OPTIONS]... [REMOTE]'),
324 inferrepo=True)
323 inferrepo=True)
325 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
324 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
326 """show or change the current narrowspec
325 """show or change the current narrowspec
327
326
328 With no argument, shows the current narrowspec entries, one per line. Each
327 With no argument, shows the current narrowspec entries, one per line. Each
329 line will be prefixed with 'I' or 'X' for included or excluded patterns,
328 line will be prefixed with 'I' or 'X' for included or excluded patterns,
330 respectively.
329 respectively.
331
330
332 The narrowspec is comprised of expressions to match remote files and/or
331 The narrowspec is comprised of expressions to match remote files and/or
333 directories that should be pulled into your client.
332 directories that should be pulled into your client.
334 The narrowspec has *include* and *exclude* expressions, with excludes always
333 The narrowspec has *include* and *exclude* expressions, with excludes always
335 trumping includes: that is, if a file matches an exclude expression, it will
334 trumping includes: that is, if a file matches an exclude expression, it will
336 be excluded even if it also matches an include expression.
335 be excluded even if it also matches an include expression.
337 Excluding files that were never included has no effect.
336 Excluding files that were never included has no effect.
338
337
339 Each included or excluded entry is in the format described by
338 Each included or excluded entry is in the format described by
340 'hg help patterns'.
339 'hg help patterns'.
341
340
342 The options allow you to add or remove included and excluded expressions.
341 The options allow you to add or remove included and excluded expressions.
343
342
344 If --clear is specified, then all previous includes and excludes are DROPPED
343 If --clear is specified, then all previous includes and excludes are DROPPED
345 and replaced by the new ones specified to --addinclude and --addexclude.
344 and replaced by the new ones specified to --addinclude and --addexclude.
346 If --clear is specified without any further options, the narrowspec will be
345 If --clear is specified without any further options, the narrowspec will be
347 empty and will not match any files.
346 empty and will not match any files.
348 """
347 """
349 opts = pycompat.byteskwargs(opts)
348 opts = pycompat.byteskwargs(opts)
350 if repository.NARROW_REQUIREMENT not in repo.requirements:
349 if repository.NARROW_REQUIREMENT not in repo.requirements:
351 ui.warn(_('The narrow command is only supported on respositories cloned'
350 ui.warn(_('The narrow command is only supported on respositories cloned'
352 ' with --narrow.\n'))
351 ' with --narrow.\n'))
353 return 1
352 return 1
354
353
355 # Before supporting, decide whether it "hg tracked --clear" should mean
354 # Before supporting, decide whether it "hg tracked --clear" should mean
356 # tracking no paths or all paths.
355 # tracking no paths or all paths.
357 if opts['clear']:
356 if opts['clear']:
358 ui.warn(_('The --clear option is not yet supported.\n'))
357 ui.warn(_('The --clear option is not yet supported.\n'))
359 return 1
358 return 1
360
359
361 # import rules from a file
360 # import rules from a file
362 newrules = opts.get('import_rules')
361 newrules = opts.get('import_rules')
363 if newrules:
362 if newrules:
364 try:
363 try:
365 filepath = os.path.join(pycompat.getcwd(), newrules)
364 filepath = os.path.join(pycompat.getcwd(), newrules)
366 fdata = util.readfile(filepath)
365 fdata = util.readfile(filepath)
367 except IOError as inst:
366 except IOError as inst:
368 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
367 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
369 (filepath, encoding.strtolocal(inst.strerror)))
368 (filepath, encoding.strtolocal(inst.strerror)))
370 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
369 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
371 'narrow')
370 'narrow')
372 if profiles:
371 if profiles:
373 raise error.Abort(_("including other spec files using '%include' "
372 raise error.Abort(_("including other spec files using '%include' "
374 "is not supported in narrowspec"))
373 "is not supported in narrowspec"))
375 opts['addinclude'].extend(includepats)
374 opts['addinclude'].extend(includepats)
376 opts['addexclude'].extend(excludepats)
375 opts['addexclude'].extend(excludepats)
377
376
378 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
377 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
379 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
378 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
380 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
379 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
381 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
380 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
382 widening = addedincludes or removedexcludes
381 widening = addedincludes or removedexcludes
383 narrowing = removedincludes or addedexcludes
382 narrowing = removedincludes or addedexcludes
384 only_show = not widening and not narrowing
383 only_show = not widening and not narrowing
385
384
386 # Only print the current narrowspec.
385 # Only print the current narrowspec.
387 if only_show:
386 if only_show:
388 include, exclude = repo.narrowpats
387 include, exclude = repo.narrowpats
389
388
390 ui.pager('tracked')
389 ui.pager('tracked')
391 fm = ui.formatter('narrow', opts)
390 fm = ui.formatter('narrow', opts)
392 for i in sorted(include):
391 for i in sorted(include):
393 fm.startitem()
392 fm.startitem()
394 fm.write('status', '%s ', 'I', label='narrow.included')
393 fm.write('status', '%s ', 'I', label='narrow.included')
395 fm.write('pat', '%s\n', i, label='narrow.included')
394 fm.write('pat', '%s\n', i, label='narrow.included')
396 for i in sorted(exclude):
395 for i in sorted(exclude):
397 fm.startitem()
396 fm.startitem()
398 fm.write('status', '%s ', 'X', label='narrow.excluded')
397 fm.write('status', '%s ', 'X', label='narrow.excluded')
399 fm.write('pat', '%s\n', i, label='narrow.excluded')
398 fm.write('pat', '%s\n', i, label='narrow.excluded')
400 fm.end()
399 fm.end()
401 return 0
400 return 0
402
401
403 with repo.wlock(), repo.lock():
402 with repo.wlock(), repo.lock():
404 cmdutil.bailifchanged(repo)
403 cmdutil.bailifchanged(repo)
405
404
406 # Find the revisions we have in common with the remote. These will
405 # Find the revisions we have in common with the remote. These will
407 # be used for finding local-only changes for narrowing. They will
406 # be used for finding local-only changes for narrowing. They will
408 # also define the set of revisions to update for widening.
407 # also define the set of revisions to update for widening.
409 remotepath = ui.expandpath(remotepath or 'default')
408 remotepath = ui.expandpath(remotepath or 'default')
410 url, branches = hg.parseurl(remotepath)
409 url, branches = hg.parseurl(remotepath)
411 ui.status(_('comparing with %s\n') % util.hidepassword(url))
410 ui.status(_('comparing with %s\n') % util.hidepassword(url))
412 remote = hg.peer(repo, opts, url)
411 remote = hg.peer(repo, opts, url)
413 commoninc = discovery.findcommonincoming(repo, remote)
412 commoninc = discovery.findcommonincoming(repo, remote)
414
413
415 oldincludes, oldexcludes = repo.narrowpats
414 oldincludes, oldexcludes = repo.narrowpats
416 if narrowing:
415 if narrowing:
417 newincludes = oldincludes - removedincludes
416 newincludes = oldincludes - removedincludes
418 newexcludes = oldexcludes | addedexcludes
417 newexcludes = oldexcludes | addedexcludes
419 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
418 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
420 newincludes, newexcludes,
419 newincludes, newexcludes,
421 opts['force_delete_local_changes'])
420 opts['force_delete_local_changes'])
422 # _narrow() updated the narrowspec and _widen() below needs to
421 # _narrow() updated the narrowspec and _widen() below needs to
423 # use the updated values as its base (otherwise removed includes
422 # use the updated values as its base (otherwise removed includes
424 # and addedexcludes will be lost in the resulting narrowspec)
423 # and addedexcludes will be lost in the resulting narrowspec)
425 oldincludes = newincludes
424 oldincludes = newincludes
426 oldexcludes = newexcludes
425 oldexcludes = newexcludes
427
426
428 if widening:
427 if widening:
429 newincludes = oldincludes | addedincludes
428 newincludes = oldincludes | addedincludes
430 newexcludes = oldexcludes - removedexcludes
429 newexcludes = oldexcludes - removedexcludes
431 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
430 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
432
431
433 return 0
432 return 0
General Comments 0
You need to be logged in to leave comments. Login now