Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions pre_commit/commands/init_templatedir.py
Original file line number Diff line number Diff line change
@@ -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),
)
11 changes: 6 additions & 5 deletions pre_commit/commands/install_uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@
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)


Expand Down Expand Up @@ -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():
Expand All @@ -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))

Expand All @@ -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:
Expand Down
126 changes: 73 additions & 53 deletions pre_commit/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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
Expand Down Expand Up @@ -131,6 +132,51 @@ 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)

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.',
)
Expand Down Expand Up @@ -167,44 +213,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.',
Expand Down Expand Up @@ -241,6 +249,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.',
)
Expand All @@ -265,29 +280,32 @@ def main(argv=None):
store = Store()
store.mark_config_used(args.config)

if args.command == 'install':
return install(
if args.command == 'autoupdate':
if args.tags_only:
logger.warning('--tags-only is the default')
return autoupdate(
args.config, store,
overwrite=args.overwrite, hooks=args.install_hooks,
hook_type=args.hook_type,
skip_on_missing_conf=args.allow_missing_config,
tags_only=not args.bleeding_edge,
repos=args.repos,
)
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(
elif args.command == 'install':
return install(
args.config, store,
tags_only=not args.bleeding_edge,
repos=args.repos,
overwrite=args.overwrite, hooks=args.install_hooks,
hook_type=args.hook_type,
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)
elif args.command == 'migrate-config':
return migrate_config(args.config)
elif args.command == 'run':
Expand All @@ -296,6 +314,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),
Expand Down
49 changes: 49 additions & 0 deletions tests/commands/init_templatedir_test.py
Original file line number Diff line number Diff line change
@@ -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')
6 changes: 3 additions & 3 deletions tests/commands/install_uninstall_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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

Expand Down
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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
Expand Down Expand Up @@ -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
6 changes: 6 additions & 0 deletions tests/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down