mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 23:07:18 +03:00
sparse: filter on file containment
Summary: This lets you find profiles that'll include a given filename. Reviewed By: markbt Differential Revision: D7876496 fbshipit-source-id: 3b375e5d8257a80853f854a6be27c74f805687e3
This commit is contained in:
parent
58ac63caab
commit
59b960eade
@ -542,13 +542,14 @@ def _wraprepo(ui, repo):
|
||||
metadata_key_value = re.compile(r'(?P<key>.*)\s*[:=]\s*(?P<value>.*)')
|
||||
|
||||
class SparseRepo(repo.__class__):
|
||||
def readsparseconfig(self, raw, filename=None):
|
||||
def readsparseconfig(self, raw, filename=None, warn=True):
|
||||
"""Takes a string sparse config and returns a SparseConfig
|
||||
|
||||
This object contains the includes, excludes, and profiles from the
|
||||
raw profile.
|
||||
|
||||
The filename is used to report errors and warnings.
|
||||
The filename is used to report errors and warnings, unless warn is
|
||||
set to False.
|
||||
|
||||
"""
|
||||
filename = filename or '<sparse profile>'
|
||||
@ -565,6 +566,7 @@ def _wraprepo(ui, repo):
|
||||
current = includes # no sections == includes
|
||||
|
||||
profiles = []
|
||||
uiwarn = self.ui.warn if warn else (lambda *ignored: None)
|
||||
|
||||
for i, line in enumerate(raw.splitlines(), start=1):
|
||||
stripped = line.strip()
|
||||
@ -591,7 +593,7 @@ def _wraprepo(ui, repo):
|
||||
# Metadata parsing, INI-style format
|
||||
if line.startswith((' ', '\t')): # continuation
|
||||
if last_key is None:
|
||||
self.ui.warn(_(
|
||||
uiwarn(_(
|
||||
'warning: sparse profile [metadata] section '
|
||||
'indented lines that do not belong to a '
|
||||
'multi-line entry, ignoring, in %s:%i\n') % (
|
||||
@ -601,7 +603,7 @@ def _wraprepo(ui, repo):
|
||||
else:
|
||||
match = metadata_key_value.match(stripped)
|
||||
if match is None:
|
||||
self.ui.warn(_(
|
||||
uiwarn(_(
|
||||
'warning: sparse profile [metadata] section '
|
||||
'does not appear to have a valid option '
|
||||
'definition, ignoring, in %s:%i\n') % (
|
||||
@ -964,6 +966,7 @@ def _discover(ui, repo, rev=None):
|
||||
|
||||
profile_directory = ui.config('sparse', 'profile_directory')
|
||||
available = set()
|
||||
ctx = scmutil.revsingle(repo, rev)
|
||||
if profile_directory is not None:
|
||||
if (os.path.isabs(profile_directory) or
|
||||
profile_directory.startswith('../')):
|
||||
@ -973,7 +976,6 @@ def _discover(ui, repo, rev=None):
|
||||
if not profile_directory.endswith('/'):
|
||||
profile_directory += '/'
|
||||
|
||||
ctx = scmutil.revsingle(repo, rev)
|
||||
mf = ctx.manifest()
|
||||
|
||||
matcher = matchmod.match(
|
||||
@ -985,7 +987,7 @@ def _discover(ui, repo, rev=None):
|
||||
# sort profiles and read profile metadata as we iterate
|
||||
for p in sorted(available | included):
|
||||
try:
|
||||
raw = repo.getrawprofile(p, rev)
|
||||
raw = repo.getrawprofile(p, ctx.hex())
|
||||
except error.ManifestLookupError:
|
||||
# ignore a missing profile; this should only happen for 'included'
|
||||
# profiles, however. repo.getactiveprofiles() above will already
|
||||
@ -1111,12 +1113,12 @@ def hintexplainverbose(*profiles):
|
||||
"size for a give profile") % ' '.join(profiles)
|
||||
|
||||
@hint('sparse-list-verbose')
|
||||
def hintlistverbose(profiles, filters):
|
||||
def hintlistverbose(profiles, filters, load_matcher):
|
||||
# move the hidden flag from the without to the with pile and count
|
||||
# the matches
|
||||
filters['with'].add('hidden')
|
||||
filters['without'].remove('hidden')
|
||||
pred = _build_profile_filter(filters)
|
||||
pred = _build_profile_filter(filters, load_matcher)
|
||||
hidden_count = sum(1 for p in filter(pred, profiles))
|
||||
if hidden_count:
|
||||
return _("%d hidden profiles not shown; "
|
||||
@ -1223,7 +1225,11 @@ def sparse(ui, repo, *pats, **opts):
|
||||
|
||||
subcmd = sparse.subcommand()
|
||||
|
||||
def _build_profile_filter(filters):
|
||||
def _contains_files(load_matcher, profile, files):
|
||||
matcher = load_matcher(profile)
|
||||
return all(matcher(f) for f in files)
|
||||
|
||||
def _build_profile_filter(filters, load_matcher):
|
||||
"""Create a callable function to filter a profile, returning a boolean"""
|
||||
predicates = {
|
||||
# we need *all* fields in a with filter to be present, so with
|
||||
@ -1235,8 +1241,9 @@ def _build_profile_filter(filters):
|
||||
'filter': lambda field_values: lambda md: (
|
||||
# all fields to test and all values for those fields are present
|
||||
all(f in md and all(v in md[f].lower() for v in vs)
|
||||
for f, vs in field_values.items())
|
||||
)
|
||||
for f, vs in field_values.items())),
|
||||
'contains_file': lambda files: lambda md: _contains_files(
|
||||
load_matcher, md['path'], files),
|
||||
}
|
||||
tests = [predicates[k](v) for k, v in sorted(filters.items()) if v]
|
||||
# pass in a dictionary with all metadata and the path as an extra key
|
||||
@ -1255,7 +1262,10 @@ def _build_profile_filter(filters):
|
||||
('', 'filter', [],
|
||||
_('Only show profiles that contain the given value as a substring in a '
|
||||
'specific metadata field.'),
|
||||
_('FIELD:VALUE'))
|
||||
_('FIELD:VALUE')),
|
||||
('', 'contains-file', [],
|
||||
_('Only show profiles that would include the named file if enabled.'),
|
||||
_('FILE')),
|
||||
] + commands.templateopts,
|
||||
'[OPTION]...')
|
||||
def _listprofiles(ui, repo, *pats, **opts):
|
||||
@ -1264,10 +1274,10 @@ def _listprofiles(ui, repo, *pats, **opts):
|
||||
Show all available sparse profiles, with the active profiles marked.
|
||||
|
||||
You can filter profiles with `--with-field [FIELD]`, `--without-field
|
||||
[FIELD]` and `--filter [FIELD:VALUE]`; you can specify these options more
|
||||
than once to set multiple criteria, which all must match for a profile to be
|
||||
listed. The field `path` is always available, and is the path of the profile
|
||||
file in the repository.
|
||||
[FIELD]`, `--filter [FIELD:VALUE]` and `--contains-file [FILENAME]`; you can
|
||||
specify these options more than once to set multiple criteria, which all
|
||||
must match for a profile to be listed. The field `path` is always available,
|
||||
and is the path of the profile file in the repository.
|
||||
|
||||
`--filter` takes a fieldname and value to look for, separated by a colon.
|
||||
The field must be present in the metadata, and the value present in the
|
||||
@ -1276,14 +1286,21 @@ def _listprofiles(ui, repo, *pats, **opts):
|
||||
e.g. --filter path:foo --filter path:bar only matches profile paths with the
|
||||
substrings foo and bar both present.
|
||||
|
||||
`--contains-file` takes a file path relative to the current directory. No
|
||||
check is made if the file actually exists; any profile that would include
|
||||
the file if it did exist will match.
|
||||
|
||||
By default, `--without-field hidden` is implied unless you use the --verbose
|
||||
switch to include hidden profiles.
|
||||
|
||||
"""
|
||||
rev = scmutil.revsingle(repo, opts.get('rev')).hex()
|
||||
tocanon = functools.partial(pathutil.canonpath, repo.root, repo.getcwd())
|
||||
filters = {
|
||||
'with': set(opts.get('with_field', ())),
|
||||
'without': set(opts.get('without_field', ())),
|
||||
'filter': {}, # dictionary of fieldnames to sets of values
|
||||
'contains_file': {tocanon(f) for f in opts.get('contains_file', ())},
|
||||
}
|
||||
for fieldvalue in opts.get('filter', ()):
|
||||
fieldname, __, value = fieldvalue.partition(':')
|
||||
@ -1317,7 +1334,11 @@ def _listprofiles(ui, repo, *pats, **opts):
|
||||
'included\n'),
|
||||
label='sparse.profile.legend')
|
||||
|
||||
predicate = _build_profile_filter(filters)
|
||||
load_matcher = lambda p: repo.sparsematch(
|
||||
rev=rev, config=repo.readsparseconfig(
|
||||
repo.getrawprofile(p, rev), filename=p, warn=False))
|
||||
|
||||
predicate = _build_profile_filter(filters, load_matcher)
|
||||
profiles = list(_discover(ui, repo, rev=opts.get('rev')))
|
||||
filtered = list(filter(predicate, profiles))
|
||||
max_width = 0
|
||||
@ -1339,7 +1360,7 @@ def _listprofiles(ui, repo, *pats, **opts):
|
||||
fm.plain('\n')
|
||||
|
||||
if not (ui.verbose or 'hidden' in filters['with']):
|
||||
hintutil.trigger('sparse-list-verbose', profiles, filters)
|
||||
hintutil.trigger('sparse-list-verbose', profiles, filters, load_matcher)
|
||||
|
||||
@subcmd('explain', [
|
||||
('r', 'rev', '', _('explain the profile(s) against the specified revision'),
|
||||
|
@ -478,6 +478,7 @@ Naming the same field in without- and with- filters is an error:
|
||||
$ hg sparse list --with-field bar --without-field bar
|
||||
abort: You can't specify fields in both --with-field and --without-field, please use only one or the other, for bar
|
||||
[255]
|
||||
|
||||
We can filter on the contents of a field or the path, case-insensitively:
|
||||
|
||||
$ hg sparse list --filter path:/bar/ --filter title:profile
|
||||
@ -485,6 +486,13 @@ We can filter on the contents of a field or the path, case-insensitively:
|
||||
~ profiles/bar/eggs - Profile including the profiles directory
|
||||
profiles/bar/ham - An extended profile including some interesting files
|
||||
|
||||
We can filter on specific files being included in a sparse profile:
|
||||
|
||||
$ hg sparse list --contains-file interesting/sizeable
|
||||
symbols: * = active profile, ~ = transitively included
|
||||
profiles/bar/ham - An extended profile including some interesting files
|
||||
profiles/bar/python
|
||||
|
||||
You can specify a revision to list profiles for; in this case the current
|
||||
sparse configuration is ignored; no profile can be 'active' or 'included':
|
||||
|
||||
@ -711,6 +719,20 @@ current working copy:
|
||||
9
|
||||
$ hg sparse explain profiles/bar/ham -T "{stats.filecount}\n" -r .
|
||||
10
|
||||
$ hg sparse list --contains-file interesting/later_revision -r ".^"
|
||||
symbols: * = active profile, ~ = transitively included
|
||||
warning: sparse profile [metadata] section indented lines that do not belong to a multi-line entry, ignoring, in profiles/foo/errors:2
|
||||
warning: sparse profile [metadata] section does not appear to have a valid option definition, ignoring, in profiles/foo/errors:3
|
||||
profiles/bar/ham - An extended profile including some interesting files
|
||||
profiles/bar/python
|
||||
profiles/foo/errors
|
||||
$ hg sparse list --contains-file interesting/later_revision -r .
|
||||
symbols: * = active profile, ~ = transitively included
|
||||
warning: sparse profile [metadata] section indented lines that do not belong to a multi-line entry, ignoring, in profiles/foo/errors:2
|
||||
warning: sparse profile [metadata] section does not appear to have a valid option definition, ignoring, in profiles/foo/errors:3
|
||||
profiles/bar/ham - An extended profile including some interesting files
|
||||
profiles/bar/python
|
||||
profiles/foo/errors
|
||||
$ hg up -q ".^"
|
||||
|
||||
We can list the files in a profile with the hg sparse files command:
|
||||
|
Loading…
Reference in New Issue
Block a user