diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2020-03-24 21:59:15 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2020-03-24 21:59:15 +0000 |
commit | 63fad53303381388673073de580a32088a4ef0fe (patch) | |
tree | a2c5c329ee5e79a220fac7e079283235fecc0cda /pre_commit/main.py | |
parent | Initial commit. (diff) | |
download | pre-commit-63fad53303381388673073de580a32088a4ef0fe.tar.xz pre-commit-63fad53303381388673073de580a32088a4ef0fe.zip |
Adding upstream version 2.2.0.upstream/2.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pre_commit/main.py')
-rw-r--r-- | pre_commit/main.py | 410 |
1 files changed, 410 insertions, 0 deletions
diff --git a/pre_commit/main.py b/pre_commit/main.py new file mode 100644 index 0000000..790b347 --- /dev/null +++ b/pre_commit/main.py @@ -0,0 +1,410 @@ +import argparse +import logging +import os +import sys +from typing import Any +from typing import Optional +from typing import Sequence +from typing import Union + +import pre_commit.constants as C +from pre_commit import color +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.hook_impl import hook_impl +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 +from pre_commit.commands.migrate_config import migrate_config +from pre_commit.commands.run import run +from pre_commit.commands.sample_config import sample_config +from pre_commit.commands.try_repo import try_repo +from pre_commit.error_handler import error_handler +from pre_commit.error_handler import FatalError +from pre_commit.logging_handler import logging_handler +from pre_commit.store import Store +from pre_commit.util import CalledProcessError + + +logger = logging.getLogger('pre_commit') + +# https://github.com/pre-commit/pre-commit/issues/217 +# On OSX, making a virtualenv using pyvenv at . causes `virtualenv` and `pip` +# to install packages to the wrong place. We don't want anything to deal with +# pyvenv +os.environ.pop('__PYVENV_LAUNCHER__', None) + + +COMMANDS_NO_GIT = {'clean', 'gc', 'init-templatedir', 'sample-config'} + + +def _add_color_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + '--color', default=os.environ.get('PRE_COMMIT_COLOR', 'auto'), + type=color.use_color, + metavar='{' + ','.join(color.COLOR_CHOICES) + '}', + help='Whether to use color in output. Defaults to `%(default)s`.', + ) + + +def _add_config_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + '-c', '--config', default=C.CONFIG_FILE, + help='Path to alternate config file', + ) + + +class AppendReplaceDefault(argparse.Action): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.appended = False + + def __call__( + self, + parser: argparse.ArgumentParser, + namespace: argparse.Namespace, + values: Union[str, Sequence[str], None], + option_string: Optional[str] = None, + ) -> None: + if not self.appended: + setattr(namespace, self.dest, []) + self.appended = True + getattr(namespace, self.dest).append(values) + + +def _add_hook_type_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + '-t', '--hook-type', choices=( + 'pre-commit', 'pre-merge-commit', 'pre-push', + 'prepare-commit-msg', 'commit-msg', 'post-checkout', + ), + action=AppendReplaceDefault, + default=['pre-commit'], + dest='hook_types', + ) + + +def _add_run_options(parser: argparse.ArgumentParser) -> None: + parser.add_argument('hook', nargs='?', help='A single hook-id to run') + parser.add_argument('--verbose', '-v', action='store_true', default=False) + mutex_group = parser.add_mutually_exclusive_group(required=False) + mutex_group.add_argument( + '--all-files', '-a', action='store_true', default=False, + help='Run on all the files in the repo.', + ) + mutex_group.add_argument( + '--files', nargs='*', default=[], + help='Specific filenames to run hooks on.', + ) + parser.add_argument( + '--show-diff-on-failure', action='store_true', + help='When hooks fail, run `git diff` directly afterward.', + ) + parser.add_argument( + '--hook-stage', choices=C.STAGES, default='commit', + help='The stage during which the hook is fired. One of %(choices)s', + ) + parser.add_argument( + '--from-ref', '--source', '-s', + help=( + '(for usage with `--from-ref`) -- this option represents the ' + 'original ref in a `from_ref...to_ref` diff expression. ' + 'For `pre-push` hooks, this represents the branch you are pushing ' + 'to. ' + 'For `post-checkout` hooks, this represents the branch that was ' + 'previously checked out.' + ), + ) + parser.add_argument( + '--to-ref', '--origin', '-o', + help=( + '(for usage with `--to-ref`) -- this option represents the ' + 'destination ref in a `from_ref...to_ref` diff expression. ' + 'For `pre-push` hooks, this represents the branch being pushed. ' + 'For `post-checkout` hooks, this represents the branch that is ' + 'now checked out.' + ), + ) + parser.add_argument( + '--commit-msg-filename', + help='Filename to check when running during `commit-msg`', + ) + parser.add_argument( + '--remote-name', help='Remote name used by `git push`.', + ) + parser.add_argument('--remote-url', help='Remote url used by `git push`.') + parser.add_argument( + '--checkout-type', + help=( + 'Indicates whether the checkout was a branch checkout ' + '(changing branches, flag=1) or a file checkout (retrieving a ' + 'file from the index, flag=0).' + ), + ) + + +def _adjust_args_and_chdir(args: argparse.Namespace) -> None: + # `--config` was specified relative to the non-root working directory + if os.path.exists(args.config): + args.config = os.path.abspath(args.config) + if args.command in {'run', 'try-repo'}: + args.files = [os.path.abspath(filename) for filename in args.files] + if args.command == 'try-repo' and os.path.exists(args.repo): + args.repo = os.path.abspath(args.repo) + + try: + toplevel = git.get_root() + except CalledProcessError: + raise FatalError( + 'git failed. Is it installed, and are you in a Git repository ' + 'directory?', + ) + else: + if toplevel == '': # pragma: no cover (old git) + raise FatalError( + 'git toplevel unexpectedly empty! make sure you are not ' + 'inside the `.git` directory of your repository.', + ) + else: + os.chdir(toplevel) + + args.config = os.path.relpath(args.config) + if args.command in {'run', 'try-repo'}: + args.files = [os.path.relpath(filename) for filename in args.files] + if args.command == 'try-repo' and os.path.exists(args.repo): + args.repo = os.path.relpath(args.repo) + + +def main(argv: Optional[Sequence[str]] = None) -> int: + argv = argv if argv is not None else sys.argv[1:] + parser = argparse.ArgumentParser(prog='pre-commit') + + # https://stackoverflow.com/a/8521644/812183 + parser.add_argument( + '-V', '--version', + action='version', + version=f'%(prog)s {C.VERSION}', + ) + + 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( + '--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( + '--freeze', action='store_true', + help='Store "frozen" hashes in `rev` instead of tag names', + ) + 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) + + hook_impl_parser = subparsers.add_parser('hook-impl') + _add_color_option(hook_impl_parser) + _add_config_option(hook_impl_parser) + hook_impl_parser.add_argument('--hook-type') + hook_impl_parser.add_argument('--hook-dir') + hook_impl_parser.add_argument( + '--skip-on-missing-config', action='store_true', + ) + hook_impl_parser.add_argument(dest='rest', nargs=argparse.REMAINDER) + + 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.', + ) + _add_color_option(install_parser) + _add_config_option(install_parser) + install_parser.add_argument( + '-f', '--overwrite', action='store_true', + help='Overwrite existing hooks / remove migration mode.', + ) + install_parser.add_argument( + '--install-hooks', action='store_true', + help=( + 'Whether to install hook environments for all environments ' + 'in the config file.' + ), + ) + _add_hook_type_option(install_parser) + install_parser.add_argument( + '--allow-missing-config', action='store_true', default=False, + help=( + 'Whether to allow a missing `pre-commit` configuration file ' + 'or exit with a failure code.' + ), + ) + + install_hooks_parser = subparsers.add_parser( + 'install-hooks', + help=( + 'Install hook environments for all environments in the config ' + 'file. You may find `pre-commit install --install-hooks` more ' + 'useful.' + ), + ) + _add_color_option(install_hooks_parser) + _add_config_option(install_hooks_parser) + + migrate_config_parser = subparsers.add_parser( + 'migrate-config', + help='Migrate list configuration to new map configuration.', + ) + _add_color_option(migrate_config_parser) + _add_config_option(migrate_config_parser) + + run_parser = subparsers.add_parser('run', help='Run hooks.') + _add_color_option(run_parser) + _add_config_option(run_parser) + _add_run_options(run_parser) + + sample_config_parser = subparsers.add_parser( + 'sample-config', help=f'Produce a sample {C.CONFIG_FILE} file', + ) + _add_color_option(sample_config_parser) + _add_config_option(sample_config_parser) + + try_repo_parser = subparsers.add_parser( + 'try-repo', + help='Try the hooks in a repository, useful for developing new hooks.', + ) + _add_color_option(try_repo_parser) + _add_config_option(try_repo_parser) + try_repo_parser.add_argument( + 'repo', help='Repository to source hooks from.', + ) + try_repo_parser.add_argument( + '--ref', '--rev', + help=( + 'Manually select a rev to run against, otherwise the `HEAD` ' + 'revision will be used.' + ), + ) + _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.', + ) + help.add_argument('help_cmd', nargs='?', help='Command to show help for.') + + # argparse doesn't really provide a way to use a `default` subparser + if len(argv) == 0: + argv = ['run'] + args = parser.parse_args(argv) + + if args.command == 'help' and args.help_cmd: + parser.parse_args([args.help_cmd, '--help']) + elif args.command == 'help': + parser.parse_args(['--help']) + + with error_handler(), logging_handler(args.color): + if args.command not in COMMANDS_NO_GIT: + _adjust_args_and_chdir(args) + + git.check_for_cygwin_mismatch() + + store = Store() + store.mark_config_used(args.config) + + if args.command == 'autoupdate': + return autoupdate( + args.config, store, + tags_only=not args.bleeding_edge, + freeze=args.freeze, + repos=args.repos, + ) + elif args.command == 'clean': + return clean(store) + elif args.command == 'gc': + return gc(store) + elif args.command == 'hook-impl': + return hook_impl( + store, + config=args.config, + color=args.color, + hook_type=args.hook_type, + hook_dir=args.hook_dir, + skip_on_missing_config=args.skip_on_missing_config, + args=args.rest[1:], + ) + elif args.command == 'install': + return install( + args.config, store, + hook_types=args.hook_types, + overwrite=args.overwrite, + hooks=args.install_hooks, + skip_on_missing_config=args.allow_missing_config, + ) + elif args.command == 'init-templatedir': + return init_templatedir( + args.config, store, args.directory, + hook_types=args.hook_types, + ) + 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': + return run(args.config, store, args) + elif args.command == 'sample-config': + return sample_config() + elif args.command == 'try-repo': + return try_repo(args) + elif args.command == 'uninstall': + return uninstall(hook_types=args.hook_types) + else: + raise NotImplementedError( + f'Command {args.command} not implemented.', + ) + + raise AssertionError( + f'Command {args.command} failed to exit with a returncode', + ) + + +if __name__ == '__main__': + exit(main()) |