git pre-commit Hooks
A very common use case for hooks in git is a "pre-commit" hook. This hook is used to verify the to-be-committed data before it is added to the repository.
One important note: hooks are not part of the repository itself. Everyone can install a hook on it's own checkout of a repository, but by default the hook is not there when you clone/checkout the repository. This avoids security problems by executing arbitrary code during "git commit", or any "git" operation.
Because of this implication it is common that developers install a hook from somewhere in the repository into the ".git/hooks" directory. And in addition, the server side (the repository) can run the same checks during "git push", to enforce the rules.
Hooks in git work in a simple way: whatever program or script is run as the hook has to set a return code. If the return code is "0", git proceeds. If it's not "0", git aborts the operation.
Back to the hook: the first problem is how to identify the to-be-committed files? "git diff" lists the files:
git diff --cached --name-only --diff-filter=ACM
The options:
- --staged: lists all files which are stated for the next commit. --staged is an alias for --cached, both versions can be used.
- --name-only: lists only the filenames, not the entire diff.
- --diff-filter: selects what kind of files will be included in the diff.
- A: Added files
- C: Copied files
- M: Modified files
The "git diff" above does not select "Renamed" or "Deleted" files. A renamed file might violate naming rules, in this case you have to include this option as well. And a deleted file is about to be removed from the repository, and most likely does not need to be checked.
Let's put this all together into a small shell script which runs as the git hook. In my example I want to enforce two things:
- No double spaces in the file
- No spaces at the end of the line
Here is the ".git/hooks/pre-commit" example:
#!/bin/bash
set -e
echo "pre-flight checks:"
file_list=`git diff --cached --name-only --diff-filter=ACM`
exit_status=0
while IFS= read -r file; do
if [[ "$file" =~ ^content/post/* ]];
then
echo "checking file: $file ..."
if grep -q ' ' "$file";
then
echo "Double spaces in file!"
exit_status=1
fi
if egrep -q ' +$' "$file";
then
echo "Spaces at end of line!"
exit_status=1
fi
fi
done <<< "$file_list"
if [ "$exit_status" -eq "0" ];
then
echo "Pre-flight check passed!"
exit 0
else
echo "There are some errors ..."
exit 1
fi
The first part identifies the changed files. The loop runs over all changed files, and checks them for the two conditons. The $exit_status variable is set by any error. Finally the last if-then-else part returns an error if there is any problem in the files.
Comments
Display comments as Linear | Threaded
Stephan on :
Andreas 'ads' Scherbaum on :
Sascha Henke on :
Sebastian on :
Andreas 'ads' Scherbaum on :