##// END OF EJS Templates
narrow: refactor code around widening complicated by previous patch...
Pulkit Goyal -
r42606:8381b706 default
parent child Browse files
Show More
@@ -1,493 +1,478
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.ELLIPSESCAP1 in pullop.remote.capabilities():
149 if wireprototypes.ELLIPSESCAP1 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 remotecap = remote.capabilities()
256 remotecap = remote.capabilities()
257 ellipsesremote = any(cap in remotecap
257 ellipsesremote = any(cap in remotecap
258 for cap in wireprototypes.SUPPORTED_ELLIPSESCAP)
258 for cap in wireprototypes.SUPPORTED_ELLIPSESCAP)
259
259
260 # check whether we are talking to a server which supports old version of
260 # check whether we are talking to a server which supports old version of
261 # ellipses capabilities
261 # ellipses capabilities
262 isoldellipses = (ellipsesremote and wireprototypes.ELLIPSESCAP1 in
262 isoldellipses = (ellipsesremote and wireprototypes.ELLIPSESCAP1 in
263 remotecap and wireprototypes.ELLIPSESCAP not in remotecap)
263 remotecap and wireprototypes.ELLIPSESCAP not in remotecap)
264
264
265 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
265 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
266 orig(pullop, kwargs)
266 orig(pullop, kwargs)
267 # The old{in,ex}cludepats have already been set by orig()
267 # The old{in,ex}cludepats have already been set by orig()
268 kwargs['includepats'] = newincludes
268 kwargs['includepats'] = newincludes
269 kwargs['excludepats'] = newexcludes
269 kwargs['excludepats'] = newexcludes
270 wrappedextraprepare = extensions.wrappedfunction(exchange,
270 wrappedextraprepare = extensions.wrappedfunction(exchange,
271 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
271 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
272
272
273 # define a function that narrowbundle2 can call after creating the
273 # define a function that narrowbundle2 can call after creating the
274 # backup bundle, but before applying the bundle from the server
274 # backup bundle, but before applying the bundle from the server
275 def setnewnarrowpats():
275 def setnewnarrowpats():
276 repo.setnarrowpats(newincludes, newexcludes)
276 repo.setnarrowpats(newincludes, newexcludes)
277 repo.setnewnarrowpats = setnewnarrowpats
277 repo.setnewnarrowpats = setnewnarrowpats
278 # silence the devel-warning of applying an empty changegroup
278 # silence the devel-warning of applying an empty changegroup
279 overrides = {('devel', 'all-warnings'): False}
279 overrides = {('devel', 'all-warnings'): False}
280
280
281 common = commoninc[0]
281 with ui.uninterruptible():
282 with ui.uninterruptible():
282 common = commoninc[0]
283 if ellipsesremote:
283 if ellipsesremote:
284 ds = repo.dirstate
284 ds = repo.dirstate
285 p1, p2 = ds.p1(), ds.p2()
285 p1, p2 = ds.p1(), ds.p2()
286 with ds.parentchange():
286 with ds.parentchange():
287 ds.setparents(node.nullid, node.nullid)
287 ds.setparents(node.nullid, node.nullid)
288 if isoldellipses:
288 with wrappedextraprepare:
289 with wrappedextraprepare:
289 if isoldellipses:
290 exchange.pull(repo, remote, heads=common)
290 exchange.pull(repo, remote, heads=common)
291 else:
292 known = [node.hex(ctx.node()) for ctx in
293 repo.set('::%ln', common)
294 if ctx.node() != node.nullid]
295
296 with remote.commandexecutor() as e:
297 bundle = e.callcommand('narrow_widen', {
298 'oldincludes': oldincludes,
299 'oldexcludes': oldexcludes,
300 'newincludes': newincludes,
301 'newexcludes': newexcludes,
302 'cgversion': '03',
303 'commonheads': common,
304 'known': known,
305 'ellipses': True,
306 }).result()
307 trmanager = exchange.transactionmanager(repo, 'widen',
308 remote.url())
309 with trmanager:
310 op = bundle2.bundleoperation(repo,
311 trmanager.transaction, source='widen')
312 bundle2.processbundle(repo, bundle, op=op)
313
314 with ds.parentchange():
315 ds.setparents(p1, p2)
316 else:
291 else:
292 known = []
293 if ellipsesremote:
294 known = [node.hex(ctx.node()) for ctx in
295 repo.set('::%ln', common)
296 if ctx.node() != node.nullid]
317 with remote.commandexecutor() as e:
297 with remote.commandexecutor() as e:
318 bundle = e.callcommand('narrow_widen', {
298 bundle = e.callcommand('narrow_widen', {
319 'oldincludes': oldincludes,
299 'oldincludes': oldincludes,
320 'oldexcludes': oldexcludes,
300 'oldexcludes': oldexcludes,
321 'newincludes': newincludes,
301 'newincludes': newincludes,
322 'newexcludes': newexcludes,
302 'newexcludes': newexcludes,
323 'cgversion': '03',
303 'cgversion': '03',
324 'commonheads': common,
304 'commonheads': common,
325 'known': [],
305 'known': known,
326 'ellipses': False,
306 'ellipses': ellipsesremote,
327 }).result()
307 }).result()
328
308
329 with repo.transaction('widening') as tr:
309 trmanager = exchange.transactionmanager(repo, 'widen', remote.url())
330 with repo.ui.configoverride(overrides, 'widen'):
310 with trmanager, repo.ui.configoverride(overrides, 'widen'):
331 tgetter = lambda: tr
311 op = bundle2.bundleoperation(repo, trmanager.transaction,
332 bundle2.processbundle(repo, bundle,
312 source='widen')
333 transactiongetter=tgetter)
313 # TODO: we should catch error.Abort here
314 bundle2.processbundle(repo, bundle, op=op)
315
316 if ellipsesremote:
317 with ds.parentchange():
318 ds.setparents(p1, p2)
334
319
335 with repo.transaction('widening'):
320 with repo.transaction('widening'):
336 repo.setnewnarrowpats()
321 repo.setnewnarrowpats()
337 narrowspec.updateworkingcopy(repo)
322 narrowspec.updateworkingcopy(repo)
338 narrowspec.copytoworkingcopy(repo)
323 narrowspec.copytoworkingcopy(repo)
339
324
340 # TODO(rdamazio): Make new matcher format and update description
325 # TODO(rdamazio): Make new matcher format and update description
341 @command('tracked',
326 @command('tracked',
342 [('', 'addinclude', [], _('new paths to include')),
327 [('', 'addinclude', [], _('new paths to include')),
343 ('', 'removeinclude', [], _('old paths to no longer include')),
328 ('', 'removeinclude', [], _('old paths to no longer include')),
344 ('', 'addexclude', [], _('new paths to exclude')),
329 ('', 'addexclude', [], _('new paths to exclude')),
345 ('', 'import-rules', '', _('import narrowspecs from a file')),
330 ('', 'import-rules', '', _('import narrowspecs from a file')),
346 ('', 'removeexclude', [], _('old paths to no longer exclude')),
331 ('', 'removeexclude', [], _('old paths to no longer exclude')),
347 ('', 'clear', False, _('whether to replace the existing narrowspec')),
332 ('', 'clear', False, _('whether to replace the existing narrowspec')),
348 ('', 'force-delete-local-changes', False,
333 ('', 'force-delete-local-changes', False,
349 _('forces deletion of local changes when narrowing')),
334 _('forces deletion of local changes when narrowing')),
350 ('', 'update-working-copy', False,
335 ('', 'update-working-copy', False,
351 _('update working copy when the store has changed')),
336 _('update working copy when the store has changed')),
352 ] + commands.remoteopts,
337 ] + commands.remoteopts,
353 _('[OPTIONS]... [REMOTE]'),
338 _('[OPTIONS]... [REMOTE]'),
354 inferrepo=True)
339 inferrepo=True)
355 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
340 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
356 """show or change the current narrowspec
341 """show or change the current narrowspec
357
342
358 With no argument, shows the current narrowspec entries, one per line. Each
343 With no argument, shows the current narrowspec entries, one per line. Each
359 line will be prefixed with 'I' or 'X' for included or excluded patterns,
344 line will be prefixed with 'I' or 'X' for included or excluded patterns,
360 respectively.
345 respectively.
361
346
362 The narrowspec is comprised of expressions to match remote files and/or
347 The narrowspec is comprised of expressions to match remote files and/or
363 directories that should be pulled into your client.
348 directories that should be pulled into your client.
364 The narrowspec has *include* and *exclude* expressions, with excludes always
349 The narrowspec has *include* and *exclude* expressions, with excludes always
365 trumping includes: that is, if a file matches an exclude expression, it will
350 trumping includes: that is, if a file matches an exclude expression, it will
366 be excluded even if it also matches an include expression.
351 be excluded even if it also matches an include expression.
367 Excluding files that were never included has no effect.
352 Excluding files that were never included has no effect.
368
353
369 Each included or excluded entry is in the format described by
354 Each included or excluded entry is in the format described by
370 'hg help patterns'.
355 'hg help patterns'.
371
356
372 The options allow you to add or remove included and excluded expressions.
357 The options allow you to add or remove included and excluded expressions.
373
358
374 If --clear is specified, then all previous includes and excludes are DROPPED
359 If --clear is specified, then all previous includes and excludes are DROPPED
375 and replaced by the new ones specified to --addinclude and --addexclude.
360 and replaced by the new ones specified to --addinclude and --addexclude.
376 If --clear is specified without any further options, the narrowspec will be
361 If --clear is specified without any further options, the narrowspec will be
377 empty and will not match any files.
362 empty and will not match any files.
378
363
379 --import-rules accepts a path to a file containing rules, allowing you to
364 --import-rules accepts a path to a file containing rules, allowing you to
380 add --addinclude, --addexclude rules in bulk. Like the other include and
365 add --addinclude, --addexclude rules in bulk. Like the other include and
381 exclude switches, the changes are applied immediately.
366 exclude switches, the changes are applied immediately.
382 """
367 """
383 opts = pycompat.byteskwargs(opts)
368 opts = pycompat.byteskwargs(opts)
384 if repository.NARROW_REQUIREMENT not in repo.requirements:
369 if repository.NARROW_REQUIREMENT not in repo.requirements:
385 raise error.Abort(_('the tracked command is only supported on '
370 raise error.Abort(_('the tracked command is only supported on '
386 'respositories cloned with --narrow'))
371 'respositories cloned with --narrow'))
387
372
388 # Before supporting, decide whether it "hg tracked --clear" should mean
373 # Before supporting, decide whether it "hg tracked --clear" should mean
389 # tracking no paths or all paths.
374 # tracking no paths or all paths.
390 if opts['clear']:
375 if opts['clear']:
391 raise error.Abort(_('the --clear option is not yet supported'))
376 raise error.Abort(_('the --clear option is not yet supported'))
392
377
393 # import rules from a file
378 # import rules from a file
394 newrules = opts.get('import_rules')
379 newrules = opts.get('import_rules')
395 if newrules:
380 if newrules:
396 try:
381 try:
397 filepath = os.path.join(encoding.getcwd(), newrules)
382 filepath = os.path.join(encoding.getcwd(), newrules)
398 fdata = util.readfile(filepath)
383 fdata = util.readfile(filepath)
399 except IOError as inst:
384 except IOError as inst:
400 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
385 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
401 (filepath, encoding.strtolocal(inst.strerror)))
386 (filepath, encoding.strtolocal(inst.strerror)))
402 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
387 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
403 'narrow')
388 'narrow')
404 if profiles:
389 if profiles:
405 raise error.Abort(_("including other spec files using '%include' "
390 raise error.Abort(_("including other spec files using '%include' "
406 "is not supported in narrowspec"))
391 "is not supported in narrowspec"))
407 opts['addinclude'].extend(includepats)
392 opts['addinclude'].extend(includepats)
408 opts['addexclude'].extend(excludepats)
393 opts['addexclude'].extend(excludepats)
409
394
410 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
395 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
411 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
396 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
412 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
397 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
413 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
398 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
414
399
415 update_working_copy = opts['update_working_copy']
400 update_working_copy = opts['update_working_copy']
416 only_show = not (addedincludes or removedincludes or addedexcludes or
401 only_show = not (addedincludes or removedincludes or addedexcludes or
417 removedexcludes or newrules or update_working_copy)
402 removedexcludes or newrules or update_working_copy)
418
403
419 oldincludes, oldexcludes = repo.narrowpats
404 oldincludes, oldexcludes = repo.narrowpats
420
405
421 # filter the user passed additions and deletions into actual additions and
406 # filter the user passed additions and deletions into actual additions and
422 # deletions of excludes and includes
407 # deletions of excludes and includes
423 addedincludes -= oldincludes
408 addedincludes -= oldincludes
424 removedincludes &= oldincludes
409 removedincludes &= oldincludes
425 addedexcludes -= oldexcludes
410 addedexcludes -= oldexcludes
426 removedexcludes &= oldexcludes
411 removedexcludes &= oldexcludes
427
412
428 widening = addedincludes or removedexcludes
413 widening = addedincludes or removedexcludes
429 narrowing = removedincludes or addedexcludes
414 narrowing = removedincludes or addedexcludes
430
415
431 # Only print the current narrowspec.
416 # Only print the current narrowspec.
432 if only_show:
417 if only_show:
433 ui.pager('tracked')
418 ui.pager('tracked')
434 fm = ui.formatter('narrow', opts)
419 fm = ui.formatter('narrow', opts)
435 for i in sorted(oldincludes):
420 for i in sorted(oldincludes):
436 fm.startitem()
421 fm.startitem()
437 fm.write('status', '%s ', 'I', label='narrow.included')
422 fm.write('status', '%s ', 'I', label='narrow.included')
438 fm.write('pat', '%s\n', i, label='narrow.included')
423 fm.write('pat', '%s\n', i, label='narrow.included')
439 for i in sorted(oldexcludes):
424 for i in sorted(oldexcludes):
440 fm.startitem()
425 fm.startitem()
441 fm.write('status', '%s ', 'X', label='narrow.excluded')
426 fm.write('status', '%s ', 'X', label='narrow.excluded')
442 fm.write('pat', '%s\n', i, label='narrow.excluded')
427 fm.write('pat', '%s\n', i, label='narrow.excluded')
443 fm.end()
428 fm.end()
444 return 0
429 return 0
445
430
446 if update_working_copy:
431 if update_working_copy:
447 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc'):
432 with repo.wlock(), repo.lock(), repo.transaction('narrow-wc'):
448 narrowspec.updateworkingcopy(repo)
433 narrowspec.updateworkingcopy(repo)
449 narrowspec.copytoworkingcopy(repo)
434 narrowspec.copytoworkingcopy(repo)
450 return 0
435 return 0
451
436
452 if not widening and not narrowing:
437 if not widening and not narrowing:
453 ui.status(_("nothing to widen or narrow\n"))
438 ui.status(_("nothing to widen or narrow\n"))
454 return 0
439 return 0
455
440
456 with repo.wlock(), repo.lock():
441 with repo.wlock(), repo.lock():
457 cmdutil.bailifchanged(repo)
442 cmdutil.bailifchanged(repo)
458
443
459 # Find the revisions we have in common with the remote. These will
444 # Find the revisions we have in common with the remote. These will
460 # be used for finding local-only changes for narrowing. They will
445 # be used for finding local-only changes for narrowing. They will
461 # also define the set of revisions to update for widening.
446 # also define the set of revisions to update for widening.
462 remotepath = ui.expandpath(remotepath or 'default')
447 remotepath = ui.expandpath(remotepath or 'default')
463 url, branches = hg.parseurl(remotepath)
448 url, branches = hg.parseurl(remotepath)
464 ui.status(_('comparing with %s\n') % util.hidepassword(url))
449 ui.status(_('comparing with %s\n') % util.hidepassword(url))
465 remote = hg.peer(repo, opts, url)
450 remote = hg.peer(repo, opts, url)
466
451
467 # check narrow support before doing anything if widening needs to be
452 # check narrow support before doing anything if widening needs to be
468 # performed. In future we should also abort if client is ellipses and
453 # performed. In future we should also abort if client is ellipses and
469 # server does not support ellipses
454 # server does not support ellipses
470 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
455 if widening and wireprototypes.NARROWCAP not in remote.capabilities():
471 raise error.Abort(_("server does not support narrow clones"))
456 raise error.Abort(_("server does not support narrow clones"))
472
457
473 commoninc = discovery.findcommonincoming(repo, remote)
458 commoninc = discovery.findcommonincoming(repo, remote)
474
459
475 if narrowing:
460 if narrowing:
476 newincludes = oldincludes - removedincludes
461 newincludes = oldincludes - removedincludes
477 newexcludes = oldexcludes | addedexcludes
462 newexcludes = oldexcludes | addedexcludes
478 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
463 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
479 newincludes, newexcludes,
464 newincludes, newexcludes,
480 opts['force_delete_local_changes'])
465 opts['force_delete_local_changes'])
481 # _narrow() updated the narrowspec and _widen() below needs to
466 # _narrow() updated the narrowspec and _widen() below needs to
482 # use the updated values as its base (otherwise removed includes
467 # use the updated values as its base (otherwise removed includes
483 # and addedexcludes will be lost in the resulting narrowspec)
468 # and addedexcludes will be lost in the resulting narrowspec)
484 oldincludes = newincludes
469 oldincludes = newincludes
485 oldexcludes = newexcludes
470 oldexcludes = newexcludes
486
471
487 if widening:
472 if widening:
488 newincludes = oldincludes | addedincludes
473 newincludes = oldincludes | addedincludes
489 newexcludes = oldexcludes - removedexcludes
474 newexcludes = oldexcludes - removedexcludes
490 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
475 _widen(ui, repo, remote, commoninc, oldincludes, oldexcludes,
491 newincludes, newexcludes)
476 newincludes, newexcludes)
492
477
493 return 0
478 return 0
General Comments 0
You need to be logged in to leave comments. Login now