##// END OF EJS Templates
sparse: rename command to debugsparse...
Gregory Szorc -
r33293:c9cbf4de default
parent child Browse files
Show More
@@ -1,1077 +1,1077 b''
1 1 # sparse.py - allow sparse checkouts of the working directory
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 """allow sparse checkouts of the working directory (EXPERIMENTAL)
9 9
10 10 (This extension is not yet protected by backwards compatibility
11 11 guarantees. Any aspect may break in future releases until this
12 12 notice is removed.)
13 13
14 14 This extension allows the working directory to only consist of a
15 15 subset of files for the revision. This allows specific files or
16 16 directories to be explicitly included or excluded. Many repository
17 17 operations have performance proportional to the number of files in
18 18 the working directory. So only realizing a subset of files in the
19 19 working directory can improve performance.
20 20 """
21 21
22 22 from __future__ import absolute_import
23 23
24 24 import collections
25 25 import hashlib
26 26 import os
27 27
28 28 from mercurial.i18n import _
29 29 from mercurial.node import nullid
30 30 from mercurial import (
31 31 cmdutil,
32 32 commands,
33 33 context,
34 34 dirstate,
35 35 error,
36 36 extensions,
37 37 hg,
38 38 localrepo,
39 39 match as matchmod,
40 40 merge as mergemod,
41 41 registrar,
42 42 util,
43 43 )
44 44
45 45 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
46 46 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
47 47 # be specifying the version(s) of Mercurial they are tested with, or
48 48 # leave the attribute unspecified.
49 49 testedwith = 'ships-with-hg-core'
50 50
51 51 cmdtable = {}
52 52 command = registrar.command(cmdtable)
53 53
54 54 def uisetup(ui):
55 55 _setupupdates(ui)
56 56 _setupcommit(ui)
57 57
58 58 def extsetup(ui):
59 59 _setupclone(ui)
60 60 _setuplog(ui)
61 61 _setupadd(ui)
62 62 _setupdirstate(ui)
63 63 # if fsmonitor is enabled, tell it to use our hash function
64 64 try:
65 65 fsmonitor = extensions.find('fsmonitor')
66 66 def _hashignore(orig, ignore):
67 67 return _hashmatcher(ignore)
68 68 extensions.wrapfunction(fsmonitor, '_hashignore', _hashignore)
69 69 except KeyError:
70 70 pass
71 71
72 72 def reposetup(ui, repo):
73 73 if not util.safehasattr(repo, 'dirstate'):
74 74 return
75 75
76 76 _wraprepo(ui, repo)
77 77
78 78 def replacefilecache(cls, propname, replacement):
79 79 """Replace a filecache property with a new class. This allows changing the
80 80 cache invalidation condition."""
81 81 origcls = cls
82 82 assert callable(replacement)
83 83 while cls is not object:
84 84 if propname in cls.__dict__:
85 85 orig = cls.__dict__[propname]
86 86 setattr(cls, propname, replacement(orig))
87 87 break
88 88 cls = cls.__bases__[0]
89 89
90 90 if cls is object:
91 91 raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
92 92 propname))
93 93
94 94 def _setupupdates(ui):
95 95 def _calculateupdates(orig, repo, wctx, mctx, ancestors, branchmerge, *arg,
96 96 **kwargs):
97 97 """Filter updates to only lay out files that match the sparse rules.
98 98 """
99 99 actions, diverge, renamedelete = orig(repo, wctx, mctx, ancestors,
100 100 branchmerge, *arg, **kwargs)
101 101
102 102 if not util.safehasattr(repo, 'sparsematch'):
103 103 return actions, diverge, renamedelete
104 104
105 105 files = set()
106 106 prunedactions = {}
107 107 oldrevs = [pctx.rev() for pctx in wctx.parents()]
108 108 oldsparsematch = repo.sparsematch(*oldrevs)
109 109
110 110 if branchmerge:
111 111 # If we're merging, use the wctx filter, since we're merging into
112 112 # the wctx.
113 113 sparsematch = repo.sparsematch(wctx.parents()[0].rev())
114 114 else:
115 115 # If we're updating, use the target context's filter, since we're
116 116 # moving to the target context.
117 117 sparsematch = repo.sparsematch(mctx.rev())
118 118
119 119 temporaryfiles = []
120 120 for file, action in actions.iteritems():
121 121 type, args, msg = action
122 122 files.add(file)
123 123 if sparsematch(file):
124 124 prunedactions[file] = action
125 125 elif type == 'm':
126 126 temporaryfiles.append(file)
127 127 prunedactions[file] = action
128 128 elif branchmerge:
129 129 if type != 'k':
130 130 temporaryfiles.append(file)
131 131 prunedactions[file] = action
132 132 elif type == 'f':
133 133 prunedactions[file] = action
134 134 elif file in wctx:
135 135 prunedactions[file] = ('r', args, msg)
136 136
137 137 if len(temporaryfiles) > 0:
138 138 ui.status(_("temporarily included %d file(s) in the sparse checkout"
139 139 " for merging\n") % len(temporaryfiles))
140 140 repo.addtemporaryincludes(temporaryfiles)
141 141
142 142 # Add the new files to the working copy so they can be merged, etc
143 143 actions = []
144 144 message = 'temporarily adding to sparse checkout'
145 145 wctxmanifest = repo[None].manifest()
146 146 for file in temporaryfiles:
147 147 if file in wctxmanifest:
148 148 fctx = repo[None][file]
149 149 actions.append((file, (fctx.flags(), False), message))
150 150
151 151 typeactions = collections.defaultdict(list)
152 152 typeactions['g'] = actions
153 153 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'],
154 154 False)
155 155
156 156 dirstate = repo.dirstate
157 157 for file, flags, msg in actions:
158 158 dirstate.normal(file)
159 159
160 160 profiles = repo.getactiveprofiles()
161 161 changedprofiles = profiles & files
162 162 # If an active profile changed during the update, refresh the checkout.
163 163 # Don't do this during a branch merge, since all incoming changes should
164 164 # have been handled by the temporary includes above.
165 165 if changedprofiles and not branchmerge:
166 166 mf = mctx.manifest()
167 167 for file in mf:
168 168 old = oldsparsematch(file)
169 169 new = sparsematch(file)
170 170 if not old and new:
171 171 flags = mf.flags(file)
172 172 prunedactions[file] = ('g', (flags, False), '')
173 173 elif old and not new:
174 174 prunedactions[file] = ('r', [], '')
175 175
176 176 return prunedactions, diverge, renamedelete
177 177
178 178 extensions.wrapfunction(mergemod, 'calculateupdates', _calculateupdates)
179 179
180 180 def _update(orig, repo, node, branchmerge, *args, **kwargs):
181 181 results = orig(repo, node, branchmerge, *args, **kwargs)
182 182
183 183 # If we're updating to a location, clean up any stale temporary includes
184 184 # (ex: this happens during hg rebase --abort).
185 185 if not branchmerge and util.safehasattr(repo, 'sparsematch'):
186 186 repo.prunetemporaryincludes()
187 187 return results
188 188
189 189 extensions.wrapfunction(mergemod, 'update', _update)
190 190
191 191 def _setupcommit(ui):
192 192 def _refreshoncommit(orig, self, node):
193 193 """Refresh the checkout when commits touch .hgsparse
194 194 """
195 195 orig(self, node)
196 196 repo = self._repo
197 197 if util.safehasattr(repo, 'getsparsepatterns'):
198 198 ctx = repo[node]
199 199 _, _, profiles = repo.getsparsepatterns(ctx.rev())
200 200 if set(profiles) & set(ctx.files()):
201 201 origstatus = repo.status()
202 202 origsparsematch = repo.sparsematch()
203 203 _refresh(repo.ui, repo, origstatus, origsparsematch, True)
204 204
205 205 repo.prunetemporaryincludes()
206 206
207 207 extensions.wrapfunction(context.committablectx, 'markcommitted',
208 208 _refreshoncommit)
209 209
210 210 def _setuplog(ui):
211 211 entry = commands.table['^log|history']
212 212 entry[1].append(('', 'sparse', None,
213 213 "limit to changesets affecting the sparse checkout"))
214 214
215 215 def _logrevs(orig, repo, opts):
216 216 revs = orig(repo, opts)
217 217 if opts.get('sparse'):
218 218 sparsematch = repo.sparsematch()
219 219 def ctxmatch(rev):
220 220 ctx = repo[rev]
221 221 return any(f for f in ctx.files() if sparsematch(f))
222 222 revs = revs.filter(ctxmatch)
223 223 return revs
224 224 extensions.wrapfunction(cmdutil, '_logrevs', _logrevs)
225 225
226 226 def _clonesparsecmd(orig, ui, repo, *args, **opts):
227 227 include_pat = opts.get('include')
228 228 exclude_pat = opts.get('exclude')
229 229 enableprofile_pat = opts.get('enable_profile')
230 230 include = exclude = enableprofile = False
231 231 if include_pat:
232 232 pat = include_pat
233 233 include = True
234 234 if exclude_pat:
235 235 pat = exclude_pat
236 236 exclude = True
237 237 if enableprofile_pat:
238 238 pat = enableprofile_pat
239 239 enableprofile = True
240 240 if sum([include, exclude, enableprofile]) > 1:
241 241 raise error.Abort(_("too many flags specified."))
242 242 if include or exclude or enableprofile:
243 243 def clonesparse(orig, self, node, overwrite, *args, **kwargs):
244 244 _config(self.ui, self.unfiltered(), pat, {}, include=include,
245 245 exclude=exclude, enableprofile=enableprofile)
246 246 return orig(self, node, overwrite, *args, **kwargs)
247 247 extensions.wrapfunction(hg, 'updaterepo', clonesparse)
248 248 return orig(ui, repo, *args, **opts)
249 249
250 250 def _setupclone(ui):
251 251 entry = commands.table['^clone']
252 252 entry[1].append(('', 'enable-profile', [],
253 253 'enable a sparse profile'))
254 254 entry[1].append(('', 'include', [],
255 255 'include sparse pattern'))
256 256 entry[1].append(('', 'exclude', [],
257 257 'exclude sparse pattern'))
258 258 extensions.wrapcommand(commands.table, 'clone', _clonesparsecmd)
259 259
260 260 def _setupadd(ui):
261 261 entry = commands.table['^add']
262 262 entry[1].append(('s', 'sparse', None,
263 263 'also include directories of added files in sparse config'))
264 264
265 265 def _add(orig, ui, repo, *pats, **opts):
266 266 if opts.get('sparse'):
267 267 dirs = set()
268 268 for pat in pats:
269 269 dirname, basename = util.split(pat)
270 270 dirs.add(dirname)
271 271 _config(ui, repo, list(dirs), opts, include=True)
272 272 return orig(ui, repo, *pats, **opts)
273 273
274 274 extensions.wrapcommand(commands.table, 'add', _add)
275 275
276 276 def _setupdirstate(ui):
277 277 """Modify the dirstate to prevent stat'ing excluded files,
278 278 and to prevent modifications to files outside the checkout.
279 279 """
280 280
281 281 def _dirstate(orig, repo):
282 282 dirstate = orig(repo)
283 283 dirstate.repo = repo
284 284 return dirstate
285 285 extensions.wrapfunction(
286 286 localrepo.localrepository.dirstate, 'func', _dirstate)
287 287
288 288 # The atrocity below is needed to wrap dirstate._ignore. It is a cached
289 289 # property, which means normal function wrapping doesn't work.
290 290 class ignorewrapper(object):
291 291 def __init__(self, orig):
292 292 self.orig = orig
293 293 self.origignore = None
294 294 self.func = None
295 295 self.sparsematch = None
296 296
297 297 def __get__(self, obj, type=None):
298 298 repo = obj.repo
299 299 origignore = self.orig.__get__(obj)
300 300 if not util.safehasattr(repo, 'sparsematch'):
301 301 return origignore
302 302
303 303 sparsematch = repo.sparsematch()
304 304 if self.sparsematch != sparsematch or self.origignore != origignore:
305 305 self.func = unionmatcher([origignore,
306 306 negatematcher(sparsematch)])
307 307 self.sparsematch = sparsematch
308 308 self.origignore = origignore
309 309 return self.func
310 310
311 311 def __set__(self, obj, value):
312 312 return self.orig.__set__(obj, value)
313 313
314 314 def __delete__(self, obj):
315 315 return self.orig.__delete__(obj)
316 316
317 317 replacefilecache(dirstate.dirstate, '_ignore', ignorewrapper)
318 318
319 319 # dirstate.rebuild should not add non-matching files
320 320 def _rebuild(orig, self, parent, allfiles, changedfiles=None):
321 321 if util.safehasattr(self.repo, 'sparsematch'):
322 322 matcher = self.repo.sparsematch()
323 323 allfiles = allfiles.matches(matcher)
324 324 if changedfiles:
325 325 changedfiles = [f for f in changedfiles if matcher(f)]
326 326
327 327 if changedfiles is not None:
328 328 # In _rebuild, these files will be deleted from the dirstate
329 329 # when they are not found to be in allfiles
330 330 dirstatefilestoremove = set(f for f in self if not matcher(f))
331 331 changedfiles = dirstatefilestoremove.union(changedfiles)
332 332
333 333 return orig(self, parent, allfiles, changedfiles)
334 334 extensions.wrapfunction(dirstate.dirstate, 'rebuild', _rebuild)
335 335
336 336 # Prevent adding files that are outside the sparse checkout
337 337 editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
338 hint = _('include file with `hg sparse --include <pattern>` or use ' +
338 hint = _('include file with `hg debugsparse --include <pattern>` or use ' +
339 339 '`hg add -s <file>` to include file directory while adding')
340 340 for func in editfuncs:
341 341 def _wrapper(orig, self, *args):
342 342 repo = self.repo
343 343 if util.safehasattr(repo, 'sparsematch'):
344 344 dirstate = repo.dirstate
345 345 sparsematch = repo.sparsematch()
346 346 for f in args:
347 347 if (f is not None and not sparsematch(f) and
348 348 f not in dirstate):
349 349 raise error.Abort(_("cannot add '%s' - it is outside "
350 350 "the sparse checkout") % f,
351 351 hint=hint)
352 352 return orig(self, *args)
353 353 extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
354 354
355 355 def _wraprepo(ui, repo):
356 356 class SparseRepo(repo.__class__):
357 357 def readsparseconfig(self, raw):
358 358 """Takes a string sparse config and returns the includes,
359 359 excludes, and profiles it specified.
360 360 """
361 361 includes = set()
362 362 excludes = set()
363 363 current = includes
364 364 profiles = []
365 365 for line in raw.split('\n'):
366 366 line = line.strip()
367 367 if not line or line.startswith('#'):
368 368 # empty or comment line, skip
369 369 continue
370 370 elif line.startswith('%include '):
371 371 line = line[9:].strip()
372 372 if line:
373 373 profiles.append(line)
374 374 elif line == '[include]':
375 375 if current != includes:
376 376 raise error.Abort(_('.hg/sparse cannot have includes ' +
377 377 'after excludes'))
378 378 continue
379 379 elif line == '[exclude]':
380 380 current = excludes
381 381 elif line:
382 382 if line.strip().startswith('/'):
383 383 self.ui.warn(_('warning: sparse profile cannot use' +
384 384 ' paths starting with /, ignoring %s\n')
385 385 % line)
386 386 continue
387 387 current.add(line)
388 388
389 389 return includes, excludes, profiles
390 390
391 391 def getsparsepatterns(self, rev):
392 392 """Returns the include/exclude patterns specified by the
393 393 given rev.
394 394 """
395 395 if not self.vfs.exists('sparse'):
396 396 return set(), set(), []
397 397 if rev is None:
398 398 raise error.Abort(_("cannot parse sparse patterns from " +
399 399 "working copy"))
400 400
401 401 raw = self.vfs.read('sparse')
402 402 includes, excludes, profiles = self.readsparseconfig(raw)
403 403
404 404 ctx = self[rev]
405 405 if profiles:
406 406 visited = set()
407 407 while profiles:
408 408 profile = profiles.pop()
409 409 if profile in visited:
410 410 continue
411 411 visited.add(profile)
412 412
413 413 try:
414 414 raw = self.getrawprofile(profile, rev)
415 415 except error.ManifestLookupError:
416 416 msg = (
417 417 "warning: sparse profile '%s' not found "
418 418 "in rev %s - ignoring it\n" % (profile, ctx))
419 419 if self.ui.configbool(
420 420 'sparse', 'missingwarning', True):
421 421 self.ui.warn(msg)
422 422 else:
423 423 self.ui.debug(msg)
424 424 continue
425 425 pincludes, pexcludes, subprofs = \
426 426 self.readsparseconfig(raw)
427 427 includes.update(pincludes)
428 428 excludes.update(pexcludes)
429 429 for subprofile in subprofs:
430 430 profiles.append(subprofile)
431 431
432 432 profiles = visited
433 433
434 434 if includes:
435 435 includes.add('.hg*')
436 436 return includes, excludes, profiles
437 437
438 438 def getrawprofile(self, profile, changeid):
439 439 # TODO add some kind of cache here because this incurs a manifest
440 440 # resolve and can be slow.
441 441 return self.filectx(profile, changeid=changeid).data()
442 442
443 443 def sparsechecksum(self, filepath):
444 444 fh = open(filepath)
445 445 return hashlib.sha1(fh.read()).hexdigest()
446 446
447 447 def _sparsesignature(self, includetemp=True):
448 448 """Returns the signature string representing the contents of the
449 449 current project sparse configuration. This can be used to cache the
450 450 sparse matcher for a given set of revs."""
451 451 signaturecache = self.signaturecache
452 452 signature = signaturecache.get('signature')
453 453 if includetemp:
454 454 tempsignature = signaturecache.get('tempsignature')
455 455 else:
456 456 tempsignature = 0
457 457
458 458 if signature is None or (includetemp and tempsignature is None):
459 459 signature = 0
460 460 try:
461 461 sparsepath = self.vfs.join('sparse')
462 462 signature = self.sparsechecksum(sparsepath)
463 463 except (OSError, IOError):
464 464 pass
465 465 signaturecache['signature'] = signature
466 466
467 467 tempsignature = 0
468 468 if includetemp:
469 469 try:
470 470 tempsparsepath = self.vfs.join('tempsparse')
471 471 tempsignature = self.sparsechecksum(tempsparsepath)
472 472 except (OSError, IOError):
473 473 pass
474 474 signaturecache['tempsignature'] = tempsignature
475 475 return '%s %s' % (str(signature), str(tempsignature))
476 476
477 477 def invalidatecaches(self):
478 478 self.invalidatesignaturecache()
479 479 return super(SparseRepo, self).invalidatecaches()
480 480
481 481 def invalidatesignaturecache(self):
482 482 self.signaturecache.clear()
483 483
484 484 def sparsematch(self, *revs, **kwargs):
485 485 """Returns the sparse match function for the given revs.
486 486
487 487 If multiple revs are specified, the match function is the union
488 488 of all the revs.
489 489
490 490 `includetemp` is used to indicate if the temporarily included file
491 491 should be part of the matcher.
492 492 """
493 493 if not revs or revs == (None,):
494 494 revs = [self.changelog.rev(node) for node in
495 495 self.dirstate.parents() if node != nullid]
496 496
497 497 includetemp = kwargs.get('includetemp', True)
498 498 signature = self._sparsesignature(includetemp=includetemp)
499 499
500 500 key = '%s %s' % (str(signature), ' '.join([str(r) for r in revs]))
501 501
502 502 result = self.sparsecache.get(key, None)
503 503 if result:
504 504 return result
505 505
506 506 matchers = []
507 507 for rev in revs:
508 508 try:
509 509 includes, excludes, profiles = self.getsparsepatterns(rev)
510 510
511 511 if includes or excludes:
512 512 # Explicitly include subdirectories of includes so
513 513 # status will walk them down to the actual include.
514 514 subdirs = set()
515 515 for include in includes:
516 516 dirname = os.path.dirname(include)
517 517 # basename is used to avoid issues with absolute
518 518 # paths (which on Windows can include the drive).
519 519 while os.path.basename(dirname):
520 520 subdirs.add(dirname)
521 521 dirname = os.path.dirname(dirname)
522 522
523 523 matcher = matchmod.match(self.root, '', [],
524 524 include=includes, exclude=excludes,
525 525 default='relpath')
526 526 if subdirs:
527 527 matcher = forceincludematcher(matcher, subdirs)
528 528 matchers.append(matcher)
529 529 except IOError:
530 530 pass
531 531
532 532 result = None
533 533 if not matchers:
534 534 result = matchmod.always(self.root, '')
535 535 elif len(matchers) == 1:
536 536 result = matchers[0]
537 537 else:
538 538 result = unionmatcher(matchers)
539 539
540 540 if kwargs.get('includetemp', True):
541 541 tempincludes = self.gettemporaryincludes()
542 542 result = forceincludematcher(result, tempincludes)
543 543
544 544 self.sparsecache[key] = result
545 545
546 546 return result
547 547
548 548 def getactiveprofiles(self):
549 549 revs = [self.changelog.rev(node) for node in
550 550 self.dirstate.parents() if node != nullid]
551 551
552 552 activeprofiles = set()
553 553 for rev in revs:
554 554 _, _, profiles = self.getsparsepatterns(rev)
555 555 activeprofiles.update(profiles)
556 556
557 557 return activeprofiles
558 558
559 559 def writesparseconfig(self, include, exclude, profiles):
560 560 raw = '%s[include]\n%s\n[exclude]\n%s\n' % (
561 561 ''.join(['%%include %s\n' % p for p in sorted(profiles)]),
562 562 '\n'.join(sorted(include)),
563 563 '\n'.join(sorted(exclude)))
564 564 self.vfs.write("sparse", raw)
565 565 self.invalidatesignaturecache()
566 566
567 567 def addtemporaryincludes(self, files):
568 568 includes = self.gettemporaryincludes()
569 569 for file in files:
570 570 includes.add(file)
571 571 self._writetemporaryincludes(includes)
572 572
573 573 def gettemporaryincludes(self):
574 574 existingtemp = set()
575 575 if self.vfs.exists('tempsparse'):
576 576 raw = self.vfs.read('tempsparse')
577 577 existingtemp.update(raw.split('\n'))
578 578 return existingtemp
579 579
580 580 def _writetemporaryincludes(self, includes):
581 581 raw = '\n'.join(sorted(includes))
582 582 self.vfs.write('tempsparse', raw)
583 583 self.invalidatesignaturecache()
584 584
585 585 def prunetemporaryincludes(self):
586 586 if repo.vfs.exists('tempsparse'):
587 587 origstatus = self.status()
588 588 modified, added, removed, deleted, a, b, c = origstatus
589 589 if modified or added or removed or deleted:
590 590 # Still have pending changes. Don't bother trying to prune.
591 591 return
592 592
593 593 sparsematch = self.sparsematch(includetemp=False)
594 594 dirstate = self.dirstate
595 595 actions = []
596 596 dropped = []
597 597 tempincludes = self.gettemporaryincludes()
598 598 for file in tempincludes:
599 599 if file in dirstate and not sparsematch(file):
600 600 message = 'dropping temporarily included sparse files'
601 601 actions.append((file, None, message))
602 602 dropped.append(file)
603 603
604 604 typeactions = collections.defaultdict(list)
605 605 typeactions['r'] = actions
606 606 mergemod.applyupdates(self, typeactions, self[None], self['.'],
607 607 False)
608 608
609 609 # Fix dirstate
610 610 for file in dropped:
611 611 dirstate.drop(file)
612 612
613 613 self.vfs.unlink('tempsparse')
614 614 self.invalidatesignaturecache()
615 615 msg = _("cleaned up %d temporarily added file(s) from the "
616 616 "sparse checkout\n")
617 617 ui.status(msg % len(tempincludes))
618 618
619 619 if 'dirstate' in repo._filecache:
620 620 repo.dirstate.repo = repo
621 621 repo.sparsecache = {}
622 622 repo.signaturecache = {}
623 623 repo.__class__ = SparseRepo
624 624
625 @command('^sparse', [
625 @command('^debugsparse', [
626 626 ('I', 'include', False, _('include files in the sparse checkout')),
627 627 ('X', 'exclude', False, _('exclude files in the sparse checkout')),
628 628 ('d', 'delete', False, _('delete an include/exclude rule')),
629 629 ('f', 'force', False, _('allow changing rules even with pending changes')),
630 630 ('', 'enable-profile', False, _('enables the specified profile')),
631 631 ('', 'disable-profile', False, _('disables the specified profile')),
632 632 ('', 'import-rules', False, _('imports rules from a file')),
633 633 ('', 'clear-rules', False, _('clears local include/exclude rules')),
634 634 ('', 'refresh', False, _('updates the working after sparseness changes')),
635 635 ('', 'reset', False, _('makes the repo full again')),
636 636 ] + commands.templateopts,
637 637 _('[--OPTION] PATTERN...'))
638 def sparse(ui, repo, *pats, **opts):
638 def debugsparse(ui, repo, *pats, **opts):
639 639 """make the current checkout sparse, or edit the existing checkout
640 640
641 641 The sparse command is used to make the current checkout sparse.
642 642 This means files that don't meet the sparse condition will not be
643 643 written to disk, or show up in any working copy operations. It does
644 644 not affect files in history in any way.
645 645
646 646 Passing no arguments prints the currently applied sparse rules.
647 647
648 648 --include and --exclude are used to add and remove files from the sparse
649 649 checkout. The effects of adding an include or exclude rule are applied
650 650 immediately. If applying the new rule would cause a file with pending
651 651 changes to be added or removed, the command will fail. Pass --force to
652 652 force a rule change even with pending changes (the changes on disk will
653 653 be preserved).
654 654
655 655 --delete removes an existing include/exclude rule. The effects are
656 656 immediate.
657 657
658 658 --refresh refreshes the files on disk based on the sparse rules. This is
659 659 only necessary if .hg/sparse was changed by hand.
660 660
661 661 --enable-profile and --disable-profile accept a path to a .hgsparse file.
662 662 This allows defining sparse checkouts and tracking them inside the
663 663 repository. This is useful for defining commonly used sparse checkouts for
664 664 many people to use. As the profile definition changes over time, the sparse
665 665 checkout will automatically be updated appropriately, depending on which
666 666 changeset is checked out. Changes to .hgsparse are not applied until they
667 667 have been committed.
668 668
669 669 --import-rules accepts a path to a file containing rules in the .hgsparse
670 670 format, allowing you to add --include, --exclude and --enable-profile rules
671 671 in bulk. Like the --include, --exclude and --enable-profile switches, the
672 672 changes are applied immediately.
673 673
674 674 --clear-rules removes all local include and exclude rules, while leaving
675 675 any enabled profiles in place.
676 676
677 677 Returns 0 if editing the sparse checkout succeeds.
678 678 """
679 679 include = opts.get('include')
680 680 exclude = opts.get('exclude')
681 681 force = opts.get('force')
682 682 enableprofile = opts.get('enable_profile')
683 683 disableprofile = opts.get('disable_profile')
684 684 importrules = opts.get('import_rules')
685 685 clearrules = opts.get('clear_rules')
686 686 delete = opts.get('delete')
687 687 refresh = opts.get('refresh')
688 688 reset = opts.get('reset')
689 689 count = sum([include, exclude, enableprofile, disableprofile, delete,
690 690 importrules, refresh, clearrules, reset])
691 691 if count > 1:
692 692 raise error.Abort(_("too many flags specified"))
693 693
694 694 if count == 0:
695 695 if repo.vfs.exists('sparse'):
696 696 ui.status(repo.vfs.read("sparse") + "\n")
697 697 temporaryincludes = repo.gettemporaryincludes()
698 698 if temporaryincludes:
699 699 ui.status(_("Temporarily Included Files (for merge/rebase):\n"))
700 700 ui.status(("\n".join(temporaryincludes) + "\n"))
701 701 else:
702 702 ui.status(_('repo is not sparse\n'))
703 703 return
704 704
705 705 if include or exclude or delete or reset or enableprofile or disableprofile:
706 706 _config(ui, repo, pats, opts, include=include, exclude=exclude,
707 707 reset=reset, delete=delete, enableprofile=enableprofile,
708 708 disableprofile=disableprofile, force=force)
709 709
710 710 if importrules:
711 711 _import(ui, repo, pats, opts, force=force)
712 712
713 713 if clearrules:
714 714 _clear(ui, repo, pats, force=force)
715 715
716 716 if refresh:
717 717 try:
718 718 wlock = repo.wlock()
719 719 fcounts = map(
720 720 len,
721 721 _refresh(ui, repo, repo.status(), repo.sparsematch(), force))
722 722 _verbose_output(ui, opts, 0, 0, 0, *fcounts)
723 723 finally:
724 724 wlock.release()
725 725
726 726 def _config(ui, repo, pats, opts, include=False, exclude=False, reset=False,
727 727 delete=False, enableprofile=False, disableprofile=False,
728 728 force=False):
729 729 """
730 730 Perform a sparse config update. Only one of the kwargs may be specified.
731 731 """
732 732 wlock = repo.wlock()
733 733 try:
734 734 oldsparsematch = repo.sparsematch()
735 735
736 736 if repo.vfs.exists('sparse'):
737 737 raw = repo.vfs.read('sparse')
738 738 oldinclude, oldexclude, oldprofiles = map(
739 739 set, repo.readsparseconfig(raw))
740 740 else:
741 741 oldinclude = set()
742 742 oldexclude = set()
743 743 oldprofiles = set()
744 744
745 745 try:
746 746 if reset:
747 747 newinclude = set()
748 748 newexclude = set()
749 749 newprofiles = set()
750 750 else:
751 751 newinclude = set(oldinclude)
752 752 newexclude = set(oldexclude)
753 753 newprofiles = set(oldprofiles)
754 754
755 755 oldstatus = repo.status()
756 756
757 757 if any(pat.startswith('/') for pat in pats):
758 758 ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
759 759 % ([pat for pat in pats if pat.startswith('/')]))
760 760 elif include:
761 761 newinclude.update(pats)
762 762 elif exclude:
763 763 newexclude.update(pats)
764 764 elif enableprofile:
765 765 newprofiles.update(pats)
766 766 elif disableprofile:
767 767 newprofiles.difference_update(pats)
768 768 elif delete:
769 769 newinclude.difference_update(pats)
770 770 newexclude.difference_update(pats)
771 771
772 772 repo.writesparseconfig(newinclude, newexclude, newprofiles)
773 773 fcounts = map(
774 774 len, _refresh(ui, repo, oldstatus, oldsparsematch, force))
775 775
776 776 profilecount = (len(newprofiles - oldprofiles) -
777 777 len(oldprofiles - newprofiles))
778 778 includecount = (len(newinclude - oldinclude) -
779 779 len(oldinclude - newinclude))
780 780 excludecount = (len(newexclude - oldexclude) -
781 781 len(oldexclude - newexclude))
782 782 _verbose_output(
783 783 ui, opts, profilecount, includecount, excludecount, *fcounts)
784 784 except Exception:
785 785 repo.writesparseconfig(oldinclude, oldexclude, oldprofiles)
786 786 raise
787 787 finally:
788 788 wlock.release()
789 789
790 790 def _import(ui, repo, files, opts, force=False):
791 791 with repo.wlock():
792 792 # load union of current active profile
793 793 revs = [repo.changelog.rev(node) for node in
794 794 repo.dirstate.parents() if node != nullid]
795 795
796 796 # read current configuration
797 797 raw = ''
798 798 if repo.vfs.exists('sparse'):
799 799 raw = repo.vfs.read('sparse')
800 800 oincludes, oexcludes, oprofiles = repo.readsparseconfig(raw)
801 801 includes, excludes, profiles = map(
802 802 set, (oincludes, oexcludes, oprofiles))
803 803
804 804 # all active rules
805 805 aincludes, aexcludes, aprofiles = set(), set(), set()
806 806 for rev in revs:
807 807 rincludes, rexcludes, rprofiles = repo.getsparsepatterns(rev)
808 808 aincludes.update(rincludes)
809 809 aexcludes.update(rexcludes)
810 810 aprofiles.update(rprofiles)
811 811
812 812 # import rules on top; only take in rules that are not yet
813 813 # part of the active rules.
814 814 changed = False
815 815 for file in files:
816 816 with util.posixfile(util.expandpath(file)) as importfile:
817 817 iincludes, iexcludes, iprofiles = repo.readsparseconfig(
818 818 importfile.read())
819 819 oldsize = len(includes) + len(excludes) + len(profiles)
820 820 includes.update(iincludes - aincludes)
821 821 excludes.update(iexcludes - aexcludes)
822 822 profiles.update(set(iprofiles) - aprofiles)
823 823 if len(includes) + len(excludes) + len(profiles) > oldsize:
824 824 changed = True
825 825
826 826 profilecount = includecount = excludecount = 0
827 827 fcounts = (0, 0, 0)
828 828
829 829 if changed:
830 830 profilecount = len(profiles - aprofiles)
831 831 includecount = len(includes - aincludes)
832 832 excludecount = len(excludes - aexcludes)
833 833
834 834 oldstatus = repo.status()
835 835 oldsparsematch = repo.sparsematch()
836 836 repo.writesparseconfig(includes, excludes, profiles)
837 837
838 838 try:
839 839 fcounts = map(
840 840 len, _refresh(ui, repo, oldstatus, oldsparsematch, force))
841 841 except Exception:
842 842 repo.writesparseconfig(oincludes, oexcludes, oprofiles)
843 843 raise
844 844
845 845 _verbose_output(ui, opts, profilecount, includecount, excludecount,
846 846 *fcounts)
847 847
848 848 def _clear(ui, repo, files, force=False):
849 849 with repo.wlock():
850 850 raw = ''
851 851 if repo.vfs.exists('sparse'):
852 852 raw = repo.vfs.read('sparse')
853 853 includes, excludes, profiles = repo.readsparseconfig(raw)
854 854
855 855 if includes or excludes:
856 856 oldstatus = repo.status()
857 857 oldsparsematch = repo.sparsematch()
858 858 repo.writesparseconfig(set(), set(), profiles)
859 859 _refresh(ui, repo, oldstatus, oldsparsematch, force)
860 860
861 861 def _refresh(ui, repo, origstatus, origsparsematch, force):
862 862 """Refreshes which files are on disk by comparing the old status and
863 863 sparsematch with the new sparsematch.
864 864
865 865 Will raise an exception if a file with pending changes is being excluded
866 866 or included (unless force=True).
867 867 """
868 868 modified, added, removed, deleted, unknown, ignored, clean = origstatus
869 869
870 870 # Verify there are no pending changes
871 871 pending = set()
872 872 pending.update(modified)
873 873 pending.update(added)
874 874 pending.update(removed)
875 875 sparsematch = repo.sparsematch()
876 876 abort = False
877 877 for file in pending:
878 878 if not sparsematch(file):
879 879 ui.warn(_("pending changes to '%s'\n") % file)
880 880 abort = not force
881 881 if abort:
882 882 raise error.Abort(_("could not update sparseness due to " +
883 883 "pending changes"))
884 884
885 885 # Calculate actions
886 886 dirstate = repo.dirstate
887 887 ctx = repo['.']
888 888 added = []
889 889 lookup = []
890 890 dropped = []
891 891 mf = ctx.manifest()
892 892 files = set(mf)
893 893
894 894 actions = {}
895 895
896 896 for file in files:
897 897 old = origsparsematch(file)
898 898 new = sparsematch(file)
899 899 # Add files that are newly included, or that don't exist in
900 900 # the dirstate yet.
901 901 if (new and not old) or (old and new and not file in dirstate):
902 902 fl = mf.flags(file)
903 903 if repo.wvfs.exists(file):
904 904 actions[file] = ('e', (fl,), '')
905 905 lookup.append(file)
906 906 else:
907 907 actions[file] = ('g', (fl, False), '')
908 908 added.append(file)
909 909 # Drop files that are newly excluded, or that still exist in
910 910 # the dirstate.
911 911 elif (old and not new) or (not old and not new and file in dirstate):
912 912 dropped.append(file)
913 913 if file not in pending:
914 914 actions[file] = ('r', [], '')
915 915
916 916 # Verify there are no pending changes in newly included files
917 917 abort = False
918 918 for file in lookup:
919 919 ui.warn(_("pending changes to '%s'\n") % file)
920 920 abort = not force
921 921 if abort:
922 922 raise error.Abort(_("cannot change sparseness due to " +
923 923 "pending changes (delete the files or use --force " +
924 924 "to bring them back dirty)"))
925 925
926 926 # Check for files that were only in the dirstate.
927 927 for file, state in dirstate.iteritems():
928 928 if not file in files:
929 929 old = origsparsematch(file)
930 930 new = sparsematch(file)
931 931 if old and not new:
932 932 dropped.append(file)
933 933
934 934 # Apply changes to disk
935 935 typeactions = dict((m, []) for m in 'a f g am cd dc r dm dg m e k'.split())
936 936 for f, (m, args, msg) in actions.iteritems():
937 937 if m not in typeactions:
938 938 typeactions[m] = []
939 939 typeactions[m].append((f, args, msg))
940 940 mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
941 941
942 942 # Fix dirstate
943 943 for file in added:
944 944 dirstate.normal(file)
945 945
946 946 for file in dropped:
947 947 dirstate.drop(file)
948 948
949 949 for file in lookup:
950 950 # File exists on disk, and we're bringing it back in an unknown state.
951 951 dirstate.normallookup(file)
952 952
953 953 return added, dropped, lookup
954 954
955 955 def _verbose_output(ui, opts, profilecount, includecount, excludecount, added,
956 956 dropped, lookup):
957 957 """Produce --verbose and templatable output
958 958
959 959 This specifically enables -Tjson, providing machine-readable stats on how
960 960 the sparse profile changed.
961 961
962 962 """
963 963 with ui.formatter('sparse', opts) as fm:
964 964 fm.startitem()
965 965 fm.condwrite(ui.verbose, 'profiles_added', 'Profile # change: %d\n',
966 966 profilecount)
967 967 fm.condwrite(ui.verbose, 'include_rules_added',
968 968 'Include rule # change: %d\n', includecount)
969 969 fm.condwrite(ui.verbose, 'exclude_rules_added',
970 970 'Exclude rule # change: %d\n', excludecount)
971 971 # In 'plain' verbose mode, mergemod.applyupdates already outputs what
972 972 # files are added or removed outside of the templating formatter
973 973 # framework. No point in repeating ourselves in that case.
974 974 if not fm.isplain():
975 975 fm.condwrite(ui.verbose, 'files_added', 'Files added: %d\n',
976 976 added)
977 977 fm.condwrite(ui.verbose, 'files_dropped', 'Files dropped: %d\n',
978 978 dropped)
979 979 fm.condwrite(ui.verbose, 'files_conflicting',
980 980 'Files conflicting: %d\n', lookup)
981 981
982 982 class forceincludematcher(object):
983 983 """A matcher that returns true for any of the forced includes before testing
984 984 against the actual matcher."""
985 985 def __init__(self, matcher, includes):
986 986 self._matcher = matcher
987 987 self._includes = includes
988 988
989 989 def __call__(self, value):
990 990 return value in self._includes or self._matcher(value)
991 991
992 992 def always(self):
993 993 return False
994 994
995 995 def files(self):
996 996 return []
997 997
998 998 def isexact(self):
999 999 return False
1000 1000
1001 1001 def anypats(self):
1002 1002 return True
1003 1003
1004 1004 def prefix(self):
1005 1005 return False
1006 1006
1007 1007 def hash(self):
1008 1008 sha1 = hashlib.sha1()
1009 1009 sha1.update(_hashmatcher(self._matcher))
1010 1010 for include in sorted(self._includes):
1011 1011 sha1.update(include + '\0')
1012 1012 return sha1.hexdigest()
1013 1013
1014 1014 class unionmatcher(object):
1015 1015 """A matcher that is the union of several matchers."""
1016 1016 def __init__(self, matchers):
1017 1017 self._matchers = matchers
1018 1018
1019 1019 def __call__(self, value):
1020 1020 for match in self._matchers:
1021 1021 if match(value):
1022 1022 return True
1023 1023 return False
1024 1024
1025 1025 def always(self):
1026 1026 return False
1027 1027
1028 1028 def files(self):
1029 1029 return []
1030 1030
1031 1031 def isexact(self):
1032 1032 return False
1033 1033
1034 1034 def anypats(self):
1035 1035 return True
1036 1036
1037 1037 def prefix(self):
1038 1038 return False
1039 1039
1040 1040 def hash(self):
1041 1041 sha1 = hashlib.sha1()
1042 1042 for m in self._matchers:
1043 1043 sha1.update(_hashmatcher(m))
1044 1044 return sha1.hexdigest()
1045 1045
1046 1046 class negatematcher(object):
1047 1047 def __init__(self, matcher):
1048 1048 self._matcher = matcher
1049 1049
1050 1050 def __call__(self, value):
1051 1051 return not self._matcher(value)
1052 1052
1053 1053 def always(self):
1054 1054 return False
1055 1055
1056 1056 def files(self):
1057 1057 return []
1058 1058
1059 1059 def isexact(self):
1060 1060 return False
1061 1061
1062 1062 def anypats(self):
1063 1063 return True
1064 1064
1065 1065 def hash(self):
1066 1066 sha1 = hashlib.sha1()
1067 1067 sha1.update('negate')
1068 1068 sha1.update(_hashmatcher(self._matcher))
1069 1069 return sha1.hexdigest()
1070 1070
1071 1071 def _hashmatcher(matcher):
1072 1072 if util.safehasattr(matcher, 'hash'):
1073 1073 return matcher.hash()
1074 1074
1075 1075 sha1 = hashlib.sha1()
1076 1076 sha1.update(repr(matcher))
1077 1077 return sha1.hexdigest()
@@ -1,73 +1,73 b''
1 1 test sparse
2 2
3 3 $ hg init myrepo
4 4 $ cd myrepo
5 5 $ cat >> $HGRCPATH <<EOF
6 6 > [extensions]
7 7 > sparse=
8 8 > purge=
9 9 > strip=
10 10 > rebase=
11 11 > EOF
12 12
13 13 $ echo a > index.html
14 14 $ echo x > data.py
15 15 $ echo z > readme.txt
16 16 $ cat > base.sparse <<EOF
17 17 > [include]
18 18 > *.sparse
19 19 > EOF
20 20 $ hg ci -Aqm 'initial'
21 21 $ cat > webpage.sparse <<EOF
22 22 > %include base.sparse
23 23 > [include]
24 24 > *.html
25 25 > EOF
26 26 $ hg ci -Aqm 'initial'
27 27
28 28 Clear rules when there are includes
29 29
30 $ hg sparse --include *.py
30 $ hg debugsparse --include *.py
31 31 $ ls
32 32 data.py
33 $ hg sparse --clear-rules
33 $ hg debugsparse --clear-rules
34 34 $ ls
35 35 base.sparse
36 36 data.py
37 37 index.html
38 38 readme.txt
39 39 webpage.sparse
40 40
41 41 Clear rules when there are excludes
42 42
43 $ hg sparse --exclude *.sparse
43 $ hg debugsparse --exclude *.sparse
44 44 $ ls
45 45 data.py
46 46 index.html
47 47 readme.txt
48 $ hg sparse --clear-rules
48 $ hg debugsparse --clear-rules
49 49 $ ls
50 50 base.sparse
51 51 data.py
52 52 index.html
53 53 readme.txt
54 54 webpage.sparse
55 55
56 56 Clearing rules should not alter profiles
57 57
58 $ hg sparse --enable-profile webpage.sparse
58 $ hg debugsparse --enable-profile webpage.sparse
59 59 $ ls
60 60 base.sparse
61 61 index.html
62 62 webpage.sparse
63 $ hg sparse --include *.py
63 $ hg debugsparse --include *.py
64 64 $ ls
65 65 base.sparse
66 66 data.py
67 67 index.html
68 68 webpage.sparse
69 $ hg sparse --clear-rules
69 $ hg debugsparse --clear-rules
70 70 $ ls
71 71 base.sparse
72 72 index.html
73 73 webpage.sparse
@@ -1,186 +1,186 b''
1 1 test sparse
2 2
3 3 $ hg init myrepo
4 4 $ cd myrepo
5 5 $ cat >> $HGRCPATH <<EOF
6 6 > [extensions]
7 7 > sparse=
8 8 > purge=
9 9 > strip=
10 10 > rebase=
11 11 > EOF
12 12
13 13 $ echo a > index.html
14 14 $ echo x > data.py
15 15 $ echo z > readme.txt
16 16 $ cat > base.sparse <<EOF
17 17 > [include]
18 18 > *.sparse
19 19 > EOF
20 20 $ hg ci -Aqm 'initial'
21 21 $ cat > webpage.sparse <<EOF
22 22 > %include base.sparse
23 23 > [include]
24 24 > *.html
25 25 > EOF
26 26 $ hg ci -Aqm 'initial'
27 27
28 28 Import a rules file against a 'blank' sparse profile
29 29
30 30 $ cat > $TESTTMP/rules_to_import <<EOF
31 31 > [include]
32 32 > *.py
33 33 > EOF
34 $ hg sparse --import-rules $TESTTMP/rules_to_import
34 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
35 35 $ ls
36 36 data.py
37 37
38 $ hg sparse --reset
38 $ hg debugsparse --reset
39 39 $ rm .hg/sparse
40 40
41 41 $ cat > $TESTTMP/rules_to_import <<EOF
42 42 > %include base.sparse
43 43 > [include]
44 44 > *.py
45 45 > EOF
46 $ hg sparse --import-rules $TESTTMP/rules_to_import
46 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
47 47 $ ls
48 48 base.sparse
49 49 data.py
50 50 webpage.sparse
51 51
52 $ hg sparse --reset
52 $ hg debugsparse --reset
53 53 $ rm .hg/sparse
54 54
55 55 Start against an existing profile; rules *already active* should be ignored
56 56
57 $ hg sparse --enable-profile webpage.sparse
58 $ hg sparse --include *.py
57 $ hg debugsparse --enable-profile webpage.sparse
58 $ hg debugsparse --include *.py
59 59 $ cat > $TESTTMP/rules_to_import <<EOF
60 60 > %include base.sparse
61 61 > [include]
62 62 > *.html
63 63 > *.txt
64 64 > [exclude]
65 65 > *.py
66 66 > EOF
67 $ hg sparse --import-rules $TESTTMP/rules_to_import
67 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
68 68 $ ls
69 69 base.sparse
70 70 index.html
71 71 readme.txt
72 72 webpage.sparse
73 73 $ cat .hg/sparse
74 74 %include webpage.sparse
75 75 [include]
76 76 *.py
77 77 *.txt
78 78 [exclude]
79 79 *.py
80 80
81 $ hg sparse --reset
81 $ hg debugsparse --reset
82 82 $ rm .hg/sparse
83 83
84 84 Same tests, with -Tjson enabled to output summaries
85 85
86 86 $ cat > $TESTTMP/rules_to_import <<EOF
87 87 > [include]
88 88 > *.py
89 89 > EOF
90 $ hg sparse --import-rules $TESTTMP/rules_to_import -Tjson
90 $ hg debugsparse --import-rules $TESTTMP/rules_to_import -Tjson
91 91 [
92 92 {
93 93 "exclude_rules_added": 0,
94 94 "files_added": 0,
95 95 "files_conflicting": 0,
96 96 "files_dropped": 4,
97 97 "include_rules_added": 1,
98 98 "profiles_added": 0
99 99 }
100 100 ]
101 101
102 $ hg sparse --reset
102 $ hg debugsparse --reset
103 103 $ rm .hg/sparse
104 104
105 105 $ cat > $TESTTMP/rules_to_import <<EOF
106 106 > %include base.sparse
107 107 > [include]
108 108 > *.py
109 109 > EOF
110 $ hg sparse --import-rules $TESTTMP/rules_to_import -Tjson
110 $ hg debugsparse --import-rules $TESTTMP/rules_to_import -Tjson
111 111 [
112 112 {
113 113 "exclude_rules_added": 0,
114 114 "files_added": 0,
115 115 "files_conflicting": 0,
116 116 "files_dropped": 2,
117 117 "include_rules_added": 1,
118 118 "profiles_added": 1
119 119 }
120 120 ]
121 121
122 $ hg sparse --reset
122 $ hg debugsparse --reset
123 123 $ rm .hg/sparse
124 124
125 $ hg sparse --enable-profile webpage.sparse
126 $ hg sparse --include *.py
125 $ hg debugsparse --enable-profile webpage.sparse
126 $ hg debugsparse --include *.py
127 127 $ cat > $TESTTMP/rules_to_import <<EOF
128 128 > %include base.sparse
129 129 > [include]
130 130 > *.html
131 131 > *.txt
132 132 > [exclude]
133 133 > *.py
134 134 > EOF
135 $ hg sparse --import-rules $TESTTMP/rules_to_import -Tjson
135 $ hg debugsparse --import-rules $TESTTMP/rules_to_import -Tjson
136 136 [
137 137 {
138 138 "exclude_rules_added": 1,
139 139 "files_added": 1,
140 140 "files_conflicting": 0,
141 141 "files_dropped": 1,
142 142 "include_rules_added": 1,
143 143 "profiles_added": 0
144 144 }
145 145 ]
146 146
147 147 If importing results in no new rules being added, no refresh should take place!
148 148
149 149 $ cat > $TESTTMP/trap_sparse_refresh.py <<EOF
150 150 > from mercurial import error, extensions
151 151 > def extsetup(ui):
152 152 > def abort_refresh(ui, *args):
153 153 > raise error.Abort('sparse._refresh called!')
154 154 > def sparseloaded(loaded):
155 155 > if not loaded:
156 156 > return
157 157 > sparse = extensions.find('sparse')
158 158 > sparse._refresh = abort_refresh
159 159 > extensions.afterloaded('sparse', sparseloaded)
160 160 > EOF
161 161 $ cat >> $HGRCPATH <<EOF
162 162 > [extensions]
163 163 > trap_sparse_refresh=$TESTTMP/trap_sparse_refresh.py
164 164 > EOF
165 165 $ cat > $TESTTMP/rules_to_import <<EOF
166 166 > [include]
167 167 > *.py
168 168 > EOF
169 $ hg sparse --import-rules $TESTTMP/rules_to_import
169 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
170 170
171 171 If an exception is raised during refresh, restore the existing rules again.
172 172
173 173 $ cat > $TESTTMP/rules_to_import <<EOF
174 174 > [exclude]
175 175 > *.html
176 176 > EOF
177 $ hg sparse --import-rules $TESTTMP/rules_to_import
177 $ hg debugsparse --import-rules $TESTTMP/rules_to_import
178 178 abort: sparse._refresh called!
179 179 [255]
180 180 $ cat .hg/sparse
181 181 %include webpage.sparse
182 182 [include]
183 183 *.py
184 184 *.txt
185 185 [exclude]
186 186 *.py
@@ -1,62 +1,62 b''
1 1 test merging things outside of the sparse checkout
2 2
3 3 $ hg init myrepo
4 4 $ cd myrepo
5 5 $ cat > .hg/hgrc <<EOF
6 6 > [extensions]
7 7 > sparse=
8 8 > EOF
9 9
10 10 $ echo foo > foo
11 11 $ echo bar > bar
12 12 $ hg add foo bar
13 13 $ hg commit -m initial
14 14
15 15 $ hg branch feature
16 16 marked working directory as branch feature
17 17 (branches are permanent and global, did you want a bookmark?)
18 18 $ echo bar2 >> bar
19 19 $ hg commit -m 'feature - bar2'
20 20
21 21 $ hg update -q default
22 $ hg sparse --exclude 'bar**'
22 $ hg debugsparse --exclude 'bar**'
23 23
24 24 $ hg merge feature
25 25 temporarily included 1 file(s) in the sparse checkout for merging
26 26 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
27 27 (branch merge, don't forget to commit)
28 28
29 29 Verify bar was merged temporarily
30 30
31 31 $ ls
32 32 bar
33 33 foo
34 34 $ hg status
35 35 M bar
36 36
37 37 Verify bar disappears automatically when the working copy becomes clean
38 38
39 39 $ hg commit -m "merged"
40 40 cleaned up 1 temporarily added file(s) from the sparse checkout
41 41 $ hg status
42 42 $ ls
43 43 foo
44 44
45 45 $ hg cat -r . bar
46 46 bar
47 47 bar2
48 48
49 49 Test merging things outside of the sparse checkout that are not in the working
50 50 copy
51 51
52 52 $ hg strip -q -r . --config extensions.strip=
53 53 $ hg up -q feature
54 54 $ touch branchonly
55 55 $ hg ci -Aqm 'add branchonly'
56 56
57 57 $ hg up -q default
58 $ hg sparse -X branchonly
58 $ hg debugsparse -X branchonly
59 59 $ hg merge feature
60 60 temporarily included 2 file(s) in the sparse checkout for merging
61 61 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
62 62 (branch merge, don't forget to commit)
@@ -1,272 +1,272 b''
1 1 test sparse
2 2
3 3 $ hg init myrepo
4 4 $ cd myrepo
5 5 $ cat > .hg/hgrc <<EOF
6 6 > [extensions]
7 7 > sparse=
8 8 > purge=
9 9 > strip=
10 10 > rebase=
11 11 > EOF
12 12
13 13 $ echo a > index.html
14 14 $ echo x > data.py
15 15 $ echo z > readme.txt
16 16 $ cat > webpage.sparse <<EOF
17 17 > # frontend sparse profile
18 18 > [include]
19 19 > *.html
20 20 > EOF
21 21 $ cat > backend.sparse <<EOF
22 22 > # backend sparse profile
23 23 > [include]
24 24 > *.py
25 25 > EOF
26 26 $ hg ci -Aqm 'initial'
27 27
28 $ hg sparse --include '*.sparse'
28 $ hg debugsparse --include '*.sparse'
29 29
30 30 Verify enabling a single profile works
31 31
32 $ hg sparse --enable-profile webpage.sparse
32 $ hg debugsparse --enable-profile webpage.sparse
33 33 $ ls
34 34 backend.sparse
35 35 index.html
36 36 webpage.sparse
37 37
38 38 Verify enabling two profiles works
39 39
40 $ hg sparse --enable-profile backend.sparse
40 $ hg debugsparse --enable-profile backend.sparse
41 41 $ ls
42 42 backend.sparse
43 43 data.py
44 44 index.html
45 45 webpage.sparse
46 46
47 47 Verify disabling a profile works
48 48
49 $ hg sparse --disable-profile webpage.sparse
49 $ hg debugsparse --disable-profile webpage.sparse
50 50 $ ls
51 51 backend.sparse
52 52 data.py
53 53 webpage.sparse
54 54
55 55 Verify that a profile is updated across multiple commits
56 56
57 57 $ cat > webpage.sparse <<EOF
58 58 > # frontend sparse profile
59 59 > [include]
60 60 > *.html
61 61 > EOF
62 62 $ cat > backend.sparse <<EOF
63 63 > # backend sparse profile
64 64 > [include]
65 65 > *.py
66 66 > *.txt
67 67 > EOF
68 68
69 69 $ echo foo >> data.py
70 70
71 71 $ hg ci -m 'edit profile'
72 72 $ ls
73 73 backend.sparse
74 74 data.py
75 75 readme.txt
76 76 webpage.sparse
77 77
78 78 $ hg up -q 0
79 79 $ ls
80 80 backend.sparse
81 81 data.py
82 82 webpage.sparse
83 83
84 84 $ hg up -q 1
85 85 $ ls
86 86 backend.sparse
87 87 data.py
88 88 readme.txt
89 89 webpage.sparse
90 90
91 91 Introduce a conflicting .hgsparse change
92 92
93 93 $ hg up -q 0
94 94 $ cat > backend.sparse <<EOF
95 95 > # Different backend sparse profile
96 96 > [include]
97 97 > *.html
98 98 > EOF
99 99 $ echo bar >> data.py
100 100
101 101 $ hg ci -qAm "edit profile other"
102 102 $ ls
103 103 backend.sparse
104 104 index.html
105 105 webpage.sparse
106 106
107 107 Verify conflicting merge pulls in the conflicting changes
108 108
109 109 $ hg merge 1
110 110 temporarily included 1 file(s) in the sparse checkout for merging
111 111 merging backend.sparse
112 112 merging data.py
113 113 warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark')
114 114 warning: conflicts while merging data.py! (edit, then use 'hg resolve --mark')
115 115 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
116 116 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
117 117 [1]
118 118
119 119 $ rm *.orig
120 120 $ ls
121 121 backend.sparse
122 122 data.py
123 123 index.html
124 124 webpage.sparse
125 125
126 126 Verify resolving the merge removes the temporarily unioned files
127 127
128 128 $ cat > backend.sparse <<EOF
129 129 > # backend sparse profile
130 130 > [include]
131 131 > *.html
132 132 > *.txt
133 133 > EOF
134 134 $ hg resolve -m backend.sparse
135 135
136 136 $ cat > data.py <<EOF
137 137 > x
138 138 > foo
139 139 > bar
140 140 > EOF
141 141 $ hg resolve -m data.py
142 142 (no more unresolved files)
143 143
144 144 $ hg ci -qAm "merge profiles"
145 145 $ ls
146 146 backend.sparse
147 147 index.html
148 148 readme.txt
149 149 webpage.sparse
150 150
151 151 $ hg cat -r . data.py
152 152 x
153 153 foo
154 154 bar
155 155
156 156 Verify stripping refreshes dirstate
157 157
158 158 $ hg strip -q -r .
159 159 $ ls
160 160 backend.sparse
161 161 index.html
162 162 webpage.sparse
163 163
164 164 Verify rebase conflicts pulls in the conflicting changes
165 165
166 166 $ hg up -q 1
167 167 $ ls
168 168 backend.sparse
169 169 data.py
170 170 readme.txt
171 171 webpage.sparse
172 172
173 173 $ hg rebase -d 2
174 174 rebasing 1:a2b1de640a62 "edit profile"
175 175 temporarily included 1 file(s) in the sparse checkout for merging
176 176 merging backend.sparse
177 177 merging data.py
178 178 warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark')
179 179 warning: conflicts while merging data.py! (edit, then use 'hg resolve --mark')
180 180 unresolved conflicts (see hg resolve, then hg rebase --continue)
181 181 [1]
182 182 $ rm *.orig
183 183 $ ls
184 184 backend.sparse
185 185 data.py
186 186 index.html
187 187 webpage.sparse
188 188
189 189 Verify resolving conflict removes the temporary files
190 190
191 191 $ cat > backend.sparse <<EOF
192 192 > [include]
193 193 > *.html
194 194 > *.txt
195 195 > EOF
196 196 $ hg resolve -m backend.sparse
197 197
198 198 $ cat > data.py <<EOF
199 199 > x
200 200 > foo
201 201 > bar
202 202 > EOF
203 203 $ hg resolve -m data.py
204 204 (no more unresolved files)
205 205 continue: hg rebase --continue
206 206
207 207 $ hg rebase -q --continue
208 208 $ ls
209 209 backend.sparse
210 210 index.html
211 211 readme.txt
212 212 webpage.sparse
213 213
214 214 $ hg cat -r . data.py
215 215 x
216 216 foo
217 217 bar
218 218
219 219 Test checking out a commit that does not contain the sparse profile. The
220 220 warning message can be suppressed by setting missingwarning = false in
221 221 [sparse] section of your config:
222 222
223 $ hg sparse --reset
223 $ hg debugsparse --reset
224 224 $ hg rm *.sparse
225 225 $ hg commit -m "delete profiles"
226 226 $ hg up -q ".^"
227 $ hg sparse --enable-profile backend.sparse
227 $ hg debugsparse --enable-profile backend.sparse
228 228 $ ls
229 229 index.html
230 230 readme.txt
231 231 $ hg up tip | grep warning
232 232 warning: sparse profile 'backend.sparse' not found in rev bfcb76de99cc - ignoring it
233 233 [1]
234 234 $ ls
235 235 data.py
236 236 index.html
237 237 readme.txt
238 $ hg sparse --disable-profile backend.sparse | grep warning
238 $ hg debugsparse --disable-profile backend.sparse | grep warning
239 239 warning: sparse profile 'backend.sparse' not found in rev bfcb76de99cc - ignoring it
240 240 [1]
241 241 $ cat >> .hg/hgrc <<EOF
242 242 > [sparse]
243 243 > missingwarning = false
244 244 > EOF
245 $ hg sparse --enable-profile backend.sparse
245 $ hg debugsparse --enable-profile backend.sparse
246 246
247 247 $ cd ..
248 248
249 249 Test file permissions changing across a sparse profile change
250 250 $ hg init sparseperm
251 251 $ cd sparseperm
252 252 $ cat > .hg/hgrc <<EOF
253 253 > [extensions]
254 254 > sparse=
255 255 > EOF
256 256 $ touch a b
257 257 $ cat > .hgsparse <<EOF
258 258 > a
259 259 > EOF
260 260 $ hg commit -Aqm 'initial'
261 261 $ chmod a+x b
262 262 $ hg commit -qm 'make executable'
263 263 $ cat >> .hgsparse <<EOF
264 264 > b
265 265 > EOF
266 266 $ hg commit -qm 'update profile'
267 267 $ hg up -q 0
268 $ hg sparse --enable-profile .hgsparse
268 $ hg debugsparse --enable-profile .hgsparse
269 269 $ hg up -q 2
270 270 $ ls -l b
271 271 -rwxr-xr-x* b (glob)
272 272
@@ -1,82 +1,82 b''
1 1 test sparse with --verbose and -T json
2 2
3 3 $ hg init myrepo
4 4 $ cd myrepo
5 5 $ cat > .hg/hgrc <<EOF
6 6 > [extensions]
7 7 > sparse=
8 8 > strip=
9 9 > EOF
10 10
11 11 $ echo a > show
12 12 $ echo x > hide
13 13 $ hg ci -Aqm 'initial'
14 14
15 15 $ echo b > show
16 16 $ echo y > hide
17 17 $ echo aa > show2
18 18 $ echo xx > hide2
19 19 $ hg ci -Aqm 'two'
20 20
21 21 Verify basic --include and --reset
22 22
23 23 $ hg up -q 0
24 $ hg sparse --include 'hide' -Tjson
24 $ hg debugsparse --include 'hide' -Tjson
25 25 [
26 26 {
27 27 "exclude_rules_added": 0,
28 28 "files_added": 0,
29 29 "files_conflicting": 0,
30 30 "files_dropped": 1,
31 31 "include_rules_added": 1,
32 32 "profiles_added": 0
33 33 }
34 34 ]
35 $ hg sparse --clear-rules
36 $ hg sparse --include 'hide' --verbose
35 $ hg debugsparse --clear-rules
36 $ hg debugsparse --include 'hide' --verbose
37 37 removing show
38 38 Profile # change: 0
39 39 Include rule # change: 1
40 40 Exclude rule # change: 0
41 41
42 $ hg sparse --reset -Tjson
42 $ hg debugsparse --reset -Tjson
43 43 [
44 44 {
45 45 "exclude_rules_added": 0,
46 46 "files_added": 1,
47 47 "files_conflicting": 0,
48 48 "files_dropped": 0,
49 49 "include_rules_added": -1,
50 50 "profiles_added": 0
51 51 }
52 52 ]
53 $ hg sparse --include 'hide'
54 $ hg sparse --reset --verbose
53 $ hg debugsparse --include 'hide'
54 $ hg debugsparse --reset --verbose
55 55 getting show
56 56 Profile # change: 0
57 57 Include rule # change: -1
58 58 Exclude rule # change: 0
59 59
60 60 Verifying that problematic files still allow us to see the deltas when forcing:
61 61
62 $ hg sparse --include 'show*'
62 $ hg debugsparse --include 'show*'
63 63 $ touch hide
64 $ hg sparse --delete 'show*' --force -Tjson
64 $ hg debugsparse --delete 'show*' --force -Tjson
65 65 pending changes to 'hide'
66 66 [
67 67 {
68 68 "exclude_rules_added": 0,
69 69 "files_added": 0,
70 70 "files_conflicting": 1,
71 71 "files_dropped": 0,
72 72 "include_rules_added": -1,
73 73 "profiles_added": 0
74 74 }
75 75 ]
76 $ hg sparse --include 'show*' --force
76 $ hg debugsparse --include 'show*' --force
77 77 pending changes to 'hide'
78 $ hg sparse --delete 'show*' --force --verbose
78 $ hg debugsparse --delete 'show*' --force --verbose
79 79 pending changes to 'hide'
80 80 Profile # change: 0
81 81 Include rule # change: -1
82 82 Exclude rule # change: 0
@@ -1,369 +1,369 b''
1 1 test sparse
2 2
3 3 $ hg init myrepo
4 4 $ cd myrepo
5 5 $ cat > .hg/hgrc <<EOF
6 6 > [extensions]
7 7 > sparse=
8 8 > strip=
9 9 > EOF
10 10
11 11 $ echo a > show
12 12 $ echo x > hide
13 13 $ hg ci -Aqm 'initial'
14 14
15 15 $ echo b > show
16 16 $ echo y > hide
17 17 $ echo aa > show2
18 18 $ echo xx > hide2
19 19 $ hg ci -Aqm 'two'
20 20
21 21 Verify basic --include
22 22
23 23 $ hg up -q 0
24 $ hg sparse --include 'hide'
24 $ hg debugsparse --include 'hide'
25 25 $ ls
26 26 hide
27 27
28 28 Absolute paths outside the repo should just be rejected
29 29
30 $ hg sparse --include /foo/bar
30 $ hg debugsparse --include /foo/bar
31 31 warning: paths cannot start with /, ignoring: ['/foo/bar']
32 $ hg sparse --include '$TESTTMP/myrepo/hide'
32 $ hg debugsparse --include '$TESTTMP/myrepo/hide'
33 33
34 $ hg sparse --include '/root'
34 $ hg debugsparse --include '/root'
35 35 warning: paths cannot start with /, ignoring: ['/root']
36 36
37 37 Verify commiting while sparse includes other files
38 38
39 39 $ echo z > hide
40 40 $ hg ci -Aqm 'edit hide'
41 41 $ ls
42 42 hide
43 43 $ hg manifest
44 44 hide
45 45 show
46 46
47 47 Verify --reset brings files back
48 48
49 $ hg sparse --reset
49 $ hg debugsparse --reset
50 50 $ ls
51 51 hide
52 52 show
53 53 $ cat hide
54 54 z
55 55 $ cat show
56 56 a
57 57
58 Verify 'hg sparse' default output
58 Verify 'hg debugsparse' default output
59 59
60 60 $ hg up -q null
61 $ hg sparse --include 'show*'
61 $ hg debugsparse --include 'show*'
62 62
63 $ hg sparse
63 $ hg debugsparse
64 64 [include]
65 65 show*
66 66 [exclude]
67 67
68 68
69 69 Verify update only writes included files
70 70
71 71 $ hg up -q 0
72 72 $ ls
73 73 show
74 74
75 75 $ hg up -q 1
76 76 $ ls
77 77 show
78 78 show2
79 79
80 80 Verify status only shows included files
81 81
82 82 $ touch hide
83 83 $ touch hide3
84 84 $ echo c > show
85 85 $ hg status
86 86 M show
87 87
88 88 Adding an excluded file should fail
89 89
90 90 $ hg add hide3
91 91 abort: cannot add 'hide3' - it is outside the sparse checkout
92 (include file with `hg sparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
92 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
93 93 [255]
94 94
95 95 Verify deleting sparseness while a file has changes fails
96 96
97 $ hg sparse --delete 'show*'
97 $ hg debugsparse --delete 'show*'
98 98 pending changes to 'hide'
99 99 abort: cannot change sparseness due to pending changes (delete the files or use --force to bring them back dirty)
100 100 [255]
101 101
102 102 Verify deleting sparseness with --force brings back files
103 103
104 $ hg sparse --delete -f 'show*'
104 $ hg debugsparse --delete -f 'show*'
105 105 pending changes to 'hide'
106 106 $ ls
107 107 hide
108 108 hide2
109 109 hide3
110 110 show
111 111 show2
112 112 $ hg st
113 113 M hide
114 114 M show
115 115 ? hide3
116 116
117 117 Verify editing sparseness fails if pending changes
118 118
119 $ hg sparse --include 'show*'
119 $ hg debugsparse --include 'show*'
120 120 pending changes to 'hide'
121 121 abort: could not update sparseness due to pending changes
122 122 [255]
123 123
124 124 Verify adding sparseness hides files
125 125
126 $ hg sparse --exclude -f 'hide*'
126 $ hg debugsparse --exclude -f 'hide*'
127 127 pending changes to 'hide'
128 128 $ ls
129 129 hide
130 130 hide3
131 131 show
132 132 show2
133 133 $ hg st
134 134 M show
135 135
136 136 $ hg up -qC .
137 137 $ hg purge --all --config extensions.purge=
138 138 $ ls
139 139 show
140 140 show2
141 141
142 142 Verify rebase temporarily includes excluded files
143 143
144 144 $ hg rebase -d 1 -r 2 --config extensions.rebase=
145 145 rebasing 2:b91df4f39e75 "edit hide" (tip)
146 146 temporarily included 1 file(s) in the sparse checkout for merging
147 147 merging hide
148 148 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
149 149 unresolved conflicts (see hg resolve, then hg rebase --continue)
150 150 [1]
151 151
152 $ hg sparse
152 $ hg debugsparse
153 153 [include]
154 154
155 155 [exclude]
156 156 hide*
157 157
158 158 Temporarily Included Files (for merge/rebase):
159 159 hide
160 160
161 161 $ cat hide
162 162 <<<<<<< dest: 39278f7c08a9 - test: two
163 163 y
164 164 =======
165 165 z
166 166 >>>>>>> source: b91df4f39e75 - test: edit hide
167 167
168 168 Verify aborting a rebase cleans up temporary files
169 169
170 170 $ hg rebase --abort --config extensions.rebase=
171 171 cleaned up 1 temporarily added file(s) from the sparse checkout
172 172 rebase aborted
173 173 $ rm hide.orig
174 174
175 175 $ ls
176 176 show
177 177 show2
178 178
179 179 Verify merge fails if merging excluded files
180 180
181 181 $ hg up -q 1
182 182 $ hg merge -r 2
183 183 temporarily included 1 file(s) in the sparse checkout for merging
184 184 merging hide
185 185 warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
186 186 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
187 187 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
188 188 [1]
189 $ hg sparse
189 $ hg debugsparse
190 190 [include]
191 191
192 192 [exclude]
193 193 hide*
194 194
195 195 Temporarily Included Files (for merge/rebase):
196 196 hide
197 197
198 198 $ hg up -C .
199 199 cleaned up 1 temporarily added file(s) from the sparse checkout
200 200 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
201 $ hg sparse
201 $ hg debugsparse
202 202 [include]
203 203
204 204 [exclude]
205 205 hide*
206 206
207 207
208 208 Verify strip -k resets dirstate correctly
209 209
210 210 $ hg status
211 $ hg sparse
211 $ hg debugsparse
212 212 [include]
213 213
214 214 [exclude]
215 215 hide*
216 216
217 217 $ hg log -r . -T '{rev}\n' --stat
218 218 1
219 219 hide | 2 +-
220 220 hide2 | 1 +
221 221 show | 2 +-
222 222 show2 | 1 +
223 223 4 files changed, 4 insertions(+), 2 deletions(-)
224 224
225 225 $ hg strip -r . -k
226 226 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/39278f7c08a9-ce59e002-backup.hg (glob)
227 227 $ hg status
228 228 M show
229 229 ? show2
230 230
231 231 Verify rebase succeeds if all changed files are in sparse checkout
232 232
233 233 $ hg commit -Aqm "add show2"
234 234 $ hg rebase -d 1 --config extensions.rebase=
235 235 rebasing 2:bdde55290160 "add show2" (tip)
236 236 saved backup bundle to $TESTTMP/myrepo/.hg/strip-backup/bdde55290160-216ed9c6-backup.hg (glob)
237 237
238 238 Verify log --sparse only shows commits that affect the sparse checkout
239 239
240 240 $ hg log -T '{rev} '
241 241 2 1 0 (no-eol)
242 242 $ hg log --sparse -T '{rev} '
243 243 2 0 (no-eol)
244 244
245 245 Test status on a file in a subdir
246 246
247 247 $ mkdir -p dir1/dir2
248 248 $ touch dir1/dir2/file
249 $ hg sparse -I dir1/dir2
249 $ hg debugsparse -I dir1/dir2
250 250 $ hg status
251 251 ? dir1/dir2/file
252 252
253 253 Test that add -s adds dirs to sparse profile
254 254
255 $ hg sparse --reset
256 $ hg sparse --include empty
257 $ hg sparse
255 $ hg debugsparse --reset
256 $ hg debugsparse --include empty
257 $ hg debugsparse
258 258 [include]
259 259 empty
260 260 [exclude]
261 261
262 262
263 263
264 264 $ mkdir add
265 265 $ touch add/foo
266 266 $ touch add/bar
267 267 $ hg add add/foo
268 268 abort: cannot add 'add/foo' - it is outside the sparse checkout
269 (include file with `hg sparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
269 (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
270 270 [255]
271 271 $ hg add -s add/foo
272 272 $ hg st
273 273 A add/foo
274 274 ? add/bar
275 $ hg sparse
275 $ hg debugsparse
276 276 [include]
277 277 add
278 278 empty
279 279 [exclude]
280 280
281 281
282 282 $ hg add -s add/*
283 283 add/foo already tracked!
284 284 $ hg st
285 285 A add/bar
286 286 A add/foo
287 $ hg sparse
287 $ hg debugsparse
288 288 [include]
289 289 add
290 290 empty
291 291 [exclude]
292 292
293 293
294 294
295 295 $ cd ..
296 296
297 297 Test non-sparse repos work while sparse is loaded
298 298 $ hg init sparserepo
299 299 $ hg init nonsparserepo
300 300 $ cd sparserepo
301 301 $ cat > .hg/hgrc <<EOF
302 302 > [extensions]
303 303 > sparse=
304 304 > EOF
305 305 $ cd ../nonsparserepo
306 306 $ echo x > x && hg add x && hg commit -qAm x
307 307 $ cd ../sparserepo
308 308 $ hg clone ../nonsparserepo ../nonsparserepo2
309 309 updating to branch default
310 310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
311 311
312 312 Test debugrebuilddirstate
313 313 $ cd ../sparserepo
314 314 $ touch included
315 315 $ touch excluded
316 316 $ hg add included excluded
317 317 $ hg commit -m 'a commit' -q
318 318 $ cp .hg/dirstate ../dirstateboth
319 $ hg sparse -X excluded
319 $ hg debugsparse -X excluded
320 320 $ cp ../dirstateboth .hg/dirstate
321 321 $ hg debugrebuilddirstate
322 322 $ hg debugdirstate
323 323 n 0 -1 unset included
324 324
325 325 Test debugdirstate --minimal where file is in the parent manifest but not the
326 326 dirstate
327 $ hg sparse -X included
327 $ hg debugsparse -X included
328 328 $ hg debugdirstate
329 329 $ cp .hg/dirstate ../dirstateallexcluded
330 $ hg sparse --reset
331 $ hg sparse -X excluded
330 $ hg debugsparse --reset
331 $ hg debugsparse -X excluded
332 332 $ cp ../dirstateallexcluded .hg/dirstate
333 333 $ touch includedadded
334 334 $ hg add includedadded
335 335 $ hg debugdirstate --nodates
336 336 a 0 -1 unset includedadded
337 337 $ hg debugrebuilddirstate --minimal
338 338 $ hg debugdirstate --nodates
339 339 n 0 -1 unset included
340 340 a 0 -1 * includedadded (glob)
341 341
342 342 Test debugdirstate --minimal where a file is not in parent manifest
343 343 but in the dirstate. This should take into account excluded files in the
344 344 manifest
345 345 $ cp ../dirstateboth .hg/dirstate
346 346 $ touch includedadded
347 347 $ hg add includedadded
348 348 $ touch excludednomanifest
349 349 $ hg add excludednomanifest
350 350 $ cp .hg/dirstate ../moreexcluded
351 351 $ hg forget excludednomanifest
352 352 $ rm excludednomanifest
353 $ hg sparse -X excludednomanifest
353 $ hg debugsparse -X excludednomanifest
354 354 $ cp ../moreexcluded .hg/dirstate
355 355 $ hg manifest
356 356 excluded
357 357 included
358 358 We have files in the dirstate that are included and excluded. Some are in the
359 359 manifest and some are not.
360 360 $ hg debugdirstate --nodates
361 361 n 644 0 * excluded (glob)
362 362 a 0 -1 * excludednomanifest (glob)
363 363 n 644 0 * included (glob)
364 364 a 0 -1 * includedadded (glob)
365 365 $ hg debugrebuilddirstate --minimal
366 366 $ hg debugdirstate --nodates
367 367 n 644 0 * included (glob)
368 368 a 0 -1 * includedadded (glob)
369 369
General Comments 0
You need to be logged in to leave comments. Login now