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
- --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.