Skip to content

Instantly share code, notes, and snippets.

@malloc47
Created October 19, 2015 14:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save malloc47/12d79be6f59654cddc8c to your computer and use it in GitHub Desktop.
Save malloc47/12d79be6f59654cddc8c to your computer and use it in GitHub Desktop.
Python pre-commit hook
#!/usr/bin/env python
# Inspired by: https://gist.github.com/thraxil/3123935
# Differs from above link by using the results of `git show`
# instead of using `git stash`, which prevents unintended
# side-effects
import os
import re
import subprocess
import sys
import atexit
import traceback
gitcmd = 'git'
tmpfile = 'check.tmp'
CHECKS = [
{
'output': 'Checking for pdbs...',
'command': 'grep -n "import pdb" %s',
'match_files': ['.*\.py$'],
'ignore_files': ['.*scripts/.*'],
},
{
'output': 'Checking for print statements...',
'command': "grep -n '\sprint(' %s",
'match_files': ['.*\.py$'],
'ignore_files': ['.*scripts/.*'],
},
{
'output': 'Checking for tabs...',
'command': 'grep -n -P "\t" %s',
'match_files': ['.*\.py$'],
},
{
'output': 'Running pyflakes...',
'command': "pyflakes %s",
'match_files': ['.*\.py$'],
'ignore_files': ['.*scripts/.*'],
},
{
'output': 'Running pep8...',
# ignore line too long, whitespace before :, etc.
'command': "pep8 --ignore=E501,E203 %s",
'match_files': ['.*\.py$'],
'ignore_files': ['.*scripts/.*'],
}
]
CMDS = [
{
'output' : 'Running git whitespace check...',
'command' : 'git diff --cached --check'
}
]
def matches_file(file_name, match_files):
return any(re.compile(match_file).match(file_name) for match_file in match_files)
@atexit.register
def exit():
try:
os.remove(tmpfile)
except:
pass
def check_files(files, check):
result = 0
print(check['output'])
def strip_prefix(s,p):
return s[len(p):] if s.startswith(p) else s
for file_name in files:
if not 'match_files' in check or matches_file(file_name, check['match_files']):
if not 'ignore_files' in check or not matches_file(file_name, check['ignore_files']):
process = subprocess.Popen(gitcmd
+ ' show :'
+ file_name
+ '> '
+ tmpfile
+' && '
+ check['command'] % 'check.tmp',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
out, err = process.communicate()
if out or err:
prefix = '\t%s:' % file_name
output_lines = ['%s%s' % (prefix, strip_prefix(line,tmpfile+':'))
for line
in out.splitlines()]
print('\n'.join(output_lines))
if err:
print(err)
result = 1
return result
def main(all_files):
files = []
if all_files:
for root, dirs, file_names in os.walk('.'):
for file_name in file_names:
files.append(os.path.join(root, file_name))
else:
p = subprocess.Popen([
gitcmd,
'diff-index',
'-z',
'--cached',
'HEAD',
'--name-only',
'--diff-filter=AM'], stdout=subprocess.PIPE)
f,err = p.communicate()
files = filter(bool,f.split('\0'))
result = 0
# for one-off commands
for cmd in CMDS:
print(cmd['output'])
result = (subprocess.call(cmd['command'], shell=True)
or result)
# for command that must be run on multiple files
for check in CHECKS:
result = check_files(files, check) or result
if not result:
print('No errors found!')
else:
print('Errors found!')
sys.exit(result)
if __name__ == '__main__':
all_files = False
if len(sys.argv) > 1 and (sys.argv[1] == '--all-files' or sys.argv[1] == '-a'):
all_files = True
try:
main(all_files)
except KeyboardInterrupt:
print("Early termination requested...")
# always fail out when exiting early
sys.exit(1)
except Exception:
traceback.print_exc(file=sys.stdout)
sys.exit(1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment