├── .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"
--------------------------------------------------------------------------------