##// END OF EJS Templates
narrow: hoist a variable to a higher level to avoid use-before-init warning...
Matt Harbison -
r51775:75b90a8e default
parent child Browse files
Show More
@@ -1,697 +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 wrappedextraprepare = util.nullcontextmanager()
90 wrappedextraprepare = util.nullcontextmanager()
91 narrowspecfile = opts['narrowspec']
91 narrowspecfile = opts['narrowspec']
92
92
93 if narrowspecfile:
93 if narrowspecfile:
94 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
94 filepath = os.path.join(encoding.getcwd(), narrowspecfile)
95 ui.status(_(b"reading narrowspec from '%s'\n") % filepath)
95 ui.status(_(b"reading narrowspec from '%s'\n") % filepath)
96 try:
96 try:
97 fdata = util.readfile(filepath)
97 fdata = util.readfile(filepath)
98 except IOError as inst:
98 except IOError as inst:
99 raise error.Abort(
99 raise error.Abort(
100 _(b"cannot read narrowspecs from '%s': %s")
100 _(b"cannot read narrowspecs from '%s': %s")
101 % (filepath, encoding.strtolocal(inst.strerror))
101 % (filepath, encoding.strtolocal(inst.strerror))
102 )
102 )
103
103
104 includes, excludes, profiles = sparse.parseconfig(ui, fdata, b'narrow')
104 includes, excludes, profiles = sparse.parseconfig(ui, fdata, b'narrow')
105 if profiles:
105 if profiles:
106 raise error.ConfigError(
106 raise error.ConfigError(
107 _(
107 _(
108 b"cannot specify other files using '%include' in"
108 b"cannot specify other files using '%include' in"
109 b" narrowspec"
109 b" narrowspec"
110 )
110 )
111 )
111 )
112
112
113 narrowspec.validatepatterns(includes)
113 narrowspec.validatepatterns(includes)
114 narrowspec.validatepatterns(excludes)
114 narrowspec.validatepatterns(excludes)
115
115
116 # 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
117 opts['narrow'] = True
117 opts['narrow'] = True
118 opts['include'].extend(includes)
118 opts['include'].extend(includes)
119 opts['exclude'].extend(excludes)
119 opts['exclude'].extend(excludes)
120
120
121 if opts['narrow']:
121 if opts['narrow']:
122
122
123 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
123 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
124 orig(pullop, kwargs)
124 orig(pullop, kwargs)
125
125
126 if opts.get('depth'):
126 if opts.get('depth'):
127 # TODO: fix exchange._pullbundle2extraprepare()
127 # TODO: fix exchange._pullbundle2extraprepare()
128 kwargs[b'depth'] = opts['depth']
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, **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 if requirements.NARROW_REQUIREMENT not in repo.requirements:
514 if requirements.NARROW_REQUIREMENT not in repo.requirements:
515 raise error.InputError(
515 raise error.InputError(
516 _(
516 _(
517 b'the tracked command is only supported on '
517 b'the tracked command is only supported on '
518 b'repositories cloned with --narrow'
518 b'repositories cloned with --narrow'
519 )
519 )
520 )
520 )
521
521
522 # Before supporting, decide whether it "hg tracked --clear" should mean
522 # Before supporting, decide whether it "hg tracked --clear" should mean
523 # tracking no paths or all paths.
523 # tracking no paths or all paths.
524 if opts['clear']:
524 if opts['clear']:
525 raise error.InputError(_(b'the --clear option is not yet supported'))
525 raise error.InputError(_(b'the --clear option is not yet supported'))
526
526
527 # import rules from a file
527 # import rules from a file
528 newrules = opts.get('import_rules')
528 newrules = opts.get('import_rules')
529 if newrules:
529 if newrules:
530 filepath = os.path.join(encoding.getcwd(), newrules)
530 try:
531 try:
531 filepath = os.path.join(encoding.getcwd(), newrules)
532 fdata = util.readfile(filepath)
532 fdata = util.readfile(filepath)
533 except IOError as inst:
533 except IOError as inst:
534 raise error.StorageError(
534 raise error.StorageError(
535 _(b"cannot read narrowspecs from '%s': %s")
535 _(b"cannot read narrowspecs from '%s': %s")
536 % (filepath, encoding.strtolocal(inst.strerror))
536 % (filepath, encoding.strtolocal(inst.strerror))
537 )
537 )
538 includepats, excludepats, profiles = sparse.parseconfig(
538 includepats, excludepats, profiles = sparse.parseconfig(
539 ui, fdata, b'narrow'
539 ui, fdata, b'narrow'
540 )
540 )
541 if profiles:
541 if profiles:
542 raise error.InputError(
542 raise error.InputError(
543 _(
543 _(
544 b"including other spec files using '%include' "
544 b"including other spec files using '%include' "
545 b"is not supported in narrowspec"
545 b"is not supported in narrowspec"
546 )
546 )
547 )
547 )
548 opts['addinclude'].extend(includepats)
548 opts['addinclude'].extend(includepats)
549 opts['addexclude'].extend(excludepats)
549 opts['addexclude'].extend(excludepats)
550
550
551 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
551 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
552 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
552 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
553 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
553 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
554 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
554 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
555 autoremoveincludes = opts['auto_remove_includes']
555 autoremoveincludes = opts['auto_remove_includes']
556
556
557 update_working_copy = opts['update_working_copy']
557 update_working_copy = opts['update_working_copy']
558 only_show = not (
558 only_show = not (
559 addedincludes
559 addedincludes
560 or removedincludes
560 or removedincludes
561 or addedexcludes
561 or addedexcludes
562 or removedexcludes
562 or removedexcludes
563 or newrules
563 or newrules
564 or autoremoveincludes
564 or autoremoveincludes
565 or update_working_copy
565 or update_working_copy
566 )
566 )
567
567
568 # Only print the current narrowspec.
568 # Only print the current narrowspec.
569 if only_show:
569 if only_show:
570 oldincludes, oldexcludes = repo.narrowpats
570 oldincludes, oldexcludes = repo.narrowpats
571 ui.pager(b'tracked')
571 ui.pager(b'tracked')
572 fm = ui.formatter(b'narrow', pycompat.byteskwargs(opts))
572 fm = ui.formatter(b'narrow', pycompat.byteskwargs(opts))
573 for i in sorted(oldincludes):
573 for i in sorted(oldincludes):
574 fm.startitem()
574 fm.startitem()
575 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')
576 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')
577 for i in sorted(oldexcludes):
577 for i in sorted(oldexcludes):
578 fm.startitem()
578 fm.startitem()
579 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')
580 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')
581 fm.end()
581 fm.end()
582 return 0
582 return 0
583
583
584 with repo.wlock(), repo.lock():
584 with repo.wlock(), repo.lock():
585 oldincludes, oldexcludes = repo.narrowpats
585 oldincludes, oldexcludes = repo.narrowpats
586
586
587 # filter the user passed additions and deletions into actual additions and
587 # filter the user passed additions and deletions into actual additions and
588 # deletions of excludes and includes
588 # deletions of excludes and includes
589 addedincludes -= oldincludes
589 addedincludes -= oldincludes
590 removedincludes &= oldincludes
590 removedincludes &= oldincludes
591 addedexcludes -= oldexcludes
591 addedexcludes -= oldexcludes
592 removedexcludes &= oldexcludes
592 removedexcludes &= oldexcludes
593
593
594 widening = addedincludes or removedexcludes
594 widening = addedincludes or removedexcludes
595 narrowing = removedincludes or addedexcludes
595 narrowing = removedincludes or addedexcludes
596
596
597 if update_working_copy:
597 if update_working_copy:
598 with repo.transaction(b'narrow-wc'), repo.dirstate.changing_parents(
598 with repo.transaction(b'narrow-wc'), repo.dirstate.changing_parents(
599 repo
599 repo
600 ):
600 ):
601 narrowspec.updateworkingcopy(repo)
601 narrowspec.updateworkingcopy(repo)
602 narrowspec.copytoworkingcopy(repo)
602 narrowspec.copytoworkingcopy(repo)
603 return 0
603 return 0
604
604
605 if not (widening or narrowing or autoremoveincludes):
605 if not (widening or narrowing or autoremoveincludes):
606 ui.status(_(b"nothing to widen or narrow\n"))
606 ui.status(_(b"nothing to widen or narrow\n"))
607 return 0
607 return 0
608
608
609 cmdutil.bailifchanged(repo)
609 cmdutil.bailifchanged(repo)
610
610
611 # 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
612 # be used for finding local-only changes for narrowing. They will
612 # be used for finding local-only changes for narrowing. They will
613 # also define the set of revisions to update for widening.
613 # also define the set of revisions to update for widening.
614 path = urlutil.get_unique_pull_path_obj(b'tracked', ui, remotepath)
614 path = urlutil.get_unique_pull_path_obj(b'tracked', ui, remotepath)
615 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(path.loc))
615 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(path.loc))
616 remote = hg.peer(repo, pycompat.byteskwargs(opts), path)
616 remote = hg.peer(repo, pycompat.byteskwargs(opts), path)
617
617
618 try:
618 try:
619 # check narrow support before doing anything if widening needs to be
619 # check narrow support before doing anything if widening needs to be
620 # 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
621 # server does not support ellipses
621 # server does not support ellipses
622 if (
622 if (
623 widening
623 widening
624 and wireprototypes.NARROWCAP not in remote.capabilities()
624 and wireprototypes.NARROWCAP not in remote.capabilities()
625 ):
625 ):
626 raise error.Abort(_(b"server does not support narrow clones"))
626 raise error.Abort(_(b"server does not support narrow clones"))
627
627
628 commoninc = discovery.findcommonincoming(repo, remote)
628 commoninc = discovery.findcommonincoming(repo, remote)
629
629
630 if autoremoveincludes:
630 if autoremoveincludes:
631 outgoing = discovery.findcommonoutgoing(
631 outgoing = discovery.findcommonoutgoing(
632 repo, remote, commoninc=commoninc
632 repo, remote, commoninc=commoninc
633 )
633 )
634 ui.status(_(b'looking for unused includes to remove\n'))
634 ui.status(_(b'looking for unused includes to remove\n'))
635 localfiles = set()
635 localfiles = set()
636 for n in itertools.chain(outgoing.missing, outgoing.excluded):
636 for n in itertools.chain(outgoing.missing, outgoing.excluded):
637 localfiles.update(repo[n].files())
637 localfiles.update(repo[n].files())
638 suggestedremovals = []
638 suggestedremovals = []
639 for include in sorted(oldincludes):
639 for include in sorted(oldincludes):
640 match = narrowspec.match(repo.root, [include], oldexcludes)
640 match = narrowspec.match(repo.root, [include], oldexcludes)
641 if not any(match(f) for f in localfiles):
641 if not any(match(f) for f in localfiles):
642 suggestedremovals.append(include)
642 suggestedremovals.append(include)
643 if suggestedremovals:
643 if suggestedremovals:
644 for s in suggestedremovals:
644 for s in suggestedremovals:
645 ui.status(b'%s\n' % s)
645 ui.status(b'%s\n' % s)
646 if (
646 if (
647 ui.promptchoice(
647 ui.promptchoice(
648 _(
648 _(
649 b'remove these unused includes (Yn)?'
649 b'remove these unused includes (Yn)?'
650 b'$$ &Yes $$ &No'
650 b'$$ &Yes $$ &No'
651 )
651 )
652 )
652 )
653 == 0
653 == 0
654 ):
654 ):
655 removedincludes.update(suggestedremovals)
655 removedincludes.update(suggestedremovals)
656 narrowing = True
656 narrowing = True
657 else:
657 else:
658 ui.status(_(b'found no unused includes\n'))
658 ui.status(_(b'found no unused includes\n'))
659
659
660 if narrowing:
660 if narrowing:
661 newincludes = oldincludes - removedincludes
661 newincludes = oldincludes - removedincludes
662 newexcludes = oldexcludes | addedexcludes
662 newexcludes = oldexcludes | addedexcludes
663 _narrow(
663 _narrow(
664 ui,
664 ui,
665 repo,
665 repo,
666 remote,
666 remote,
667 commoninc,
667 commoninc,
668 oldincludes,
668 oldincludes,
669 oldexcludes,
669 oldexcludes,
670 newincludes,
670 newincludes,
671 newexcludes,
671 newexcludes,
672 opts['force_delete_local_changes'],
672 opts['force_delete_local_changes'],
673 opts['backup'],
673 opts['backup'],
674 )
674 )
675 # _narrow() updated the narrowspec and _widen() below needs to
675 # _narrow() updated the narrowspec and _widen() below needs to
676 # use the updated values as its base (otherwise removed includes
676 # use the updated values as its base (otherwise removed includes
677 # and addedexcludes will be lost in the resulting narrowspec)
677 # and addedexcludes will be lost in the resulting narrowspec)
678 oldincludes = newincludes
678 oldincludes = newincludes
679 oldexcludes = newexcludes
679 oldexcludes = newexcludes
680
680
681 if widening:
681 if widening:
682 newincludes = oldincludes | addedincludes
682 newincludes = oldincludes | addedincludes
683 newexcludes = oldexcludes - removedexcludes
683 newexcludes = oldexcludes - removedexcludes
684 _widen(
684 _widen(
685 ui,
685 ui,
686 repo,
686 repo,
687 remote,
687 remote,
688 commoninc,
688 commoninc,
689 oldincludes,
689 oldincludes,
690 oldexcludes,
690 oldexcludes,
691 newincludes,
691 newincludes,
692 newexcludes,
692 newexcludes,
693 )
693 )
694 finally:
694 finally:
695 remote.close()
695 remote.close()
696
696
697 return 0
697 return 0
General Comments 0
You need to be logged in to leave comments. Login now