diff --git a/hgext3rd/fbsparse.py b/hgext3rd/fbsparse.py --- a/hgext3rd/fbsparse.py +++ b/hgext3rd/fbsparse.py @@ -655,6 +655,55 @@ repo.signaturecache = {} repo.__class__ = SparseRepo +ProfileInfo = collections.namedtuple('ProfileInfo', 'path active') +# A profile is either active, inacive or included; the latter is a profile +# included (transitively) by an active profile. +PROFILE_INACTIVE, PROFILE_ACTIVE, PROFILE_INCLUDED = range(3) + +def _discover(ui, repo): + """Produce a list of available profiles with metadata + + Returns a list of ProfileInfo objects, paths are relative to the + repository root, the list is sorted by path. + + If no sparse.profile_directory path is configured, will only + list active and included profiles. + + README(.*) files are filtered out. + + """ + included = repo.getactiveprofiles() + sparse = repo.vfs.read('sparse') + _, _, active = repo.readsparseconfig(sparse) + active = set(active) + + profile_directory = ui.config('sparse', 'profile_directory') + available = set() + if profile_directory is not None: + root = repo.root + if not os.path.isabs(profile_directory): + profile_directory = os.path.join(root, profile_directory) + for path, dirs, files in os.walk(profile_directory): + dirpath = os.path.relpath(path, root) + available.update( + os.path.join(dirpath, fname) for fname in files + if not fname.startswith('README.') and fname != 'README') + + return [ProfileInfo(p, ( + PROFILE_ACTIVE if p in active else + PROFILE_INCLUDED if p in included else + PROFILE_INACTIVE)) + for p in sorted(available | included)] + +def _listprofiles(ui, repo, opts): + charmap = {PROFILE_INACTIVE: '', PROFILE_INCLUDED: '~', PROFILE_ACTIVE: '#'} + with ui.formatter('sparse', opts) as fm: + for info in _discover(ui, repo): + fm.startitem() + fm.write(b'active', '%1s ', charmap[info.active]) + fm.write(b'path', '%s', info.path) + fm.plain('\n') + @command('^sparse', [ ('I', 'include', False, _('include files in the sparse checkout')), ('X', 'exclude', False, _('exclude files in the sparse checkout')), @@ -668,6 +717,7 @@ ('', 'reset', False, _('makes the repo full again')), ('', 'cwd-list', False, _('list the full contents of the current ' 'directory')), + ('', 'list-profiles', False, _('list available profiles')), ] + commands.templateopts, _('[--OPTION] PATTERN...')) def sparse(ui, repo, *pats, **opts): @@ -713,6 +763,10 @@ are excluded by the current sparse checkout are annotated with a hyphen ('-') before the name. + --list-profiles lists all available profiles, indicating which ones are + currently active. Activated profiles are marked with a *, profiles + included transitively are marked with a ~. + The following config option defines whether sparse treats supplied paths as relative to repo root or to the current working dir for include and exclude options: @@ -727,6 +781,15 @@ [sparse] enablereporootpaths = on + You can configure a path to find sparse profiles in; this path is + used to discover available sparse profiles. Nested directories are + reflected in the UI. + + [sparse] + profile_directory = tools/scm/sparse + + It is not set by default. + Returns 0 if editing the sparse checkout succeeds. """ include = opts.get('include') @@ -740,8 +803,10 @@ refresh = opts.get('refresh') reset = opts.get('reset') cwdlist = opts.get('cwd_list') + listprofiles = opts.get('list_profiles') count = sum([include, exclude, enableprofile, disableprofile, delete, - importrules, refresh, clearrules, reset, cwdlist]) + importrules, refresh, clearrules, reset, cwdlist, + listprofiles]) if count > 1: raise error.Abort(_("too many flags specified")) @@ -780,6 +845,9 @@ if cwdlist: _cwdlist(repo) + if listprofiles: + _listprofiles(ui, repo, opts) + def _config(ui, repo, pats, opts, include=False, exclude=False, reset=False, delete=False, enableprofile=False, disableprofile=False, force=False): diff --git a/tests/test-sparse-profiles.t b/tests/test-sparse-profiles.t --- a/tests/test-sparse-profiles.t +++ b/tests/test-sparse-profiles.t @@ -271,3 +271,68 @@ $ ls -l b -rwxr-xr-x* b (glob) + $ cd .. + +Test profile discovery + $ hg init sparseprofiles + $ cd sparseprofiles + $ cat > .hg/hgrc < [extensions] + > sparse=$TESTDIR/../hgext3rd/fbsparse.py + > EOF + $ mkdir -p profiles/foo profiles/bar + $ touch profiles/README.txt + $ cat > profiles/foo/spam < %include profiles/bar/eggs + > EOF + $ cat > profiles/bar/eggs < [include] + > profiles + > EOF + $ touch profiles/foo/monty + $ touch profiles/bar/python + $ hg add -q profiles + $ hg commit -qm 'created profiles' + $ hg sparse --enable-profile profiles/foo/spam + $ hg sparse --list-profiles + ~ profiles/bar/eggs + # profiles/foo/spam + $ hg sparse --list-profiles -T json + [ + { + "active": "~", + "path": "profiles/bar/eggs" + }, + { + "active": "#", + "path": "profiles/foo/spam" + } + ] + $ cat >> .hg/hgrc < [sparse] + > profile_directory = profiles/ + > EOF + $ hg sparse --list-profiles + ~ profiles/bar/eggs + profiles/bar/python + profiles/foo/monty + # profiles/foo/spam + $ hg sparse --list-profiles -T json + [ + { + "active": "~", + "path": "profiles/bar/eggs" + }, + { + "active": "", + "path": "profiles/bar/python" + }, + { + "active": "", + "path": "profiles/foo/monty" + }, + { + "active": "#", + "path": "profiles/foo/spam" + } + ]