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