##// END OF EJS Templates
sparse: add an action argument to parseconfig()...
Pulkit Goyal -
r38874:8fe62ad9 default
parent child Browse files
Show More
@@ -1,693 +1,698 b''
1 1 # sparse.py - functionality for sparse checkouts
2 2 #
3 3 # Copyright 2014 Facebook, 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
8 8 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import hashlib
12 12 import os
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 hex,
17 17 nullid,
18 18 )
19 19 from . import (
20 20 error,
21 21 match as matchmod,
22 22 merge as mergemod,
23 23 pathutil,
24 24 pycompat,
25 25 scmutil,
26 26 util,
27 27 )
28 28
29 29 # Whether sparse features are enabled. This variable is intended to be
30 30 # temporary to facilitate porting sparse to core. It should eventually be
31 31 # a per-repo option, possibly a repo requirement.
32 32 enabled = False
33 33
34 def parseconfig(ui, raw):
34 def parseconfig(ui, raw, action):
35 35 """Parse sparse config file content.
36 36
37 action is the command which is trigerring this read, can be narrow, sparse
38
37 39 Returns a tuple of includes, excludes, and profiles.
38 40 """
39 41 includes = set()
40 42 excludes = set()
41 43 profiles = set()
42 44 current = None
43 45 havesection = False
44 46
45 47 for line in raw.split('\n'):
46 48 line = line.strip()
47 49 if not line or line.startswith('#'):
48 50 # empty or comment line, skip
49 51 continue
50 52 elif line.startswith('%include '):
51 53 line = line[9:].strip()
52 54 if line:
53 55 profiles.add(line)
54 56 elif line == '[include]':
55 57 if havesection and current != includes:
56 58 # TODO pass filename into this API so we can report it.
57 raise error.Abort(_('sparse config cannot have includes ' +
58 'after excludes'))
59 raise error.Abort(_('%s config cannot have includes ' +
60 'after excludes') % action)
59 61 havesection = True
60 62 current = includes
61 63 continue
62 64 elif line == '[exclude]':
63 65 havesection = True
64 66 current = excludes
65 67 elif line:
66 68 if current is None:
67 raise error.Abort(_('sparse config entry outside of '
68 'section: %s') % line,
69 raise error.Abort(_('%s config entry outside of '
70 'section: %s') % (action, line),
69 71 hint=_('add an [include] or [exclude] line '
70 72 'to declare the entry type'))
71 73
72 74 if line.strip().startswith('/'):
73 ui.warn(_('warning: sparse profile cannot use' +
74 ' paths starting with /, ignoring %s\n') % line)
75 ui.warn(_('warning: %s profile cannot use' +
76 ' paths starting with /, ignoring %s\n')
77 % (action, line))
75 78 continue
76 79 current.add(line)
77 80
78 81 return includes, excludes, profiles
79 82
80 83 # Exists as separate function to facilitate monkeypatching.
81 84 def readprofile(repo, profile, changeid):
82 85 """Resolve the raw content of a sparse profile file."""
83 86 # TODO add some kind of cache here because this incurs a manifest
84 87 # resolve and can be slow.
85 88 return repo.filectx(profile, changeid=changeid).data()
86 89
87 90 def patternsforrev(repo, rev):
88 91 """Obtain sparse checkout patterns for the given rev.
89 92
90 93 Returns a tuple of iterables representing includes, excludes, and
91 94 patterns.
92 95 """
93 96 # Feature isn't enabled. No-op.
94 97 if not enabled:
95 98 return set(), set(), set()
96 99
97 100 raw = repo.vfs.tryread('sparse')
98 101 if not raw:
99 102 return set(), set(), set()
100 103
101 104 if rev is None:
102 105 raise error.Abort(_('cannot parse sparse patterns from working '
103 106 'directory'))
104 107
105 includes, excludes, profiles = parseconfig(repo.ui, raw)
108 includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
106 109 ctx = repo[rev]
107 110
108 111 if profiles:
109 112 visited = set()
110 113 while profiles:
111 114 profile = profiles.pop()
112 115 if profile in visited:
113 116 continue
114 117
115 118 visited.add(profile)
116 119
117 120 try:
118 121 raw = readprofile(repo, profile, rev)
119 122 except error.ManifestLookupError:
120 123 msg = (
121 124 "warning: sparse profile '%s' not found "
122 125 "in rev %s - ignoring it\n" % (profile, ctx))
123 126 # experimental config: sparse.missingwarning
124 127 if repo.ui.configbool(
125 128 'sparse', 'missingwarning'):
126 129 repo.ui.warn(msg)
127 130 else:
128 131 repo.ui.debug(msg)
129 132 continue
130 133
131 pincludes, pexcludes, subprofs = parseconfig(repo.ui, raw)
134 pincludes, pexcludes, subprofs = parseconfig(repo.ui, raw, 'sparse')
132 135 includes.update(pincludes)
133 136 excludes.update(pexcludes)
134 137 profiles.update(subprofs)
135 138
136 139 profiles = visited
137 140
138 141 if includes:
139 142 includes.add('.hg*')
140 143
141 144 return includes, excludes, profiles
142 145
143 146 def activeconfig(repo):
144 147 """Determine the active sparse config rules.
145 148
146 149 Rules are constructed by reading the current sparse config and bringing in
147 150 referenced profiles from parents of the working directory.
148 151 """
149 152 revs = [repo.changelog.rev(node) for node in
150 153 repo.dirstate.parents() if node != nullid]
151 154
152 155 allincludes = set()
153 156 allexcludes = set()
154 157 allprofiles = set()
155 158
156 159 for rev in revs:
157 160 includes, excludes, profiles = patternsforrev(repo, rev)
158 161 allincludes |= includes
159 162 allexcludes |= excludes
160 163 allprofiles |= profiles
161 164
162 165 return allincludes, allexcludes, allprofiles
163 166
164 167 def configsignature(repo, includetemp=True):
165 168 """Obtain the signature string for the current sparse configuration.
166 169
167 170 This is used to construct a cache key for matchers.
168 171 """
169 172 cache = repo._sparsesignaturecache
170 173
171 174 signature = cache.get('signature')
172 175
173 176 if includetemp:
174 177 tempsignature = cache.get('tempsignature')
175 178 else:
176 179 tempsignature = '0'
177 180
178 181 if signature is None or (includetemp and tempsignature is None):
179 182 signature = hex(hashlib.sha1(repo.vfs.tryread('sparse')).digest())
180 183 cache['signature'] = signature
181 184
182 185 if includetemp:
183 186 raw = repo.vfs.tryread('tempsparse')
184 187 tempsignature = hex(hashlib.sha1(raw).digest())
185 188 cache['tempsignature'] = tempsignature
186 189
187 190 return '%s %s' % (signature, tempsignature)
188 191
189 192 def writeconfig(repo, includes, excludes, profiles):
190 193 """Write the sparse config file given a sparse configuration."""
191 194 with repo.vfs('sparse', 'wb') as fh:
192 195 for p in sorted(profiles):
193 196 fh.write('%%include %s\n' % p)
194 197
195 198 if includes:
196 199 fh.write('[include]\n')
197 200 for i in sorted(includes):
198 201 fh.write(i)
199 202 fh.write('\n')
200 203
201 204 if excludes:
202 205 fh.write('[exclude]\n')
203 206 for e in sorted(excludes):
204 207 fh.write(e)
205 208 fh.write('\n')
206 209
207 210 repo._sparsesignaturecache.clear()
208 211
209 212 def readtemporaryincludes(repo):
210 213 raw = repo.vfs.tryread('tempsparse')
211 214 if not raw:
212 215 return set()
213 216
214 217 return set(raw.split('\n'))
215 218
216 219 def writetemporaryincludes(repo, includes):
217 220 repo.vfs.write('tempsparse', '\n'.join(sorted(includes)))
218 221 repo._sparsesignaturecache.clear()
219 222
220 223 def addtemporaryincludes(repo, additional):
221 224 includes = readtemporaryincludes(repo)
222 225 for i in additional:
223 226 includes.add(i)
224 227 writetemporaryincludes(repo, includes)
225 228
226 229 def prunetemporaryincludes(repo):
227 230 if not enabled or not repo.vfs.exists('tempsparse'):
228 231 return
229 232
230 233 s = repo.status()
231 234 if s.modified or s.added or s.removed or s.deleted:
232 235 # Still have pending changes. Don't bother trying to prune.
233 236 return
234 237
235 238 sparsematch = matcher(repo, includetemp=False)
236 239 dirstate = repo.dirstate
237 240 actions = []
238 241 dropped = []
239 242 tempincludes = readtemporaryincludes(repo)
240 243 for file in tempincludes:
241 244 if file in dirstate and not sparsematch(file):
242 245 message = _('dropping temporarily included sparse files')
243 246 actions.append((file, None, message))
244 247 dropped.append(file)
245 248
246 249 typeactions = collections.defaultdict(list)
247 250 typeactions['r'] = actions
248 251 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
249 252
250 253 # Fix dirstate
251 254 for file in dropped:
252 255 dirstate.drop(file)
253 256
254 257 repo.vfs.unlink('tempsparse')
255 258 repo._sparsesignaturecache.clear()
256 259 msg = _('cleaned up %d temporarily added file(s) from the '
257 260 'sparse checkout\n')
258 261 repo.ui.status(msg % len(tempincludes))
259 262
260 263 def forceincludematcher(matcher, includes):
261 264 """Returns a matcher that returns true for any of the forced includes
262 265 before testing against the actual matcher."""
263 266 kindpats = [('path', include, '') for include in includes]
264 267 includematcher = matchmod.includematcher('', '', kindpats)
265 268 return matchmod.unionmatcher([includematcher, matcher])
266 269
267 270 def matcher(repo, revs=None, includetemp=True):
268 271 """Obtain a matcher for sparse working directories for the given revs.
269 272
270 273 If multiple revisions are specified, the matcher is the union of all
271 274 revs.
272 275
273 276 ``includetemp`` indicates whether to use the temporary sparse profile.
274 277 """
275 278 # If sparse isn't enabled, sparse matcher matches everything.
276 279 if not enabled:
277 280 return matchmod.always(repo.root, '')
278 281
279 282 if not revs or revs == [None]:
280 283 revs = [repo.changelog.rev(node)
281 284 for node in repo.dirstate.parents() if node != nullid]
282 285
283 286 signature = configsignature(repo, includetemp=includetemp)
284 287
285 288 key = '%s %s' % (signature, ' '.join(map(pycompat.bytestr, revs)))
286 289
287 290 result = repo._sparsematchercache.get(key)
288 291 if result:
289 292 return result
290 293
291 294 matchers = []
292 295 for rev in revs:
293 296 try:
294 297 includes, excludes, profiles = patternsforrev(repo, rev)
295 298
296 299 if includes or excludes:
297 300 matcher = matchmod.match(repo.root, '', [],
298 301 include=includes, exclude=excludes,
299 302 default='relpath')
300 303 matchers.append(matcher)
301 304 except IOError:
302 305 pass
303 306
304 307 if not matchers:
305 308 result = matchmod.always(repo.root, '')
306 309 elif len(matchers) == 1:
307 310 result = matchers[0]
308 311 else:
309 312 result = matchmod.unionmatcher(matchers)
310 313
311 314 if includetemp:
312 315 tempincludes = readtemporaryincludes(repo)
313 316 result = forceincludematcher(result, tempincludes)
314 317
315 318 repo._sparsematchercache[key] = result
316 319
317 320 return result
318 321
319 322 def filterupdatesactions(repo, wctx, mctx, branchmerge, actions):
320 323 """Filter updates to only lay out files that match the sparse rules."""
321 324 if not enabled:
322 325 return actions
323 326
324 327 oldrevs = [pctx.rev() for pctx in wctx.parents()]
325 328 oldsparsematch = matcher(repo, oldrevs)
326 329
327 330 if oldsparsematch.always():
328 331 return actions
329 332
330 333 files = set()
331 334 prunedactions = {}
332 335
333 336 if branchmerge:
334 337 # If we're merging, use the wctx filter, since we're merging into
335 338 # the wctx.
336 339 sparsematch = matcher(repo, [wctx.parents()[0].rev()])
337 340 else:
338 341 # If we're updating, use the target context's filter, since we're
339 342 # moving to the target context.
340 343 sparsematch = matcher(repo, [mctx.rev()])
341 344
342 345 temporaryfiles = []
343 346 for file, action in actions.iteritems():
344 347 type, args, msg = action
345 348 files.add(file)
346 349 if sparsematch(file):
347 350 prunedactions[file] = action
348 351 elif type == 'm':
349 352 temporaryfiles.append(file)
350 353 prunedactions[file] = action
351 354 elif branchmerge:
352 355 if type != 'k':
353 356 temporaryfiles.append(file)
354 357 prunedactions[file] = action
355 358 elif type == 'f':
356 359 prunedactions[file] = action
357 360 elif file in wctx:
358 361 prunedactions[file] = ('r', args, msg)
359 362
360 363 if len(temporaryfiles) > 0:
361 364 repo.ui.status(_('temporarily included %d file(s) in the sparse '
362 365 'checkout for merging\n') % len(temporaryfiles))
363 366 addtemporaryincludes(repo, temporaryfiles)
364 367
365 368 # Add the new files to the working copy so they can be merged, etc
366 369 actions = []
367 370 message = 'temporarily adding to sparse checkout'
368 371 wctxmanifest = repo[None].manifest()
369 372 for file in temporaryfiles:
370 373 if file in wctxmanifest:
371 374 fctx = repo[None][file]
372 375 actions.append((file, (fctx.flags(), False), message))
373 376
374 377 typeactions = collections.defaultdict(list)
375 378 typeactions['g'] = actions
376 379 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'],
377 380 False)
378 381
379 382 dirstate = repo.dirstate
380 383 for file, flags, msg in actions:
381 384 dirstate.normal(file)
382 385
383 386 profiles = activeconfig(repo)[2]
384 387 changedprofiles = profiles & files
385 388 # If an active profile changed during the update, refresh the checkout.
386 389 # Don't do this during a branch merge, since all incoming changes should
387 390 # have been handled by the temporary includes above.
388 391 if changedprofiles and not branchmerge:
389 392 mf = mctx.manifest()
390 393 for file in mf:
391 394 old = oldsparsematch(file)
392 395 new = sparsematch(file)
393 396 if not old and new:
394 397 flags = mf.flags(file)
395 398 prunedactions[file] = ('g', (flags, False), '')
396 399 elif old and not new:
397 400 prunedactions[file] = ('r', [], '')
398 401
399 402 return prunedactions
400 403
401 404 def refreshwdir(repo, origstatus, origsparsematch, force=False):
402 405 """Refreshes working directory by taking sparse config into account.
403 406
404 407 The old status and sparse matcher is compared against the current sparse
405 408 matcher.
406 409
407 410 Will abort if a file with pending changes is being excluded or included
408 411 unless ``force`` is True.
409 412 """
410 413 # Verify there are no pending changes
411 414 pending = set()
412 415 pending.update(origstatus.modified)
413 416 pending.update(origstatus.added)
414 417 pending.update(origstatus.removed)
415 418 sparsematch = matcher(repo)
416 419 abort = False
417 420
418 421 for f in pending:
419 422 if not sparsematch(f):
420 423 repo.ui.warn(_("pending changes to '%s'\n") % f)
421 424 abort = not force
422 425
423 426 if abort:
424 427 raise error.Abort(_('could not update sparseness due to pending '
425 428 'changes'))
426 429
427 430 # Calculate actions
428 431 dirstate = repo.dirstate
429 432 ctx = repo['.']
430 433 added = []
431 434 lookup = []
432 435 dropped = []
433 436 mf = ctx.manifest()
434 437 files = set(mf)
435 438
436 439 actions = {}
437 440
438 441 for file in files:
439 442 old = origsparsematch(file)
440 443 new = sparsematch(file)
441 444 # Add files that are newly included, or that don't exist in
442 445 # the dirstate yet.
443 446 if (new and not old) or (old and new and not file in dirstate):
444 447 fl = mf.flags(file)
445 448 if repo.wvfs.exists(file):
446 449 actions[file] = ('e', (fl,), '')
447 450 lookup.append(file)
448 451 else:
449 452 actions[file] = ('g', (fl, False), '')
450 453 added.append(file)
451 454 # Drop files that are newly excluded, or that still exist in
452 455 # the dirstate.
453 456 elif (old and not new) or (not old and not new and file in dirstate):
454 457 dropped.append(file)
455 458 if file not in pending:
456 459 actions[file] = ('r', [], '')
457 460
458 461 # Verify there are no pending changes in newly included files
459 462 abort = False
460 463 for file in lookup:
461 464 repo.ui.warn(_("pending changes to '%s'\n") % file)
462 465 abort = not force
463 466 if abort:
464 467 raise error.Abort(_('cannot change sparseness due to pending '
465 468 'changes (delete the files or use '
466 469 '--force to bring them back dirty)'))
467 470
468 471 # Check for files that were only in the dirstate.
469 472 for file, state in dirstate.iteritems():
470 473 if not file in files:
471 474 old = origsparsematch(file)
472 475 new = sparsematch(file)
473 476 if old and not new:
474 477 dropped.append(file)
475 478
476 479 # Apply changes to disk
477 480 typeactions = dict((m, [])
478 481 for m in 'a f g am cd dc r dm dg m e k p pr'.split())
479 482 for f, (m, args, msg) in actions.iteritems():
480 483 if m not in typeactions:
481 484 typeactions[m] = []
482 485 typeactions[m].append((f, args, msg))
483 486
484 487 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
485 488
486 489 # Fix dirstate
487 490 for file in added:
488 491 dirstate.normal(file)
489 492
490 493 for file in dropped:
491 494 dirstate.drop(file)
492 495
493 496 for file in lookup:
494 497 # File exists on disk, and we're bringing it back in an unknown state.
495 498 dirstate.normallookup(file)
496 499
497 500 return added, dropped, lookup
498 501
499 502 def aftercommit(repo, node):
500 503 """Perform actions after a working directory commit."""
501 504 # This function is called unconditionally, even if sparse isn't
502 505 # enabled.
503 506 ctx = repo[node]
504 507
505 508 profiles = patternsforrev(repo, ctx.rev())[2]
506 509
507 510 # profiles will only have data if sparse is enabled.
508 511 if profiles & set(ctx.files()):
509 512 origstatus = repo.status()
510 513 origsparsematch = matcher(repo)
511 514 refreshwdir(repo, origstatus, origsparsematch, force=True)
512 515
513 516 prunetemporaryincludes(repo)
514 517
515 518 def _updateconfigandrefreshwdir(repo, includes, excludes, profiles,
516 519 force=False, removing=False):
517 520 """Update the sparse config and working directory state."""
518 521 raw = repo.vfs.tryread('sparse')
519 oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw)
522 oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw, 'sparse')
520 523
521 524 oldstatus = repo.status()
522 525 oldmatch = matcher(repo)
523 526 oldrequires = set(repo.requirements)
524 527
525 528 # TODO remove this try..except once the matcher integrates better
526 529 # with dirstate. We currently have to write the updated config
527 530 # because that will invalidate the matcher cache and force a
528 531 # re-read. We ideally want to update the cached matcher on the
529 532 # repo instance then flush the new config to disk once wdir is
530 533 # updated. But this requires massive rework to matcher() and its
531 534 # consumers.
532 535
533 536 if 'exp-sparse' in oldrequires and removing:
534 537 repo.requirements.discard('exp-sparse')
535 538 scmutil.writerequires(repo.vfs, repo.requirements)
536 539 elif 'exp-sparse' not in oldrequires:
537 540 repo.requirements.add('exp-sparse')
538 541 scmutil.writerequires(repo.vfs, repo.requirements)
539 542
540 543 try:
541 544 writeconfig(repo, includes, excludes, profiles)
542 545 return refreshwdir(repo, oldstatus, oldmatch, force=force)
543 546 except Exception:
544 547 if repo.requirements != oldrequires:
545 548 repo.requirements.clear()
546 549 repo.requirements |= oldrequires
547 550 scmutil.writerequires(repo.vfs, repo.requirements)
548 551 writeconfig(repo, oldincludes, oldexcludes, oldprofiles)
549 552 raise
550 553
551 554 def clearrules(repo, force=False):
552 555 """Clears include/exclude rules from the sparse config.
553 556
554 557 The remaining sparse config only has profiles, if defined. The working
555 558 directory is refreshed, as needed.
556 559 """
557 560 with repo.wlock():
558 561 raw = repo.vfs.tryread('sparse')
559 includes, excludes, profiles = parseconfig(repo.ui, raw)
562 includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
560 563
561 564 if not includes and not excludes:
562 565 return
563 566
564 567 _updateconfigandrefreshwdir(repo, set(), set(), profiles, force=force)
565 568
566 569 def importfromfiles(repo, opts, paths, force=False):
567 570 """Import sparse config rules from files.
568 571
569 572 The updated sparse config is written out and the working directory
570 573 is refreshed, as needed.
571 574 """
572 575 with repo.wlock():
573 576 # read current configuration
574 577 raw = repo.vfs.tryread('sparse')
575 includes, excludes, profiles = parseconfig(repo.ui, raw)
578 includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
576 579 aincludes, aexcludes, aprofiles = activeconfig(repo)
577 580
578 581 # Import rules on top; only take in rules that are not yet
579 582 # part of the active rules.
580 583 changed = False
581 584 for p in paths:
582 585 with util.posixfile(util.expandpath(p), mode='rb') as fh:
583 586 raw = fh.read()
584 587
585 iincludes, iexcludes, iprofiles = parseconfig(repo.ui, raw)
588 iincludes, iexcludes, iprofiles = parseconfig(repo.ui, raw,
589 'sparse')
586 590 oldsize = len(includes) + len(excludes) + len(profiles)
587 591 includes.update(iincludes - aincludes)
588 592 excludes.update(iexcludes - aexcludes)
589 593 profiles.update(iprofiles - aprofiles)
590 594 if len(includes) + len(excludes) + len(profiles) > oldsize:
591 595 changed = True
592 596
593 597 profilecount = includecount = excludecount = 0
594 598 fcounts = (0, 0, 0)
595 599
596 600 if changed:
597 601 profilecount = len(profiles - aprofiles)
598 602 includecount = len(includes - aincludes)
599 603 excludecount = len(excludes - aexcludes)
600 604
601 605 fcounts = map(len, _updateconfigandrefreshwdir(
602 606 repo, includes, excludes, profiles, force=force))
603 607
604 608 printchanges(repo.ui, opts, profilecount, includecount, excludecount,
605 609 *fcounts)
606 610
607 611 def updateconfig(repo, pats, opts, include=False, exclude=False, reset=False,
608 612 delete=False, enableprofile=False, disableprofile=False,
609 613 force=False, usereporootpaths=False):
610 614 """Perform a sparse config update.
611 615
612 616 Only one of the actions may be performed.
613 617
614 618 The new config is written out and a working directory refresh is performed.
615 619 """
616 620 with repo.wlock():
617 621 raw = repo.vfs.tryread('sparse')
618 oldinclude, oldexclude, oldprofiles = parseconfig(repo.ui, raw)
622 oldinclude, oldexclude, oldprofiles = parseconfig(repo.ui, raw,
623 'sparse')
619 624
620 625 if reset:
621 626 newinclude = set()
622 627 newexclude = set()
623 628 newprofiles = set()
624 629 else:
625 630 newinclude = set(oldinclude)
626 631 newexclude = set(oldexclude)
627 632 newprofiles = set(oldprofiles)
628 633
629 634 if any(os.path.isabs(pat) for pat in pats):
630 635 raise error.Abort(_('paths cannot be absolute'))
631 636
632 637 if not usereporootpaths:
633 638 # let's treat paths as relative to cwd
634 639 root, cwd = repo.root, repo.getcwd()
635 640 abspats = []
636 641 for kindpat in pats:
637 642 kind, pat = matchmod._patsplit(kindpat, None)
638 643 if kind in matchmod.cwdrelativepatternkinds or kind is None:
639 644 ap = (kind + ':' if kind else '') +\
640 645 pathutil.canonpath(root, cwd, pat)
641 646 abspats.append(ap)
642 647 else:
643 648 abspats.append(kindpat)
644 649 pats = abspats
645 650
646 651 if include:
647 652 newinclude.update(pats)
648 653 elif exclude:
649 654 newexclude.update(pats)
650 655 elif enableprofile:
651 656 newprofiles.update(pats)
652 657 elif disableprofile:
653 658 newprofiles.difference_update(pats)
654 659 elif delete:
655 660 newinclude.difference_update(pats)
656 661 newexclude.difference_update(pats)
657 662
658 663 profilecount = (len(newprofiles - oldprofiles) -
659 664 len(oldprofiles - newprofiles))
660 665 includecount = (len(newinclude - oldinclude) -
661 666 len(oldinclude - newinclude))
662 667 excludecount = (len(newexclude - oldexclude) -
663 668 len(oldexclude - newexclude))
664 669
665 670 fcounts = map(len, _updateconfigandrefreshwdir(
666 671 repo, newinclude, newexclude, newprofiles, force=force,
667 672 removing=reset))
668 673
669 674 printchanges(repo.ui, opts, profilecount, includecount,
670 675 excludecount, *fcounts)
671 676
672 677 def printchanges(ui, opts, profilecount=0, includecount=0, excludecount=0,
673 678 added=0, dropped=0, conflicting=0):
674 679 """Print output summarizing sparse config changes."""
675 680 with ui.formatter('sparse', opts) as fm:
676 681 fm.startitem()
677 682 fm.condwrite(ui.verbose, 'profiles_added', _('Profiles changed: %d\n'),
678 683 profilecount)
679 684 fm.condwrite(ui.verbose, 'include_rules_added',
680 685 _('Include rules changed: %d\n'), includecount)
681 686 fm.condwrite(ui.verbose, 'exclude_rules_added',
682 687 _('Exclude rules changed: %d\n'), excludecount)
683 688
684 689 # In 'plain' verbose mode, mergemod.applyupdates already outputs what
685 690 # files are added or removed outside of the templating formatter
686 691 # framework. No point in repeating ourselves in that case.
687 692 if not fm.isplain():
688 693 fm.condwrite(ui.verbose, 'files_added', _('Files added: %d\n'),
689 694 added)
690 695 fm.condwrite(ui.verbose, 'files_dropped', _('Files dropped: %d\n'),
691 696 dropped)
692 697 fm.condwrite(ui.verbose, 'files_conflicting',
693 698 _('Files conflicting: %d\n'), conflicting)
General Comments 0
You need to be logged in to leave comments. Login now