#!/bin/sh # git pre-commit hook that runs an Uncrustify stylecheck. # Features: # - abort commit when commit does not comply with the style guidelines # - create a patch of the proposed style changes # # More info on Uncrustify: http://uncrustify.sourceforge.net/ # This file is part of a set of unofficial pre-commit hooks available # at github. # Link: https://github.com/ddddavidmartin/Pre-commit-hooks # Contact: David Martin, ddddavidmartin@fastmail.com ################################################################## # There should be no need to change anything below this line. # For configuration see pre-commit-uncrustify.cfg and # pre-commit-uncrustify.example.cfg. # exit on error set -e # Absolute path to this script, e.g. /home/user/bin/foo.sh SCRIPT="$(readlink -f "$0")" # Absolute path this script is in, e.g. /home/user/bin/ SCRIPTPATH="$(dirname -- "$SCRIPT")" CONFIG="$SCRIPTPATH/pre-commit-uncrustify.cfg" REPO="$(git rev-parse --show-toplevel)" if [ ! -f "$CONFIG" ] ; then echo "Missing config file $CONFIG." exit 1 else . "$CONFIG" fi # check whether the given file matches any of the set extensions matches_extension() { local filename="$(basename -- "$1")" local extension=".${filename##*.}" local ext for ext in $FILE_EXTS; do [ "$ext" = "$extension" ] && return 0; done return 1 } # necessary check for initial commit if git rev-parse --verify HEAD >/dev/null 2>&1 ; then against=HEAD else # Initial commit: diff against an empty tree object against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 fi # make sure the config file and executable are correctly set if [ ! -f "$UNCRUST_CONFIG" ] ; then printf "Error: uncrustify config file not found.\n" printf "Set the correct path in $CONFIG.\n" exit 1 fi if ! command -v "$UNCRUSTIFY" > /dev/null ; then printf "Error: uncrustify executable not found.\n" printf "Set the correct path in $CONFIG.\n" exit 1 fi # create a filename to store our generated patch prefix="pre-commit-uncrustify" suffix="$(date +%C%y-%m-%d_%Hh%Mm%Ss)" patch="/tmp/$prefix-$suffix.patch" # clean up any older uncrustify patches $DELETE_OLD_PATCHES && rm -f /tmp/$prefix*.patch # create one patch containing all changes to the files # sed to remove quotes around the filename, if inserted by the system # (done sometimes, if the filename contains special characters, like the quote itself) git diff-index --cached --diff-filter=ACMR --name-only $against -- | \ sed -e 's/^"\(.*\)"$/\1/' | \ while read file do # ignore file if we do check for file extensions and the file # does not match any of the extensions specified in $FILE_EXTS if $PARSE_EXTS && ! matches_extension "$file"; then continue; fi # escape special characters in the source filename: # - '\': backslash needs to be escaped # - '*': used as matching string => '*' would mean expansion # (curiously, '?' must not be escaped) # - '[': used as matching string => '[' would mean start of set # - '|': used as sed split char instead of '/', so it needs to be escaped # in the filename # printf %s particularly important if the filename contains the % character file_escaped_source=$(printf "%s" "$file" | sed -e 's/[\*[|]/\\&/g') # escape special characters in the target filename: # phase 1 (characters escaped in the output diff): # - '\': backslash needs to be escaped in the output diff # - '"': quote needs to be escaped in the output diff if present inside # of the filename, as it used to bracket the entire filename part # phase 2 (characters escaped in the match replacement): # - '\': backslash needs to be escaped again for sed itself # (i.e. double escaping after phase 1) # - '&': would expand to matched string # - '|': used as sed split char instead of '/' # printf %s particularly important if the filename contains the % character file_escaped_target=$(printf "%s" "$file" | sed -e 's/[\"]/\\&/g' -e 's/[\&|]/\\&/g') # Uncrustify detects the language automatically if it is not specified language_option="" if [ "$SOURCE_LANGUAGE" != "AUTO" ] ; then language_option="-l $SOURCE_LANGUAGE" fi # uncrustify our sourcefile, create a patch with diff and append it to our $patch # The sed call is necessary to transform the patch from # --- $file timestamp # +++ - timestamp # to both lines working on the same file and having a a/ and b/ prefix. # Else it can not be applied with 'git apply'. "$UNCRUSTIFY" -c "$UNCRUST_CONFIG" -f "$file" $language_option | \ diff -u -- "$file" - | \ sed -e "1s|--- $file_escaped_source|--- \"a/$file_escaped_target\"|" -e "2s|+++ -|+++ \"b/$file_escaped_target\"|" >> "$patch" done # if no patch has been generated all is ok, clean up the file stub and exit if [ ! -s "$patch" ] ; then printf "Files in this commit comply with the uncrustify rules.\n" rm -f "$patch" exit 0 fi # a patch has been created, notify the user and exit printf "\nThe following differences were found between the code to commit " printf "and the uncrustify rules:\n\n" cat "$patch" printf "\nYou can apply these changes with:\n git apply $patch\n" printf "(may need to be called from the root directory of your repository)\n" printf "Aborting commit. Apply changes and commit again or skip checking with" printf " --no-verify (not recommended).\n" exit 1