From b096c0b8f2074ec5c7e05528b27be4a1bf3df8d7 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Fri, 14 Dec 2018 12:29:52 +0000 Subject: [PATCH] Allow aliasing a hook and calling it by it's alias --- pre_commit/clientlib.py | 18 +++++++++++++++++- pre_commit/commands/run.py | 8 ++++++-- tests/commands/run_test.py | 26 ++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 2fa7b153..0722f5e6 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -29,6 +29,20 @@ def _make_argparser(filenames_help): return parser +class OptionalAlias(object): + + def check(self, dct): + if 'alias' in dct: + cfgv.check_string(dct['alias']) + + def apply_default(self, dct): + if 'alias' not in dct: + dct['alias'] = dct['id'] + + def remove_default(self, dct): + pass + + MANIFEST_HOOK_DICT = cfgv.Map( 'Hook', 'id', @@ -36,6 +50,7 @@ MANIFEST_HOOK_DICT = cfgv.Map( cfgv.Required('name', cfgv.check_string), cfgv.Required('entry', cfgv.check_string), cfgv.Required('language', cfgv.check_one_of(all_languages)), + cfgv.OptionalNoDefault('alias', cfgv.check_string), cfgv.Optional( 'files', cfgv.check_and(cfgv.check_string, cfgv.check_regex), '', @@ -125,6 +140,7 @@ CONFIG_HOOK_DICT = cfgv.Map( 'Hook', 'id', cfgv.Required('id', cfgv.check_string), + OptionalAlias(), # All keys in manifest hook dict are valid in a config hook dict, but # are optional. @@ -133,7 +149,7 @@ CONFIG_HOOK_DICT = cfgv.Map( *[ cfgv.OptionalNoDefault(item.key, item.check_fn) for item in MANIFEST_HOOK_DICT.items - if item.key != 'id' + if item.key not in ('id', 'alias') ] ) CONFIG_REPO_DICT = cfgv.Map( diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py index f2ff7b38..9cd3dfcf 100644 --- a/pre_commit/commands/run.py +++ b/pre_commit/commands/run.py @@ -257,13 +257,17 @@ def run(config_file, store, args, environ=os.environ): for repo in repositories(config, store): for _, hook in repo.hooks: if ( - (not args.hook or hook['id'] == args.hook) and + (not args.hook or hook['id'] == args.hook or ( + hook['alias'] and hook['alias'] == args.hook + )) and (not hook['stages'] or args.hook_stage in hook['stages']) ): repo_hooks.append((repo, hook)) if args.hook and not repo_hooks: - output.write_line('No hook with id `{}`'.format(args.hook)) + output.write_line( + 'No hook with id or alias `{}`'.format(args.hook), + ) return 1 for repo in {repo for repo, _ in repo_hooks}: diff --git a/tests/commands/run_test.py b/tests/commands/run_test.py index bb233f28..1cec51f2 100644 --- a/tests/commands/run_test.py +++ b/tests/commands/run_test.py @@ -416,6 +416,32 @@ def test_multiple_hooks_same_id(cap_out, store, repo_with_passing_hook): assert output.count(b'Bash hook') == 2 +def test_aliased_hook_run(cap_out, store, repo_with_passing_hook): + with cwd(repo_with_passing_hook): + # Add bash hook on there again, aliased + with modify_config() as config: + config['repos'][0]['hooks'].append( + {'id': 'bash_hook', 'alias': 'foo_bash'}, + ) + stage_a_file() + + ret, output = _do_run( + cap_out, store, repo_with_passing_hook, + run_opts(verbose=True, hook='bash_hook'), + ) + assert ret == 0 + # Both hooks will run since they share the same ID + assert output.count(b'Bash hook') == 2 + + ret, output = _do_run( + cap_out, store, repo_with_passing_hook, + run_opts(verbose=True, hook='foo_bash'), + ) + assert ret == 0 + # Only the aliased hook runs + assert output.count(b'Bash hook') == 1 + + def test_non_ascii_hook_id(repo_with_passing_hook, tempdir_factory): with cwd(repo_with_passing_hook): _, stdout, _ = cmd_output_mocked_pre_commit_home(