From c86df75ab11643fa4649cfe6ed5c4692d4ee342b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 20:05:20 +0200 Subject: Adding upstream version 3.6.2. Signed-off-by: Daniel Baumann --- pre_commit/main.py | 442 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 pre_commit/main.py (limited to 'pre_commit/main.py') diff --git a/pre_commit/main.py b/pre_commit/main.py new file mode 100644 index 0000000..559c927 --- /dev/null +++ b/pre_commit/main.py @@ -0,0 +1,442 @@ +from __future__ import annotations + +import argparse +import logging +import os +import sys +from collections.abc import Sequence + +import pre_commit.constants as C +from pre_commit import clientlib +from pre_commit import git +from pre_commit.color import add_color_option +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.commands.validate_config import validate_config +from pre_commit.commands.validate_manifest import validate_manifest +from pre_commit.error_handler import error_handler +from pre_commit.logging_handler import logging_handler +from pre_commit.store import Store + + +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) + +# https://github.com/getsentry/snuba/pull/5388 +os.environ.pop('PYTHONEXECUTABLE', None) + +COMMANDS_NO_GIT = { + 'clean', 'gc', 'init-templatedir', 'sample-config', + 'validate-config', 'validate-manifest', +} + + +def _add_config_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + '-c', '--config', default=C.CONFIG_FILE, + help='Path to alternate config file', + ) + + +def _add_hook_type_option(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + '-t', '--hook-type', + choices=clientlib.HOOK_TYPES, action='append', 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=clientlib.STAGES, + type=clientlib.transform_stage, + default='pre-commit', + help='The stage during which the hook is fired. One of %(choices)s', + ) + parser.add_argument( + '--remote-branch', help='Remote branch ref used by `git push`.', + ) + parser.add_argument( + '--local-branch', help='Local branch ref used by `git push`.', + ) + parser.add_argument( + '--from-ref', '--source', '-s', + help=( + '(for usage with `--to-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 `--from-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( + '--pre-rebase-upstream', help=( + 'The upstream from which the series was forked.' + ), + ) + parser.add_argument( + '--pre-rebase-branch', help=( + 'The branch being rebased, and is not set when ' + 'rebasing the current branch.' + ), + ) + parser.add_argument( + '--commit-msg-filename', + help='Filename to check when running during `commit-msg`', + ) + parser.add_argument( + '--prepare-commit-message-source', + help=( + 'Source of the commit message ' + '(typically the second argument to .git/hooks/prepare-commit-msg)' + ), + ) + parser.add_argument( + '--commit-object-name', + help=( + 'Commit object name ' + '(typically the third argument to .git/hooks/prepare-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).' + ), + ) + parser.add_argument( + '--is-squash-merge', + help=( + 'During a post-merge hook, indicates whether the merge was a ' + 'squash merge' + ), + ) + parser.add_argument( + '--rewrite-command', + help=( + 'During a post-rewrite hook, specifies the command that invoked ' + 'the rewrite' + ), + ) + + +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.commit_msg_filename is not None: + args.commit_msg_filename = os.path.abspath( + args.commit_msg_filename, + ) + if args.command == 'try-repo' and os.path.exists(args.repo): + args.repo = os.path.abspath(args.repo) + + toplevel = git.get_root() + 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.commit_msg_filename is not None: + args.commit_msg_filename = os.path.relpath( + args.commit_msg_filename, + ) + if args.command == 'try-repo' and os.path.exists(args.repo): + args.repo = os.path.relpath(args.repo) + + +def main(argv: Sequence[str] | None = 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') + + def _add_cmd(name: str, *, help: str) -> argparse.ArgumentParser: + parser = subparsers.add_parser(name, help=help) + add_color_option(parser) + return parser + + autoupdate_parser = _add_cmd( + 'autoupdate', + help="Auto-update pre-commit config to the latest repos' versions.", + ) + _add_config_option(autoupdate_parser) + autoupdate_parser.add_argument( + '--bleeding-edge', action='store_true', + help=( + 'Update to the bleeding edge of `HEAD` 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', default=[], + help='Only update this repository -- may be specified multiple times.', + ) + autoupdate_parser.add_argument( + '-j', '--jobs', type=int, default=1, + help='Number of threads to use. (default %(default)s).', + ) + + _add_cmd('clean', help='Clean out pre-commit files.') + + _add_cmd('gc', help='Clean unused cached repos.') + + init_templatedir_parser = _add_cmd( + 'init-templatedir', + help=( + 'Install hook script in a directory intended for use with ' + '`git config init.templateDir`.' + ), + ) + _add_config_option(init_templatedir_parser) + init_templatedir_parser.add_argument( + 'directory', help='The directory in which to write the hook script.', + ) + init_templatedir_parser.add_argument( + '--no-allow-missing-config', + action='store_false', + dest='allow_missing_config', + help='Assume cloned repos should have a `pre-commit` config.', + ) + _add_hook_type_option(init_templatedir_parser) + + install_parser = _add_cmd('install', help='Install the pre-commit script.') + _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 = _add_cmd( + 'install-hooks', + help=( + 'Install hook environments for all environments in the config ' + 'file. You may find `pre-commit install --install-hooks` more ' + 'useful.' + ), + ) + _add_config_option(install_hooks_parser) + + migrate_config_parser = _add_cmd( + 'migrate-config', + help='Migrate list configuration to new map configuration.', + ) + _add_config_option(migrate_config_parser) + + run_parser = _add_cmd('run', help='Run hooks.') + _add_config_option(run_parser) + _add_run_options(run_parser) + + _add_cmd('sample-config', help=f'Produce a sample {C.CONFIG_FILE} file') + + try_repo_parser = _add_cmd( + 'try-repo', + help='Try the hooks in a repository, useful for developing new hooks.', + ) + _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 = _add_cmd( + 'uninstall', help='Uninstall the pre-commit script.', + ) + _add_config_option(uninstall_parser) + _add_hook_type_option(uninstall_parser) + + validate_config_parser = _add_cmd( + 'validate-config', help='Validate .pre-commit-config.yaml files', + ) + validate_config_parser.add_argument('filenames', nargs='*') + + validate_manifest_parser = _add_cmd( + 'validate-manifest', help='Validate .pre-commit-hooks.yaml files', + ) + validate_manifest_parser.add_argument('filenames', nargs='*') + + # does not use `_add_cmd` because it doesn't use `--color` + help = subparsers.add_parser( + 'help', help='Show help for a specific command.', + ) + help.add_argument('help_cmd', nargs='?', help='Command to show help for.') + + # not intended for users to call this directly + 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) + + # 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): + git.check_for_cygwin_mismatch() + + store = Store() + + if args.command not in COMMANDS_NO_GIT: + _adjust_args_and_chdir(args) + store.mark_config_used(args.config) + + if args.command == 'autoupdate': + return autoupdate( + args.config, + tags_only=not args.bleeding_edge, + freeze=args.freeze, + repos=args.repos, + jobs=args.jobs, + ) + 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, + skip_on_missing_config=args.allow_missing_config, + ) + 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( + config_file=args.config, + hook_types=args.hook_types, + ) + elif args.command == 'validate-config': + return validate_config(args.filenames) + elif args.command == 'validate-manifest': + return validate_manifest(args.filenames) + else: + raise NotImplementedError( + f'Command {args.command} not implemented.', + ) + + raise AssertionError( + f'Command {args.command} failed to exit with a returncode', + ) + + +if __name__ == '__main__': + raise SystemExit(main()) -- cgit v1.2.3