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