├── .jshintrc ├── README.md ├── addons └── module.sh ├── config.sh ├── git-hooks ├── helper.sh ├── hooks ├── commit-msg.sh ├── modules │ ├── debug.sh │ ├── js-console-call.sh │ ├── jshint.sh │ ├── phplint.sh │ ├── prevent-master-commit.sh │ └── prevent-merge-marker-commits.sh ├── post-commit.sh └── pre-commit.sh └── install.sh /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "predef": [ "jQuery", "BBBX", "$", "contain", "asyncTest" ], 4 | "strict": true, 5 | "white": true, 6 | "undef": true, 7 | "unused": false, 8 | "trailing": true, 9 | "camelcase": false 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | Git hooks is a little framework that I've written to easily add code validations and checks in our Git workflow every time a developer tries to commit code. 4 | 5 | This way you are sure that all code that is commited meets certain standards. 6 | 7 | # Checks 8 | 9 | For the moment we perform the following checks: 10 | 11 | * JShint 12 | * PHPLint 13 | * Prevent commit on master (We work with git flow so commit to the master is a big no no) 14 | * Check for merge markers 15 | * Commit message must at least be 8 characters 16 | * Check for uncommented console.log calls in javascript files which can break your code in older browsers like IE 17 | 18 | # Modules 19 | 20 | Some extra information about some of the modules 21 | 22 | ## JSHint 23 | 24 | You can overwrite the default jshint config by placing a `.jshintrc` file in the root of your repository. 25 | Git hooks also supports the use of `.jshintignore` in the root folder of your repo. 26 | 27 | # Install 28 | 29 | Git clone the repo in your home folder in .hooks directory: 30 | 31 | >cd ~
32 | >git clone https://github.com/Sitebase/git-hooks.git .hooks 33 | 34 | Create a link to the git-hooks script in /usr/bin 35 | 36 | >ln -s ~/.hooks/git-hooks /usr/bin/git-hooks 37 | 38 | Now to install the hooks on your repository 39 | 40 | > cd /path/to/my/repo
41 | > git hooks enable 42 | 43 | And that's it. 44 | If you now do a commit the needed commit checks will be run for that specific file extension. 45 | 46 | # Modules 47 | Modules currently only run when the pre-commit hook is called. 48 | 49 | ## Listing modules 50 | An overview of the installed modules and their current status can be retrieved using `git hooks module list`. 51 | 52 | ## Enabling and disabling modules 53 | These modules can be enabled and disabled using `git hooks module [enabled|disable] module_name`. 54 | 55 | # Addons 56 | Addons will be loaded into git-hooks whenever the `helper.sh` script is included, they reside in the `addons` folder. 57 | 58 | To include your own addon, simply add a shell script to the folder and it will be loaded. 59 | 60 | ## Addon convention 61 | Addons should have their functions start with a fixed prefix. For example `my_addon` as prefix will result in functions like `my_addon_usage`. 62 | 63 | Providing a function with only the prefix as name will allow you to use it on the commandline. For example `git hooks my_addon` 64 | 65 | # Update 66 | 67 | Just run `git pull` and all your projects that use hooks are up to date. 68 | 69 | # Skip checks 70 | 71 | Of course from time to time it will happen that a check fails but actually you now the code you've written is valid. The you can add `--no-verify` to skip the hooks. 72 | 73 | >git commit -m "Awesome new feature" --no-verify 74 | 75 | # Todo 76 | 77 | * Make PHP mess detection module 78 | * Make PHP check style module 79 | 80 | # Contributors 81 | This project is made possible due to the efforts of these fine people: 82 | 83 | * [Wim Mostmans](http://twitter.com/Sitebase) - Original author and maintainer
84 | * [Jorgen Evens](https://twitter.com/JorgenEvens) 85 | 86 | # License 87 | 88 | (The MIT License) 89 | 90 | Copyright (c) 2013 Wim Mostmans 91 | 92 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 93 | 94 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 95 | 96 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /addons/module.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | MODULE_CONFIG="${CONFIG}.module" 4 | 5 | module() { 6 | case "$1" in 7 | "list") 8 | module_list 9 | ;; 10 | "enable") 11 | module_enable $2 12 | ;; 13 | "disable") 14 | module_disable $2 15 | ;; 16 | *) 17 | module_usage 18 | ;; 19 | esac 20 | } 21 | 22 | module_dir() { 23 | echo "$(base_dir)hooks/modules/" 24 | } 25 | 26 | module_list() { 27 | for m in $(module_available); do 28 | status="disabled" 29 | if [ $(module_is_enabled "$m") -eq 1 ]; then 30 | status="enabled" 31 | fi 32 | echo "$(padRight "$status" 10 ) $m" 33 | done 34 | } 35 | 36 | module_usage() { 37 | echo "usage: git hooks module [list|[enable|disable] module]" 38 | } 39 | 40 | module_available() { 41 | echo `ls $(module_dir) | xargs -L1 basename` 42 | } 43 | 44 | module_is_enabled() { 45 | echo " $(module_enabled) " | grep -q " $1 " 46 | 47 | if [ $? -gt 0 ]; then 48 | echo 0 49 | else 50 | echo 1 51 | fi 52 | } 53 | 54 | module_enabled() { 55 | echo "$(config_get ${MODULE_CONFIG}.enabled)" 56 | } 57 | 58 | module_enable() { 59 | if [ $(module_is_enabled $1) -eq 0 ]; then 60 | enabled="$(module_enabled) $1" 61 | enabled=$(trim "$enabled") 62 | config_set "${MODULE_CONFIG}.enabled" "$enabled" 63 | fi 64 | } 65 | 66 | module_disable() { 67 | enabled=$( echo " $(module_enabled) " | sed "s/ $1 / /" ) 68 | enabled=$(trim "$enabled") 69 | config_set "${MODULE_CONFIG}.enabled" "$enabled" 70 | } 71 | 72 | module_firstuse() { 73 | if [ -z "$(config_get ${MODULE_CONFIG}.configured)" ]; then 74 | module_enable debug.sh 75 | module_enable prevent-master-commit.sh 76 | module_enable prevent-merge-marker-commits.sh 77 | config_set "${MODULE_CONFIG}.configured" "true" 78 | fi 79 | } 80 | 81 | module_firstuse -------------------------------------------------------------------------------- /config.sh: -------------------------------------------------------------------------------- 1 | # pre-commit 2 | pre_commit_modules="jshint.sh phplint.sh prevent-master-commit.sh prevent-merge-marker-commits.sh" 3 | 4 | # Colors 5 | RED=`printf '\033[1;31m'` 6 | GREEN=`printf '\033[1;32m'` 7 | WHITE=`printf '\033[1;37m'` 8 | GREY=`printf '\033[1;36m'` 9 | 10 | # Icons 11 | CHECK=`printf ${GREEN}'✔'${WHITE}` 12 | CROSS=`printf ${RED}'✘'${WHITE}` 13 | -------------------------------------------------------------------------------- /git-hooks: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPT_PATH="$0" 3 | while [ -h "$SCRIPT_PATH" ]; do SCRIPT_PATH=`readlink "$SCRIPT_PATH"`; done 4 | . "$(dirname $SCRIPT_PATH)/helper.sh" 5 | . "$(dirname $SCRIPT_PATH)/config.sh" 6 | 7 | REPO=`pwd` 8 | 9 | # Validations 10 | REPO_GIT_FOLDER="$REPO/.git/" 11 | [ ! -d $REPO_GIT_FOLDER ] && echo "${CROSS}${RED} The path you provided doesn't seem to be a Git repository because it doesn't contain a .git folder.${WHITE}" && exit 1 12 | 13 | enable() { 14 | echo "Install hooks" 15 | # Create symlinks 16 | echo "- Create symlinks to the hooks repo ..." 17 | ln -s "$(hooks_dir)pre-commit.sh" "$REPO_GIT_FOLDER/hooks/pre-commit" 18 | ln -s "$(hooks_dir)post-commit.sh" "$REPO_GIT_FOLDER/hooks/post-commit" 19 | ln -s "$(hooks_dir)commit-msg.sh" "$REPO_GIT_FOLDER/hooks/commit-msg" 20 | } 21 | 22 | disable() { 23 | # Remove existing hooks 24 | echo "- Remove existing hooks ..." 25 | rm "$REPO_GIT_FOLDER/hooks/post-commit" 2> /dev/null 26 | rm "$REPO_GIT_FOLDER/hooks/pre-commit" 2> /dev/null 27 | rm "$REPO_GIT_FOLDER/hooks/commit-msg" 2> /dev/null 28 | } 29 | 30 | usage() { 31 | echo "usage: git hooks [enable|disable]" 32 | } 33 | 34 | case "$1" in 35 | "enable") 36 | disable 37 | enable 38 | ;; 39 | "disable") 40 | disable 41 | ;; 42 | *) 43 | addon=$1 44 | if [ "$(type -t $1)" == "function" ]; then 45 | shift 46 | $addon $@ 47 | else 48 | usage 49 | fi 50 | ;; 51 | esac -------------------------------------------------------------------------------- /helper.sh: -------------------------------------------------------------------------------- 1 | # Get the absolute path of the script that has been called. 2 | SCRIPT_PATH="$0" 3 | while [ -h "$SCRIPT_PATH" ]; do SCRIPT_PATH=`readlink "$SCRIPT_PATH"`; done 4 | 5 | # Use our own name ( helper.sh ) as a marker for the git-hooks root 6 | ROOT_DIR=$(dirname "$SCRIPT_PATH") 7 | while [ ! -f "$ROOT_DIR/helper.sh" ] && [ ! "." == "$ROOT_DIR" ]; do ROOT_DIR=$(dirname "$ROOT_DIR"); done 8 | 9 | # Colors 10 | RED=`printf '\033[1;31m'` 11 | GREEN=`printf '\033[1;32m'` 12 | WHITE=`printf '\033[1;37m'` 13 | GREY=`printf '\033[1;36m'` 14 | 15 | # Icons 16 | CHECK=`printf ${GREEN}'✔'${WHITE}` 17 | CROSS=`printf ${RED}'✘'${WHITE}` 18 | 19 | CONFIG="git-hooks" 20 | 21 | # Char art 22 | HR=\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\#\# 23 | 24 | helloworld() { 25 | echo "usage: git timetrack command [options]" 26 | echo "" 27 | echo " -s, --start start/continue counting time spent" 28 | echo " -p, --stop stop/pause counting time spent" 29 | echo " -r, --reset reset counting time spent" 30 | echo " -e, --set set time spent in minutes" 31 | echo " -c, --current time spent currently in next commit" 32 | echo " -u, --summary show summary of total time spent" 33 | echo " addhook adds commit-msg hook to the project" 34 | } 35 | 36 | base_dir() { 37 | echo "$ROOT_DIR/" 38 | } 39 | 40 | hooks_dir() { 41 | echo "$(base_dir)hooks/" 42 | } 43 | 44 | addons_dir() { 45 | echo "$(base_dir)addons/" 46 | } 47 | 48 | h1() { 49 | echo "\n${WHITE}$1 ...\n" 50 | } 51 | 52 | fail() { 53 | echo "\t"${CROSS} ${GREY}$1${WHITE} 54 | } 55 | 56 | ok() { 57 | echo "\t"${CHECK} ${GREY}$1${WHITE} 58 | } 59 | 60 | # String helpers 61 | padRight() { 62 | input=$1 63 | length=$2 64 | char=$3 65 | i=${#input} 66 | 67 | if [ -z $char ]; then 68 | char=" " 69 | fi 70 | 71 | while [ $i -lt $length ]; do 72 | input="$input$char" 73 | i=${#input} 74 | done 75 | 76 | echo "$input" 77 | } 78 | 79 | trim() { 80 | echo $@ 81 | } 82 | 83 | # Config helpers 84 | config_get() { 85 | echo $(git config "$1" 2> /dev/null) 86 | } 87 | 88 | config_set() { 89 | git config "$1" "$2" 90 | } 91 | 92 | # Function to get a list of files that will be committed by extension 93 | # you can for example do "$(commit_files js css)" to get a list of js and css files that wil lbe commited 94 | commit_files() { 95 | 96 | if [ $# -eq 0 ] ; then 97 | echo $(git diff-index --name-only --diff-filter=ACM --cached HEAD --) 98 | exit 0 99 | fi 100 | 101 | extensions='' 102 | for extension in "$@" 103 | do 104 | extensions="${extensions}(${extension})|" 105 | done 106 | regex="\.(${extensions%?})$" 107 | echo $(git diff-index --name-only --diff-filter=ACM --cached HEAD -- | grep -E "$regex") 108 | } 109 | 110 | count_commit_files() { 111 | echo $(commit_files $@) | wc -w | tr -d ' ' 112 | } 113 | 114 | if [ "." == "$ROOT_DIR" ]; then 115 | fail "Could not locate git-hooks root directory." 116 | exit 1 117 | fi 118 | 119 | # Load config 120 | . $(base_dir)config.sh 121 | 122 | # Load addons 123 | for module in `ls $(addons_dir)*.sh 2> /dev/null`; do 124 | . $module 125 | done -------------------------------------------------------------------------------- /hooks/commit-msg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPT_PATH="$0" 3 | while [ -h "$SCRIPT_PATH" ]; do SCRIPT_PATH=`readlink "$SCRIPT_PATH"`; done 4 | . "$(dirname $SCRIPT_PATH)/../helper.sh" 5 | 6 | HOOK_ERROR=0 7 | MESSAGE=$(cat $1) 8 | LEN=$(echo ${#MESSAGE}) 9 | 10 | if [ $LEN -lt 8 ]; then 11 | echo "${CROSS}${RED} Such a short commit message? Give this commit a real meaning with a good commit message!${WHITE}" 12 | echo "" 13 | HOOK_ERROR=1 14 | fi 15 | 16 | exit $HOOK_ERROR -------------------------------------------------------------------------------- /hooks/modules/debug.sh: -------------------------------------------------------------------------------- 1 | if [ $(count_commit_files php) -eq 0 ] ; then 2 | return 0 3 | fi 4 | 5 | h1 "Debug code detect module" 6 | 7 | ERROR=0 8 | for file in $(commit_files php); do 9 | if egrep -n 'debugger|alert' $file >/dev/null ; then 10 | fail $file 11 | egrep -n 'debugger|alert' $file 12 | ERROR=1 13 | else 14 | ok $file 15 | fi 16 | done 17 | 18 | return $ERROR -------------------------------------------------------------------------------- /hooks/modules/js-console-call.sh: -------------------------------------------------------------------------------- 1 | if [ $(count_commit_files js) -eq 0 ] ; then 2 | return 0 3 | fi 4 | 5 | h1 "JS console call module" 6 | 7 | ERROR=0 8 | for file in $(commit_files js); do 9 | if grep -P '^(?!.*//.*console\.(log|error|info|debug))(?=.*console\.(log|error|info|debug))' $file >/dev/null ; then 10 | fail $file 11 | grep -P '^(?!.*//.*console\.(log|error|info|debug))(?=.*console\.(log|error|info|debug))' $file 12 | ERROR=1 13 | else 14 | ok $file 15 | fi 16 | done 17 | 18 | return $ERROR -------------------------------------------------------------------------------- /hooks/modules/jshint.sh: -------------------------------------------------------------------------------- 1 | if [ $(count_commit_files js) -eq 0 ] ; then 2 | return 0 3 | fi 4 | 5 | REPO_ROOT_JSHINTRC=$(pwd)"/.jshintrc" 6 | 7 | if [ -f $REPO_ROOT_JSHINTRC ] ; then 8 | JSHINTRC=$REPO_ROOT_JSHINTRC 9 | else 10 | JSHINTRC=$(base_dir)".jshintrc" 11 | fi 12 | 13 | JSHINT="jshint" 14 | 15 | h1 "JSHint module" 16 | 17 | ERROR=0 18 | for file in $(commit_files js); do 19 | if $JSHINT --config=$JSHINTRC $file 2>&1 | grep 'error' >/dev/null ; then 20 | fail $file 21 | $JSHINT --config=$JSHINTRC $file | sed "s/^/ ${GREY}--> /" | sed '$ d' | sed '$ d' 22 | ERROR=1 23 | else 24 | ok $file 25 | fi 26 | done 27 | 28 | return $ERROR -------------------------------------------------------------------------------- /hooks/modules/phplint.sh: -------------------------------------------------------------------------------- 1 | if [ $(count_commit_files php) -eq 0 ] ; then 2 | return 0 3 | fi 4 | 5 | h1 "PHPLint module" 6 | 7 | ERROR=0 8 | for file in $(commit_files php); do 9 | if php -l $file 2>&1 | grep 'No syntax errors' >/dev/null ; then 10 | ok $file 11 | else 12 | fail $file 13 | echo $(php -l $file) | sed "s/^/ ${GREY}--> /" 14 | ERROR=1 15 | fi 16 | done 17 | 18 | return $ERROR -------------------------------------------------------------------------------- /hooks/modules/prevent-master-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | h1 "Master protection" 4 | 5 | branch=`git symbolic-ref HEAD` 6 | if [ $branch == "refs/heads/master" ]; then 7 | fail "Direct commits to the branch master are not allowed" 8 | exit 1 9 | else 10 | ok "Branch is not master" 11 | fi -------------------------------------------------------------------------------- /hooks/modules/prevent-merge-marker-commits.sh: -------------------------------------------------------------------------------- 1 | h1 "Prevent merge marker commits" 2 | 3 | ERROR=0 4 | for file in $(commit_files); do 5 | if egrep -rls "^<<<<<<< |^>>>>>>> |^======= $" $file >/dev/null ; then 6 | fail $file 7 | ERROR=1 8 | fi 9 | done 10 | 11 | if [ $ERROR -eq 0 ]; then 12 | ok "No merge markers found" 13 | fi 14 | 15 | return $ERROR -------------------------------------------------------------------------------- /hooks/post-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Hook for the Git time tracker that we use 4 | hacking=$(git config --local timetrack.hacking) 5 | git timetrack -p > /dev/null 6 | spent=$(git config --local timetrack.spent) 7 | git config --local --remove-section timetrack 2> /dev/null 8 | 9 | if [ $spent ] 10 | then 11 | git notes --ref timetracker add -m "Time-spent: $spent" 12 | 13 | if [ $hacking ] 14 | then 15 | git config --local timetrack.start $(date +%s) 16 | git config --local timetrack.hacking $hacking 17 | fi 18 | fi -------------------------------------------------------------------------------- /hooks/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SCRIPT_PATH="$0" 3 | while [ -h "$SCRIPT_PATH" ]; do SCRIPT_PATH=`readlink "$SCRIPT_PATH"`; done 4 | . "$(dirname $SCRIPT_PATH)/../helper.sh" 5 | 6 | HOOK_ERROR=0 7 | 8 | echo ${WHITE} 9 | 10 | for module in $(module_enabled); do 11 | module_path="$(module_dir)$module" 12 | [ ! -e $module_path ] && continue 13 | . "$(module_dir)$module" 14 | if [ $? -eq 1 ] ; then 15 | HOOK_ERROR=1 16 | fi 17 | done 18 | 19 | if [ $HOOK_ERROR -eq 1 ] ; then 20 | echo "\n${CROSS}${RED} Time to fix your code!\n" 21 | else 22 | echo "\n${CHECK}${GREEN} Good job!\n" 23 | fi 24 | 25 | echo ${WHITE} 26 | exit $HOOK_ERROR -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | . "helper.sh" 2 | 3 | TMP_DIR=`mktemp -d /tmp/sitebase.hooks.XXXXXXXXXX` 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | HOOK_PRE_COMMIT="${DIR}/hooks/pre-commit.sh" 7 | HOOK_POST_COMMIT="${DIR}/hooks/post-commit.sh" 8 | HOOK_COMMIT_MSG="${DIR}/hooks/commit-msg.sh" 9 | 10 | echo "Install hooks" 11 | 12 | # Validations 13 | [ -z $1 ] && echo "${CROSS}${RED} Provide as argument a path to a git repository${WHITE}" && exit 1 14 | REPO_GIT_FOLDER="$1/.git/" 15 | [ ! -d $REPO_GIT_FOLDER ] && echo "${CROSS}${RED} The path you provided doesn't seem to be a Git repository because it doesn't contain a .git folder.${WHITE}" && exit 1 16 | 17 | # Remove existing hooks 18 | echo "- Remove existing hooks ..." 19 | rm "$REPO_GIT_FOLDER/hooks/post-commit" 20 | rm "$REPO_GIT_FOLDER/hooks/pre-commit" 21 | rm "$REPO_GIT_FOLDER/hooks/commit-msg" 22 | 23 | # Create symlinks 24 | echo "- Create symlinks to the hooks repo ..." 25 | ln -s "$(hooks_dir)pre-commit.sh" "$REPO_GIT_FOLDER/hooks/pre-commit" 26 | ln -s "$(hooks_dir)post-commit.sh" "$REPO_GIT_FOLDER/hooks/post-commit" 27 | ln -s "$(hooks_dir)commit-msg.sh" "$REPO_GIT_FOLDER/hooks/commit-msg" --------------------------------------------------------------------------------