##// END OF EJS Templates
fileset: add kind:pat operator...
fileset: add kind:pat operator ":" isn't taken as a symbol character but an infix operator so we can write e.g. "path:'foo bar'" as well as "'path:foo bar'". An invalid pattern kind is rejected in the former form as we know a kind is specified explicitly. The binding strength is copied from "x:y" range operator of revset. Perhaps it can be adjusted later if we want to parse "foo:bar()" as "(foo:bar)()", not "foo:(bar())". We can also add "kind:" postfix operator if we want. One possible confusion is that the scope of the leading "set:" vs "kind:pat" operator. The former is consumed by a matcher so applies to the whole fileset expression: $ hg files 'set:foo() or kind:bar or baz' ^^^^^^^^^^^^^^^^^^^^^^^^ Whereas the scope of kind:pat operator is narrow: $ hg files 'set:foo() or kind:bar or baz' ^^^

File last commit:

r35600:72b91f90 default
r35759:73432eee default
Show More
sparse.py
708 lines | 24.0 KiB | text/x-python | PythonLexer
Gregory Szorc
sparse: move config parsing into core...
r33297 # sparse.py - functionality for sparse checkouts
#
# Copyright 2014 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
Gregory Szorc
sparse: move pruning of temporary includes into core...
r33321 import collections
Gregory Szorc
sparse: move config signature logic into core...
r33317 import hashlib
Gregory Szorc
sparse: move function for resolving sparse matcher into core...
r33320 import os
Gregory Szorc
sparse: move config signature logic into core...
r33317
Gregory Szorc
sparse: move config parsing into core...
r33297 from .i18n import _
Pulkit Goyal
py3: use node.hex(h.digest()) instead of h.hexdigest()...
r35600 from .node import (
hex,
nullid,
)
Gregory Szorc
sparse: move config parsing into core...
r33297 from . import (
error,
Gregory Szorc
sparse: move function for resolving sparse matcher into core...
r33320 match as matchmod,
Gregory Szorc
sparse: move pruning of temporary includes into core...
r33321 merge as mergemod,
Kostia Balytskyi
sparse: treat paths as cwd-relative...
r33648 pathutil,
Gregory Szorc
sparse: move function for resolving sparse matcher into core...
r33320 pycompat,
Gregory Szorc
sparse: add a requirement when a repository uses sparse (BC)...
r33556 scmutil,
Gregory Szorc
sparse: move code for importing rules from files into core...
r33371 util,
Gregory Szorc
sparse: move config parsing into core...
r33297 )
Gregory Szorc
sparse: variable to track if sparse is enabled...
r33299 # Whether sparse features are enabled. This variable is intended to be
# temporary to facilitate porting sparse to core. It should eventually be
# a per-repo option, possibly a repo requirement.
enabled = False
Gregory Szorc
sparse: move config parsing into core...
r33297 def parseconfig(ui, raw):
"""Parse sparse config file content.
Returns a tuple of includes, excludes, and profiles.
"""
includes = set()
excludes = set()
Gregory Szorc
sparse: use set for capturing profiles...
r33550 profiles = set()
Gregory Szorc
sparse: require [section] in sparse config files (BC)...
r33551 current = None
havesection = False
Gregory Szorc
sparse: move config parsing into core...
r33297 for line in raw.split('\n'):
line = line.strip()
if not line or line.startswith('#'):
# empty or comment line, skip
continue
elif line.startswith('%include '):
line = line[9:].strip()
if line:
Gregory Szorc
sparse: use set for capturing profiles...
r33550 profiles.add(line)
Gregory Szorc
sparse: move config parsing into core...
r33297 elif line == '[include]':
Gregory Szorc
sparse: require [section] in sparse config files (BC)...
r33551 if havesection and current != includes:
Gregory Szorc
sparse: move config parsing into core...
r33297 # TODO pass filename into this API so we can report it.
raise error.Abort(_('sparse config cannot have includes ' +
'after excludes'))
Gregory Szorc
sparse: require [section] in sparse config files (BC)...
r33551 havesection = True
current = includes
Gregory Szorc
sparse: move config parsing into core...
r33297 continue
elif line == '[exclude]':
Gregory Szorc
sparse: require [section] in sparse config files (BC)...
r33551 havesection = True
Gregory Szorc
sparse: move config parsing into core...
r33297 current = excludes
elif line:
Gregory Szorc
sparse: require [section] in sparse config files (BC)...
r33551 if current is None:
raise error.Abort(_('sparse config entry outside of '
'section: %s') % line,
hint=_('add an [include] or [exclude] line '
'to declare the entry type'))
Gregory Szorc
sparse: move config parsing into core...
r33297 if line.strip().startswith('/'):
ui.warn(_('warning: sparse profile cannot use' +
' paths starting with /, ignoring %s\n') % line)
continue
current.add(line)
return includes, excludes, profiles
Gregory Szorc
sparse: move profile reading into core...
r33298
# Exists as separate function to facilitate monkeypatching.
def readprofile(repo, profile, changeid):
"""Resolve the raw content of a sparse profile file."""
# TODO add some kind of cache here because this incurs a manifest
# resolve and can be slow.
return repo.filectx(profile, changeid=changeid).data()
Gregory Szorc
sparse: move resolving of sparse patterns for rev into core...
r33300
def patternsforrev(repo, rev):
"""Obtain sparse checkout patterns for the given rev.
Returns a tuple of iterables representing includes, excludes, and
patterns.
"""
# Feature isn't enabled. No-op.
if not enabled:
Gregory Szorc
sparse: use set for capturing profiles...
r33550 return set(), set(), set()
Gregory Szorc
sparse: move resolving of sparse patterns for rev into core...
r33300
raw = repo.vfs.tryread('sparse')
if not raw:
Gregory Szorc
sparse: use set for capturing profiles...
r33550 return set(), set(), set()
Gregory Szorc
sparse: move resolving of sparse patterns for rev into core...
r33300
if rev is None:
raise error.Abort(_('cannot parse sparse patterns from working '
'directory'))
includes, excludes, profiles = parseconfig(repo.ui, raw)
ctx = repo[rev]
if profiles:
visited = set()
while profiles:
profile = profiles.pop()
if profile in visited:
continue
visited.add(profile)
try:
raw = readprofile(repo, profile, rev)
except error.ManifestLookupError:
msg = (
"warning: sparse profile '%s' not found "
"in rev %s - ignoring it\n" % (profile, ctx))
# experimental config: sparse.missingwarning
if repo.ui.configbool(
Jun Wu
codemod: register core configitems using a script...
r33499 'sparse', 'missingwarning'):
Gregory Szorc
sparse: move resolving of sparse patterns for rev into core...
r33300 repo.ui.warn(msg)
else:
repo.ui.debug(msg)
continue
pincludes, pexcludes, subprofs = parseconfig(repo.ui, raw)
includes.update(pincludes)
excludes.update(pexcludes)
Gregory Szorc
sparse: use set for capturing profiles...
r33550 profiles.update(subprofs)
Gregory Szorc
sparse: move resolving of sparse patterns for rev into core...
r33300
profiles = visited
if includes:
includes.add('.hg*')
return includes, excludes, profiles
Gregory Szorc
sparse: move active profiles function into core...
r33301
Gregory Szorc
sparse: refactor activeprofiles into a generic function (API)...
r33370 def activeconfig(repo):
"""Determine the active sparse config rules.
Rules are constructed by reading the current sparse config and bringing in
referenced profiles from parents of the working directory.
"""
Gregory Szorc
sparse: move active profiles function into core...
r33301 revs = [repo.changelog.rev(node) for node in
repo.dirstate.parents() if node != nullid]
Gregory Szorc
sparse: refactor activeprofiles into a generic function (API)...
r33370 allincludes = set()
allexcludes = set()
allprofiles = set()
Gregory Szorc
sparse: move active profiles function into core...
r33301 for rev in revs:
Gregory Szorc
sparse: refactor activeprofiles into a generic function (API)...
r33370 includes, excludes, profiles = patternsforrev(repo, rev)
allincludes |= includes
allexcludes |= excludes
Gregory Szorc
sparse: use set for capturing profiles...
r33550 allprofiles |= profiles
Gregory Szorc
sparse: move active profiles function into core...
r33301
Gregory Szorc
sparse: refactor activeprofiles into a generic function (API)...
r33370 return allincludes, allexcludes, allprofiles
Gregory Szorc
localrepo: add sparse caches...
r33302
Gregory Szorc
sparse: move config signature logic into core...
r33317 def configsignature(repo, includetemp=True):
"""Obtain the signature string for the current sparse configuration.
This is used to construct a cache key for matchers.
"""
cache = repo._sparsesignaturecache
signature = cache.get('signature')
if includetemp:
tempsignature = cache.get('tempsignature')
else:
Gregory Szorc
sparse: clean up config signature code...
r33318 tempsignature = '0'
Gregory Szorc
sparse: move config signature logic into core...
r33317
if signature is None or (includetemp and tempsignature is None):
Pulkit Goyal
py3: use node.hex(h.digest()) instead of h.hexdigest()...
r35600 signature = hex(hashlib.sha1(repo.vfs.tryread('sparse')).digest())
Gregory Szorc
sparse: move config signature logic into core...
r33317 cache['signature'] = signature
if includetemp:
Gregory Szorc
sparse: clean up config signature code...
r33318 raw = repo.vfs.tryread('tempsparse')
Pulkit Goyal
py3: use node.hex(h.digest()) instead of h.hexdigest()...
r35600 tempsignature = hex(hashlib.sha1(raw).digest())
Gregory Szorc
sparse: move config signature logic into core...
r33317 cache['tempsignature'] = tempsignature
Gregory Szorc
sparse: clean up config signature code...
r33318 return '%s %s' % (signature, tempsignature)
Gregory Szorc
sparse: move config signature logic into core...
r33317
Gregory Szorc
sparse: move config file writing into core...
r33303 def writeconfig(repo, includes, excludes, profiles):
"""Write the sparse config file given a sparse configuration."""
with repo.vfs('sparse', 'wb') as fh:
for p in sorted(profiles):
fh.write('%%include %s\n' % p)
if includes:
fh.write('[include]\n')
for i in sorted(includes):
fh.write(i)
fh.write('\n')
if excludes:
fh.write('[exclude]\n')
for e in sorted(excludes):
fh.write(e)
fh.write('\n')
Gregory Szorc
sparse: inline signature cache clearing...
r33325 repo._sparsesignaturecache.clear()
Gregory Szorc
sparse: move some temporary includes functions into core...
r33304
def readtemporaryincludes(repo):
raw = repo.vfs.tryread('tempsparse')
if not raw:
return set()
return set(raw.split('\n'))
def writetemporaryincludes(repo, includes):
repo.vfs.write('tempsparse', '\n'.join(sorted(includes)))
Gregory Szorc
sparse: inline signature cache clearing...
r33325 repo._sparsesignaturecache.clear()
Gregory Szorc
sparse: move some temporary includes functions into core...
r33304
def addtemporaryincludes(repo, additional):
includes = readtemporaryincludes(repo)
for i in additional:
includes.add(i)
writetemporaryincludes(repo, includes)
Gregory Szorc
sparse: move function for resolving sparse matcher into core...
r33320
Gregory Szorc
sparse: move pruning of temporary includes into core...
r33321 def prunetemporaryincludes(repo):
if not enabled or not repo.vfs.exists('tempsparse'):
return
Martin von Zweigbergk
sparse: access status fields by name instead of deconstructing it...
r33356 s = repo.status()
if s.modified or s.added or s.removed or s.deleted:
Gregory Szorc
sparse: move pruning of temporary includes into core...
r33321 # Still have pending changes. Don't bother trying to prune.
return
sparsematch = matcher(repo, includetemp=False)
dirstate = repo.dirstate
actions = []
dropped = []
tempincludes = readtemporaryincludes(repo)
for file in tempincludes:
if file in dirstate and not sparsematch(file):
message = _('dropping temporarily included sparse files')
actions.append((file, None, message))
dropped.append(file)
typeactions = collections.defaultdict(list)
typeactions['r'] = actions
mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
# Fix dirstate
for file in dropped:
dirstate.drop(file)
repo.vfs.unlink('tempsparse')
Gregory Szorc
sparse: inline signature cache clearing...
r33325 repo._sparsesignaturecache.clear()
Gregory Szorc
sparse: move pruning of temporary includes into core...
r33321 msg = _('cleaned up %d temporarily added file(s) from the '
'sparse checkout\n')
repo.ui.status(msg % len(tempincludes))
Martin von Zweigbergk
match: write forceincludematcher using unionmatcher...
r33447 def forceincludematcher(matcher, includes):
"""Returns a matcher that returns true for any of the forced includes
before testing against the actual matcher."""
kindpats = [('path', include, '') for include in includes]
includematcher = matchmod.includematcher('', '', kindpats)
return matchmod.unionmatcher([includematcher, matcher])
Gregory Szorc
sparse: move function for resolving sparse matcher into core...
r33320 def matcher(repo, revs=None, includetemp=True):
"""Obtain a matcher for sparse working directories for the given revs.
If multiple revisions are specified, the matcher is the union of all
revs.
``includetemp`` indicates whether to use the temporary sparse profile.
"""
# If sparse isn't enabled, sparse matcher matches everything.
if not enabled:
return matchmod.always(repo.root, '')
if not revs or revs == [None]:
revs = [repo.changelog.rev(node)
for node in repo.dirstate.parents() if node != nullid]
signature = configsignature(repo, includetemp=includetemp)
key = '%s %s' % (signature, ' '.join(map(pycompat.bytestr, revs)))
result = repo._sparsematchercache.get(key)
if result:
return result
matchers = []
for rev in revs:
try:
includes, excludes, profiles = patternsforrev(repo, rev)
if includes or excludes:
# Explicitly include subdirectories of includes so
# status will walk them down to the actual include.
subdirs = set()
for include in includes:
# TODO consider using posix path functions here so Windows
# \ directory separators don't come into play.
dirname = os.path.dirname(include)
# basename is used to avoid issues with absolute
# paths (which on Windows can include the drive).
while os.path.basename(dirname):
subdirs.add(dirname)
dirname = os.path.dirname(dirname)
matcher = matchmod.match(repo.root, '', [],
include=includes, exclude=excludes,
default='relpath')
if subdirs:
Martin von Zweigbergk
match: write forceincludematcher using unionmatcher...
r33447 matcher = forceincludematcher(matcher, subdirs)
Gregory Szorc
sparse: move function for resolving sparse matcher into core...
r33320 matchers.append(matcher)
except IOError:
pass
if not matchers:
result = matchmod.always(repo.root, '')
elif len(matchers) == 1:
result = matchers[0]
else:
result = matchmod.unionmatcher(matchers)
if includetemp:
tempincludes = readtemporaryincludes(repo)
Martin von Zweigbergk
match: write forceincludematcher using unionmatcher...
r33447 result = forceincludematcher(result, tempincludes)
Gregory Szorc
sparse: move function for resolving sparse matcher into core...
r33320
repo._sparsematchercache[key] = result
return result
Gregory Szorc
sparse: move update action filtering into core...
r33322
Gregory Szorc
sparse: refactor update actions filtering and call from core...
r33323 def filterupdatesactions(repo, wctx, mctx, branchmerge, actions):
"""Filter updates to only lay out files that match the sparse rules."""
if not enabled:
return actions
Gregory Szorc
sparse: move update action filtering into core...
r33322
oldrevs = [pctx.rev() for pctx in wctx.parents()]
oldsparsematch = matcher(repo, oldrevs)
if oldsparsematch.always():
Gregory Szorc
sparse: refactor update actions filtering and call from core...
r33323 return actions
Gregory Szorc
sparse: move update action filtering into core...
r33322
files = set()
prunedactions = {}
if branchmerge:
# If we're merging, use the wctx filter, since we're merging into
# the wctx.
sparsematch = matcher(repo, [wctx.parents()[0].rev()])
else:
# If we're updating, use the target context's filter, since we're
# moving to the target context.
sparsematch = matcher(repo, [mctx.rev()])
temporaryfiles = []
for file, action in actions.iteritems():
type, args, msg = action
files.add(file)
if sparsematch(file):
prunedactions[file] = action
elif type == 'm':
temporaryfiles.append(file)
prunedactions[file] = action
elif branchmerge:
if type != 'k':
temporaryfiles.append(file)
prunedactions[file] = action
elif type == 'f':
prunedactions[file] = action
elif file in wctx:
prunedactions[file] = ('r', args, msg)
if len(temporaryfiles) > 0:
repo.ui.status(_('temporarily included %d file(s) in the sparse '
'checkout for merging\n') % len(temporaryfiles))
addtemporaryincludes(repo, temporaryfiles)
# Add the new files to the working copy so they can be merged, etc
actions = []
message = 'temporarily adding to sparse checkout'
wctxmanifest = repo[None].manifest()
for file in temporaryfiles:
if file in wctxmanifest:
fctx = repo[None][file]
actions.append((file, (fctx.flags(), False), message))
typeactions = collections.defaultdict(list)
typeactions['g'] = actions
mergemod.applyupdates(repo, typeactions, repo[None], repo['.'],
False)
dirstate = repo.dirstate
for file, flags, msg in actions:
dirstate.normal(file)
Gregory Szorc
sparse: refactor activeprofiles into a generic function (API)...
r33370 profiles = activeconfig(repo)[2]
Gregory Szorc
sparse: move update action filtering into core...
r33322 changedprofiles = profiles & files
# If an active profile changed during the update, refresh the checkout.
# Don't do this during a branch merge, since all incoming changes should
# have been handled by the temporary includes above.
if changedprofiles and not branchmerge:
mf = mctx.manifest()
for file in mf:
old = oldsparsematch(file)
new = sparsematch(file)
if not old and new:
flags = mf.flags(file)
prunedactions[file] = ('g', (flags, False), '')
elif old and not new:
prunedactions[file] = ('r', [], '')
Gregory Szorc
sparse: refactor update actions filtering and call from core...
r33323 return prunedactions
Gregory Szorc
sparse: move working directory refreshing into core...
r33324
def refreshwdir(repo, origstatus, origsparsematch, force=False):
"""Refreshes working directory by taking sparse config into account.
The old status and sparse matcher is compared against the current sparse
matcher.
Will abort if a file with pending changes is being excluded or included
unless ``force`` is True.
"""
# Verify there are no pending changes
pending = set()
Martin von Zweigbergk
sparse: access status fields by name instead of deconstructing it...
r33356 pending.update(origstatus.modified)
pending.update(origstatus.added)
pending.update(origstatus.removed)
Gregory Szorc
sparse: move working directory refreshing into core...
r33324 sparsematch = matcher(repo)
abort = False
for f in pending:
if not sparsematch(f):
repo.ui.warn(_("pending changes to '%s'\n") % f)
abort = not force
if abort:
raise error.Abort(_('could not update sparseness due to pending '
'changes'))
# Calculate actions
dirstate = repo.dirstate
ctx = repo['.']
added = []
lookup = []
dropped = []
mf = ctx.manifest()
files = set(mf)
actions = {}
for file in files:
old = origsparsematch(file)
new = sparsematch(file)
# Add files that are newly included, or that don't exist in
# the dirstate yet.
if (new and not old) or (old and new and not file in dirstate):
fl = mf.flags(file)
if repo.wvfs.exists(file):
actions[file] = ('e', (fl,), '')
lookup.append(file)
else:
actions[file] = ('g', (fl, False), '')
added.append(file)
# Drop files that are newly excluded, or that still exist in
# the dirstate.
elif (old and not new) or (not old and not new and file in dirstate):
dropped.append(file)
if file not in pending:
actions[file] = ('r', [], '')
# Verify there are no pending changes in newly included files
abort = False
for file in lookup:
repo.ui.warn(_("pending changes to '%s'\n") % file)
abort = not force
if abort:
raise error.Abort(_('cannot change sparseness due to pending '
'changes (delete the files or use '
'--force to bring them back dirty)'))
# Check for files that were only in the dirstate.
for file, state in dirstate.iteritems():
if not file in files:
old = origsparsematch(file)
new = sparsematch(file)
if old and not new:
dropped.append(file)
# Apply changes to disk
Mark Thomas
merge: add merge action 'p' to record path conflicts during update...
r34548 typeactions = dict((m, [])
Mark Thomas
merge: add merge action 'pr' to rename files during update...
r34549 for m in 'a f g am cd dc r dm dg m e k p pr'.split())
Gregory Szorc
sparse: move working directory refreshing into core...
r33324 for f, (m, args, msg) in actions.iteritems():
if m not in typeactions:
typeactions[m] = []
typeactions[m].append((f, args, msg))
mergemod.applyupdates(repo, typeactions, repo[None], repo['.'], False)
# Fix dirstate
for file in added:
dirstate.normal(file)
for file in dropped:
dirstate.drop(file)
for file in lookup:
# File exists on disk, and we're bringing it back in an unknown state.
dirstate.normallookup(file)
return added, dropped, lookup
Gregory Szorc
sparse: move post commit actions into core...
r33353
def aftercommit(repo, node):
"""Perform actions after a working directory commit."""
# This function is called unconditionally, even if sparse isn't
# enabled.
ctx = repo[node]
profiles = patternsforrev(repo, ctx.rev())[2]
# profiles will only have data if sparse is enabled.
Gregory Szorc
sparse: use set for capturing profiles...
r33550 if profiles & set(ctx.files()):
Gregory Szorc
sparse: move post commit actions into core...
r33353 origstatus = repo.status()
origsparsematch = matcher(repo)
refreshwdir(repo, origstatus, origsparsematch, force=True)
prunetemporaryincludes(repo)
Gregory Szorc
sparse: move code for clearing rules to core...
r33354
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 def _updateconfigandrefreshwdir(repo, includes, excludes, profiles,
Gregory Szorc
sparse: add a requirement when a repository uses sparse (BC)...
r33556 force=False, removing=False):
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 """Update the sparse config and working directory state."""
raw = repo.vfs.tryread('sparse')
oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw)
oldstatus = repo.status()
oldmatch = matcher(repo)
Gregory Szorc
sparse: add a requirement when a repository uses sparse (BC)...
r33556 oldrequires = set(repo.requirements)
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555
# TODO remove this try..except once the matcher integrates better
# with dirstate. We currently have to write the updated config
# because that will invalidate the matcher cache and force a
# re-read. We ideally want to update the cached matcher on the
# repo instance then flush the new config to disk once wdir is
# updated. But this requires massive rework to matcher() and its
# consumers.
Gregory Szorc
sparse: add a requirement when a repository uses sparse (BC)...
r33556 if 'exp-sparse' in oldrequires and removing:
repo.requirements.discard('exp-sparse')
scmutil.writerequires(repo.vfs, repo.requirements)
elif 'exp-sparse' not in oldrequires:
repo.requirements.add('exp-sparse')
scmutil.writerequires(repo.vfs, repo.requirements)
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555
try:
Gregory Szorc
sparse: add a requirement when a repository uses sparse (BC)...
r33556 writeconfig(repo, includes, excludes, profiles)
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 return refreshwdir(repo, oldstatus, oldmatch, force=force)
except Exception:
Gregory Szorc
sparse: add a requirement when a repository uses sparse (BC)...
r33556 if repo.requirements != oldrequires:
repo.requirements.clear()
repo.requirements |= oldrequires
scmutil.writerequires(repo.vfs, repo.requirements)
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 writeconfig(repo, oldincludes, oldexcludes, oldprofiles)
raise
Gregory Szorc
sparse: move code for clearing rules to core...
r33354 def clearrules(repo, force=False):
"""Clears include/exclude rules from the sparse config.
The remaining sparse config only has profiles, if defined. The working
directory is refreshed, as needed.
"""
with repo.wlock():
raw = repo.vfs.tryread('sparse')
includes, excludes, profiles = parseconfig(repo.ui, raw)
if not includes and not excludes:
return
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 _updateconfigandrefreshwdir(repo, set(), set(), profiles, force=force)
Gregory Szorc
sparse: move printing of sparse config changes function into core...
r33355
Gregory Szorc
sparse: move code for importing rules from files into core...
r33371 def importfromfiles(repo, opts, paths, force=False):
"""Import sparse config rules from files.
The updated sparse config is written out and the working directory
is refreshed, as needed.
"""
with repo.wlock():
# read current configuration
raw = repo.vfs.tryread('sparse')
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 includes, excludes, profiles = parseconfig(repo.ui, raw)
Gregory Szorc
sparse: move code for importing rules from files into core...
r33371 aincludes, aexcludes, aprofiles = activeconfig(repo)
# Import rules on top; only take in rules that are not yet
# part of the active rules.
changed = False
for p in paths:
with util.posixfile(util.expandpath(p)) as fh:
raw = fh.read()
iincludes, iexcludes, iprofiles = parseconfig(repo.ui, raw)
oldsize = len(includes) + len(excludes) + len(profiles)
includes.update(iincludes - aincludes)
excludes.update(iexcludes - aexcludes)
Gregory Szorc
sparse: use set for capturing profiles...
r33550 profiles.update(iprofiles - aprofiles)
Gregory Szorc
sparse: move code for importing rules from files into core...
r33371 if len(includes) + len(excludes) + len(profiles) > oldsize:
changed = True
profilecount = includecount = excludecount = 0
fcounts = (0, 0, 0)
if changed:
profilecount = len(profiles - aprofiles)
includecount = len(includes - aincludes)
excludecount = len(excludes - aexcludes)
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 fcounts = map(len, _updateconfigandrefreshwdir(
repo, includes, excludes, profiles, force=force))
Gregory Szorc
sparse: move code for importing rules from files into core...
r33371
printchanges(repo.ui, opts, profilecount, includecount, excludecount,
*fcounts)
Gregory Szorc
sparse: move config updating function into core...
r33374 def updateconfig(repo, pats, opts, include=False, exclude=False, reset=False,
delete=False, enableprofile=False, disableprofile=False,
Kostia Balytskyi
sparse: treat paths as cwd-relative...
r33648 force=False, usereporootpaths=False):
Gregory Szorc
sparse: move config updating function into core...
r33374 """Perform a sparse config update.
Only one of the actions may be performed.
The new config is written out and a working directory refresh is performed.
"""
Gregory Szorc
sparse: clean up updateconfig()...
r33375 with repo.wlock():
Gregory Szorc
sparse: move config updating function into core...
r33374 raw = repo.vfs.tryread('sparse')
Gregory Szorc
sparse: clean up updateconfig()...
r33375 oldinclude, oldexclude, oldprofiles = parseconfig(repo.ui, raw)
Gregory Szorc
sparse: move config updating function into core...
r33374
Gregory Szorc
sparse: shorten try..except block in updateconfig()...
r33376 if reset:
newinclude = set()
newexclude = set()
newprofiles = set()
else:
newinclude = set(oldinclude)
newexclude = set(oldexclude)
newprofiles = set(oldprofiles)
Gregory Szorc
sparse: move config updating function into core...
r33374
Kostia Balytskyi
sparse: properly error out when absolute paths are used...
r33646 if any(os.path.isabs(pat) for pat in pats):
raise error.Abort(_('paths cannot be absolute'))
Kostia Balytskyi
sparse: treat paths as cwd-relative...
r33648 if not usereporootpaths:
# let's treat paths as relative to cwd
root, cwd = repo.root, repo.getcwd()
abspats = []
for kindpat in pats:
kind, pat = matchmod._patsplit(kindpat, None)
if kind in matchmod.cwdrelativepatternkinds or kind is None:
ap = (kind + ':' if kind else '') +\
pathutil.canonpath(root, cwd, pat)
abspats.append(ap)
else:
abspats.append(kindpat)
pats = abspats
Kostia Balytskyi
sparse: properly error out when absolute paths are used...
r33646 if include:
Gregory Szorc
sparse: shorten try..except block in updateconfig()...
r33376 newinclude.update(pats)
elif exclude:
newexclude.update(pats)
elif enableprofile:
newprofiles.update(pats)
elif disableprofile:
newprofiles.difference_update(pats)
elif delete:
newinclude.difference_update(pats)
newexclude.difference_update(pats)
Gregory Szorc
sparse: move config updating function into core...
r33374
Gregory Szorc
sparse: shorten try..except block in updateconfig()...
r33376 profilecount = (len(newprofiles - oldprofiles) -
len(oldprofiles - newprofiles))
includecount = (len(newinclude - oldinclude) -
len(oldinclude - newinclude))
excludecount = (len(newexclude - oldexclude) -
len(oldexclude - newexclude))
Gregory Szorc
sparse: move config updating function into core...
r33374
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 fcounts = map(len, _updateconfigandrefreshwdir(
Gregory Szorc
sparse: add a requirement when a repository uses sparse (BC)...
r33556 repo, newinclude, newexclude, newprofiles, force=force,
removing=reset))
Gregory Szorc
sparse: shorten try..except block in updateconfig()...
r33376
Gregory Szorc
sparse: consolidate common code for writing sparse config...
r33555 printchanges(repo.ui, opts, profilecount, includecount,
excludecount, *fcounts)
Gregory Szorc
sparse: move config updating function into core...
r33374
Gregory Szorc
sparse: move printing of sparse config changes function into core...
r33355 def printchanges(ui, opts, profilecount=0, includecount=0, excludecount=0,
added=0, dropped=0, conflicting=0):
"""Print output summarizing sparse config changes."""
with ui.formatter('sparse', opts) as fm:
fm.startitem()
fm.condwrite(ui.verbose, 'profiles_added', _('Profiles changed: %d\n'),
profilecount)
fm.condwrite(ui.verbose, 'include_rules_added',
_('Include rules changed: %d\n'), includecount)
fm.condwrite(ui.verbose, 'exclude_rules_added',
_('Exclude rules changed: %d\n'), excludecount)
# In 'plain' verbose mode, mergemod.applyupdates already outputs what
# files are added or removed outside of the templating formatter
# framework. No point in repeating ourselves in that case.
if not fm.isplain():
fm.condwrite(ui.verbose, 'files_added', _('Files added: %d\n'),
added)
fm.condwrite(ui.verbose, 'files_dropped', _('Files dropped: %d\n'),
dropped)
fm.condwrite(ui.verbose, 'files_conflicting',
_('Files conflicting: %d\n'), conflicting)