diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e2af43b..bfd8db3c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ hooks: - id: pyflakes - files: '*.py' + files: '\.py$' - repo: git@github.com:pre-commit/jshint @@ -13,7 +13,7 @@ hooks: - id: jshint - files: '*.js' + files: '\.js$' - repo: git@github.com:pre-commit/scss-lint @@ -21,4 +21,4 @@ hooks: - id: scss-lint - files: '*.scss' + files: '\.scss$' diff --git a/pre_commit/git.py b/pre_commit/git.py index 3475f566..92efe4a4 100644 --- a/pre_commit/git.py +++ b/pre_commit/git.py @@ -1,4 +1,5 @@ import os +import re import pkg_resources from plumbum import local @@ -27,4 +28,31 @@ def remove_pre_commit(): def get_head_sha(git_repo_path): with local.cwd(git_repo_path): - return (local['git']['rev-parse', 'HEAD'])().strip() + return local['git']['rev-parse', 'HEAD']().strip() + + +@memoize_by_cwd +def get_staged_files(): + return local['git']['diff', '--staged', '--name-only']().splitlines() + + +@memoize_by_cwd +def get_staged_files_matching(expr): + regex = re.compile(expr) + return set( + filename for filename in get_staged_files() if regex.search(filename) + ) + + +@memoize_by_cwd +def get_all_files(): + return local['git']['ls-files']().splitlines() + + +# Smell: this is duplicated above +@memoize_by_cwd +def get_all_files_matching(expr): + regex = re.compile(expr) + return set( + filename for filename in get_all_files() if regex.search(filename) + ) \ No newline at end of file diff --git a/pre_commit/run.py b/pre_commit/run.py index 63ba44cc..464923b3 100644 --- a/pre_commit/run.py +++ b/pre_commit/run.py @@ -1,10 +1,19 @@ import argparse import os.path +import subprocess + from pre_commit import git from pre_commit.clientlib.validate_config import validate_config from pre_commit.repository import Repository + +RED = '\033[41m' +GREEN = '\033[42m' +NORMAL = '\033[0m' +COLS = int(subprocess.Popen(['tput', 'cols'], stdout=subprocess.PIPE).communicate()[0]) + + def install(): """Install the pre-commit hook.""" git.create_pre_commit() @@ -15,27 +24,76 @@ def uninstall(): git.remove_pre_commit() -def run_hooks(arguments): - """Actually run the hooks.""" - raise NotImplementedError +def _run_single_hook(repository, hook_id, run_all_the_things=False): + repository.install() + + if run_all_the_things: + get_filenames = git.get_all_files_matching + else: + get_filenames = git.get_staged_files_matching + + hook = repository.hooks[hook_id] + + retcode, stdout, stderr = repository.run_hook( + hook_id, + map(os.path.abspath, get_filenames(hook['files'])), + ) + + if retcode != repository.hooks[hook_id].get('expected_return_value', 0): + output = '\n'.join([stdout, stderr]).strip() + retcode = 1 + color = RED + pass_fail = 'Failed' + else: + output = '' + retcode = 0 + color = GREEN + pass_fail = 'Passed' + + + print '{0}{1}{2}{3}{4}'.format( + hook['name'], + '.' * (COLS - len(hook['name']) - len(pass_fail) - 6), + color, + pass_fail, + NORMAL, + ) + + if output: + print + print output + print + + return retcode + + +def run_hooks(run_all_the_things=False): + """Actually run the hooks.""" + retval = 0 -def run_single_hook(hook_id): configs = validate_config([]) + for config in configs: + repo = Repository(config) + for hook_id in repo.hooks: + retval |= _run_single_hook( + repo, + hook_id, + run_all_the_things=run_all_the_things, + ) + + return retval + + +def run_single_hook(hook_id, configs=None, run_all_the_things=False): + configs = configs or validate_config([]) for config in configs: repo = Repository(config) if hook_id in repo.hooks: - repo.install() - - retcode, stdout, stderr = repo.run_hook(hook_id, map(os.path.abspath, ['pre_commit/constants.py'])) - - if retcode != repo.hooks[hook_id].get('expected_return_value', 0): - for out in (stdout, stderr): - out = out.rstrip() - if len(out) > 0: - print out - return 1 - else: - return 0 + return _run_single_hook( + repo, + hook_id, + run_all_the_things=run_all_the_things, + ) else: print "No hook with id {0}".format(hook_id) return 1 @@ -60,6 +118,11 @@ def run(argv): help='Run a hook' ) + parser.add_argument( + '--run-fucking-everything', action='store_true', default=False, + help='Run on all the files in the repo', + ) + args = parser.parse_args(argv) if args.install: @@ -67,6 +130,6 @@ def run(argv): elif args.uninstall: return uninstall() elif args.run: - return run_single_hook(args.run) + return run_single_hook(args.run, run_all_the_things=args.run_fucking_everything) else: - return run_hooks(args) + return run_hooks(run_all_the_things=args.run_fucking_everything)