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