def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ): # Set up our logging handler logger.addHandler(LoggingHandler(args.color, write=write)) logger.setLevel(logging.INFO) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(runner): logger.error('Unmerged files. Resolve before committing.') return 1 if (args.source and not args.origin) or \ (args.origin and not args.source): logger.error('--origin and --source depend on each other.') return 1 # Don't stash if specified or files are specified if args.no_stash or args.all_files or args.files: ctx = noop_context() else: ctx = staged_files_only(runner.cmd_runner) with ctx: if args.hook: return _run_hook(runner, args, write=write) else: return _run_hooks(runner, args, write=write, environ=environ)
def run_context(self, staged: bool, run_step: RunStep) -> Iterator[List[Path]]: """ Provides a context within which to run tools and returns list of paths that should be analyzed. Possible contexts include: Head Context: all files in current branch HEAD Staged Files Context: all files in current branch HEAD plus staged changes (hides all untracked files) Noop: all files as currently available on filesystem Returned list of paths are all abolute paths and include all files that are - not ignored based on .bentoignore rules and - exist in any path filters specified. :param staged: Whether to use remove file diffs :param run_step: Which run step is in use (baseline if tool is determining baseline, check if tool is finding new results) :return: A Python with-expression :raises subprocess.CalledProcessError: If git encounters an exception :raises NoGitHeadException: If git cannot detect a HEAD commit :raises UnsupportedGitStateException: If unmerged files are detected """ if staged and run_step == RunStep.BASELINE: stash_context = self._head_context() elif staged: stash_context = staged_files_only(PATCH_CACHE) else: # staged is False stash_context = noop_context() with stash_context: yield self._target_paths
def test_diff_returns_1_no_diff_though(fake_logging_handler, foo_staged): cmd_runner = mock.Mock() cmd_runner.run.return_value = (1, '', '') cmd_runner.path.return_value = '.pre-commit-files_patch' with staged_files_only(cmd_runner): pass assert not fake_logging_handler.logs
def test_does_not_crash_patch_dir_does_not_exist(foo_staged, patch_dir): with open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write('hello\nworld\n') shutil.rmtree(patch_dir) with staged_files_only(patch_dir): pass
def test_does_not_crash_patch_dir_does_not_exist(foo_staged, patch_dir): with io.open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write('hello\nworld\n') shutil.rmtree(patch_dir) with staged_files_only(patch_dir): pass
def test_stage_utf8_changes(foo_staged, patch_dir): contents = '\u2603' with open('foo', 'w', encoding='UTF-8') as foo_file: foo_file.write(contents) _test_foo_state(foo_staged, contents, 'AM') with staged_files_only(patch_dir): _test_foo_state(foo_staged) _test_foo_state(foo_staged, contents, 'AM')
def test_stage_utf8_changes(foo_staged, cmd_runner): contents = '\u2603' with io.open('foo', 'w', encoding='UTF-8') as foo_file: foo_file.write(contents) _test_foo_state(foo_staged, contents, 'AM') with staged_files_only(cmd_runner): _test_foo_state(foo_staged) _test_foo_state(foo_staged, contents, 'AM')
def test_img_something_unstaged(img_staged, cmd_runner): shutil.copy(get_resource_path('img2.jpg'), img_staged.img_filename) _test_img_state(img_staged, 'img2.jpg', 'AM') with staged_files_only(cmd_runner): _test_img_state(img_staged) _test_img_state(img_staged, 'img2.jpg', 'AM')
def test_intent_to_add(in_git_dir, patch_dir): """Regression test for #881""" _write(b'hello\nworld\n') cmd_output('git', 'add', '--intent-to-add', 'foo') assert git.intent_to_add_files() == ['foo'] with staged_files_only(patch_dir): assert_no_diff() assert git.intent_to_add_files() == ['foo']
def test_img_something_unstaged(img_staged, patch_dir): shutil.copy(get_resource_path('img2.jpg'), img_staged.img_filename) _test_img_state(img_staged, 'img2.jpg', 'AM') with staged_files_only(patch_dir): _test_img_state(img_staged) _test_img_state(img_staged, 'img2.jpg', 'AM')
def _head_context(self) -> Iterator[None]: """ Runs a block of code on files from the current branch HEAD. :raises subprocess.CalledProcessError: If git encounters an exception :raises NoGitHeadException: If git cannot detect a HEAD commit :raises UnsupportedGitStateException: If unmerged files are detected """ repo = bento.git.repo() if not repo: yield return commit = bento.git.commit() if commit is None: raise NoGitHeadException() else: added, removed, unmerged = self._git_status() # Need to look for unmerged files first, otherwise staged_files_only will eat them if unmerged: echo_error( "Please resolve merge conflicts in these files before continuing:" ) for f in unmerged: click.secho(f, err=True) raise UnsupportedGitStateException() with staged_files_only(PATCH_CACHE): tree = cmd_output("git", "write-tree")[1].strip() self._abort_if_untracked_and_removed(removed) try: for a in added: (repo.working_tree_dir / Path(a)).unlink() cmd_output("git", "checkout", "HEAD", "--", ".") yield finally: # git checkout will fail if the checked-out index deletes all files in the repo # In this case, we still want to continue without error. # Note that we have no good way of detecting this issue without inspecting the checkout output # message, which means we are fragile with respect to git version here. try: cmd_output("git", "checkout", tree.strip(), "--", ".") except CalledProcessError as ex: if (ex.output and len(ex.output) >= 2 and "pathspec '.' did not match any file(s) known to git" in ex.output[1].strip()): logging.warning( "Restoring git index failed due to total repository deletion; skipping checkout" ) else: raise ex if removed: cmd_output("git", "rm", *removed)
def test_foo_something_unstaged(foo_staged, cmd_runner): with io.open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write('herp\nderp\n') _test_foo_state(foo_staged, 'herp\nderp\n', 'AM') with staged_files_only(cmd_runner): _test_foo_state(foo_staged) _test_foo_state(foo_staged, 'herp\nderp\n', 'AM')
def test_sub_something_unstaged(sub_staged, cmd_runner): checkout_submodule(sub_staged.submodule.sha2) _test_sub_state(sub_staged, 'sha2', 'AM') with staged_files_only(cmd_runner): # This is different from others, we don't want to touch subs _test_sub_state(sub_staged, 'sha2', 'AM') _test_sub_state(sub_staged, 'sha2', 'AM')
def test_foo_something_unstaged(foo_staged, patch_dir): with open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write('herp\nderp\n') _test_foo_state(foo_staged, 'herp\nderp\n', 'AM') with staged_files_only(patch_dir): _test_foo_state(foo_staged) _test_foo_state(foo_staged, 'herp\nderp\n', 'AM')
def test_stage_non_utf8_changes(foo_staged, patch_dir): contents = 'ú' # Produce a latin-1 diff with open('foo', 'w', encoding='latin-1') as foo_file: foo_file.write(contents) _test_foo_state(foo_staged, contents, 'AM', encoding='latin-1') with staged_files_only(patch_dir): _test_foo_state(foo_staged) _test_foo_state(foo_staged, contents, 'AM', encoding='latin-1')
def test_sub_something_unstaged(sub_staged, patch_dir): checkout_submodule(sub_staged.submodule.rev2) _test_sub_state(sub_staged, 'rev2', 'AM') with staged_files_only(patch_dir): # This is different from others, we don't want to touch subs _test_sub_state(sub_staged, 'rev2', 'AM') _test_sub_state(sub_staged, 'rev2', 'AM')
def test_stage_non_utf8_changes(foo_staged, cmd_runner): contents = 'ú' # Produce a latin-1 diff with io.open('foo', 'w', encoding='latin-1') as foo_file: foo_file.write(contents) _test_foo_state(foo_staged, contents, 'AM', encoding='latin-1') with staged_files_only(cmd_runner): _test_foo_state(foo_staged) _test_foo_state(foo_staged, contents, 'AM', encoding='latin-1')
def run(runner, args, environ=os.environ): no_stash = args.no_stash or args.all_files or bool(args.files) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(runner): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(runner) and not no_stash: if args.allow_unstaged_config: logger.warn( 'You have an unstaged config file and have specified the ' '--allow-unstaged-config option.\n' 'Note that your config will be stashed before the config is ' 'parsed unless --no-stash is specified.', ) else: logger.error( 'Your .pre-commit-config.yaml is unstaged.\n' '`git add .pre-commit-config.yaml` to fix this.\n' 'Run pre-commit with --allow-unstaged-config to silence this.' ) return 1 # Expose origin / source as environment variables for hooks to consume if args.origin and args.source: environ['PRE_COMMIT_ORIGIN'] = args.origin environ['PRE_COMMIT_SOURCE'] = args.source if no_stash: ctx = noop_context() else: ctx = staged_files_only(runner.cmd_runner) with ctx: repo_hooks = list(get_repo_hooks(runner)) if args.hook: repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if hook['id'] == args.hook ] if not repo_hooks: output.write_line('No hook with id `{}`'.format(args.hook)) return 1 # Filter hooks for stages repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if not hook['stages'] or args.hook_stage in hook['stages'] ] return _run_hooks(repo_hooks, args, environ)
def test_crlf(in_git_dir, patch_dir, crlf_before, crlf_after, autocrlf): cmd_output('git', 'config', '--local', 'core.autocrlf', autocrlf) before, after = b'1\n2\n', b'3\n4\n\n' before = before.replace(b'\n', b'\r\n') if crlf_before else before after = after.replace(b'\n', b'\r\n') if crlf_after else after _write(before) cmd_output('git', 'add', 'foo') _write(after) with staged_files_only(patch_dir): assert_no_diff()
def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ): no_stash = args.no_stash or args.all_files or bool(args.files) # Set up our logging handler logger.addHandler(LoggingHandler(args.color, write=write)) logger.setLevel(logging.INFO) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(runner): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(runner) and not no_stash: if args.allow_unstaged_config: logger.warn( 'You have an unstaged config file and have specified the ' '--allow-unstaged-config option.\n' 'Note that your config will be stashed before the config is ' 'parsed unless --no-stash is specified.', ) else: logger.error( 'Your .pre-commit-config.yaml is unstaged.\n' '`git add .pre-commit-config.yaml` to fix this.\n' 'Run pre-commit with --allow-unstaged-config to silence this.' ) return 1 if no_stash: ctx = noop_context() else: ctx = staged_files_only(runner.cmd_runner) with ctx: repo_hooks = list(get_repo_hooks(runner)) if args.hook: repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if hook['id'] == args.hook ] if not repo_hooks: write('No hook with id `{}`\n'.format(args.hook)) return 1 # Filter hooks for stages repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if not hook['stages'] or args.hook_stage in hook['stages'] ] return _run_hooks(repo_hooks, args, write, environ)
def test_autocrlf_commited_crlf(in_git_dir, patch_dir): """Regression test for #570""" cmd_output('git', 'config', '--local', 'core.autocrlf', 'false') _write(b'1\r\n2\r\n') cmd_output('git', 'add', 'foo') git_commit() cmd_output('git', 'config', '--local', 'core.autocrlf', 'true') _write(b'1\r\n2\r\n\r\n\r\n\r\n') with staged_files_only(patch_dir): assert_no_diff()
def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ): no_stash = args.no_stash or args.all_files or bool(args.files) # Set up our logging handler logger.addHandler(LoggingHandler(args.color, write=write)) logger.setLevel(logging.INFO) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(runner): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(runner) and not no_stash: if args.allow_unstaged_config: logger.warn( 'You have an unstaged config file and have specified the ' '--allow-unstaged-config option.\n' 'Note that your config will be stashed before the config is ' 'parsed unless --no-stash is specified.', ) else: logger.error( 'Your .pre-commit-config.yaml is unstaged.\n' '`git add .pre-commit-config.yaml` to fix this.\n' 'Run pre-commit with --allow-unstaged-config to silence this.' ) return 1 if no_stash: ctx = noop_context() else: ctx = staged_files_only(runner.cmd_runner) with ctx: repo_hooks = list(get_repo_hooks(runner)) if args.hook: repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if hook['id'] == args.hook ] if not repo_hooks: write('No hook with id `{0}`\n'.format(args.hook)) return 1 # Filter hooks for stages repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if not hook['stages'] or args.hook_stage in hook['stages'] ] return _run_hooks(repo_hooks, args, write, environ)
def run( config_file: str, store: Store, args: argparse.Namespace, environ: EnvironT = os.environ, ) -> int: stash = not args.all_files and not args.files # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if stash and _has_unstaged_config(config_file): logger.error( f'Your pre-commit configuration is unstaged.\n' f'`git add {config_file}` to fix this.', ) return 1 # Expose origin / source as environment variables for hooks to consume if args.origin and args.source: environ['PRE_COMMIT_ORIGIN'] = args.origin environ['PRE_COMMIT_SOURCE'] = args.source if args.remote_name and args.remote_url: environ['PRE_COMMIT_REMOTE_NAME'] = args.remote_name environ['PRE_COMMIT_REMOTE_URL'] = args.remote_url with contextlib.ExitStack() as exit_stack: if stash: exit_stack.enter_context(staged_files_only(store.directory)) config = load_config(config_file) hooks = [ hook for hook in all_hooks(config, store) if not args.hook or hook.id == args.hook or hook.alias == args.hook if args.hook_stage in hook.stages ] if args.hook and not hooks: output.write_line( f'No hook with id `{args.hook}` in stage `{args.hook_stage}`', ) return 1 install_hook_envs(hooks, store) return _run_hooks(config, hooks, args, environ) # https://github.com/python/mypy/issues/7726 raise AssertionError('unreachable')
def test_autocrlf_committed_crlf(in_git_dir, patch_dir): """Regression test for #570""" cmd_output('git', 'config', '--local', 'core.autocrlf', 'false') _write(b'1\r\n2\r\n') cmd_output('git', 'add', 'foo') git_commit() cmd_output('git', 'config', '--local', 'core.autocrlf', 'true') _write(b'1\r\n2\r\n\r\n\r\n\r\n') with staged_files_only(patch_dir): assert_no_diff()
def test_img_conflict(img_staged, cmd_runner): """Admittedly, this shouldn't happen, but just in case.""" shutil.copy(get_resource_path('img2.jpg'), img_staged.img_filename) _test_img_state(img_staged, 'img2.jpg', 'AM') with staged_files_only(cmd_runner): _test_img_state(img_staged) shutil.copy(get_resource_path('img3.jpg'), img_staged.img_filename) _test_img_state(img_staged, 'img3.jpg', 'AM') _test_img_state(img_staged, 'img2.jpg', 'AM')
def test_img_conflict(img_staged, patch_dir): """Admittedly, this shouldn't happen, but just in case.""" shutil.copy(get_resource_path('img2.jpg'), img_staged.img_filename) _test_img_state(img_staged, 'img2.jpg', 'AM') with staged_files_only(patch_dir): _test_img_state(img_staged) shutil.copy(get_resource_path('img3.jpg'), img_staged.img_filename) _test_img_state(img_staged, 'img3.jpg', 'AM') _test_img_state(img_staged, 'img2.jpg', 'AM')
def run_context( context: Context, target_paths: List[Path], staged: bool, run_step: RunStep, show_bars: bool = True, ) -> Iterator[Runner]: """ Provides a context within which to run tools. This context obeys the following behaviors: Filesystem modifications: staged is true - file diffs are removed otherwise - no changes Paths to be checked: explicit paths - these paths are used staged is true - only paths with staged changes are used otherwise - only paths with diffs vs the head git index are used :param context: The Bento command context :param input_paths: A list of paths to check, or None to indicate that check should operate against the base path :param staged: Whether to use remove file diffs :param run_step: Which run step is in use (baseline if tool is determining baseline, check if tool is finding new results) :param show_bars: If true, attempts to configure Runner to display progress bars (these may not be displayed if not supported by environment) :return: A Python with-expression, which is passed a Runner object :raises Exception: If comparison is not HEAD and run_step is not CHECK """ use_cache = False skip_setup = True if staged and run_step == RunStep.BASELINE: stash_context = head_context() use_cache = True elif staged: # run_step = RunStep.CHECK stash_context = staged_files_only(PATCH_CACHE) else: # staged is False stash_context = noop_context() skip_setup = False with stash_context: yield Runner( paths=target_paths, use_cache=use_cache, skip_setup=skip_setup, show_bars=show_bars, )
def test_foo_both_modify_non_conflicting(foo_staged, cmd_runner): with io.open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write(FOO_CONTENTS + '9\n') _test_foo_state(foo_staged, FOO_CONTENTS + '9\n', 'AM') with staged_files_only(cmd_runner): _test_foo_state(foo_staged) # Modify the file as part of the "pre-commit" with io.open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write(FOO_CONTENTS.replace('1', 'a')) _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a'), 'AM') _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a') + '9\n', 'AM')
def test_foo_both_modify_conflicting(foo_staged, patch_dir): with open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write(FOO_CONTENTS.replace('1', 'a')) _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a'), 'AM') with staged_files_only(patch_dir): _test_foo_state(foo_staged) # Modify in the same place as the stashed diff with open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write(FOO_CONTENTS.replace('1', 'b')) _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'b'), 'AM') _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a'), 'AM')
def test_foo_both_modify_non_conflicting(foo_staged, patch_dir): with open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write(f'{FOO_CONTENTS}9\n') _test_foo_state(foo_staged, f'{FOO_CONTENTS}9\n', 'AM') with staged_files_only(patch_dir): _test_foo_state(foo_staged) # Modify the file as part of the "pre-commit" with open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write(FOO_CONTENTS.replace('1', 'a')) _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a'), 'AM') _test_foo_state(foo_staged, f'{FOO_CONTENTS.replace("1", "a")}9\n', 'AM')
def test_foo_both_modify_conflicting(foo_staged, cmd_runner): with io.open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write(FOO_CONTENTS.replace('1', 'a')) _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a'), 'AM') with staged_files_only(cmd_runner): _test_foo_state(foo_staged) # Modify in the same place as the stashed diff with io.open(foo_staged.foo_filename, 'w') as foo_file: foo_file.write(FOO_CONTENTS.replace('1', 'b')) _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'b'), 'AM') _test_foo_state(foo_staged, FOO_CONTENTS.replace('1', 'a'), 'AM')
def run(config_file, store, args, environ=os.environ): no_stash = args.all_files or bool(args.files) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(config_file) and not no_stash: logger.error( 'Your pre-commit configuration is unstaged.\n' '`git add {}` to fix this.'.format(config_file), ) return 1 # Expose origin / source as environment variables for hooks to consume if args.origin and args.source: environ['PRE_COMMIT_ORIGIN'] = args.origin environ['PRE_COMMIT_SOURCE'] = args.source if no_stash: ctx = noop_context() else: ctx = staged_files_only(store.directory) with ctx: config = load_config(config_file) hooks = [ hook for hook in all_hooks(config, store) if not args.hook or hook.id == args.hook or hook.alias == args.hook if args.hook_stage in hook.stages ] if args.hook and not hooks: output.write_line( 'No hook with id `{}` in stage `{}`'.format( args.hook, args.hook_stage, ), ) return 1 install_hook_envs(hooks, store) return _run_hooks(config, hooks, args, environ)
def run(runner, args, environ=os.environ): no_stash = args.all_files or bool(args.files) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(runner) and not no_stash: logger.error( 'Your .pre-commit-config.yaml is unstaged.\n' '`git add .pre-commit-config.yaml` to fix this.', ) return 1 # Expose origin / source as environment variables for hooks to consume if args.origin and args.source: environ['PRE_COMMIT_ORIGIN'] = args.origin environ['PRE_COMMIT_SOURCE'] = args.source if no_stash: ctx = noop_context() else: ctx = staged_files_only(runner.store.directory) with ctx: repo_hooks = list(get_repo_hooks(runner)) if args.hook: repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if hook['id'] == args.hook ] if not repo_hooks: output.write_line('No hook with id `{}`'.format(args.hook)) return 1 # Filter hooks for stages repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if not hook['stages'] or args.hook_stage in hook['stages'] ] return _run_hooks(runner.config, repo_hooks, args, environ)
def run(runner, args, environ=os.environ): no_stash = args.all_files or bool(args.files) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(runner) and not no_stash: logger.error( 'Your pre-commit configuration is unstaged.\n' '`git add {}` to fix this.'.format(runner.config_file), ) return 1 # Expose origin / source as environment variables for hooks to consume if args.origin and args.source: environ['PRE_COMMIT_ORIGIN'] = args.origin environ['PRE_COMMIT_SOURCE'] = args.source if no_stash: ctx = noop_context() else: ctx = staged_files_only(runner.store.directory) with ctx: repo_hooks = [] for repo in runner.repositories: for _, hook in repo.hooks: if ( (not args.hook or hook['id'] == 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)) return 1 for repo in {repo for repo, _ in repo_hooks}: repo.require_installed() return _run_hooks(runner.config, repo_hooks, args, environ)
def run(runner, args, environ=os.environ): no_stash = args.all_files or bool(args.files) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(runner) and not no_stash: logger.error( 'Your .pre-commit-config.yaml is unstaged.\n' '`git add .pre-commit-config.yaml` to fix this.', ) return 1 # Expose origin / source as environment variables for hooks to consume if args.origin and args.source: environ['PRE_COMMIT_ORIGIN'] = args.origin environ['PRE_COMMIT_SOURCE'] = args.source if no_stash: ctx = noop_context() else: ctx = staged_files_only(runner.store.directory) with ctx: repo_hooks = list(get_repo_hooks(runner)) if args.hook: repo_hooks = [(repo, hook) for repo, hook in repo_hooks if hook['id'] == args.hook] if not repo_hooks: output.write_line('No hook with id `{}`'.format(args.hook)) return 1 # Filter hooks for stages repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if not hook['stages'] or args.hook_stage in hook['stages'] ] return _run_hooks(runner.config, repo_hooks, args, environ)
def run(config_file, store, args, environ=os.environ): no_stash = args.all_files or bool(args.files) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(config_file) and not no_stash: logger.error( 'Your pre-commit configuration is unstaged.\n' '`git add {}` to fix this.'.format(config_file), ) return 1 # Expose origin / source as environment variables for hooks to consume if args.origin and args.source: environ['PRE_COMMIT_ORIGIN'] = args.origin environ['PRE_COMMIT_SOURCE'] = args.source if no_stash: ctx = noop_context() else: ctx = staged_files_only(store.directory) with ctx: config = load_config(config_file) hooks = [ hook for hook in all_hooks(config, store) if not args.hook or hook.id == args.hook or hook.alias == args.hook if args.hook_stage in hook.stages ] if args.hook and not hooks: output.write_line('No hook with id `{}`'.format(args.hook)) return 1 install_hook_envs(hooks, store) return _run_hooks(config, hooks, args, environ)
def test_submodule_does_not_discard_changes(sub_staged, patch_dir): with open('bar', 'w') as f: f.write('unstaged changes') foo_path = os.path.join(sub_staged.sub_path, 'foo') with open(foo_path, 'w') as f: f.write('foo contents') with staged_files_only(patch_dir): with open('bar') as f: assert f.read() == '' with open(foo_path) as f: assert f.read() == 'foo contents' with open('bar') as f: assert f.read() == 'unstaged changes' with open(foo_path) as f: assert f.read() == 'foo contents'
def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ): # Set up our logging handler logger.addHandler(LoggingHandler(args.color, write=write)) logger.setLevel(logging.INFO) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(runner): logger.error('Unmerged files. Resolve before committing.') return 1 if args.no_stash or args.all_files: ctx = noop_context() else: ctx = staged_files_only(runner.cmd_runner) with ctx: if args.hook: return _run_hook(runner, args, write=write) else: return _run_hooks(runner, args, write=write, environ=environ)
def run(runner, args, write=sys_stdout_write_wrapper, environ=os.environ): # Set up our logging handler logger.addHandler(LoggingHandler(args.color, write=write)) logger.setLevel(logging.INFO) # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(runner): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.source) != bool(args.origin): logger.error('Specify both --origin and --source.') return 1 if _has_unstaged_config(runner) and not args.no_stash: if args.allow_unstaged_config: logger.warn('You have an unstaged config file and have ' 'specified the --allow-unstaged-config option.\n' 'Note that your config will be stashed before the ' 'config is parsed unless --no-stash is specified.') else: logger.error('You have an unstaged config file and have not ' 'specified the --allow-unstaged-config option.\n') return 1 # Don't stash if specified or files are specified if args.no_stash or args.all_files or args.files: ctx = noop_context() else: ctx = staged_files_only(runner.cmd_runner) with ctx: repo_hooks = list(get_repo_hooks(runner)) if args.hook: repo_hooks = [ (repo, hook) for repo, hook in repo_hooks if hook['id'] == args.hook ] if not repo_hooks: write('No hook with id `{0}`\n'.format(args.hook)) return 1 return _run_hooks(repo_hooks, args, write, environ)
def test_non_utf8_conflicting_diff(foo_staged, cmd_runner): """Regression test for #397""" # The trailing whitespace is important here, this triggers git to produce # an error message which looks like: # # ...patch1471530032:14: trailing whitespace. # [[unprintable character]][[space character]] # error: patch failed: foo:1 # error: foo: patch does not apply # # Previously, the error message (though discarded immediately) was being # decoded with the UTF-8 codec (causing a crash) contents = 'ú \n' with io.open('foo', 'w', encoding='latin-1') as foo_file: foo_file.write(contents) _test_foo_state(foo_staged, contents, 'AM', encoding='latin-1') with staged_files_only(cmd_runner): _test_foo_state(foo_staged) # Create a conflicting diff that will need to be rolled back with io.open('foo', 'w') as foo_file: foo_file.write('') _test_foo_state(foo_staged, contents, 'AM', encoding='latin-1')
def test_non_utf8_conflicting_diff(foo_staged, patch_dir): """Regression test for #397""" # The trailing whitespace is important here, this triggers git to produce # an error message which looks like: # # ...patch1471530032:14: trailing whitespace. # [[unprintable character]][[space character]] # error: patch failed: foo:1 # error: foo: patch does not apply # # Previously, the error message (though discarded immediately) was being # decoded with the UTF-8 codec (causing a crash) contents = 'ú \n' with open('foo', 'w', encoding='latin-1') as foo_file: foo_file.write(contents) _test_foo_state(foo_staged, contents, 'AM', encoding='latin-1') with staged_files_only(patch_dir): _test_foo_state(foo_staged) # Create a conflicting diff that will need to be rolled back with open('foo', 'w') as foo_file: foo_file.write('') _test_foo_state(foo_staged, contents, 'AM', encoding='latin-1')
def test_foo_nothing_unstaged(foo_staged, patch_dir): with staged_files_only(patch_dir): _test_foo_state(foo_staged) _test_foo_state(foo_staged)
def test_img_nothing_unstaged(img_staged, patch_dir): with staged_files_only(patch_dir): _test_img_state(img_staged) _test_img_state(img_staged)
def test_sub_nothing_unstaged(sub_staged, cmd_runner): with staged_files_only(cmd_runner): _test_sub_state(sub_staged) _test_sub_state(sub_staged)
def test_sub_nothing_unstaged(sub_staged, patch_dir): with staged_files_only(patch_dir): _test_sub_state(sub_staged) _test_sub_state(sub_staged)
def test_foo_nothing_unstaged(foo_staged, cmd_runner): with staged_files_only(cmd_runner): _test_foo_state(foo_staged) _test_foo_state(foo_staged)
def run( config_file: str, store: Store, args: argparse.Namespace, environ: MutableMapping[str, str] = os.environ, ) -> int: stash = not args.all_files and not args.files # Check if we have unresolved merge conflict files and fail fast. if _has_unmerged_paths(): logger.error('Unmerged files. Resolve before committing.') return 1 if bool(args.from_ref) != bool(args.to_ref): logger.error('Specify both --from-ref and --to-ref.') return 1 if stash and _has_unstaged_config(config_file): logger.error( f'Your pre-commit configuration is unstaged.\n' f'`git add {config_file}` to fix this.', ) return 1 if (args.hook_stage in {'prepare-commit-msg', 'commit-msg'} and not args.commit_msg_filename): logger.error( f'`--commit-msg-filename` is required for ' f'`--hook-stage {args.hook_stage}`', ) return 1 # prevent recursive post-checkout hooks (#1418) if (args.hook_stage == 'post-checkout' and environ.get('_PRE_COMMIT_SKIP_POST_CHECKOUT')): return 0 # Expose from-ref / to-ref as environment variables for hooks to consume if args.from_ref and args.to_ref: # legacy names environ['PRE_COMMIT_ORIGIN'] = args.from_ref environ['PRE_COMMIT_SOURCE'] = args.to_ref # new names environ['PRE_COMMIT_FROM_REF'] = args.from_ref environ['PRE_COMMIT_TO_REF'] = args.to_ref if args.remote_name and args.remote_url: environ['PRE_COMMIT_REMOTE_NAME'] = args.remote_name environ['PRE_COMMIT_REMOTE_URL'] = args.remote_url if args.checkout_type: environ['PRE_COMMIT_CHECKOUT_TYPE'] = args.checkout_type # Set pre_commit flag environ['PRE_COMMIT'] = '1' with contextlib.ExitStack() as exit_stack: if stash: exit_stack.enter_context(staged_files_only(store.directory)) config = load_config(config_file) hooks = [ hook for hook in all_hooks(config, store) if not args.hook or hook.id == args.hook or hook.alias == args.hook if args.hook_stage in hook.stages ] if args.hook and not hooks: output.write_line( f'No hook with id `{args.hook}` in stage `{args.hook_stage}`', ) return 1 install_hook_envs(hooks, store) return _run_hooks(config, hooks, args, environ) # https://github.com/python/mypy/issues/7726 raise AssertionError('unreachable')
def test_img_nothing_unstaged(img_staged, cmd_runner): with staged_files_only(cmd_runner): _test_img_state(img_staged) _test_img_state(img_staged)