##// END OF EJS Templates
narrow: migrate `opts` to native kwargs
Matt Harbison -
r51774:4803cea1 default
parent child Browse files
Show More
@@ -1,698 +1,697 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
7
8 import itertools
8 import itertools
9 import os
9 import os
10
10
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial.node import (
12 from mercurial.node import (
13 hex,
13 hex,
14 short,
14 short,
15 )
15 )
16 from mercurial import (
16 from mercurial import (
17 bundle2,
17 bundle2,
18 cmdutil,
18 cmdutil,
19 commands,
19 commands,
20 discovery,
20 discovery,
21 encoding,
21 encoding,
22 error,
22 error,
23 exchange,
23 exchange,
24 extensions,
24 extensions,
25 hg,
25 hg,
26 narrowspec,
26 narrowspec,
27 pathutil,
27 pathutil,
28 pycompat,
28 pycompat,
29 registrar,
29 registrar,
30 repair,
30 repair,
31 repoview,
31 repoview,
32 requirements,
32 requirements,
33 sparse,
33 sparse,
34 util,
34 util,
35 wireprototypes,
35 wireprototypes,
36 )
36 )
37 from mercurial.utils import (
37 from mercurial.utils import (
38 urlutil,
38 urlutil,
39 )
39 )
40
40
41 table = {}
41 table = {}
42 command = registrar.command(table)
42 command = registrar.command(table)
43
43
44
44
45 def setup():
45 def setup():
46 """Wraps user-facing mercurial commands with narrow-aware versions."""
46 """Wraps user-facing mercurial commands with narrow-aware versions."""
47
47
48 entry = extensions.wrapcommand(commands.table, b'clone', clonenarrowcmd)
48 entry = extensions.wrapcommand(commands.table, b'clone', clonenarrowcmd)
49 entry[1].append(
49 entry[1].append(
50 (b'', b'narrow', None, _(b"create a narrow clone of select files"))
50 (b'', b'narrow', None, _(b"create a narrow clone of select files"))
51 )
51 )
52 entry[1].append(
52 entry[1].append(
53 (
53 (
54 b'',
54 b'',
55 b'depth',
55 b'depth',
56 b'',
56 b'',
57 _(b"limit the history fetched by distance from heads"),
57 _(b"limit the history fetched by distance from heads"),
58 )
58 )
59 )
59 )
60 entry[1].append((b'', b'narrowspec', b'', _(b"read narrowspecs from file")))
60 entry[1].append((b'', b'narrowspec', b'', _(b"read narrowspecs from file")))
61 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
61 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
62 if b'sparse' not in extensions.enabled():
62 if b'sparse' not in extensions.enabled():
63 entry[1].append(
63 entry[1].append(
64 (b'', b'include', [], _(b"specifically fetch this file/directory"))
64 (b'', b'include', [], _(b"specifically fetch this file/directory"))
65 )
65 )
66 entry[1].append(
66 entry[1].append(
67 (
67 (
68 b'',
68 b'',
69 b'exclude',
69 b'exclude',
70 [],
70 [],
71 _(b"do not fetch this file/directory, even if included"),
71 _(b"do not fetch this file/directory, even if included"),
72 )
72 )
73 )
73 )
74
74
75 entry = extensions.wrapcommand(commands.table, b'pull', pullnarrowcmd)
75 entry = extensions.wrapcommand(commands.table, b'pull', pullnarrowcmd)
76 entry[1].append(
76 entry[1].append(
77 (
77 (
78 b'',
78 b'',
79 b'depth',
79 b'depth',
80 b'',
80 b'',
81 _(b"limit the history fetched by distance from heads"),
81 _(b"limit the history fetched by distance from heads"),
82 )
82 )
83 )
83 )
84
84
85 extensions.wrapcommand(commands.table, b'archive', archivenarrowcmd)
85 extensions.wrapcommand(commands.table, b'archive', archivenarrowcmd)
86
86
87
87
88 def clonenarrowcmd(orig, ui, repo, *args, **opts):
88 def clonenarrowcmd(orig, ui, repo, *args, **opts):
89 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
89 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
90 opts = pycompat.byteskwargs(opts)
91 wrappedextraprepare = util.nullcontextmanager()
90 wrappedextraprepare = util.nullcontextmanager()
92 narrowspecfile = opts[b'narrowspec']
91 narrowspecfile = opts['narrowspec']
93
92
94 if narrowspecfile:
93 if narrowspecfile:
95 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
94 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
96 ui.status(_(b"reading narrowspec from '%s'\n") % filepath)
95 ui.status(_(b"reading narrowspec from '%s'\n") % filepath)
97 try:
96 try:
98 fdata = util.readfile(filepath)
97 fdata = util.readfile(filepath)
99 except IOError as inst:
98 except IOError as inst:
100 raise error.Abort(
99 raise error.Abort(
101 _(b"cannot read narrowspecs from '%s': %s")
100 _(b"cannot read narrowspecs from '%s': %s")
102 % (filepath, encoding.strtolocal(inst.strerror))
101 % (filepath, encoding.strtolocal(inst.strerror))
103 )
102 )
104
103
105 includes, excludes, profiles = sparse.parseconfig(ui, fdata, b'narrow')
104 includes, excludes, profiles = sparse.parseconfig(ui, fdata, b'narrow')
106 if profiles:
105 if profiles:
107 raise error.ConfigError(
106 raise error.ConfigError(
108 _(
107 _(
109 b"cannot specify other files using '%include' in"
108 b"cannot specify other files using '%include' in"
110 b" narrowspec"
109 b" narrowspec"
111 )
110 )
112 )
111 )
113
112
114 narrowspec.validatepatterns(includes)
113 narrowspec.validatepatterns(includes)
115 narrowspec.validatepatterns(excludes)
114 narrowspec.validatepatterns(excludes)
116
115
117 # narrowspec is passed so we should assume that user wants narrow clone
116 # narrowspec is passed so we should assume that user wants narrow clone
118 opts[b'narrow'] = True
117 opts['narrow'] = True
119 opts[b'include'].extend(includes)
118 opts['include'].extend(includes)
120 opts[b'exclude'].extend(excludes)
119 opts['exclude'].extend(excludes)
121
120
122 if opts[b'narrow']:
121 if opts['narrow']:
123
122
124 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
123 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
125 orig(pullop, kwargs)
124 orig(pullop, kwargs)
126
125
127 if opts.get(b'depth'):
126 if opts.get('depth'):
128 kwargs[b'depth'] = opts[b'depth']
127 # TODO: fix exchange._pullbundle2extraprepare()
128 kwargs[b'depth'] = opts['depth']
129
129
130 wrappedextraprepare = extensions.wrappedfunction(
130 wrappedextraprepare = extensions.wrappedfunction(
131 exchange, '_pullbundle2extraprepare', pullbundle2extraprepare_widen
131 exchange, '_pullbundle2extraprepare', pullbundle2extraprepare_widen
132 )
132 )
133
133
134 with wrappedextraprepare:
134 with wrappedextraprepare:
135 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
135 return orig(ui, repo, *args, **opts)
136
136
137
137
138 def pullnarrowcmd(orig, ui, repo, *args, **opts):
138 def pullnarrowcmd(orig, ui, repo, *args, **opts):
139 """Wraps pull command to allow modifying narrow spec."""
139 """Wraps pull command to allow modifying narrow spec."""
140 wrappedextraprepare = util.nullcontextmanager()
140 wrappedextraprepare = util.nullcontextmanager()
141 if requirements.NARROW_REQUIREMENT in repo.requirements:
141 if requirements.NARROW_REQUIREMENT in repo.requirements:
142
142
143 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
143 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
144 orig(pullop, kwargs)
144 orig(pullop, kwargs)
145 if opts.get('depth'):
145 if opts.get('depth'):
146 kwargs[b'depth'] = opts['depth']
146 kwargs[b'depth'] = opts['depth']
147
147
148 wrappedextraprepare = extensions.wrappedfunction(
148 wrappedextraprepare = extensions.wrappedfunction(
149 exchange, '_pullbundle2extraprepare', pullbundle2extraprepare_widen
149 exchange, '_pullbundle2extraprepare', pullbundle2extraprepare_widen
150 )
150 )
151
151
152 with wrappedextraprepare:
152 with wrappedextraprepare:
153 return orig(ui, repo, *args, **opts)
153 return orig(ui, repo, *args, **opts)
154
154
155
155
156 def archivenarrowcmd(orig, ui, repo, *args, **opts):
156 def archivenarrowcmd(orig, ui, repo, *args, **opts):
157 """Wraps archive command to narrow the default includes."""
157 """Wraps archive command to narrow the default includes."""
158 if requirements.NARROW_REQUIREMENT in repo.requirements:
158 if requirements.NARROW_REQUIREMENT in repo.requirements:
159 repo_includes, repo_excludes = repo.narrowpats
159 repo_includes, repo_excludes = repo.narrowpats
160 includes = set(opts.get('include', []))
160 includes = set(opts.get('include', []))
161 excludes = set(opts.get('exclude', []))
161 excludes = set(opts.get('exclude', []))
162 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
162 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
163 includes, excludes, repo_includes, repo_excludes
163 includes, excludes, repo_includes, repo_excludes
164 )
164 )
165 if includes:
165 if includes:
166 opts['include'] = includes
166 opts['include'] = includes
167 if excludes:
167 if excludes:
168 opts['exclude'] = excludes
168 opts['exclude'] = excludes
169 return orig(ui, repo, *args, **opts)
169 return orig(ui, repo, *args, **opts)
170
170
171
171
172 def pullbundle2extraprepare(orig, pullop, kwargs):
172 def pullbundle2extraprepare(orig, pullop, kwargs):
173 repo = pullop.repo
173 repo = pullop.repo
174 if requirements.NARROW_REQUIREMENT not in repo.requirements:
174 if requirements.NARROW_REQUIREMENT not in repo.requirements:
175 return orig(pullop, kwargs)
175 return orig(pullop, kwargs)
176
176
177 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
177 if wireprototypes.NARROWCAP not in pullop.remote.capabilities():
178 raise error.Abort(_(b"server does not support narrow clones"))
178 raise error.Abort(_(b"server does not support narrow clones"))
179 orig(pullop, kwargs)
179 orig(pullop, kwargs)
180 kwargs[b'narrow'] = True
180 kwargs[b'narrow'] = True
181 include, exclude = repo.narrowpats
181 include, exclude = repo.narrowpats
182 kwargs[b'oldincludepats'] = include
182 kwargs[b'oldincludepats'] = include
183 kwargs[b'oldexcludepats'] = exclude
183 kwargs[b'oldexcludepats'] = exclude
184 if include:
184 if include:
185 kwargs[b'includepats'] = include
185 kwargs[b'includepats'] = include
186 if exclude:
186 if exclude:
187 kwargs[b'excludepats'] = exclude
187 kwargs[b'excludepats'] = exclude
188 # calculate known nodes only in ellipses cases because in non-ellipses cases
188 # calculate known nodes only in ellipses cases because in non-ellipses cases
189 # we have all the nodes
189 # we have all the nodes
190 if wireprototypes.ELLIPSESCAP1 in pullop.remote.capabilities():
190 if wireprototypes.ELLIPSESCAP1 in pullop.remote.capabilities():
191 kwargs[b'known'] = [
191 kwargs[b'known'] = [
192 hex(ctx.node())
192 hex(ctx.node())
193 for ctx in repo.set(b'::%ln', pullop.common)
193 for ctx in repo.set(b'::%ln', pullop.common)
194 if ctx.node() != repo.nullid
194 if ctx.node() != repo.nullid
195 ]
195 ]
196 if not kwargs[b'known']:
196 if not kwargs[b'known']:
197 # Mercurial serializes an empty list as '' and deserializes it as
197 # Mercurial serializes an empty list as '' and deserializes it as
198 # [''], so delete it instead to avoid handling the empty string on
198 # [''], so delete it instead to avoid handling the empty string on
199 # the server.
199 # the server.
200 del kwargs[b'known']
200 del kwargs[b'known']
201
201
202
202
203 extensions.wrapfunction(
203 extensions.wrapfunction(
204 exchange, '_pullbundle2extraprepare', pullbundle2extraprepare
204 exchange, '_pullbundle2extraprepare', pullbundle2extraprepare
205 )
205 )
206
206
207
207
208 def _narrow(
208 def _narrow(
209 ui,
209 ui,
210 repo,
210 repo,
211 remote,
211 remote,
212 commoninc,
212 commoninc,
213 oldincludes,
213 oldincludes,
214 oldexcludes,
214 oldexcludes,
215 newincludes,
215 newincludes,
216 newexcludes,
216 newexcludes,
217 force,
217 force,
218 backup,
218 backup,
219 ):
219 ):
220 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
220 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
221 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
221 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
222
222
223 # This is essentially doing "hg outgoing" to find all local-only
223 # This is essentially doing "hg outgoing" to find all local-only
224 # commits. We will then check that the local-only commits don't
224 # commits. We will then check that the local-only commits don't
225 # have any changes to files that will be untracked.
225 # have any changes to files that will be untracked.
226 unfi = repo.unfiltered()
226 unfi = repo.unfiltered()
227 outgoing = discovery.findcommonoutgoing(unfi, remote, commoninc=commoninc)
227 outgoing = discovery.findcommonoutgoing(unfi, remote, commoninc=commoninc)
228 ui.status(_(b'looking for local changes to affected paths\n'))
228 ui.status(_(b'looking for local changes to affected paths\n'))
229 progress = ui.makeprogress(
229 progress = ui.makeprogress(
230 topic=_(b'changesets'),
230 topic=_(b'changesets'),
231 unit=_(b'changesets'),
231 unit=_(b'changesets'),
232 total=len(outgoing.missing) + len(outgoing.excluded),
232 total=len(outgoing.missing) + len(outgoing.excluded),
233 )
233 )
234 localnodes = []
234 localnodes = []
235 with progress:
235 with progress:
236 for n in itertools.chain(outgoing.missing, outgoing.excluded):
236 for n in itertools.chain(outgoing.missing, outgoing.excluded):
237 progress.increment()
237 progress.increment()
238 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
238 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
239 localnodes.append(n)
239 localnodes.append(n)
240 revstostrip = unfi.revs(b'descendants(%ln)', localnodes)
240 revstostrip = unfi.revs(b'descendants(%ln)', localnodes)
241 hiddenrevs = repoview.filterrevs(repo, b'visible')
241 hiddenrevs = repoview.filterrevs(repo, b'visible')
242 visibletostrip = list(
242 visibletostrip = list(
243 repo.changelog.node(r) for r in (revstostrip - hiddenrevs)
243 repo.changelog.node(r) for r in (revstostrip - hiddenrevs)
244 )
244 )
245 if visibletostrip:
245 if visibletostrip:
246 ui.status(
246 ui.status(
247 _(
247 _(
248 b'The following changeset(s) or their ancestors have '
248 b'The following changeset(s) or their ancestors have '
249 b'local changes not on the remote:\n'
249 b'local changes not on the remote:\n'
250 )
250 )
251 )
251 )
252 maxnodes = 10
252 maxnodes = 10
253 if ui.verbose or len(visibletostrip) <= maxnodes:
253 if ui.verbose or len(visibletostrip) <= maxnodes:
254 for n in visibletostrip:
254 for n in visibletostrip:
255 ui.status(b'%s\n' % short(n))
255 ui.status(b'%s\n' % short(n))
256 else:
256 else:
257 for n in visibletostrip[:maxnodes]:
257 for n in visibletostrip[:maxnodes]:
258 ui.status(b'%s\n' % short(n))
258 ui.status(b'%s\n' % short(n))
259 ui.status(
259 ui.status(
260 _(b'...and %d more, use --verbose to list all\n')
260 _(b'...and %d more, use --verbose to list all\n')
261 % (len(visibletostrip) - maxnodes)
261 % (len(visibletostrip) - maxnodes)
262 )
262 )
263 if not force:
263 if not force:
264 raise error.StateError(
264 raise error.StateError(
265 _(b'local changes found'),
265 _(b'local changes found'),
266 hint=_(b'use --force-delete-local-changes to ignore'),
266 hint=_(b'use --force-delete-local-changes to ignore'),
267 )
267 )
268
268
269 with ui.uninterruptible():
269 with ui.uninterruptible():
270 if revstostrip:
270 if revstostrip:
271 tostrip = [unfi.changelog.node(r) for r in revstostrip]
271 tostrip = [unfi.changelog.node(r) for r in revstostrip]
272 if repo[b'.'].node() in tostrip:
272 if repo[b'.'].node() in tostrip:
273 # stripping working copy, so move to a different commit first
273 # stripping working copy, so move to a different commit first
274 urev = max(
274 urev = max(
275 repo.revs(
275 repo.revs(
276 b'(::%n) - %ln + null',
276 b'(::%n) - %ln + null',
277 repo[b'.'].node(),
277 repo[b'.'].node(),
278 visibletostrip,
278 visibletostrip,
279 )
279 )
280 )
280 )
281 hg.clean(repo, urev)
281 hg.clean(repo, urev)
282 overrides = {(b'devel', b'strip-obsmarkers'): False}
282 overrides = {(b'devel', b'strip-obsmarkers'): False}
283 if backup:
283 if backup:
284 ui.status(_(b'moving unwanted changesets to backup\n'))
284 ui.status(_(b'moving unwanted changesets to backup\n'))
285 else:
285 else:
286 ui.status(_(b'deleting unwanted changesets\n'))
286 ui.status(_(b'deleting unwanted changesets\n'))
287 with ui.configoverride(overrides, b'narrow'):
287 with ui.configoverride(overrides, b'narrow'):
288 repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup)
288 repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup)
289
289
290 todelete = []
290 todelete = []
291 for entry in repo.store.data_entries():
291 for entry in repo.store.data_entries():
292 if not entry.is_revlog:
292 if not entry.is_revlog:
293 continue
293 continue
294 if entry.is_filelog:
294 if entry.is_filelog:
295 if not newmatch(entry.target_id):
295 if not newmatch(entry.target_id):
296 for file_ in entry.files():
296 for file_ in entry.files():
297 todelete.append(file_.unencoded_path)
297 todelete.append(file_.unencoded_path)
298 elif entry.is_manifestlog:
298 elif entry.is_manifestlog:
299 dir = entry.target_id
299 dir = entry.target_id
300 dirs = sorted(pathutil.dirs({dir})) + [dir]
300 dirs = sorted(pathutil.dirs({dir})) + [dir]
301 include = True
301 include = True
302 for d in dirs:
302 for d in dirs:
303 visit = newmatch.visitdir(d)
303 visit = newmatch.visitdir(d)
304 if not visit:
304 if not visit:
305 include = False
305 include = False
306 break
306 break
307 if visit == b'all':
307 if visit == b'all':
308 break
308 break
309 if not include:
309 if not include:
310 for file_ in entry.files():
310 for file_ in entry.files():
311 todelete.append(file_.unencoded_path)
311 todelete.append(file_.unencoded_path)
312
312
313 repo.destroying()
313 repo.destroying()
314
314
315 with repo.transaction(b'narrowing'):
315 with repo.transaction(b'narrowing'):
316 # Update narrowspec before removing revlogs, so repo won't be
316 # Update narrowspec before removing revlogs, so repo won't be
317 # corrupt in case of crash
317 # corrupt in case of crash
318 repo.setnarrowpats(newincludes, newexcludes)
318 repo.setnarrowpats(newincludes, newexcludes)
319
319
320 for f in todelete:
320 for f in todelete:
321 ui.status(_(b'deleting %s\n') % f)
321 ui.status(_(b'deleting %s\n') % f)
322 util.unlinkpath(repo.svfs.join(f))
322 util.unlinkpath(repo.svfs.join(f))
323 repo.store.markremoved(f)
323 repo.store.markremoved(f)
324
324
325 ui.status(_(b'deleting unwanted files from working copy\n'))
325 ui.status(_(b'deleting unwanted files from working copy\n'))
326 with repo.dirstate.changing_parents(repo):
326 with repo.dirstate.changing_parents(repo):
327 narrowspec.updateworkingcopy(repo, assumeclean=True)
327 narrowspec.updateworkingcopy(repo, assumeclean=True)
328 narrowspec.copytoworkingcopy(repo)
328 narrowspec.copytoworkingcopy(repo)
329
329
330 repo.destroyed()
330 repo.destroyed()
331
331
332
332
333 def _widen(
333 def _widen(
334 ui,
334 ui,
335 repo,
335 repo,
336 remote,
336 remote,
337 commoninc,
337 commoninc,
338 oldincludes,
338 oldincludes,
339 oldexcludes,
339 oldexcludes,
340 newincludes,
340 newincludes,
341 newexcludes,
341 newexcludes,
342 ):
342 ):
343 # for now we assume that if a server has ellipses enabled, we will be
343 # for now we assume that if a server has ellipses enabled, we will be
344 # exchanging ellipses nodes. In future we should add ellipses as a client
344 # exchanging ellipses nodes. In future we should add ellipses as a client
345 # side requirement (maybe) to distinguish a client is shallow or not and
345 # side requirement (maybe) to distinguish a client is shallow or not and
346 # then send that information to server whether we want ellipses or not.
346 # then send that information to server whether we want ellipses or not.
347 # Theoretically a non-ellipses repo should be able to use narrow
347 # Theoretically a non-ellipses repo should be able to use narrow
348 # functionality from an ellipses enabled server
348 # functionality from an ellipses enabled server
349 remotecap = remote.capabilities()
349 remotecap = remote.capabilities()
350 ellipsesremote = any(
350 ellipsesremote = any(
351 cap in remotecap for cap in wireprototypes.SUPPORTED_ELLIPSESCAP
351 cap in remotecap for cap in wireprototypes.SUPPORTED_ELLIPSESCAP
352 )
352 )
353
353
354 # check whether we are talking to a server which supports old version of
354 # check whether we are talking to a server which supports old version of
355 # ellipses capabilities
355 # ellipses capabilities
356 isoldellipses = (
356 isoldellipses = (
357 ellipsesremote
357 ellipsesremote
358 and wireprototypes.ELLIPSESCAP1 in remotecap
358 and wireprototypes.ELLIPSESCAP1 in remotecap
359 and wireprototypes.ELLIPSESCAP not in remotecap
359 and wireprototypes.ELLIPSESCAP not in remotecap
360 )
360 )
361
361
362 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
362 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
363 orig(pullop, kwargs)
363 orig(pullop, kwargs)
364 # The old{in,ex}cludepats have already been set by orig()
364 # The old{in,ex}cludepats have already been set by orig()
365 kwargs[b'includepats'] = newincludes
365 kwargs[b'includepats'] = newincludes
366 kwargs[b'excludepats'] = newexcludes
366 kwargs[b'excludepats'] = newexcludes
367
367
368 wrappedextraprepare = extensions.wrappedfunction(
368 wrappedextraprepare = extensions.wrappedfunction(
369 exchange, '_pullbundle2extraprepare', pullbundle2extraprepare_widen
369 exchange, '_pullbundle2extraprepare', pullbundle2extraprepare_widen
370 )
370 )
371
371
372 # define a function that narrowbundle2 can call after creating the
372 # define a function that narrowbundle2 can call after creating the
373 # backup bundle, but before applying the bundle from the server
373 # backup bundle, but before applying the bundle from the server
374 def setnewnarrowpats():
374 def setnewnarrowpats():
375 repo.setnarrowpats(newincludes, newexcludes)
375 repo.setnarrowpats(newincludes, newexcludes)
376
376
377 repo.setnewnarrowpats = setnewnarrowpats
377 repo.setnewnarrowpats = setnewnarrowpats
378 # silence the devel-warning of applying an empty changegroup
378 # silence the devel-warning of applying an empty changegroup
379 overrides = {(b'devel', b'all-warnings'): False}
379 overrides = {(b'devel', b'all-warnings'): False}
380
380
381 common = commoninc[0]
381 common = commoninc[0]
382 with ui.uninterruptible():
382 with ui.uninterruptible():
383 if ellipsesremote:
383 if ellipsesremote:
384 ds = repo.dirstate
384 ds = repo.dirstate
385 p1, p2 = ds.p1(), ds.p2()
385 p1, p2 = ds.p1(), ds.p2()
386 with ds.changing_parents(repo):
386 with ds.changing_parents(repo):
387 ds.setparents(repo.nullid, repo.nullid)
387 ds.setparents(repo.nullid, repo.nullid)
388 if isoldellipses:
388 if isoldellipses:
389 with wrappedextraprepare:
389 with wrappedextraprepare:
390 exchange.pull(repo, remote, heads=common)
390 exchange.pull(repo, remote, heads=common)
391 else:
391 else:
392 known = []
392 known = []
393 if ellipsesremote:
393 if ellipsesremote:
394 known = [
394 known = [
395 ctx.node()
395 ctx.node()
396 for ctx in repo.set(b'::%ln', common)
396 for ctx in repo.set(b'::%ln', common)
397 if ctx.node() != repo.nullid
397 if ctx.node() != repo.nullid
398 ]
398 ]
399 with remote.commandexecutor() as e:
399 with remote.commandexecutor() as e:
400 bundle = e.callcommand(
400 bundle = e.callcommand(
401 b'narrow_widen',
401 b'narrow_widen',
402 {
402 {
403 b'oldincludes': oldincludes,
403 b'oldincludes': oldincludes,
404 b'oldexcludes': oldexcludes,
404 b'oldexcludes': oldexcludes,
405 b'newincludes': newincludes,
405 b'newincludes': newincludes,
406 b'newexcludes': newexcludes,
406 b'newexcludes': newexcludes,
407 b'cgversion': b'03',
407 b'cgversion': b'03',
408 b'commonheads': common,
408 b'commonheads': common,
409 b'known': known,
409 b'known': known,
410 b'ellipses': ellipsesremote,
410 b'ellipses': ellipsesremote,
411 },
411 },
412 ).result()
412 ).result()
413
413
414 trmanager = exchange.transactionmanager(
414 trmanager = exchange.transactionmanager(
415 repo, b'widen', remote.url()
415 repo, b'widen', remote.url()
416 )
416 )
417 with trmanager, repo.ui.configoverride(overrides, b'widen'):
417 with trmanager, repo.ui.configoverride(overrides, b'widen'):
418 op = bundle2.bundleoperation(
418 op = bundle2.bundleoperation(
419 repo, trmanager.transaction, source=b'widen'
419 repo, trmanager.transaction, source=b'widen'
420 )
420 )
421 # TODO: we should catch error.Abort here
421 # TODO: we should catch error.Abort here
422 bundle2.processbundle(repo, bundle, op=op, remote=remote)
422 bundle2.processbundle(repo, bundle, op=op, remote=remote)
423
423
424 if ellipsesremote:
424 if ellipsesremote:
425 with ds.changing_parents(repo):
425 with ds.changing_parents(repo):
426 ds.setparents(p1, p2)
426 ds.setparents(p1, p2)
427
427
428 with repo.transaction(b'widening'), repo.dirstate.changing_parents(
428 with repo.transaction(b'widening'), repo.dirstate.changing_parents(
429 repo
429 repo
430 ):
430 ):
431 repo.setnewnarrowpats()
431 repo.setnewnarrowpats()
432 narrowspec.updateworkingcopy(repo)
432 narrowspec.updateworkingcopy(repo)
433 narrowspec.copytoworkingcopy(repo)
433 narrowspec.copytoworkingcopy(repo)
434
434
435
435
436 # TODO(rdamazio): Make new matcher format and update description
436 # TODO(rdamazio): Make new matcher format and update description
437 @command(
437 @command(
438 b'tracked',
438 b'tracked',
439 [
439 [
440 (b'', b'addinclude', [], _(b'new paths to include')),
440 (b'', b'addinclude', [], _(b'new paths to include')),
441 (b'', b'removeinclude', [], _(b'old paths to no longer include')),
441 (b'', b'removeinclude', [], _(b'old paths to no longer include')),
442 (
442 (
443 b'',
443 b'',
444 b'auto-remove-includes',
444 b'auto-remove-includes',
445 False,
445 False,
446 _(b'automatically choose unused includes to remove'),
446 _(b'automatically choose unused includes to remove'),
447 ),
447 ),
448 (b'', b'addexclude', [], _(b'new paths to exclude')),
448 (b'', b'addexclude', [], _(b'new paths to exclude')),
449 (b'', b'import-rules', b'', _(b'import narrowspecs from a file')),
449 (b'', b'import-rules', b'', _(b'import narrowspecs from a file')),
450 (b'', b'removeexclude', [], _(b'old paths to no longer exclude')),
450 (b'', b'removeexclude', [], _(b'old paths to no longer exclude')),
451 (
451 (
452 b'',
452 b'',
453 b'clear',
453 b'clear',
454 False,
454 False,
455 _(b'whether to replace the existing narrowspec'),
455 _(b'whether to replace the existing narrowspec'),
456 ),
456 ),
457 (
457 (
458 b'',
458 b'',
459 b'force-delete-local-changes',
459 b'force-delete-local-changes',
460 False,
460 False,
461 _(b'forces deletion of local changes when narrowing'),
461 _(b'forces deletion of local changes when narrowing'),
462 ),
462 ),
463 (
463 (
464 b'',
464 b'',
465 b'backup',
465 b'backup',
466 True,
466 True,
467 _(b'back up local changes when narrowing'),
467 _(b'back up local changes when narrowing'),
468 ),
468 ),
469 (
469 (
470 b'',
470 b'',
471 b'update-working-copy',
471 b'update-working-copy',
472 False,
472 False,
473 _(b'update working copy when the store has changed'),
473 _(b'update working copy when the store has changed'),
474 ),
474 ),
475 ]
475 ]
476 + commands.remoteopts,
476 + commands.remoteopts,
477 _(b'[OPTIONS]... [REMOTE]'),
477 _(b'[OPTIONS]... [REMOTE]'),
478 inferrepo=True,
478 inferrepo=True,
479 helpcategory=command.CATEGORY_MAINTENANCE,
479 helpcategory=command.CATEGORY_MAINTENANCE,
480 )
480 )
481 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
481 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
482 """show or change the current narrowspec
482 """show or change the current narrowspec
483
483
484 With no argument, shows the current narrowspec entries, one per line. Each
484 With no argument, shows the current narrowspec entries, one per line. Each
485 line will be prefixed with 'I' or 'X' for included or excluded patterns,
485 line will be prefixed with 'I' or 'X' for included or excluded patterns,
486 respectively.
486 respectively.
487
487
488 The narrowspec is comprised of expressions to match remote files and/or
488 The narrowspec is comprised of expressions to match remote files and/or
489 directories that should be pulled into your client.
489 directories that should be pulled into your client.
490 The narrowspec has *include* and *exclude* expressions, with excludes always
490 The narrowspec has *include* and *exclude* expressions, with excludes always
491 trumping includes: that is, if a file matches an exclude expression, it will
491 trumping includes: that is, if a file matches an exclude expression, it will
492 be excluded even if it also matches an include expression.
492 be excluded even if it also matches an include expression.
493 Excluding files that were never included has no effect.
493 Excluding files that were never included has no effect.
494
494
495 Each included or excluded entry is in the format described by
495 Each included or excluded entry is in the format described by
496 'hg help patterns'.
496 'hg help patterns'.
497
497
498 The options allow you to add or remove included and excluded expressions.
498 The options allow you to add or remove included and excluded expressions.
499
499
500 If --clear is specified, then all previous includes and excludes are DROPPED
500 If --clear is specified, then all previous includes and excludes are DROPPED
501 and replaced by the new ones specified to --addinclude and --addexclude.
501 and replaced by the new ones specified to --addinclude and --addexclude.
502 If --clear is specified without any further options, the narrowspec will be
502 If --clear is specified without any further options, the narrowspec will be
503 empty and will not match any files.
503 empty and will not match any files.
504
504
505 If --auto-remove-includes is specified, then those includes that don't match
505 If --auto-remove-includes is specified, then those includes that don't match
506 any files modified by currently visible local commits (those not shared by
506 any files modified by currently visible local commits (those not shared by
507 the remote) will be added to the set of explicitly specified includes to
507 the remote) will be added to the set of explicitly specified includes to
508 remove.
508 remove.
509
509
510 --import-rules accepts a path to a file containing rules, allowing you to
510 --import-rules accepts a path to a file containing rules, allowing you to
511 add --addinclude, --addexclude rules in bulk. Like the other include and
511 add --addinclude, --addexclude rules in bulk. Like the other include and
512 exclude switches, the changes are applied immediately.
512 exclude switches, the changes are applied immediately.
513 """
513 """
514 opts = pycompat.byteskwargs(opts)
515 if requirements.NARROW_REQUIREMENT not in repo.requirements:
514 if requirements.NARROW_REQUIREMENT not in repo.requirements:
516 raise error.InputError(
515 raise error.InputError(
517 _(
516 _(
518 b'the tracked command is only supported on '
517 b'the tracked command is only supported on '
519 b'repositories cloned with --narrow'
518 b'repositories cloned with --narrow'
520 )
519 )
521 )
520 )
522
521
523 # Before supporting, decide whether it "hg tracked --clear" should mean
522 # Before supporting, decide whether it "hg tracked --clear" should mean
524 # tracking no paths or all paths.
523 # tracking no paths or all paths.
525 if opts[b'clear']:
524 if opts['clear']:
526 raise error.InputError(_(b'the --clear option is not yet supported'))
525 raise error.InputError(_(b'the --clear option is not yet supported'))
527
526
528 # import rules from a file
527 # import rules from a file
529 newrules = opts.get(b'import_rules')
528 newrules = opts.get('import_rules')
530 if newrules:
529 if newrules:
531 try:
530 try:
532 filepath = os.path.join(encoding.getcwd(), newrules)
531 filepath = os.path.join(encoding.getcwd(), newrules)
533 fdata = util.readfile(filepath)
532 fdata = util.readfile(filepath)
534 except IOError as inst:
533 except IOError as inst:
535 raise error.StorageError(
534 raise error.StorageError(
536 _(b"cannot read narrowspecs from '%s': %s")
535 _(b"cannot read narrowspecs from '%s': %s")
537 % (filepath, encoding.strtolocal(inst.strerror))
536 % (filepath, encoding.strtolocal(inst.strerror))
538 )
537 )
539 includepats, excludepats, profiles = sparse.parseconfig(
538 includepats, excludepats, profiles = sparse.parseconfig(
540 ui, fdata, b'narrow'
539 ui, fdata, b'narrow'
541 )
540 )
542 if profiles:
541 if profiles:
543 raise error.InputError(
542 raise error.InputError(
544 _(
543 _(
545 b"including other spec files using '%include' "
544 b"including other spec files using '%include' "
546 b"is not supported in narrowspec"
545 b"is not supported in narrowspec"
547 )
546 )
548 )
547 )
549 opts[b'addinclude'].extend(includepats)
548 opts['addinclude'].extend(includepats)
550 opts[b'addexclude'].extend(excludepats)
549 opts['addexclude'].extend(excludepats)
551
550
552 addedincludes = narrowspec.parsepatterns(opts[b'addinclude'])
551 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
553 removedincludes = narrowspec.parsepatterns(opts[b'removeinclude'])
552 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
554 addedexcludes = narrowspec.parsepatterns(opts[b'addexclude'])
553 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
555 removedexcludes = narrowspec.parsepatterns(opts[b'removeexclude'])
554 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
556 autoremoveincludes = opts[b'auto_remove_includes']
555 autoremoveincludes = opts['auto_remove_includes']
557
556
558 update_working_copy = opts[b'update_working_copy']
557 update_working_copy = opts['update_working_copy']
559 only_show = not (
558 only_show = not (
560 addedincludes
559 addedincludes
561 or removedincludes
560 or removedincludes
562 or addedexcludes
561 or addedexcludes
563 or removedexcludes
562 or removedexcludes
564 or newrules
563 or newrules
565 or autoremoveincludes
564 or autoremoveincludes
566 or update_working_copy
565 or update_working_copy
567 )
566 )
568
567
569 # Only print the current narrowspec.
568 # Only print the current narrowspec.
570 if only_show:
569 if only_show:
571 oldincludes, oldexcludes = repo.narrowpats
570 oldincludes, oldexcludes = repo.narrowpats
572 ui.pager(b'tracked')
571 ui.pager(b'tracked')
573 fm = ui.formatter(b'narrow', opts)
572 fm = ui.formatter(b'narrow', pycompat.byteskwargs(opts))
574 for i in sorted(oldincludes):
573 for i in sorted(oldincludes):
575 fm.startitem()
574 fm.startitem()
576 fm.write(b'status', b'%s ', b'I', label=b'narrow.included')
575 fm.write(b'status', b'%s ', b'I', label=b'narrow.included')
577 fm.write(b'pat', b'%s\n', i, label=b'narrow.included')
576 fm.write(b'pat', b'%s\n', i, label=b'narrow.included')
578 for i in sorted(oldexcludes):
577 for i in sorted(oldexcludes):
579 fm.startitem()
578 fm.startitem()
580 fm.write(b'status', b'%s ', b'X', label=b'narrow.excluded')
579 fm.write(b'status', b'%s ', b'X', label=b'narrow.excluded')
581 fm.write(b'pat', b'%s\n', i, label=b'narrow.excluded')
580 fm.write(b'pat', b'%s\n', i, label=b'narrow.excluded')
582 fm.end()
581 fm.end()
583 return 0
582 return 0
584
583
585 with repo.wlock(), repo.lock():
584 with repo.wlock(), repo.lock():
586 oldincludes, oldexcludes = repo.narrowpats
585 oldincludes, oldexcludes = repo.narrowpats
587
586
588 # filter the user passed additions and deletions into actual additions and
587 # filter the user passed additions and deletions into actual additions and
589 # deletions of excludes and includes
588 # deletions of excludes and includes
590 addedincludes -= oldincludes
589 addedincludes -= oldincludes
591 removedincludes &= oldincludes
590 removedincludes &= oldincludes
592 addedexcludes -= oldexcludes
591 addedexcludes -= oldexcludes
593 removedexcludes &= oldexcludes
592 removedexcludes &= oldexcludes
594
593
595 widening = addedincludes or removedexcludes
594 widening = addedincludes or removedexcludes
596 narrowing = removedincludes or addedexcludes
595 narrowing = removedincludes or addedexcludes
597
596
598 if update_working_copy:
597 if update_working_copy:
599 with repo.transaction(b'narrow-wc'), repo.dirstate.changing_parents(
598 with repo.transaction(b'narrow-wc'), repo.dirstate.changing_parents(
600 repo
599 repo
601 ):
600 ):
602 narrowspec.updateworkingcopy(repo)
601 narrowspec.updateworkingcopy(repo)
603 narrowspec.copytoworkingcopy(repo)
602 narrowspec.copytoworkingcopy(repo)
604 return 0
603 return 0
605
604
606 if not (widening or narrowing or autoremoveincludes):
605 if not (widening or narrowing or autoremoveincludes):
607 ui.status(_(b"nothing to widen or narrow\n"))
606 ui.status(_(b"nothing to widen or narrow\n"))
608 return 0
607 return 0
609
608
610 cmdutil.bailifchanged(repo)
609 cmdutil.bailifchanged(repo)
611
610
612 # Find the revisions we have in common with the remote. These will
611 # Find the revisions we have in common with the remote. These will
613 # be used for finding local-only changes for narrowing. They will
612 # be used for finding local-only changes for narrowing. They will
614 # also define the set of revisions to update for widening.
613 # also define the set of revisions to update for widening.
615 path = urlutil.get_unique_pull_path_obj(b'tracked', ui, remotepath)
614 path = urlutil.get_unique_pull_path_obj(b'tracked', ui, remotepath)
616 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(path.loc))
615 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(path.loc))
617 remote = hg.peer(repo, opts, path)
616 remote = hg.peer(repo, pycompat.byteskwargs(opts), path)
618
617
619 try:
618 try:
620 # check narrow support before doing anything if widening needs to be
619 # check narrow support before doing anything if widening needs to be
621 # performed. In future we should also abort if client is ellipses and
620 # performed. In future we should also abort if client is ellipses and
622 # server does not support ellipses
621 # server does not support ellipses
623 if (
622 if (
624 widening
623 widening
625 and wireprototypes.NARROWCAP not in remote.capabilities()
624 and wireprototypes.NARROWCAP not in remote.capabilities()
626 ):
625 ):
627 raise error.Abort(_(b"server does not support narrow clones"))
626 raise error.Abort(_(b"server does not support narrow clones"))
628
627
629 commoninc = discovery.findcommonincoming(repo, remote)
628 commoninc = discovery.findcommonincoming(repo, remote)
630
629
631 if autoremoveincludes:
630 if autoremoveincludes:
632 outgoing = discovery.findcommonoutgoing(
631 outgoing = discovery.findcommonoutgoing(
633 repo, remote, commoninc=commoninc
632 repo, remote, commoninc=commoninc
634 )
633 )
635 ui.status(_(b'looking for unused includes to remove\n'))
634 ui.status(_(b'looking for unused includes to remove\n'))
636 localfiles = set()
635 localfiles = set()
637 for n in itertools.chain(outgoing.missing, outgoing.excluded):
636 for n in itertools.chain(outgoing.missing, outgoing.excluded):
638 localfiles.update(repo[n].files())
637 localfiles.update(repo[n].files())
639 suggestedremovals = []
638 suggestedremovals = []
640 for include in sorted(oldincludes):
639 for include in sorted(oldincludes):
641 match = narrowspec.match(repo.root, [include], oldexcludes)
640 match = narrowspec.match(repo.root, [include], oldexcludes)
642 if not any(match(f) for f in localfiles):
641 if not any(match(f) for f in localfiles):
643 suggestedremovals.append(include)
642 suggestedremovals.append(include)
644 if suggestedremovals:
643 if suggestedremovals:
645 for s in suggestedremovals:
644 for s in suggestedremovals:
646 ui.status(b'%s\n' % s)
645 ui.status(b'%s\n' % s)
647 if (
646 if (
648 ui.promptchoice(
647 ui.promptchoice(
649 _(
648 _(
650 b'remove these unused includes (Yn)?'
649 b'remove these unused includes (Yn)?'
651 b'$$ &Yes $$ &No'
650 b'$$ &Yes $$ &No'
652 )
651 )
653 )
652 )
654 == 0
653 == 0
655 ):
654 ):
656 removedincludes.update(suggestedremovals)
655 removedincludes.update(suggestedremovals)
657 narrowing = True
656 narrowing = True
658 else:
657 else:
659 ui.status(_(b'found no unused includes\n'))
658 ui.status(_(b'found no unused includes\n'))
660
659
661 if narrowing:
660 if narrowing:
662 newincludes = oldincludes - removedincludes
661 newincludes = oldincludes - removedincludes
663 newexcludes = oldexcludes | addedexcludes
662 newexcludes = oldexcludes | addedexcludes
664 _narrow(
663 _narrow(
665 ui,
664 ui,
666 repo,
665 repo,
667 remote,
666 remote,
668 commoninc,
667 commoninc,
669 oldincludes,
668 oldincludes,
670 oldexcludes,
669 oldexcludes,
671 newincludes,
670 newincludes,
672 newexcludes,
671 newexcludes,
673 opts[b'force_delete_local_changes'],
672 opts['force_delete_local_changes'],
674 opts[b'backup'],
673 opts['backup'],
675 )
674 )
676 # _narrow() updated the narrowspec and _widen() below needs to
675 # _narrow() updated the narrowspec and _widen() below needs to
677 # use the updated values as its base (otherwise removed includes
676 # use the updated values as its base (otherwise removed includes
678 # and addedexcludes will be lost in the resulting narrowspec)
677 # and addedexcludes will be lost in the resulting narrowspec)
679 oldincludes = newincludes
678 oldincludes = newincludes
680 oldexcludes = newexcludes
679 oldexcludes = newexcludes
681
680
682 if widening:
681 if widening:
683 newincludes = oldincludes | addedincludes
682 newincludes = oldincludes | addedincludes
684 newexcludes = oldexcludes - removedexcludes
683 newexcludes = oldexcludes - removedexcludes
685 _widen(
684 _widen(
686 ui,
685 ui,
687 repo,
686 repo,
688 remote,
687 remote,
689 commoninc,
688 commoninc,
690 oldincludes,
689 oldincludes,
691 oldexcludes,
690 oldexcludes,
692 newincludes,
691 newincludes,
693 newexcludes,
692 newexcludes,
694 )
693 )
695 finally:
694 finally:
696 remote.close()
695 remote.close()
697
696
698 return 0
697 return 0
General Comments 0
You need to be logged in to leave comments. Login now