##// END OF EJS Templates
narrow: set opts['narrow'] instead of local variable...
Gregory Szorc -
r39582:4afd2af3 default
parent child Browse files
Show More
@@ -1,433 +1,432 b''
1 1 # narrowcommands.py - command modifications for narrowhg extension
2 2 #
3 3 # Copyright 2017 Google, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from __future__ import absolute_import
8 8
9 9 import itertools
10 10 import os
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 cmdutil,
15 15 commands,
16 16 discovery,
17 17 encoding,
18 18 error,
19 19 exchange,
20 20 extensions,
21 21 hg,
22 22 merge,
23 23 narrowspec,
24 24 node,
25 25 pycompat,
26 26 registrar,
27 27 repair,
28 28 repository,
29 29 repoview,
30 30 sparse,
31 31 util,
32 32 )
33 33
34 34 from . import (
35 35 narrowwirepeer,
36 36 )
37 37
38 38 table = {}
39 39 command = registrar.command(table)
40 40
41 41 def setup():
42 42 """Wraps user-facing mercurial commands with narrow-aware versions."""
43 43
44 44 entry = extensions.wrapcommand(commands.table, 'clone', clonenarrowcmd)
45 45 entry[1].append(('', 'narrow', None,
46 46 _("create a narrow clone of select files")))
47 47 entry[1].append(('', 'depth', '',
48 48 _("limit the history fetched by distance from heads")))
49 49 entry[1].append(('', 'narrowspec', '',
50 50 _("read narrowspecs from file")))
51 51 # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
52 52 if 'sparse' not in extensions.enabled():
53 53 entry[1].append(('', 'include', [],
54 54 _("specifically fetch this file/directory")))
55 55 entry[1].append(
56 56 ('', 'exclude', [],
57 57 _("do not fetch this file/directory, even if included")))
58 58
59 59 entry = extensions.wrapcommand(commands.table, 'pull', pullnarrowcmd)
60 60 entry[1].append(('', 'depth', '',
61 61 _("limit the history fetched by distance from heads")))
62 62
63 63 extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
64 64
65 65 def clonenarrowcmd(orig, ui, repo, *args, **opts):
66 66 """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
67 67 opts = pycompat.byteskwargs(opts)
68 68 wrappedextraprepare = util.nullcontextmanager()
69 opts_narrow = opts['narrow']
70 69 narrowspecfile = opts['narrowspec']
71 70
72 71 if narrowspecfile:
73 72 filepath = os.path.join(pycompat.getcwd(), narrowspecfile)
74 73 ui.status(_("reading narrowspec from '%s'\n") % filepath)
75 74 try:
76 75 fdata = util.readfile(filepath)
77 76 except IOError as inst:
78 77 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
79 78 (filepath, encoding.strtolocal(inst.strerror)))
80 79
81 80 includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
82 81 if profiles:
83 82 raise error.Abort(_("cannot specify other files using '%include' in"
84 83 " narrowspec"))
85 84
86 85 narrowspec.validatepatterns(includes)
87 86 narrowspec.validatepatterns(excludes)
88 87
89 88 # narrowspec is passed so we should assume that user wants narrow clone
90 opts_narrow = True
89 opts['narrow'] = True
91 90 opts['include'].extend(includes)
92 91 opts['exclude'].extend(excludes)
93 92
94 if opts_narrow:
93 if opts['narrow']:
95 94 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
96 95 # Create narrow spec patterns from clone flags
97 96 includepats = narrowspec.parsepatterns(opts['include'])
98 97 excludepats = narrowspec.parsepatterns(opts['exclude'])
99 98
100 99 if not includepats and excludepats:
101 100 # If nothing was included, we assume the user meant to include
102 101 # everything, except what they asked to exclude.
103 102 includepats = {'path:.'}
104 103
105 104 pullop.repo.setnarrowpats(includepats, excludepats)
106 105
107 106 # This will populate 'includepats' etc with the values from the
108 107 # narrowspec we just saved.
109 108 orig(pullop, kwargs)
110 109
111 110 if opts.get('depth'):
112 111 kwargs['depth'] = opts['depth']
113 112 wrappedextraprepare = extensions.wrappedfunction(exchange,
114 113 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
115 114
116 115 def pullnarrow(orig, repo, *args, **kwargs):
117 if opts_narrow:
116 if opts['narrow']:
118 117 repo.requirements.add(repository.NARROW_REQUIREMENT)
119 118 repo._writerequirements()
120 119
121 120 return orig(repo, *args, **kwargs)
122 121
123 122 wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
124 123
125 124 with wrappedextraprepare, wrappedpull:
126 125 return orig(ui, repo, *args, **pycompat.strkwargs(opts))
127 126
128 127 def pullnarrowcmd(orig, ui, repo, *args, **opts):
129 128 """Wraps pull command to allow modifying narrow spec."""
130 129 wrappedextraprepare = util.nullcontextmanager()
131 130 if repository.NARROW_REQUIREMENT in repo.requirements:
132 131
133 132 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
134 133 orig(pullop, kwargs)
135 134 if opts.get(r'depth'):
136 135 kwargs['depth'] = opts[r'depth']
137 136 wrappedextraprepare = extensions.wrappedfunction(exchange,
138 137 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
139 138
140 139 with wrappedextraprepare:
141 140 return orig(ui, repo, *args, **opts)
142 141
143 142 def archivenarrowcmd(orig, ui, repo, *args, **opts):
144 143 """Wraps archive command to narrow the default includes."""
145 144 if repository.NARROW_REQUIREMENT in repo.requirements:
146 145 repo_includes, repo_excludes = repo.narrowpats
147 146 includes = set(opts.get(r'include', []))
148 147 excludes = set(opts.get(r'exclude', []))
149 148 includes, excludes, unused_invalid = narrowspec.restrictpatterns(
150 149 includes, excludes, repo_includes, repo_excludes)
151 150 if includes:
152 151 opts[r'include'] = includes
153 152 if excludes:
154 153 opts[r'exclude'] = excludes
155 154 return orig(ui, repo, *args, **opts)
156 155
157 156 def pullbundle2extraprepare(orig, pullop, kwargs):
158 157 repo = pullop.repo
159 158 if repository.NARROW_REQUIREMENT not in repo.requirements:
160 159 return orig(pullop, kwargs)
161 160
162 161 if narrowwirepeer.NARROWCAP not in pullop.remote.capabilities():
163 162 raise error.Abort(_("server doesn't support narrow clones"))
164 163 orig(pullop, kwargs)
165 164 kwargs['narrow'] = True
166 165 include, exclude = repo.narrowpats
167 166 kwargs['oldincludepats'] = include
168 167 kwargs['oldexcludepats'] = exclude
169 168 kwargs['includepats'] = include
170 169 kwargs['excludepats'] = exclude
171 170 # calculate known nodes only in ellipses cases because in non-ellipses cases
172 171 # we have all the nodes
173 172 if narrowwirepeer.ELLIPSESCAP in pullop.remote.capabilities():
174 173 kwargs['known'] = [node.hex(ctx.node()) for ctx in
175 174 repo.set('::%ln', pullop.common)
176 175 if ctx.node() != node.nullid]
177 176 if not kwargs['known']:
178 177 # Mercurial serializes an empty list as '' and deserializes it as
179 178 # [''], so delete it instead to avoid handling the empty string on
180 179 # the server.
181 180 del kwargs['known']
182 181
183 182 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
184 183 pullbundle2extraprepare)
185 184
186 185 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
187 186 newincludes, newexcludes, force):
188 187 oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
189 188 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
190 189
191 190 # This is essentially doing "hg outgoing" to find all local-only
192 191 # commits. We will then check that the local-only commits don't
193 192 # have any changes to files that will be untracked.
194 193 unfi = repo.unfiltered()
195 194 outgoing = discovery.findcommonoutgoing(unfi, remote,
196 195 commoninc=commoninc)
197 196 ui.status(_('looking for local changes to affected paths\n'))
198 197 localnodes = []
199 198 for n in itertools.chain(outgoing.missing, outgoing.excluded):
200 199 if any(oldmatch(f) and not newmatch(f) for f in unfi[n].files()):
201 200 localnodes.append(n)
202 201 revstostrip = unfi.revs('descendants(%ln)', localnodes)
203 202 hiddenrevs = repoview.filterrevs(repo, 'visible')
204 203 visibletostrip = list(repo.changelog.node(r)
205 204 for r in (revstostrip - hiddenrevs))
206 205 if visibletostrip:
207 206 ui.status(_('The following changeset(s) or their ancestors have '
208 207 'local changes not on the remote:\n'))
209 208 maxnodes = 10
210 209 if ui.verbose or len(visibletostrip) <= maxnodes:
211 210 for n in visibletostrip:
212 211 ui.status('%s\n' % node.short(n))
213 212 else:
214 213 for n in visibletostrip[:maxnodes]:
215 214 ui.status('%s\n' % node.short(n))
216 215 ui.status(_('...and %d more, use --verbose to list all\n') %
217 216 (len(visibletostrip) - maxnodes))
218 217 if not force:
219 218 raise error.Abort(_('local changes found'),
220 219 hint=_('use --force-delete-local-changes to '
221 220 'ignore'))
222 221
223 222 with ui.uninterruptable():
224 223 if revstostrip:
225 224 tostrip = [unfi.changelog.node(r) for r in revstostrip]
226 225 if repo['.'].node() in tostrip:
227 226 # stripping working copy, so move to a different commit first
228 227 urev = max(repo.revs('(::%n) - %ln + null',
229 228 repo['.'].node(), visibletostrip))
230 229 hg.clean(repo, urev)
231 230 repair.strip(ui, unfi, tostrip, topic='narrow')
232 231
233 232 todelete = []
234 233 for f, f2, size in repo.store.datafiles():
235 234 if f.startswith('data/'):
236 235 file = f[5:-2]
237 236 if not newmatch(file):
238 237 todelete.append(f)
239 238 elif f.startswith('meta/'):
240 239 dir = f[5:-13]
241 240 dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
242 241 include = True
243 242 for d in dirs:
244 243 visit = newmatch.visitdir(d)
245 244 if not visit:
246 245 include = False
247 246 break
248 247 if visit == 'all':
249 248 break
250 249 if not include:
251 250 todelete.append(f)
252 251
253 252 repo.destroying()
254 253
255 254 with repo.transaction("narrowing"):
256 255 for f in todelete:
257 256 ui.status(_('deleting %s\n') % f)
258 257 util.unlinkpath(repo.svfs.join(f))
259 258 repo.store.markremoved(f)
260 259
261 260 for f in repo.dirstate:
262 261 if not newmatch(f):
263 262 repo.dirstate.drop(f)
264 263 repo.wvfs.unlinkpath(f)
265 264 repo.setnarrowpats(newincludes, newexcludes)
266 265
267 266 repo.destroyed()
268 267
269 268 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
270 269 newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
271 270
272 271 def pullbundle2extraprepare_widen(orig, pullop, kwargs):
273 272 orig(pullop, kwargs)
274 273 # The old{in,ex}cludepats have already been set by orig()
275 274 kwargs['includepats'] = newincludes
276 275 kwargs['excludepats'] = newexcludes
277 276 kwargs['widen'] = True
278 277 wrappedextraprepare = extensions.wrappedfunction(exchange,
279 278 '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
280 279
281 280 # define a function that narrowbundle2 can call after creating the
282 281 # backup bundle, but before applying the bundle from the server
283 282 def setnewnarrowpats():
284 283 repo.setnarrowpats(newincludes, newexcludes)
285 284 repo.setnewnarrowpats = setnewnarrowpats
286 285
287 286 with ui.uninterruptable():
288 287 ds = repo.dirstate
289 288 p1, p2 = ds.p1(), ds.p2()
290 289 with ds.parentchange():
291 290 ds.setparents(node.nullid, node.nullid)
292 291 common = commoninc[0]
293 292 with wrappedextraprepare:
294 293 exchange.pull(repo, remote, heads=common)
295 294 with ds.parentchange():
296 295 ds.setparents(p1, p2)
297 296
298 297 repo.setnewnarrowpats()
299 298 actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
300 299 addgaction = actions['g'].append
301 300
302 301 mf = repo['.'].manifest().matches(newmatch)
303 302 for f, fn in mf.iteritems():
304 303 if f not in repo.dirstate:
305 304 addgaction((f, (mf.flags(f), False),
306 305 "add from widened narrow clone"))
307 306
308 307 merge.applyupdates(repo, actions, wctx=repo[None],
309 308 mctx=repo['.'], overwrite=False)
310 309 merge.recordupdates(repo, actions, branchmerge=False)
311 310
312 311 # TODO(rdamazio): Make new matcher format and update description
313 312 @command('tracked',
314 313 [('', 'addinclude', [], _('new paths to include')),
315 314 ('', 'removeinclude', [], _('old paths to no longer include')),
316 315 ('', 'addexclude', [], _('new paths to exclude')),
317 316 ('', 'import-rules', '', _('import narrowspecs from a file')),
318 317 ('', 'removeexclude', [], _('old paths to no longer exclude')),
319 318 ('', 'clear', False, _('whether to replace the existing narrowspec')),
320 319 ('', 'force-delete-local-changes', False,
321 320 _('forces deletion of local changes when narrowing')),
322 321 ] + commands.remoteopts,
323 322 _('[OPTIONS]... [REMOTE]'),
324 323 inferrepo=True)
325 324 def trackedcmd(ui, repo, remotepath=None, *pats, **opts):
326 325 """show or change the current narrowspec
327 326
328 327 With no argument, shows the current narrowspec entries, one per line. Each
329 328 line will be prefixed with 'I' or 'X' for included or excluded patterns,
330 329 respectively.
331 330
332 331 The narrowspec is comprised of expressions to match remote files and/or
333 332 directories that should be pulled into your client.
334 333 The narrowspec has *include* and *exclude* expressions, with excludes always
335 334 trumping includes: that is, if a file matches an exclude expression, it will
336 335 be excluded even if it also matches an include expression.
337 336 Excluding files that were never included has no effect.
338 337
339 338 Each included or excluded entry is in the format described by
340 339 'hg help patterns'.
341 340
342 341 The options allow you to add or remove included and excluded expressions.
343 342
344 343 If --clear is specified, then all previous includes and excludes are DROPPED
345 344 and replaced by the new ones specified to --addinclude and --addexclude.
346 345 If --clear is specified without any further options, the narrowspec will be
347 346 empty and will not match any files.
348 347 """
349 348 opts = pycompat.byteskwargs(opts)
350 349 if repository.NARROW_REQUIREMENT not in repo.requirements:
351 350 ui.warn(_('The narrow command is only supported on respositories cloned'
352 351 ' with --narrow.\n'))
353 352 return 1
354 353
355 354 # Before supporting, decide whether it "hg tracked --clear" should mean
356 355 # tracking no paths or all paths.
357 356 if opts['clear']:
358 357 ui.warn(_('The --clear option is not yet supported.\n'))
359 358 return 1
360 359
361 360 # import rules from a file
362 361 newrules = opts.get('import_rules')
363 362 if newrules:
364 363 try:
365 364 filepath = os.path.join(pycompat.getcwd(), newrules)
366 365 fdata = util.readfile(filepath)
367 366 except IOError as inst:
368 367 raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
369 368 (filepath, encoding.strtolocal(inst.strerror)))
370 369 includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
371 370 'narrow')
372 371 if profiles:
373 372 raise error.Abort(_("including other spec files using '%include' "
374 373 "is not supported in narrowspec"))
375 374 opts['addinclude'].extend(includepats)
376 375 opts['addexclude'].extend(excludepats)
377 376
378 377 addedincludes = narrowspec.parsepatterns(opts['addinclude'])
379 378 removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
380 379 addedexcludes = narrowspec.parsepatterns(opts['addexclude'])
381 380 removedexcludes = narrowspec.parsepatterns(opts['removeexclude'])
382 381 widening = addedincludes or removedexcludes
383 382 narrowing = removedincludes or addedexcludes
384 383 only_show = not widening and not narrowing
385 384
386 385 # Only print the current narrowspec.
387 386 if only_show:
388 387 include, exclude = repo.narrowpats
389 388
390 389 ui.pager('tracked')
391 390 fm = ui.formatter('narrow', opts)
392 391 for i in sorted(include):
393 392 fm.startitem()
394 393 fm.write('status', '%s ', 'I', label='narrow.included')
395 394 fm.write('pat', '%s\n', i, label='narrow.included')
396 395 for i in sorted(exclude):
397 396 fm.startitem()
398 397 fm.write('status', '%s ', 'X', label='narrow.excluded')
399 398 fm.write('pat', '%s\n', i, label='narrow.excluded')
400 399 fm.end()
401 400 return 0
402 401
403 402 with repo.wlock(), repo.lock():
404 403 cmdutil.bailifchanged(repo)
405 404
406 405 # Find the revisions we have in common with the remote. These will
407 406 # be used for finding local-only changes for narrowing. They will
408 407 # also define the set of revisions to update for widening.
409 408 remotepath = ui.expandpath(remotepath or 'default')
410 409 url, branches = hg.parseurl(remotepath)
411 410 ui.status(_('comparing with %s\n') % util.hidepassword(url))
412 411 remote = hg.peer(repo, opts, url)
413 412 commoninc = discovery.findcommonincoming(repo, remote)
414 413
415 414 oldincludes, oldexcludes = repo.narrowpats
416 415 if narrowing:
417 416 newincludes = oldincludes - removedincludes
418 417 newexcludes = oldexcludes | addedexcludes
419 418 _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
420 419 newincludes, newexcludes,
421 420 opts['force_delete_local_changes'])
422 421 # _narrow() updated the narrowspec and _widen() below needs to
423 422 # use the updated values as its base (otherwise removed includes
424 423 # and addedexcludes will be lost in the resulting narrowspec)
425 424 oldincludes = newincludes
426 425 oldexcludes = newexcludes
427 426
428 427 if widening:
429 428 newincludes = oldincludes | addedincludes
430 429 newexcludes = oldexcludes - removedexcludes
431 430 _widen(ui, repo, remote, commoninc, newincludes, newexcludes)
432 431
433 432 return 0
General Comments 0
You need to be logged in to leave comments. Login now