From 3def940574f1812d1d5627ec0d7deeb07fb21a27 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 20 Jul 2019 16:24:10 -0700 Subject: [PATCH 1/2] reorder pre-commit sub commands --- pre_commit/main.py | 106 ++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/pre_commit/main.py b/pre_commit/main.py index aa7ff2a7..d5c488f8 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -131,6 +131,37 @@ def main(argv=None): subparsers = parser.add_subparsers(dest='command') + autoupdate_parser = subparsers.add_parser( + 'autoupdate', + help="Auto-update pre-commit config to the latest repos' versions.", + ) + _add_color_option(autoupdate_parser) + _add_config_option(autoupdate_parser) + autoupdate_parser.add_argument( + '--tags-only', action='store_true', help='LEGACY: for compatibility', + ) + autoupdate_parser.add_argument( + '--bleeding-edge', action='store_true', + help=( + 'Update to the bleeding edge of `master` instead of the latest ' + 'tagged version (the default behavior).' + ), + ) + autoupdate_parser.add_argument( + '--repo', dest='repos', action='append', metavar='REPO', + help='Only update this repository -- may be specified multiple times.', + ) + + clean_parser = subparsers.add_parser( + 'clean', help='Clean out pre-commit files.', + ) + _add_color_option(clean_parser) + _add_config_option(clean_parser) + + gc_parser = subparsers.add_parser('gc', help='Clean unused cached repos.') + _add_color_option(gc_parser) + _add_config_option(gc_parser) + install_parser = subparsers.add_parser( 'install', help='Install the pre-commit script.', ) @@ -167,44 +198,6 @@ def main(argv=None): _add_color_option(install_hooks_parser) _add_config_option(install_hooks_parser) - uninstall_parser = subparsers.add_parser( - 'uninstall', help='Uninstall the pre-commit script.', - ) - _add_color_option(uninstall_parser) - _add_config_option(uninstall_parser) - _add_hook_type_option(uninstall_parser) - - clean_parser = subparsers.add_parser( - 'clean', help='Clean out pre-commit files.', - ) - _add_color_option(clean_parser) - _add_config_option(clean_parser) - - gc_parser = subparsers.add_parser('gc', help='Clean unused cached repos.') - _add_color_option(gc_parser) - _add_config_option(gc_parser) - - autoupdate_parser = subparsers.add_parser( - 'autoupdate', - help="Auto-update pre-commit config to the latest repos' versions.", - ) - _add_color_option(autoupdate_parser) - _add_config_option(autoupdate_parser) - autoupdate_parser.add_argument( - '--tags-only', action='store_true', help='LEGACY: for compatibility', - ) - autoupdate_parser.add_argument( - '--bleeding-edge', action='store_true', - help=( - 'Update to the bleeding edge of `master` instead of the latest ' - 'tagged version (the default behavior).' - ), - ) - autoupdate_parser.add_argument( - '--repo', dest='repos', action='append', metavar='REPO', - help='Only update this repository -- may be specified multiple times.', - ) - migrate_config_parser = subparsers.add_parser( 'migrate-config', help='Migrate list configuration to new map configuration.', @@ -241,6 +234,13 @@ def main(argv=None): ) _add_run_options(try_repo_parser) + uninstall_parser = subparsers.add_parser( + 'uninstall', help='Uninstall the pre-commit script.', + ) + _add_color_option(uninstall_parser) + _add_config_option(uninstall_parser) + _add_hook_type_option(uninstall_parser) + help = subparsers.add_parser( 'help', help='Show help for a specific command.', ) @@ -265,7 +265,19 @@ def main(argv=None): store = Store() store.mark_config_used(args.config) - if args.command == 'install': + if args.command == 'autoupdate': + if args.tags_only: + logger.warning('--tags-only is the default') + return autoupdate( + args.config, store, + tags_only=not args.bleeding_edge, + repos=args.repos, + ) + elif args.command == 'clean': + return clean(store) + elif args.command == 'gc': + return gc(store) + elif args.command == 'install': return install( args.config, store, overwrite=args.overwrite, hooks=args.install_hooks, @@ -274,20 +286,6 @@ def main(argv=None): ) elif args.command == 'install-hooks': return install_hooks(args.config, store) - elif args.command == 'uninstall': - return uninstall(hook_type=args.hook_type) - elif args.command == 'clean': - return clean(store) - elif args.command == 'gc': - return gc(store) - elif args.command == 'autoupdate': - if args.tags_only: - logger.warning('--tags-only is the default') - return autoupdate( - args.config, store, - tags_only=not args.bleeding_edge, - repos=args.repos, - ) elif args.command == 'migrate-config': return migrate_config(args.config) elif args.command == 'run': @@ -296,6 +294,8 @@ def main(argv=None): return sample_config() elif args.command == 'try-repo': return try_repo(args) + elif args.command == 'uninstall': + return uninstall(hook_type=args.hook_type) else: raise NotImplementedError( 'Command {} not implemented.'.format(args.command), From 9a52eefc99d3d9a392110249a6b938b510a66410 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 20 Jul 2019 19:10:50 -0700 Subject: [PATCH 2/2] Implement `pre-commit init-templatedir` --- pre_commit/commands/init_templatedir.py | 21 ++++++++++ pre_commit/commands/install_uninstall.py | 11 +++--- pre_commit/main.py | 22 ++++++++++- tests/commands/init_templatedir_test.py | 49 ++++++++++++++++++++++++ tests/commands/install_uninstall_test.py | 6 +-- tests/conftest.py | 8 ++++ tests/main_test.py | 6 +++ 7 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 pre_commit/commands/init_templatedir.py create mode 100644 tests/commands/init_templatedir_test.py diff --git a/pre_commit/commands/init_templatedir.py b/pre_commit/commands/init_templatedir.py new file mode 100644 index 00000000..c1b95621 --- /dev/null +++ b/pre_commit/commands/init_templatedir.py @@ -0,0 +1,21 @@ +import logging +import os.path + +from pre_commit.commands.install_uninstall import install +from pre_commit.util import cmd_output + +logger = logging.getLogger('pre_commit') + + +def init_templatedir(config_file, store, directory, hook_type): + install( + config_file, store, overwrite=True, hook_type=hook_type, + skip_on_missing_config=True, git_dir=directory, + ) + _, out, _ = cmd_output('git', 'config', 'init.templateDir', retcode=None) + dest = os.path.realpath(directory) + if os.path.realpath(out.strip()) != dest: + logger.warning('`init.templateDir` not set to the target directory') + logger.warning( + 'maybe `git config --global init.templateDir {}`?'.format(dest), + ) diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index 701afccb..9b2c3b80 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -34,8 +34,9 @@ TEMPLATE_START = '# start templated\n' TEMPLATE_END = '# end templated\n' -def _hook_paths(hook_type): - pth = os.path.join(git.get_git_dir(), 'hooks', hook_type) +def _hook_paths(hook_type, git_dir=None): + git_dir = git_dir if git_dir is not None else git.get_git_dir() + pth = os.path.join(git_dir, 'hooks', hook_type) return pth, '{}.legacy'.format(pth) @@ -69,7 +70,7 @@ def shebang(): def install( config_file, store, overwrite=False, hooks=False, hook_type='pre-commit', - skip_on_missing_conf=False, + skip_on_missing_config=False, git_dir=None, ): """Install the pre-commit hooks.""" if cmd_output('git', 'config', 'core.hooksPath', retcode=None)[1].strip(): @@ -79,7 +80,7 @@ def install( ) return 1 - hook_path, legacy_path = _hook_paths(hook_type) + hook_path, legacy_path = _hook_paths(hook_type, git_dir=git_dir) mkdirp(os.path.dirname(hook_path)) @@ -100,7 +101,7 @@ def install( 'CONFIG': config_file, 'HOOK_TYPE': hook_type, 'INSTALL_PYTHON': sys.executable, - 'SKIP_ON_MISSING_CONFIG': skip_on_missing_conf, + 'SKIP_ON_MISSING_CONFIG': skip_on_missing_config, } with io.open(hook_path, 'w') as hook_file: diff --git a/pre_commit/main.py b/pre_commit/main.py index d5c488f8..67a67a05 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -12,6 +12,7 @@ from pre_commit import git from pre_commit.commands.autoupdate import autoupdate from pre_commit.commands.clean import clean from pre_commit.commands.gc import gc +from pre_commit.commands.init_templatedir import init_templatedir from pre_commit.commands.install_uninstall import install from pre_commit.commands.install_uninstall import install_hooks from pre_commit.commands.install_uninstall import uninstall @@ -162,6 +163,20 @@ def main(argv=None): _add_color_option(gc_parser) _add_config_option(gc_parser) + init_templatedir_parser = subparsers.add_parser( + 'init-templatedir', + help=( + 'Install hook script in a directory intended for use with ' + '`git config init.templateDir`.' + ), + ) + _add_color_option(init_templatedir_parser) + _add_config_option(init_templatedir_parser) + init_templatedir_parser.add_argument( + 'directory', help='The directory in which to write the hook script.', + ) + _add_hook_type_option(init_templatedir_parser) + install_parser = subparsers.add_parser( 'install', help='Install the pre-commit script.', ) @@ -282,7 +297,12 @@ def main(argv=None): args.config, store, overwrite=args.overwrite, hooks=args.install_hooks, hook_type=args.hook_type, - skip_on_missing_conf=args.allow_missing_config, + skip_on_missing_config=args.allow_missing_config, + ) + elif args.command == 'init-templatedir': + return init_templatedir( + args.config, store, + args.directory, hook_type=args.hook_type, ) elif args.command == 'install-hooks': return install_hooks(args.config, store) diff --git a/tests/commands/init_templatedir_test.py b/tests/commands/init_templatedir_test.py new file mode 100644 index 00000000..2910ac9e --- /dev/null +++ b/tests/commands/init_templatedir_test.py @@ -0,0 +1,49 @@ +import subprocess + +import pre_commit.constants as C +from pre_commit.commands.init_templatedir import init_templatedir +from pre_commit.envcontext import envcontext +from pre_commit.util import cmd_output +from testing.fixtures import git_dir +from testing.fixtures import make_consuming_repo +from testing.util import cmd_output_mocked_pre_commit_home +from testing.util import cwd +from testing.util import git_commit + + +def test_init_templatedir(tmpdir, tempdir_factory, store, cap_out): + target = str(tmpdir.join('tmpl')) + init_templatedir(C.CONFIG_FILE, store, target, hook_type='pre-commit') + lines = cap_out.get().splitlines() + assert lines[0].startswith('pre-commit installed at ') + assert lines[1] == ( + '[WARNING] `init.templateDir` not set to the target directory' + ) + assert lines[2].startswith( + '[WARNING] maybe `git config --global init.templateDir', + ) + + with envcontext([('GIT_TEMPLATE_DIR', target)]): + path = make_consuming_repo(tempdir_factory, 'script_hooks_repo') + + with cwd(path): + retcode, output, _ = git_commit( + fn=cmd_output_mocked_pre_commit_home, + tempdir_factory=tempdir_factory, + # git commit puts pre-commit to stderr + stderr=subprocess.STDOUT, + ) + assert retcode == 0 + assert 'Bash hook....' in output + + +def test_init_templatedir_already_set(tmpdir, tempdir_factory, store, cap_out): + target = str(tmpdir.join('tmpl')) + tmp_git_dir = git_dir(tempdir_factory) + with cwd(tmp_git_dir): + cmd_output('git', 'config', 'init.templateDir', target) + init_templatedir(C.CONFIG_FILE, store, target, hook_type='pre-commit') + + lines = cap_out.get().splitlines() + assert len(lines) == 1 + assert lines[0].startswith('pre-commit installed at') diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index 5fdf9499..913bf74e 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -735,7 +735,7 @@ def test_install_disallow_missing_config(tempdir_factory, store): with cwd(path): remove_config_from_repo(path) ret = install( - C.CONFIG_FILE, store, overwrite=True, skip_on_missing_conf=False, + C.CONFIG_FILE, store, overwrite=True, skip_on_missing_config=False, ) assert ret == 0 @@ -748,7 +748,7 @@ def test_install_allow_missing_config(tempdir_factory, store): with cwd(path): remove_config_from_repo(path) ret = install( - C.CONFIG_FILE, store, overwrite=True, skip_on_missing_conf=True, + C.CONFIG_FILE, store, overwrite=True, skip_on_missing_config=True, ) assert ret == 0 @@ -766,7 +766,7 @@ def test_install_temporarily_allow_mising_config(tempdir_factory, store): with cwd(path): remove_config_from_repo(path) ret = install( - C.CONFIG_FILE, store, overwrite=True, skip_on_missing_conf=False, + C.CONFIG_FILE, store, overwrite=True, skip_on_missing_config=False, ) assert ret == 0 diff --git a/tests/conftest.py b/tests/conftest.py index 23ff7460..635ea39a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ import pytest import six from pre_commit import output +from pre_commit.envcontext import envcontext from pre_commit.logging_handler import logging_handler from pre_commit.store import Store from pre_commit.util import cmd_output @@ -272,3 +273,10 @@ def fake_log_handler(): logger.addHandler(handler) yield handler logger.removeHandler(handler) + + +@pytest.fixture(scope='session', autouse=True) +def set_git_templatedir(tmpdir_factory): + tdir = str(tmpdir_factory.mktemp('git_template_dir')) + with envcontext([('GIT_TEMPLATE_DIR', tdir)]): + yield diff --git a/tests/main_test.py b/tests/main_test.py index e5573b88..75fd5600 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -140,6 +140,12 @@ def test_try_repo(mock_store_dir): assert patch.call_count == 1 +def test_init_templatedir(mock_store_dir): + with mock.patch.object(main, 'init_templatedir') as patch: + main.main(('init-templatedir', 'tdir')) + assert patch.call_count == 1 + + def test_help_cmd_in_empty_directory( in_tmpdir, mock_commands,