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