##// END OF EJS Templates
narrowcommands: drop unnecessary adding of '' for root directory...
Martin von Zweigbergk -
r42532:7d4ee14f default
parent child Browse files
Show More
@@ -1,463 +1,463 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 bundle2,
14 bundle2,
15 cmdutil,
15 cmdutil,
16 commands,
16 commands,
17 discovery,
17 discovery,
18 encoding,
18 encoding,
19 error,
19 error,
20 exchange,
20 exchange,
21 extensions,
21 extensions,
22 hg,
22 hg,
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 wireprototypes,
32 wireprototypes,
33 )
33 )
34
34
35 table = {}
35 table = {}
36 command = registrar.command(table)
36 command = registrar.command(table)
37
37
38 def setup():
38 def setup():
39 """Wraps user-facing mercurial commands with narrow-aware versions."""
39 """Wraps user-facing mercurial commands with narrow-aware versions."""
40
40
41 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
41 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
42 entry[1].append(('', 'narrow', None,
42 entry[1].append(('', 'narrow', None,
43 _("create a narrow clone of select files")))
43 _("create a narrow clone of select files")))
44 entry[1].append(('', 'depth', '',
44 entry[1].append(('', 'depth', '',
45 _("limit the history fetched by distance from heads")))
45 _("limit the history fetched by distance from heads")))
46 entry[1].append(('', 'narrowspec', '',
46 entry[1].append(('', 'narrowspec', '',
47 _("read narrowspecs from file")))
47 _("read narrowspecs from file")))
48 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
48 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
49 if 'sparse' not in extensions.enabled():
49 if 'sparse' not in extensions.enabled():
50 entry[1].append(('', 'include', [],
50 entry[1].append(('', 'include', [],
51 _("specifically fetch this file/directory")))
51 _("specifically fetch this file/directory")))
52 entry[1].append(
52 entry[1].append(
53 ('', 'exclude', [],
53 ('', 'exclude', [],
54 _("do not fetch this file/directory, even if included")))
54 _("do not fetch this file/directory, even if included")))
55
55
56 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
56 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
57 entry[1].append(('', 'depth', '',
57 entry[1].append(('', 'depth', '',
58 _("limit the history fetched by distance from heads")))
58 _("limit the history fetched by distance from heads")))
59
59
60 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
60 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
61
61
62 def clonenarrowcmd(orig, ui, repo, *args, **opts):
62 def clonenarrowcmd(orig, ui, repo, *args, **opts):
63 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
63 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
64 opts = pycompat.byteskwargs(opts)
64 opts = pycompat.byteskwargs(opts)
65 wrappedextraprepare = util.nullcontextmanager()
65 wrappedextraprepare = util.nullcontextmanager()
66 narrowspecfile = opts['narrowspec']
66 narrowspecfile = opts['narrowspec']
67
67
68 if narrowspecfile:
68 if narrowspecfile:
69 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
69 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
70 ui.status(_("reading narrowspec from '%s'\n") % filepath)
70 ui.status(_("reading narrowspec from '%s'\n") % filepath)
71 try:
71 try:
72 fdata = util.readfile(filepath)
72 fdata = util.readfile(filepath)
73 except IOError as inst:
73 except IOError as inst:
74 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
74 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
75 (filepath, encoding.strtolocal(inst.strerror)))
75 (filepath, encoding.strtolocal(inst.strerror)))
76
76
77 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
77 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
78 if profiles:
78 if profiles:
79 raise error.Abort(_("cannot specify other files using '%include' in"
79 raise error.Abort(_("cannot specify other files using '%include' in"
80 " narrowspec"))
80 " narrowspec"))
81
81
82 narrowspec.validatepatterns(includes)
82 narrowspec.validatepatterns(includes)
83 narrowspec.validatepatterns(excludes)
83 narrowspec.validatepatterns(excludes)
84
84
85 # narrowspec is passed so we should assume that user wants narrow clone
85 # narrowspec is passed so we should assume that user wants narrow clone
86 opts['narrow'] = True
86 opts['narrow'] = True
87 opts['include'].extend(includes)
87 opts['include'].extend(includes)
88 opts['exclude'].extend(excludes)
88 opts['exclude'].extend(excludes)
89
89
90 if opts['narrow']:
90 if opts['narrow']:
91 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
91 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
92 orig(pullop, kwargs)
92 orig(pullop, kwargs)
93
93
94 if opts.get('depth'):
94 if opts.get('depth'):
95 kwargs['depth'] = opts['depth']
95 kwargs['depth'] = opts['depth']
96 wrappedextraprepare = extensions.wrappedfunction(exchange,
96 wrappedextraprepare = extensions.wrappedfunction(exchange,
97 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
97 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
98
98
99 with wrappedextraprepare:
99 with wrappedextraprepare:
100 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
100 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
101
101
102 def pullnarrowcmd(orig, ui, repo, *args, **opts):
102 def pullnarrowcmd(orig, ui, repo, *args, **opts):
103 """Wraps pull command to allow modifying narrow spec."""
103 """Wraps pull command to allow modifying narrow spec."""
104 wrappedextraprepare = util.nullcontextmanager()
104 wrappedextraprepare = util.nullcontextmanager()
105 if repository.NARROW_REQUIREMENT in repo.requirements:
105 if repository.NARROW_REQUIREMENT in repo.requirements:
106
106
107 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
107 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
108 orig(pullop, kwargs)
108 orig(pullop, kwargs)
109 if opts.get(r'depth'):
109 if opts.get(r'depth'):
110 kwargs['depth'] = opts[r'depth']
110 kwargs['depth'] = opts[r'depth']
111 wrappedextraprepare = extensions.wrappedfunction(exchange,
111 wrappedextraprepare = extensions.wrappedfunction(exchange,
112 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
112 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
113
113
114 with wrappedextraprepare:
114 with wrappedextraprepare:
115 return orig(ui, repo, *args, **opts)
115 return orig(ui, repo, *args, **opts)
116
116
117 def archivenarrowcmd(orig, ui, repo, *args, **opts):
117 def archivenarrowcmd(orig, ui, repo, *args, **opts):
118 """Wraps archive command to narrow the default includes."""
118 """Wraps archive command to narrow the default includes."""
119 if repository.NARROW_REQUIREMENT in repo.requirements:
119 if repository.NARROW_REQUIREMENT in repo.requirements:
120 repo_includes, repo_excludes = repo.narrowpats
120 repo_includes, repo_excludes = repo.narrowpats
121 includes = set(opts.get(r'include', []))
121 includes = set(opts.get(r'include', []))
122 excludes = set(opts.get(r'exclude', []))
122 excludes = set(opts.get(r'exclude', []))
123 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
123 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
124 includes, excludes, repo_includes, repo_excludes)
124 includes, excludes, repo_includes, repo_excludes)
125 if includes:
125 if includes:
126 opts[r'include'] = includes
126 opts[r'include'] = includes
127 if excludes:
127 if excludes:
128 opts[r'exclude'] = excludes
128 opts[r'exclude'] = excludes
129 return orig(ui, repo, *args, **opts)
129 return orig(ui, repo, *args, **opts)
130
130
131 def pullbundle2extraprepare(orig, pullop, kwargs):
131 def pullbundle2extraprepare(orig, pullop, kwargs):
132 repo = pullop.repo
132 repo = pullop.repo
133 if repository.NARROW_REQUIREMENT not in repo.requirements:
133 if repository.NARROW_REQUIREMENT not in repo.requirements:
134 return orig(pullop, kwargs)
134 return orig(pullop, kwargs)
135
135
136 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
136 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
137 raise error.Abort(_("server does not support narrow clones"))
137 raise error.Abort(_("server does not support narrow clones"))
138 orig(pullop, kwargs)
138 orig(pullop, kwargs)
139 kwargs['narrow'] = True
139 kwargs['narrow'] = True
140 include, exclude = repo.narrowpats
140 include, exclude = repo.narrowpats
141 kwargs['oldincludepats'] = include
141 kwargs['oldincludepats'] = include
142 kwargs['oldexcludepats'] = exclude
142 kwargs['oldexcludepats'] = exclude
143 if include:
143 if include:
144 kwargs['includepats'] = include
144 kwargs['includepats'] = include
145 if exclude:
145 if exclude:
146 kwargs['excludepats'] = exclude
146 kwargs['excludepats'] = exclude
147 # calculate known nodes only in ellipses cases because in non-ellipses cases
147 # calculate known nodes only in ellipses cases because in non-ellipses cases
148 # we have all the nodes
148 # we have all the nodes
149 if wireprototypes.ELLIPSESCAP in pullop.remote.capabilities():
149 if wireprototypes.ELLIPSESCAP in pullop.remote.capabilities():
150 kwargs['known'] = [node.hex(ctx.node()) for ctx in
150 kwargs['known'] = [node.hex(ctx.node()) for ctx in
151 repo.set('::%ln', pullop.common)
151 repo.set('::%ln', pullop.common)
152 if ctx.node() != node.nullid]
152 if ctx.node() != node.nullid]
153 if not kwargs['known']:
153 if not kwargs['known']:
154 # Mercurial serializes an empty list as '' and deserializes it as
154 # Mercurial serializes an empty list as '' and deserializes it as
155 # [''], so delete it instead to avoid handling the empty string on
155 # [''], so delete it instead to avoid handling the empty string on
156 # the server.
156 # the server.
157 del kwargs['known']
157 del kwargs['known']
158
158
159 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
159 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
160 pullbundle2extraprepare)
160 pullbundle2extraprepare)
161
161
162 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
162 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
163 newincludes, newexcludes, force):
163 newincludes, newexcludes, force):
164 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
164 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
165 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
165 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
166
166
167 # This is essentially doing "hg outgoing" to find all local-only
167 # This is essentially doing "hg outgoing" to find all local-only
168 # commits. We will then check that the local-only commits don't
168 # commits. We will then check that the local-only commits don't
169 # have any changes to files that will be untracked.
169 # have any changes to files that will be untracked.
170 unfi = repo.unfiltered()
170 unfi = repo.unfiltered()
171 outgoing = discovery.findcommonoutgoing(unfi, remote,
171 outgoing = discovery.findcommonoutgoing(unfi, remote,
172 commoninc=commoninc)
172 commoninc=commoninc)
173 ui.status(_('looking for local changes to affected paths\n'))
173 ui.status(_('looking for local changes to affected paths\n'))
174 localnodes = []
174 localnodes = []
175 for n in itertools.chain(outgoing.missing, outgoing.excluded):
175 for n in itertools.chain(outgoing.missing, outgoing.excluded):
176 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
176 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
177 localnodes.append(n)
177 localnodes.append(n)
178 revstostrip = unfi.revs('descendants(%ln)', localnodes)
178 revstostrip = unfi.revs('descendants(%ln)', localnodes)
179 hiddenrevs = repoview.filterrevs(repo, 'visible')
179 hiddenrevs = repoview.filterrevs(repo, 'visible')
180 visibletostrip = list(repo.changelog.node(r)
180 visibletostrip = list(repo.changelog.node(r)
181 for r in (revstostrip - hiddenrevs))
181 for r in (revstostrip - hiddenrevs))
182 if visibletostrip:
182 if visibletostrip:
183 ui.status(_('The following changeset(s) or their ancestors have '
183 ui.status(_('The following changeset(s) or their ancestors have '
184 'local changes not on the remote:\n'))
184 'local changes not on the remote:\n'))
185 maxnodes = 10
185 maxnodes = 10
186 if ui.verbose or len(visibletostrip) <= maxnodes:
186 if ui.verbose or len(visibletostrip) <= maxnodes:
187 for n in visibletostrip:
187 for n in visibletostrip:
188 ui.status('%s\n' % node.short(n))
188 ui.status('%s\n' % node.short(n))
189 else:
189 else:
190 for n in visibletostrip[:maxnodes]:
190 for n in visibletostrip[:maxnodes]:
191 ui.status('%s\n' % node.short(n))
191 ui.status('%s\n' % node.short(n))
192 ui.status(_('...and %d more, use --verbose to list all\n') %
192 ui.status(_('...and %d more, use --verbose to list all\n') %
193 (len(visibletostrip) - maxnodes))
193 (len(visibletostrip) - maxnodes))
194 if not force:
194 if not force:
195 raise error.Abort(_('local changes found'),
195 raise error.Abort(_('local changes found'),
196 hint=_('use --force-delete-local-changes to '
196 hint=_('use --force-delete-local-changes to '
197 'ignore'))
197 'ignore'))
198
198
199 with ui.uninterruptible():
199 with ui.uninterruptible():
200 if revstostrip:
200 if revstostrip:
201 tostrip = [unfi.changelog.node(r) for r in revstostrip]
201 tostrip = [unfi.changelog.node(r) for r in revstostrip]
202 if repo['.'].node() in tostrip:
202 if repo['.'].node() in tostrip:
203 # stripping working copy, so move to a different commit first
203 # stripping working copy, so move to a different commit first
204 urev = max(repo.revs('(::%n) - %ln + null',
204 urev = max(repo.revs('(::%n) - %ln + null',
205 repo['.'].node(), visibletostrip))
205 repo['.'].node(), visibletostrip))
206 hg.clean(repo, urev)
206 hg.clean(repo, urev)
207 overrides = {('devel', 'strip-obsmarkers'): False}
207 overrides = {('devel', 'strip-obsmarkers'): False}
208 with ui.configoverride(overrides, 'narrow'):
208 with ui.configoverride(overrides, 'narrow'):
209 repair.strip(ui, unfi, tostrip, topic='narrow')
209 repair.strip(ui, unfi, tostrip, topic='narrow')
210
210
211 todelete = []
211 todelete = []
212 for f, f2, size in repo.store.datafiles():
212 for f, f2, size in repo.store.datafiles():
213 if f.startswith('data/'):
213 if f.startswith('data/'):
214 file = f[5:-2]
214 file = f[5:-2]
215 if not newmatch(file):
215 if not newmatch(file):
216 todelete.append(f)
216 todelete.append(f)
217 elif f.startswith('meta/'):
217 elif f.startswith('meta/'):
218 dir = f[5:-13]
218 dir = f[5:-13]
219 dirs = [''] + sorted(util.dirs({dir})) + [dir]
219 dirs = sorted(util.dirs({dir})) + [dir]
220 include = True
220 include = True
221 for d in dirs:
221 for d in dirs:
222 visit = newmatch.visitdir(d)
222 visit = newmatch.visitdir(d)
223 if not visit:
223 if not visit:
224 include = False
224 include = False
225 break
225 break
226 if visit == 'all':
226 if visit == 'all':
227 break
227 break
228 if not include:
228 if not include:
229 todelete.append(f)
229 todelete.append(f)
230
230
231 repo.destroying()
231 repo.destroying()
232
232
233 with repo.transaction('narrowing'):
233 with repo.transaction('narrowing'):
234 # Update narrowspec before removing revlogs, so repo won't be
234 # Update narrowspec before removing revlogs, so repo won't be
235 # corrupt in case of crash
235 # corrupt in case of crash
236 repo.setnarrowpats(newincludes, newexcludes)
236 repo.setnarrowpats(newincludes, newexcludes)
237
237
238 for f in todelete:
238 for f in todelete:
239 ui.status(_('deleting %s\n') % f)
239 ui.status(_('deleting %s\n') % f)
240 util.unlinkpath(repo.svfs.join(f))
240 util.unlinkpath(repo.svfs.join(f))
241 repo.store.markremoved(f)
241 repo.store.markremoved(f)
242
242
243 narrowspec.updateworkingcopy(repo, assumeclean=True)
243 narrowspec.updateworkingcopy(repo, assumeclean=True)
244 narrowspec.copytoworkingcopy(repo)
244 narrowspec.copytoworkingcopy(repo)
245
245
246 repo.destroyed()
246 repo.destroyed()
247
247
248 def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
248 def _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
249 newincludes, newexcludes):
249 newincludes, newexcludes):
250 # for now we assume that if a server has ellipses enabled, we will be
250 # for now we assume that if a server has ellipses enabled, we will be
251 # exchanging ellipses nodes. In future we should add ellipses as a client
251 # exchanging ellipses nodes. In future we should add ellipses as a client
252 # side requirement (maybe) to distinguish a client is shallow or not and
252 # side requirement (maybe) to distinguish a client is shallow or not and
253 # then send that information to server whether we want ellipses or not.
253 # then send that information to server whether we want ellipses or not.
254 # Theoretically a non-ellipses repo should be able to use narrow
254 # Theoretically a non-ellipses repo should be able to use narrow
255 # functionality from an ellipses enabled server
255 # functionality from an ellipses enabled server
256 ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities()
256 ellipsesremote = wireprototypes.ELLIPSESCAP in remote.capabilities()
257
257
258 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
258 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
259 orig(pullop, kwargs)
259 orig(pullop, kwargs)
260 # The old{in,ex}cludepats have already been set by orig()
260 # The old{in,ex}cludepats have already been set by orig()
261 kwargs['includepats'] = newincludes
261 kwargs['includepats'] = newincludes
262 kwargs['excludepats'] = newexcludes
262 kwargs['excludepats'] = newexcludes
263 wrappedextraprepare = extensions.wrappedfunction(exchange,
263 wrappedextraprepare = extensions.wrappedfunction(exchange,
264 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
264 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
265
265
266 # define a function that narrowbundle2 can call after creating the
266 # define a function that narrowbundle2 can call after creating the
267 # backup bundle, but before applying the bundle from the server
267 # backup bundle, but before applying the bundle from the server
268 def setnewnarrowpats():
268 def setnewnarrowpats():
269 repo.setnarrowpats(newincludes, newexcludes)
269 repo.setnarrowpats(newincludes, newexcludes)
270 repo.setnewnarrowpats = setnewnarrowpats
270 repo.setnewnarrowpats = setnewnarrowpats
271 # silence the devel-warning of applying an empty changegroup
271 # silence the devel-warning of applying an empty changegroup
272 overrides = {('devel', 'all-warnings'): False}
272 overrides = {('devel', 'all-warnings'): False}
273
273
274 with ui.uninterruptible():
274 with ui.uninterruptible():
275 common = commoninc[0]
275 common = commoninc[0]
276 if ellipsesremote:
276 if ellipsesremote:
277 ds = repo.dirstate
277 ds = repo.dirstate
278 p1, p2 = ds.p1(), ds.p2()
278 p1, p2 = ds.p1(), ds.p2()
279 with ds.parentchange():
279 with ds.parentchange():
280 ds.setparents(node.nullid, node.nullid)
280 ds.setparents(node.nullid, node.nullid)
281 with wrappedextraprepare:
281 with wrappedextraprepare:
282 with repo.ui.configoverride(overrides, 'widen'):
282 with repo.ui.configoverride(overrides, 'widen'):
283 exchange.pull(repo, remote, heads=common)
283 exchange.pull(repo, remote, heads=common)
284 with ds.parentchange():
284 with ds.parentchange():
285 ds.setparents(p1, p2)
285 ds.setparents(p1, p2)
286 else:
286 else:
287 with remote.commandexecutor() as e:
287 with remote.commandexecutor() as e:
288 bundle = e.callcommand('narrow_widen', {
288 bundle = e.callcommand('narrow_widen', {
289 'oldincludes': oldincludes,
289 'oldincludes': oldincludes,
290 'oldexcludes': oldexcludes,
290 'oldexcludes': oldexcludes,
291 'newincludes': newincludes,
291 'newincludes': newincludes,
292 'newexcludes': newexcludes,
292 'newexcludes': newexcludes,
293 'cgversion': '03',
293 'cgversion': '03',
294 'commonheads': common,
294 'commonheads': common,
295 'known': [],
295 'known': [],
296 'ellipses': False,
296 'ellipses': False,
297 }).result()
297 }).result()
298
298
299 with repo.transaction('widening') as tr:
299 with repo.transaction('widening') as tr:
300 with repo.ui.configoverride(overrides, 'widen'):
300 with repo.ui.configoverride(overrides, 'widen'):
301 tgetter = lambda: tr
301 tgetter = lambda: tr
302 bundle2.processbundle(repo, bundle,
302 bundle2.processbundle(repo, bundle,
303 transactiongetter=tgetter)
303 transactiongetter=tgetter)
304
304
305 with repo.transaction('widening'):
305 with repo.transaction('widening'):
306 repo.setnewnarrowpats()
306 repo.setnewnarrowpats()
307 narrowspec.updateworkingcopy(repo)
307 narrowspec.updateworkingcopy(repo)
308 narrowspec.copytoworkingcopy(repo)
308 narrowspec.copytoworkingcopy(repo)
309
309
310 # TODO(rdamazio): Make new matcher format and update description
310 # TODO(rdamazio): Make new matcher format and update description
311 @command('tracked',
311 @command('tracked',
312 [('', 'addinclude', [], _('new paths to include')),
312 [('', 'addinclude', [], _('new paths to include')),
313 ('', 'removeinclude', [], _('old paths to no longer include')),
313 ('', 'removeinclude', [], _('old paths to no longer include')),
314 ('', 'addexclude', [], _('new paths to exclude')),
314 ('', 'addexclude', [], _('new paths to exclude')),
315 ('', 'import-rules', '', _('import narrowspecs from a file')),
315 ('', 'import-rules', '', _('import narrowspecs from a file')),
316 ('', 'removeexclude', [], _('old paths to no longer exclude')),
316 ('', 'removeexclude', [], _('old paths to no longer exclude')),
317 ('', 'clear', False, _('whether to replace the existing narrowspec')),
317 ('', 'clear', False, _('whether to replace the existing narrowspec')),
318 ('', 'force-delete-local-changes', False,
318 ('', 'force-delete-local-changes', False,
319 _('forces deletion of local changes when narrowing')),
319 _('forces deletion of local changes when narrowing')),
320 ('', 'update-working-copy', False,
320 ('', 'update-working-copy', False,
321 _('update working copy when the store has changed')),
321 _('update working copy when the store has changed')),
322 ] + commands.remoteopts,
322 ] + commands.remoteopts,
323 _('[OPTIONS]... [REMOTE]'),
323 _('[OPTIONS]... [REMOTE]'),
324 inferrepo=True)
324 inferrepo=True)
325 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
325 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
326 """show or change the current narrowspec
326 """show or change the current narrowspec
327
327
328 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
329 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,
330 respectively.
330 respectively.
331
331
332 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
333 directories that should be pulled into your client.
333 directories that should be pulled into your client.
334 The narrowspec has *include* and *exclude* expressions, with excludes always
334 The narrowspec has *include* and *exclude* expressions, with excludes always
335 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
336 be excluded even if it also matches an include expression.
336 be excluded even if it also matches an include expression.
337 Excluding files that were never included has no effect.
337 Excluding files that were never included has no effect.
338
338
339 Each included or excluded entry is in the format described by
339 Each included or excluded entry is in the format described by
340 'hg help patterns'.
340 'hg help patterns'.
341
341
342 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.
343
343
344 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
345 and replaced by the new ones specified to --addinclude and --addexclude.
345 and replaced by the new ones specified to --addinclude and --addexclude.
346 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
347 empty and will not match any files.
347 empty and will not match any files.
348
348
349 --import-rules accepts a path to a file containing rules, allowing you to
349 --import-rules accepts a path to a file containing rules, allowing you to
350 add --addinclude, --addexclude rules in bulk. Like the other include and
350 add --addinclude, --addexclude rules in bulk. Like the other include and
351 exclude switches, the changes are applied immediately.
351 exclude switches, the changes are applied immediately.
352 """
352 """
353 opts = pycompat.byteskwargs(opts)
353 opts = pycompat.byteskwargs(opts)
354 if repository.NARROW_REQUIREMENT not in repo.requirements:
354 if repository.NARROW_REQUIREMENT not in repo.requirements:
355 raise error.Abort(_('the tracked command is only supported on '
355 raise error.Abort(_('the tracked command is only supported on '
356 'respositories cloned with --narrow'))
356 'respositories cloned with --narrow'))
357
357
358 # Before supporting, decide whether it "hg tracked --clear" should mean
358 # Before supporting, decide whether it "hg tracked --clear" should mean
359 # tracking no paths or all paths.
359 # tracking no paths or all paths.
360 if opts['clear']:
360 if opts['clear']:
361 raise error.Abort(_('the --clear option is not yet supported'))
361 raise error.Abort(_('the --clear option is not yet supported'))
362
362
363 # import rules from a file
363 # import rules from a file
364 newrules = opts.get('import_rules')
364 newrules = opts.get('import_rules')
365 if newrules:
365 if newrules:
366 try:
366 try:
367 filepath = os.path.join(encoding.getcwd(), newrules)
367 filepath = os.path.join(encoding.getcwd(), newrules)
368 fdata = util.readfile(filepath)
368 fdata = util.readfile(filepath)
369 except IOError as inst:
369 except IOError as inst:
370 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
370 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
371 (filepath, encoding.strtolocal(inst.strerror)))
371 (filepath, encoding.strtolocal(inst.strerror)))
372 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
372 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
373 'narrow')
373 'narrow')
374 if profiles:
374 if profiles:
375 raise error.Abort(_("including other spec files using '%include' "
375 raise error.Abort(_("including other spec files using '%include' "
376 "is not supported in narrowspec"))
376 "is not supported in narrowspec"))
377 opts['addinclude'].extend(includepats)
377 opts['addinclude'].extend(includepats)
378 opts['addexclude'].extend(excludepats)
378 opts['addexclude'].extend(excludepats)
379
379
380 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
380 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
381 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
381 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
382 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
382 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
383 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
383 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
384
384
385 update_working_copy = opts['update_working_copy']
385 update_working_copy = opts['update_working_copy']
386 only_show = not (addedincludes or removedincludes or addedexcludes or
386 only_show = not (addedincludes or removedincludes or addedexcludes or
387 removedexcludes or newrules or update_working_copy)
387 removedexcludes or newrules or update_working_copy)
388
388
389 oldincludes, oldexcludes = repo.narrowpats
389 oldincludes, oldexcludes = repo.narrowpats
390
390
391 # filter the user passed additions and deletions into actual additions and
391 # filter the user passed additions and deletions into actual additions and
392 # deletions of excludes and includes
392 # deletions of excludes and includes
393 addedincludes -= oldincludes
393 addedincludes -= oldincludes
394 removedincludes &= oldincludes
394 removedincludes &= oldincludes
395 addedexcludes -= oldexcludes
395 addedexcludes -= oldexcludes
396 removedexcludes &= oldexcludes
396 removedexcludes &= oldexcludes
397
397
398 widening = addedincludes or removedexcludes
398 widening = addedincludes or removedexcludes
399 narrowing = removedincludes or addedexcludes
399 narrowing = removedincludes or addedexcludes
400
400
401 # Only print the current narrowspec.
401 # Only print the current narrowspec.
402 if only_show:
402 if only_show:
403 ui.pager('tracked')
403 ui.pager('tracked')
404 fm = ui.formatter('narrow', opts)
404 fm = ui.formatter('narrow', opts)
405 for i in sorted(oldincludes):
405 for i in sorted(oldincludes):
406 fm.startitem()
406 fm.startitem()
407 fm.write('status', '%s ', 'I', label='narrow.included')
407 fm.write('status', '%s ', 'I', label='narrow.included')
408 fm.write('pat', '%s\n', i, label='narrow.included')
408 fm.write('pat', '%s\n', i, label='narrow.included')
409 for i in sorted(oldexcludes):
409 for i in sorted(oldexcludes):
410 fm.startitem()
410 fm.startitem()
411 fm.write('status', '%s ', 'X', label='narrow.excluded')
411 fm.write('status', '%s ', 'X', label='narrow.excluded')
412 fm.write('pat', '%s\n', i, label='narrow.excluded')
412 fm.write('pat', '%s\n', i, label='narrow.excluded')
413 fm.end()
413 fm.end()
414 return 0
414 return 0
415
415
416 if update_working_copy:
416 if update_working_copy:
417 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc'):
417 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc'):
418 narrowspec.updateworkingcopy(repo)
418 narrowspec.updateworkingcopy(repo)
419 narrowspec.copytoworkingcopy(repo)
419 narrowspec.copytoworkingcopy(repo)
420 return 0
420 return 0
421
421
422 if not widening and not narrowing:
422 if not widening and not narrowing:
423 ui.status(_("nothing to widen or narrow\n"))
423 ui.status(_("nothing to widen or narrow\n"))
424 return 0
424 return 0
425
425
426 with repo.wlock(), repo.lock():
426 with repo.wlock(), repo.lock():
427 cmdutil.bailifchanged(repo)
427 cmdutil.bailifchanged(repo)
428
428
429 # Find the revisions we have in common with the remote. These will
429 # Find the revisions we have in common with the remote. These will
430 # be used for finding local-only changes for narrowing. They will
430 # be used for finding local-only changes for narrowing. They will
431 # also define the set of revisions to update for widening.
431 # also define the set of revisions to update for widening.
432 remotepath = ui.expandpath(remotepath or 'default')
432 remotepath = ui.expandpath(remotepath or 'default')
433 url, branches = hg.parseurl(remotepath)
433 url, branches = hg.parseurl(remotepath)
434 ui.status(_('comparing with %s\n') % util.hidepassword(url))
434 ui.status(_('comparing with %s\n') % util.hidepassword(url))
435 remote = hg.peer(repo, opts, url)
435 remote = hg.peer(repo, opts, url)
436
436
437 # check narrow support before doing anything if widening needs to be
437 # check narrow support before doing anything if widening needs to be
438 # performed. In future we should also abort if client is ellipses and
438 # performed. In future we should also abort if client is ellipses and
439 # server does not support ellipses
439 # server does not support ellipses
440 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
440 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
441 raise error.Abort(_("server does not support narrow clones"))
441 raise error.Abort(_("server does not support narrow clones"))
442
442
443 commoninc = discovery.findcommonincoming(repo, remote)
443 commoninc = discovery.findcommonincoming(repo, remote)
444
444
445 if narrowing:
445 if narrowing:
446 newincludes = oldincludes - removedincludes
446 newincludes = oldincludes - removedincludes
447 newexcludes = oldexcludes | addedexcludes
447 newexcludes = oldexcludes | addedexcludes
448 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
448 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
449 newincludes, newexcludes,
449 newincludes, newexcludes,
450 opts['force_delete_local_changes'])
450 opts['force_delete_local_changes'])
451 # _narrow() updated the narrowspec and _widen() below needs to
451 # _narrow() updated the narrowspec and _widen() below needs to
452 # use the updated values as its base (otherwise removed includes
452 # use the updated values as its base (otherwise removed includes
453 # and addedexcludes will be lost in the resulting narrowspec)
453 # and addedexcludes will be lost in the resulting narrowspec)
454 oldincludes = newincludes
454 oldincludes = newincludes
455 oldexcludes = newexcludes
455 oldexcludes = newexcludes
456
456
457 if widening:
457 if widening:
458 newincludes = oldincludes | addedincludes
458 newincludes = oldincludes | addedincludes
459 newexcludes = oldexcludes - removedexcludes
459 newexcludes = oldexcludes - removedexcludes
460 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
460 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
461 newincludes, newexcludes)
461 newincludes, newexcludes)
462
462
463 return 0
463 return 0
General Comments 0
You need to be logged in to leave comments. Login now