##// END OF EJS Templates
narrow: update narrow spec within a dirstate.parentchange context...
marmoute -
r48404:090fc6a9 default
parent child Browse files
Show More
@@ -1,691 +1,694 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(
230 progress = ui.makeprogress(
231 topic=_(b'changesets'),
231 topic=_(b'changesets'),
232 unit=_(b'changesets'),
232 unit=_(b'changesets'),
233 total=len(outgoing.missing) + len(outgoing.excluded),
233 total=len(outgoing.missing) + len(outgoing.excluded),
234 )
234 )
235 localnodes = []
235 localnodes = []
236 with progress:
236 with progress:
237 for n in itertools.chain(outgoing.missing, outgoing.excluded):
237 for n in itertools.chain(outgoing.missing, outgoing.excluded):
238 progress.increment()
238 progress.increment()
239 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
239 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
240 localnodes.append(n)
240 localnodes.append(n)
241 revstostrip = unfi.revs(b'descendants(%ln)', localnodes)
241 revstostrip = unfi.revs(b'descendants(%ln)', localnodes)
242 hiddenrevs = repoview.filterrevs(repo, b'visible')
242 hiddenrevs = repoview.filterrevs(repo, b'visible')
243 visibletostrip = list(
243 visibletostrip = list(
244 repo.changelog.node(r) for r in (revstostrip - hiddenrevs)
244 repo.changelog.node(r) for r in (revstostrip - hiddenrevs)
245 )
245 )
246 if visibletostrip:
246 if visibletostrip:
247 ui.status(
247 ui.status(
248 _(
248 _(
249 b'The following changeset(s) or their ancestors have '
249 b'The following changeset(s) or their ancestors have '
250 b'local changes not on the remote:\n'
250 b'local changes not on the remote:\n'
251 )
251 )
252 )
252 )
253 maxnodes = 10
253 maxnodes = 10
254 if ui.verbose or len(visibletostrip) <= maxnodes:
254 if ui.verbose or len(visibletostrip) <= maxnodes:
255 for n in visibletostrip:
255 for n in visibletostrip:
256 ui.status(b'%s\n' % short(n))
256 ui.status(b'%s\n' % short(n))
257 else:
257 else:
258 for n in visibletostrip[:maxnodes]:
258 for n in visibletostrip[:maxnodes]:
259 ui.status(b'%s\n' % short(n))
259 ui.status(b'%s\n' % short(n))
260 ui.status(
260 ui.status(
261 _(b'...and %d more, use --verbose to list all\n')
261 _(b'...and %d more, use --verbose to list all\n')
262 % (len(visibletostrip) - maxnodes)
262 % (len(visibletostrip) - maxnodes)
263 )
263 )
264 if not force:
264 if not force:
265 raise error.StateError(
265 raise error.StateError(
266 _(b'local changes found'),
266 _(b'local changes found'),
267 hint=_(b'use --force-delete-local-changes to ignore'),
267 hint=_(b'use --force-delete-local-changes to ignore'),
268 )
268 )
269
269
270 with ui.uninterruptible():
270 with ui.uninterruptible():
271 if revstostrip:
271 if revstostrip:
272 tostrip = [unfi.changelog.node(r) for r in revstostrip]
272 tostrip = [unfi.changelog.node(r) for r in revstostrip]
273 if repo[b'.'].node() in tostrip:
273 if repo[b'.'].node() in tostrip:
274 # stripping working copy, so move to a different commit first
274 # stripping working copy, so move to a different commit first
275 urev = max(
275 urev = max(
276 repo.revs(
276 repo.revs(
277 b'(::%n) - %ln + null',
277 b'(::%n) - %ln + null',
278 repo[b'.'].node(),
278 repo[b'.'].node(),
279 visibletostrip,
279 visibletostrip,
280 )
280 )
281 )
281 )
282 hg.clean(repo, urev)
282 hg.clean(repo, urev)
283 overrides = {(b'devel', b'strip-obsmarkers'): False}
283 overrides = {(b'devel', b'strip-obsmarkers'): False}
284 if backup:
284 if backup:
285 ui.status(_(b'moving unwanted changesets to backup\n'))
285 ui.status(_(b'moving unwanted changesets to backup\n'))
286 else:
286 else:
287 ui.status(_(b'deleting unwanted changesets\n'))
287 ui.status(_(b'deleting unwanted changesets\n'))
288 with ui.configoverride(overrides, b'narrow'):
288 with ui.configoverride(overrides, b'narrow'):
289 repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup)
289 repair.strip(ui, unfi, tostrip, topic=b'narrow', backup=backup)
290
290
291 todelete = []
291 todelete = []
292 for t, f, f2, size in repo.store.datafiles():
292 for t, f, f2, size in repo.store.datafiles():
293 if f.startswith(b'data/'):
293 if f.startswith(b'data/'):
294 file = f[5:-2]
294 file = f[5:-2]
295 if not newmatch(file):
295 if not newmatch(file):
296 todelete.append(f)
296 todelete.append(f)
297 elif f.startswith(b'meta/'):
297 elif f.startswith(b'meta/'):
298 dir = f[5:-13]
298 dir = f[5:-13]
299 dirs = sorted(pathutil.dirs({dir})) + [dir]
299 dirs = sorted(pathutil.dirs({dir})) + [dir]
300 include = True
300 include = True
301 for d in dirs:
301 for d in dirs:
302 visit = newmatch.visitdir(d)
302 visit = newmatch.visitdir(d)
303 if not visit:
303 if not visit:
304 include = False
304 include = False
305 break
305 break
306 if visit == b'all':
306 if visit == b'all':
307 break
307 break
308 if not include:
308 if not include:
309 todelete.append(f)
309 todelete.append(f)
310
310
311 repo.destroying()
311 repo.destroying()
312
312
313 with repo.transaction(b'narrowing'):
313 with repo.transaction(b'narrowing'):
314 # Update narrowspec before removing revlogs, so repo won't be
314 # Update narrowspec before removing revlogs, so repo won't be
315 # corrupt in case of crash
315 # corrupt in case of crash
316 repo.setnarrowpats(newincludes, newexcludes)
316 repo.setnarrowpats(newincludes, newexcludes)
317
317
318 for f in todelete:
318 for f in todelete:
319 ui.status(_(b'deleting %s\n') % f)
319 ui.status(_(b'deleting %s\n') % f)
320 util.unlinkpath(repo.svfs.join(f))
320 util.unlinkpath(repo.svfs.join(f))
321 repo.store.markremoved(f)
321 repo.store.markremoved(f)
322
322
323 ui.status(_(b'deleting unwanted files from working copy\n'))
323 ui.status(_(b'deleting unwanted files from working copy\n'))
324 narrowspec.updateworkingcopy(repo, assumeclean=True)
324 with repo.dirstate.parentchange():
325 narrowspec.copytoworkingcopy(repo)
325 narrowspec.updateworkingcopy(repo, assumeclean=True)
326 narrowspec.copytoworkingcopy(repo)
326
327
327 repo.destroyed()
328 repo.destroyed()
328
329
329
330
330 def _widen(
331 def _widen(
331 ui,
332 ui,
332 repo,
333 repo,
333 remote,
334 remote,
334 commoninc,
335 commoninc,
335 oldincludes,
336 oldincludes,
336 oldexcludes,
337 oldexcludes,
337 newincludes,
338 newincludes,
338 newexcludes,
339 newexcludes,
339 ):
340 ):
340 # for now we assume that if a server has ellipses enabled, we will be
341 # for now we assume that if a server has ellipses enabled, we will be
341 # exchanging ellipses nodes. In future we should add ellipses as a client
342 # exchanging ellipses nodes. In future we should add ellipses as a client
342 # side requirement (maybe) to distinguish a client is shallow or not and
343 # side requirement (maybe) to distinguish a client is shallow or not and
343 # then send that information to server whether we want ellipses or not.
344 # then send that information to server whether we want ellipses or not.
344 # Theoretically a non-ellipses repo should be able to use narrow
345 # Theoretically a non-ellipses repo should be able to use narrow
345 # functionality from an ellipses enabled server
346 # functionality from an ellipses enabled server
346 remotecap = remote.capabilities()
347 remotecap = remote.capabilities()
347 ellipsesremote = any(
348 ellipsesremote = any(
348 cap in remotecap for cap in wireprototypes.SUPPORTED_ELLIPSESCAP
349 cap in remotecap for cap in wireprototypes.SUPPORTED_ELLIPSESCAP
349 )
350 )
350
351
351 # check whether we are talking to a server which supports old version of
352 # check whether we are talking to a server which supports old version of
352 # ellipses capabilities
353 # ellipses capabilities
353 isoldellipses = (
354 isoldellipses = (
354 ellipsesremote
355 ellipsesremote
355 and wireprototypes.ELLIPSESCAP1 in remotecap
356 and wireprototypes.ELLIPSESCAP1 in remotecap
356 and wireprototypes.ELLIPSESCAP not in remotecap
357 and wireprototypes.ELLIPSESCAP not in remotecap
357 )
358 )
358
359
359 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
360 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
360 orig(pullop, kwargs)
361 orig(pullop, kwargs)
361 # The old{in,ex}cludepats have already been set by orig()
362 # The old{in,ex}cludepats have already been set by orig()
362 kwargs[b'includepats'] = newincludes
363 kwargs[b'includepats'] = newincludes
363 kwargs[b'excludepats'] = newexcludes
364 kwargs[b'excludepats'] = newexcludes
364
365
365 wrappedextraprepare = extensions.wrappedfunction(
366 wrappedextraprepare = extensions.wrappedfunction(
366 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
367 exchange, b'_pullbundle2extraprepare', pullbundle2extraprepare_widen
367 )
368 )
368
369
369 # define a function that narrowbundle2 can call after creating the
370 # define a function that narrowbundle2 can call after creating the
370 # backup bundle, but before applying the bundle from the server
371 # backup bundle, but before applying the bundle from the server
371 def setnewnarrowpats():
372 def setnewnarrowpats():
372 repo.setnarrowpats(newincludes, newexcludes)
373 repo.setnarrowpats(newincludes, newexcludes)
373
374
374 repo.setnewnarrowpats = setnewnarrowpats
375 repo.setnewnarrowpats = setnewnarrowpats
375 # silence the devel-warning of applying an empty changegroup
376 # silence the devel-warning of applying an empty changegroup
376 overrides = {(b'devel', b'all-warnings'): False}
377 overrides = {(b'devel', b'all-warnings'): False}
377
378
378 common = commoninc[0]
379 common = commoninc[0]
379 with ui.uninterruptible():
380 with ui.uninterruptible():
380 if ellipsesremote:
381 if ellipsesremote:
381 ds = repo.dirstate
382 ds = repo.dirstate
382 p1, p2 = ds.p1(), ds.p2()
383 p1, p2 = ds.p1(), ds.p2()
383 with ds.parentchange():
384 with ds.parentchange():
384 ds.setparents(repo.nullid, repo.nullid)
385 ds.setparents(repo.nullid, repo.nullid)
385 if isoldellipses:
386 if isoldellipses:
386 with wrappedextraprepare:
387 with wrappedextraprepare:
387 exchange.pull(repo, remote, heads=common)
388 exchange.pull(repo, remote, heads=common)
388 else:
389 else:
389 known = []
390 known = []
390 if ellipsesremote:
391 if ellipsesremote:
391 known = [
392 known = [
392 ctx.node()
393 ctx.node()
393 for ctx in repo.set(b'::%ln', common)
394 for ctx in repo.set(b'::%ln', common)
394 if ctx.node() != repo.nullid
395 if ctx.node() != repo.nullid
395 ]
396 ]
396 with remote.commandexecutor() as e:
397 with remote.commandexecutor() as e:
397 bundle = e.callcommand(
398 bundle = e.callcommand(
398 b'narrow_widen',
399 b'narrow_widen',
399 {
400 {
400 b'oldincludes': oldincludes,
401 b'oldincludes': oldincludes,
401 b'oldexcludes': oldexcludes,
402 b'oldexcludes': oldexcludes,
402 b'newincludes': newincludes,
403 b'newincludes': newincludes,
403 b'newexcludes': newexcludes,
404 b'newexcludes': newexcludes,
404 b'cgversion': b'03',
405 b'cgversion': b'03',
405 b'commonheads': common,
406 b'commonheads': common,
406 b'known': known,
407 b'known': known,
407 b'ellipses': ellipsesremote,
408 b'ellipses': ellipsesremote,
408 },
409 },
409 ).result()
410 ).result()
410
411
411 trmanager = exchange.transactionmanager(
412 trmanager = exchange.transactionmanager(
412 repo, b'widen', remote.url()
413 repo, b'widen', remote.url()
413 )
414 )
414 with trmanager, repo.ui.configoverride(overrides, b'widen'):
415 with trmanager, repo.ui.configoverride(overrides, b'widen'):
415 op = bundle2.bundleoperation(
416 op = bundle2.bundleoperation(
416 repo, trmanager.transaction, source=b'widen'
417 repo, trmanager.transaction, source=b'widen'
417 )
418 )
418 # TODO: we should catch error.Abort here
419 # TODO: we should catch error.Abort here
419 bundle2.processbundle(repo, bundle, op=op)
420 bundle2.processbundle(repo, bundle, op=op)
420
421
421 if ellipsesremote:
422 if ellipsesremote:
422 with ds.parentchange():
423 with ds.parentchange():
423 ds.setparents(p1, p2)
424 ds.setparents(p1, p2)
424
425
425 with repo.transaction(b'widening'):
426 with repo.transaction(b'widening'), repo.dirstate.parentchange():
426 repo.setnewnarrowpats()
427 repo.setnewnarrowpats()
427 narrowspec.updateworkingcopy(repo)
428 narrowspec.updateworkingcopy(repo)
428 narrowspec.copytoworkingcopy(repo)
429 narrowspec.copytoworkingcopy(repo)
429
430
430
431
431 # TODO(rdamazio): Make new matcher format and update description
432 # TODO(rdamazio): Make new matcher format and update description
432 @command(
433 @command(
433 b'tracked',
434 b'tracked',
434 [
435 [
435 (b'', b'addinclude', [], _(b'new paths to include')),
436 (b'', b'addinclude', [], _(b'new paths to include')),
436 (b'', b'removeinclude', [], _(b'old paths to no longer include')),
437 (b'', b'removeinclude', [], _(b'old paths to no longer include')),
437 (
438 (
438 b'',
439 b'',
439 b'auto-remove-includes',
440 b'auto-remove-includes',
440 False,
441 False,
441 _(b'automatically choose unused includes to remove'),
442 _(b'automatically choose unused includes to remove'),
442 ),
443 ),
443 (b'', b'addexclude', [], _(b'new paths to exclude')),
444 (b'', b'addexclude', [], _(b'new paths to exclude')),
444 (b'', b'import-rules', b'', _(b'import narrowspecs from a file')),
445 (b'', b'import-rules', b'', _(b'import narrowspecs from a file')),
445 (b'', b'removeexclude', [], _(b'old paths to no longer exclude')),
446 (b'', b'removeexclude', [], _(b'old paths to no longer exclude')),
446 (
447 (
447 b'',
448 b'',
448 b'clear',
449 b'clear',
449 False,
450 False,
450 _(b'whether to replace the existing narrowspec'),
451 _(b'whether to replace the existing narrowspec'),
451 ),
452 ),
452 (
453 (
453 b'',
454 b'',
454 b'force-delete-local-changes',
455 b'force-delete-local-changes',
455 False,
456 False,
456 _(b'forces deletion of local changes when narrowing'),
457 _(b'forces deletion of local changes when narrowing'),
457 ),
458 ),
458 (
459 (
459 b'',
460 b'',
460 b'backup',
461 b'backup',
461 True,
462 True,
462 _(b'back up local changes when narrowing'),
463 _(b'back up local changes when narrowing'),
463 ),
464 ),
464 (
465 (
465 b'',
466 b'',
466 b'update-working-copy',
467 b'update-working-copy',
467 False,
468 False,
468 _(b'update working copy when the store has changed'),
469 _(b'update working copy when the store has changed'),
469 ),
470 ),
470 ]
471 ]
471 + commands.remoteopts,
472 + commands.remoteopts,
472 _(b'[OPTIONS]... [REMOTE]'),
473 _(b'[OPTIONS]... [REMOTE]'),
473 inferrepo=True,
474 inferrepo=True,
474 helpcategory=command.CATEGORY_MAINTENANCE,
475 helpcategory=command.CATEGORY_MAINTENANCE,
475 )
476 )
476 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
477 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
477 """show or change the current narrowspec
478 """show or change the current narrowspec
478
479
479 With no argument, shows the current narrowspec entries, one per line. Each
480 With no argument, shows the current narrowspec entries, one per line. Each
480 line will be prefixed with 'I' or 'X' for included or excluded patterns,
481 line will be prefixed with 'I' or 'X' for included or excluded patterns,
481 respectively.
482 respectively.
482
483
483 The narrowspec is comprised of expressions to match remote files and/or
484 The narrowspec is comprised of expressions to match remote files and/or
484 directories that should be pulled into your client.
485 directories that should be pulled into your client.
485 The narrowspec has *include* and *exclude* expressions, with excludes always
486 The narrowspec has *include* and *exclude* expressions, with excludes always
486 trumping includes: that is, if a file matches an exclude expression, it will
487 trumping includes: that is, if a file matches an exclude expression, it will
487 be excluded even if it also matches an include expression.
488 be excluded even if it also matches an include expression.
488 Excluding files that were never included has no effect.
489 Excluding files that were never included has no effect.
489
490
490 Each included or excluded entry is in the format described by
491 Each included or excluded entry is in the format described by
491 'hg help patterns'.
492 'hg help patterns'.
492
493
493 The options allow you to add or remove included and excluded expressions.
494 The options allow you to add or remove included and excluded expressions.
494
495
495 If --clear is specified, then all previous includes and excludes are DROPPED
496 If --clear is specified, then all previous includes and excludes are DROPPED
496 and replaced by the new ones specified to --addinclude and --addexclude.
497 and replaced by the new ones specified to --addinclude and --addexclude.
497 If --clear is specified without any further options, the narrowspec will be
498 If --clear is specified without any further options, the narrowspec will be
498 empty and will not match any files.
499 empty and will not match any files.
499
500
500 If --auto-remove-includes is specified, then those includes that don't match
501 If --auto-remove-includes is specified, then those includes that don't match
501 any files modified by currently visible local commits (those not shared by
502 any files modified by currently visible local commits (those not shared by
502 the remote) will be added to the set of explicitly specified includes to
503 the remote) will be added to the set of explicitly specified includes to
503 remove.
504 remove.
504
505
505 --import-rules accepts a path to a file containing rules, allowing you to
506 --import-rules accepts a path to a file containing rules, allowing you to
506 add --addinclude, --addexclude rules in bulk. Like the other include and
507 add --addinclude, --addexclude rules in bulk. Like the other include and
507 exclude switches, the changes are applied immediately.
508 exclude switches, the changes are applied immediately.
508 """
509 """
509 opts = pycompat.byteskwargs(opts)
510 opts = pycompat.byteskwargs(opts)
510 if requirements.NARROW_REQUIREMENT not in repo.requirements:
511 if requirements.NARROW_REQUIREMENT not in repo.requirements:
511 raise error.InputError(
512 raise error.InputError(
512 _(
513 _(
513 b'the tracked command is only supported on '
514 b'the tracked command is only supported on '
514 b'repositories cloned with --narrow'
515 b'repositories cloned with --narrow'
515 )
516 )
516 )
517 )
517
518
518 # Before supporting, decide whether it "hg tracked --clear" should mean
519 # Before supporting, decide whether it "hg tracked --clear" should mean
519 # tracking no paths or all paths.
520 # tracking no paths or all paths.
520 if opts[b'clear']:
521 if opts[b'clear']:
521 raise error.InputError(_(b'the --clear option is not yet supported'))
522 raise error.InputError(_(b'the --clear option is not yet supported'))
522
523
523 # import rules from a file
524 # import rules from a file
524 newrules = opts.get(b'import_rules')
525 newrules = opts.get(b'import_rules')
525 if newrules:
526 if newrules:
526 try:
527 try:
527 filepath = os.path.join(encoding.getcwd(), newrules)
528 filepath = os.path.join(encoding.getcwd(), newrules)
528 fdata = util.readfile(filepath)
529 fdata = util.readfile(filepath)
529 except IOError as inst:
530 except IOError as inst:
530 raise error.StorageError(
531 raise error.StorageError(
531 _(b"cannot read narrowspecs from '%s': %s")
532 _(b"cannot read narrowspecs from '%s': %s")
532 % (filepath, encoding.strtolocal(inst.strerror))
533 % (filepath, encoding.strtolocal(inst.strerror))
533 )
534 )
534 includepats, excludepats, profiles = sparse.parseconfig(
535 includepats, excludepats, profiles = sparse.parseconfig(
535 ui, fdata, b'narrow'
536 ui, fdata, b'narrow'
536 )
537 )
537 if profiles:
538 if profiles:
538 raise error.InputError(
539 raise error.InputError(
539 _(
540 _(
540 b"including other spec files using '%include' "
541 b"including other spec files using '%include' "
541 b"is not supported in narrowspec"
542 b"is not supported in narrowspec"
542 )
543 )
543 )
544 )
544 opts[b'addinclude'].extend(includepats)
545 opts[b'addinclude'].extend(includepats)
545 opts[b'addexclude'].extend(excludepats)
546 opts[b'addexclude'].extend(excludepats)
546
547
547 addedincludes = narrowspec.parsepatterns(opts[b'addinclude'])
548 addedincludes = narrowspec.parsepatterns(opts[b'addinclude'])
548 removedincludes = narrowspec.parsepatterns(opts[b'removeinclude'])
549 removedincludes = narrowspec.parsepatterns(opts[b'removeinclude'])
549 addedexcludes = narrowspec.parsepatterns(opts[b'addexclude'])
550 addedexcludes = narrowspec.parsepatterns(opts[b'addexclude'])
550 removedexcludes = narrowspec.parsepatterns(opts[b'removeexclude'])
551 removedexcludes = narrowspec.parsepatterns(opts[b'removeexclude'])
551 autoremoveincludes = opts[b'auto_remove_includes']
552 autoremoveincludes = opts[b'auto_remove_includes']
552
553
553 update_working_copy = opts[b'update_working_copy']
554 update_working_copy = opts[b'update_working_copy']
554 only_show = not (
555 only_show = not (
555 addedincludes
556 addedincludes
556 or removedincludes
557 or removedincludes
557 or addedexcludes
558 or addedexcludes
558 or removedexcludes
559 or removedexcludes
559 or newrules
560 or newrules
560 or autoremoveincludes
561 or autoremoveincludes
561 or update_working_copy
562 or update_working_copy
562 )
563 )
563
564
564 oldincludes, oldexcludes = repo.narrowpats
565 oldincludes, oldexcludes = repo.narrowpats
565
566
566 # filter the user passed additions and deletions into actual additions and
567 # filter the user passed additions and deletions into actual additions and
567 # deletions of excludes and includes
568 # deletions of excludes and includes
568 addedincludes -= oldincludes
569 addedincludes -= oldincludes
569 removedincludes &= oldincludes
570 removedincludes &= oldincludes
570 addedexcludes -= oldexcludes
571 addedexcludes -= oldexcludes
571 removedexcludes &= oldexcludes
572 removedexcludes &= oldexcludes
572
573
573 widening = addedincludes or removedexcludes
574 widening = addedincludes or removedexcludes
574 narrowing = removedincludes or addedexcludes
575 narrowing = removedincludes or addedexcludes
575
576
576 # Only print the current narrowspec.
577 # Only print the current narrowspec.
577 if only_show:
578 if only_show:
578 ui.pager(b'tracked')
579 ui.pager(b'tracked')
579 fm = ui.formatter(b'narrow', opts)
580 fm = ui.formatter(b'narrow', opts)
580 for i in sorted(oldincludes):
581 for i in sorted(oldincludes):
581 fm.startitem()
582 fm.startitem()
582 fm.write(b'status', b'%s ', b'I', label=b'narrow.included')
583 fm.write(b'status', b'%s ', b'I', label=b'narrow.included')
583 fm.write(b'pat', b'%s\n', i, label=b'narrow.included')
584 fm.write(b'pat', b'%s\n', i, label=b'narrow.included')
584 for i in sorted(oldexcludes):
585 for i in sorted(oldexcludes):
585 fm.startitem()
586 fm.startitem()
586 fm.write(b'status', b'%s ', b'X', label=b'narrow.excluded')
587 fm.write(b'status', b'%s ', b'X', label=b'narrow.excluded')
587 fm.write(b'pat', b'%s\n', i, label=b'narrow.excluded')
588 fm.write(b'pat', b'%s\n', i, label=b'narrow.excluded')
588 fm.end()
589 fm.end()
589 return 0
590 return 0
590
591
591 if update_working_copy:
592 if update_working_copy:
592 with repo.wlock(), repo.lock(), repo.transaction(b'narrow-wc'):
593 with repo.wlock(), repo.lock(), repo.transaction(
594 b'narrow-wc'
595 ), repo.dirstate.parentchange():
593 narrowspec.updateworkingcopy(repo)
596 narrowspec.updateworkingcopy(repo)
594 narrowspec.copytoworkingcopy(repo)
597 narrowspec.copytoworkingcopy(repo)
595 return 0
598 return 0
596
599
597 if not (widening or narrowing or autoremoveincludes):
600 if not (widening or narrowing or autoremoveincludes):
598 ui.status(_(b"nothing to widen or narrow\n"))
601 ui.status(_(b"nothing to widen or narrow\n"))
599 return 0
602 return 0
600
603
601 with repo.wlock(), repo.lock():
604 with repo.wlock(), repo.lock():
602 cmdutil.bailifchanged(repo)
605 cmdutil.bailifchanged(repo)
603
606
604 # Find the revisions we have in common with the remote. These will
607 # Find the revisions we have in common with the remote. These will
605 # be used for finding local-only changes for narrowing. They will
608 # be used for finding local-only changes for narrowing. They will
606 # also define the set of revisions to update for widening.
609 # also define the set of revisions to update for widening.
607 r = urlutil.get_unique_pull_path(b'tracked', repo, ui, remotepath)
610 r = urlutil.get_unique_pull_path(b'tracked', repo, ui, remotepath)
608 url, branches = r
611 url, branches = r
609 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(url))
612 ui.status(_(b'comparing with %s\n') % urlutil.hidepassword(url))
610 remote = hg.peer(repo, opts, url)
613 remote = hg.peer(repo, opts, url)
611
614
612 try:
615 try:
613 # check narrow support before doing anything if widening needs to be
616 # check narrow support before doing anything if widening needs to be
614 # performed. In future we should also abort if client is ellipses and
617 # performed. In future we should also abort if client is ellipses and
615 # server does not support ellipses
618 # server does not support ellipses
616 if (
619 if (
617 widening
620 widening
618 and wireprototypes.NARROWCAP not in remote.capabilities()
621 and wireprototypes.NARROWCAP not in remote.capabilities()
619 ):
622 ):
620 raise error.Abort(_(b"server does not support narrow clones"))
623 raise error.Abort(_(b"server does not support narrow clones"))
621
624
622 commoninc = discovery.findcommonincoming(repo, remote)
625 commoninc = discovery.findcommonincoming(repo, remote)
623
626
624 if autoremoveincludes:
627 if autoremoveincludes:
625 outgoing = discovery.findcommonoutgoing(
628 outgoing = discovery.findcommonoutgoing(
626 repo, remote, commoninc=commoninc
629 repo, remote, commoninc=commoninc
627 )
630 )
628 ui.status(_(b'looking for unused includes to remove\n'))
631 ui.status(_(b'looking for unused includes to remove\n'))
629 localfiles = set()
632 localfiles = set()
630 for n in itertools.chain(outgoing.missing, outgoing.excluded):
633 for n in itertools.chain(outgoing.missing, outgoing.excluded):
631 localfiles.update(repo[n].files())
634 localfiles.update(repo[n].files())
632 suggestedremovals = []
635 suggestedremovals = []
633 for include in sorted(oldincludes):
636 for include in sorted(oldincludes):
634 match = narrowspec.match(repo.root, [include], oldexcludes)
637 match = narrowspec.match(repo.root, [include], oldexcludes)
635 if not any(match(f) for f in localfiles):
638 if not any(match(f) for f in localfiles):
636 suggestedremovals.append(include)
639 suggestedremovals.append(include)
637 if suggestedremovals:
640 if suggestedremovals:
638 for s in suggestedremovals:
641 for s in suggestedremovals:
639 ui.status(b'%s\n' % s)
642 ui.status(b'%s\n' % s)
640 if (
643 if (
641 ui.promptchoice(
644 ui.promptchoice(
642 _(
645 _(
643 b'remove these unused includes (yn)?'
646 b'remove these unused includes (yn)?'
644 b'$$ &Yes $$ &No'
647 b'$$ &Yes $$ &No'
645 )
648 )
646 )
649 )
647 == 0
650 == 0
648 ):
651 ):
649 removedincludes.update(suggestedremovals)
652 removedincludes.update(suggestedremovals)
650 narrowing = True
653 narrowing = True
651 else:
654 else:
652 ui.status(_(b'found no unused includes\n'))
655 ui.status(_(b'found no unused includes\n'))
653
656
654 if narrowing:
657 if narrowing:
655 newincludes = oldincludes - removedincludes
658 newincludes = oldincludes - removedincludes
656 newexcludes = oldexcludes | addedexcludes
659 newexcludes = oldexcludes | addedexcludes
657 _narrow(
660 _narrow(
658 ui,
661 ui,
659 repo,
662 repo,
660 remote,
663 remote,
661 commoninc,
664 commoninc,
662 oldincludes,
665 oldincludes,
663 oldexcludes,
666 oldexcludes,
664 newincludes,
667 newincludes,
665 newexcludes,
668 newexcludes,
666 opts[b'force_delete_local_changes'],
669 opts[b'force_delete_local_changes'],
667 opts[b'backup'],
670 opts[b'backup'],
668 )
671 )
669 # _narrow() updated the narrowspec and _widen() below needs to
672 # _narrow() updated the narrowspec and _widen() below needs to
670 # use the updated values as its base (otherwise removed includes
673 # use the updated values as its base (otherwise removed includes
671 # and addedexcludes will be lost in the resulting narrowspec)
674 # and addedexcludes will be lost in the resulting narrowspec)
672 oldincludes = newincludes
675 oldincludes = newincludes
673 oldexcludes = newexcludes
676 oldexcludes = newexcludes
674
677
675 if widening:
678 if widening:
676 newincludes = oldincludes | addedincludes
679 newincludes = oldincludes | addedincludes
677 newexcludes = oldexcludes - removedexcludes
680 newexcludes = oldexcludes - removedexcludes
678 _widen(
681 _widen(
679 ui,
682 ui,
680 repo,
683 repo,
681 remote,
684 remote,
682 commoninc,
685 commoninc,
683 oldincludes,
686 oldincludes,
684 oldexcludes,
687 oldexcludes,
685 newincludes,
688 newincludes,
686 newexcludes,
689 newexcludes,
687 )
690 )
688 finally:
691 finally:
689 remote.close()
692 remote.close()
690
693
691 return 0
694 return 0
General Comments 0
You need to be logged in to leave comments. Login now