Fast JSHint Git Pre-Commit Hook
If you use a linter like JSHint for your JavaScript projects, here is a way to automatically ensure all code is linted before commiting.
You could have a very simple pre-commit hook like this:
#!/bin/sh
#
# Lints files with jshint before commiting them.
jshint
However, if you have a large project linting the entire project can become very slow. This will make git commit
very slow and annoying. The only files that need to be linted are the files you are commiting. The pre-commit hook below uses git to lint only the files that have been staged for commiting and is much faster.
#!/bin/sh
#
# Lints staged files with jshint before commiting them.
# get the files that we should lint
files="$(
# NOTE: the file paths returned are relative to the git root despite the current working directory
# get all staged files
git diff --name-only --diff-filter=ACMRTUXB --cached |
# only lint files that end with .js
grep '\.js$'
)"
# cd up to the git root
# example: if we are in {gitRoot}/fu/bar/ then cd ../../
cd "$(git rev-parse --show-cdup)"
# get the current working directory (now git root) absolute path
cwd="$(pwd)"
# escape sed special chars and add trailing slash
# we will use this with sed below
sedCWD="$(echo $cwd | sed -e 's/\\/\\\\/g' -e 's/\//\\\//g' -e 's/&/\\\&/g')\/"
finalRetVal=0
# for each staged file...
for file in $files
do
# lint the STAGED content of the file (the colon tells git to output the staged content)
git show :"$file" |
# this assumes you have a .jshintrc config file at the git root
# the dash at the end tells jshint to lint stdin
jshint --config .jshintrc --filename "$file" - |
# jshint displays the absolute path for each file which is annoying
# lets trim that so they are relative to the git root
sed "s/^$sedCWD//"
# get the return value of the jshint command (second command in the pipeline)
retVal=${PIPESTATUS[1]}
# if any of the linting failed we should return a non-zero status
if [ $retVal != 0 ]
then
finalRetVal=$retVal
fi
done;
# use the status of the last failed jshint command or 0 if none failed
exit $finalRetVal;
Save this as {gitRoot}/.git/hooks/pre-commit
and make it executable with chmod +x pre-commit
. Now when you commit all staged files will be linted. Any errors will cause the commit to fail.
You can use a similar script to lint all changed files in your project at any time. I describe how here.