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