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