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