This article explains how to create git hooks in mac, and how to use the customized git hook chains.
Preparation for creating hook chain
(This part refers to the hook-chain, and make some modification for running in mac.)
Motivation is that when you run git commit, the pre-commit script will be invoked before commit operation actually happen. If you want to do multiple checks in the pre-commit phrase, instead of putting all logic in one file of pre-commit, a better idea is separating the check logic in separated files, and trigger each check one by one as a chain.
Create git templates hooks folder
1
2mkdir -p ~/.git-templates/hooks
cd ~/.git-templates/hooksAll files in this folder will be copied to each git project’s hook folder when running git init.
Create hook-chain file under the hooks folder
file name : hook-chain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
hookname=`basename $0`
git_folder=`dirname $0`
for hook in $(ls $git_folder/$hookname.*)
do
if [ -f $hook ]
then
echo "executing hook : $hook"
sh "$hook"
status=$?
if test $status -ne 0; then
echo "Hook $hook failed with error code $status"
exit $status
fi
fi
doneWhat this do is executing all hook files whose name begin with the current git hook. For example, when invoking git hook of pre-commit, this will invoke all scripts starting with pre-commit, such as pre-commit.json, pre-commit.yaml, etc.
Create hook files
Refer to git hooks for all client side git hooks and their meaning. For example, pre-rebase, pre-commit, post-checkout, post-merge, pre-push, etc. Create the ones that you need under ~/.git-templates/hooks.
In my case, I want to create pre-commit hook, so I run:
1
ln -s hook-chain pre-commit
Then I will also create pre-commit.json, pre-commit.yaml, pre-commit.awskey in the same folder as explained in the following sessions. When pre-commit is invoked, it will trigger all these three files of pre-commit.json, pre-commit.yaml and pre-commit.awskey one by one.
Note that each hook file should be executable.
1
chmod +x ~/.git-templates/hooks/.
Write customized hooks
Prevent commit the aws access key and secret key
(script is from)
- file name : pre-commit.awskey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Install globally using https://coderwall.com/p/jp7d5q/create-a-global-git-commit-hook
# The checks are simple and can give false positives. Amend the hook in the specific repository.
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
EMPTY_TREE=$(git hash-object -t tree /dev/null)
against=$EMPTY_TREE
fi
# Redirect output to stderr.
exec 1>&2
# Check changed files for an AWS keys
FILES=$(git diff --cached --name-only $against)
if [ -n "$FILES" ]; then
KEY_ID=$(grep -rE --line-number '(^|[^A-Za-z0-9/+=])AKIA[A-Z0-9]{16}($|[^A-Za-z0-9/+=])' $FILES)
KEY=$(grep -rE --line-number '(^|[^A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}($|[^A-Za-z0-9/+=])' $FILES)
if [ -n "$KEY_ID" ] || [ -n "$KEY" ]; then
exec < /dev/tty # Capture input
echo "=========== Possible AWS Access Key IDs ==========="
echo "${KEY_ID}"
echo ""
echo "=========== Possible AWS Secret Access Keys ==========="
echo "${KEY}"
echo ""
while true; do
read -p "[AWS Key Check] Possible AWS keys found. Commit files anyway? (y/N) " yn
if [ "$yn" = "" ]; then
yn='N'
fi
case $yn in
[Yy] ) exit 0;;
[Nn] ) exit 1;;
* ) echo "Please answer y or n for yes or no.";;
esac
done
exec <&- # Release input
fi
fi
# Normal exit
exit 0
Prevent commiting invalid json files
- file name : pre-commit.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
git_dir=$(git rev-parse --show-toplevel)
for file in $(git diff-index --name-only --diff-filter=ACM --cached HEAD -- \
| grep -E '\.((js)|(json))$'); do
python -mjson.tool $file 2> /dev/null
if [ $? -ne 0 ] ; then
read -p "Find unbroken json in $git_dir/$file, is that what you intended? [y|n] " -n 1 -r < /dev/tty
echo
if echo $REPLY | grep -E '^[Yy]$' > /dev/null
then
exit 0
fi
exit 1
fi
done
Prevent commiting yaml files with quote in key
Note that standard yaml allows containing quote in keys, but that is probably not something we intented to do.
file name : pre-commit.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
git_dir=$(git rev-parse --show-toplevel)
for file in $(git diff-index --name-only --diff-filter=ACM --cached HEAD -- \
| grep -E '\.((yaml)|(yml))$'); do
python3 "${git_dir}/.git/hooks/is_yaml_key_contains_quote.py" $file
if [ $? -ne 0 ] ; then
read -p "Find quote in key in $git_dir/$file, is that what you intended? [y|n] " -n 1 -r < /dev/tty
echo
if echo $REPLY | grep -E '^[Yy]$' > /dev/null
then
exit 0
fi
exit 1
fi
donefile name : is_yaml_key_contains_quote.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31#!/usr/bin/env python3
import sys
import yaml
import json
def find_quote_in_key(json_obj):
if not json_obj:
return
if type(json_obj) is list:
for item in json_obj:
find_quote_in_key(item)
if type(json_obj) is dict:
for key, value in json_obj.items():
if "\"" in key or "'" in key:
print("quote is in : {}".format(key))
global is_quote_found
is_quote_found = True
find_quote_in_key(value)
file_path = sys.argv[1]
is_quote_found = False
yaml_content = ""
with open(file_path, 'r') as stream:
yaml_content = yaml.load(stream)
json_obj = json.dumps(yaml_content)
find_quote_in_key(json.loads(json_obj))
if is_quote_found:
exit(1)
exit(0)Note : It is needed to install python3 and pyymal for running this python hook.
1
2brew install python3
pip3 install pyyaml
Preventing push to the master branch
(Script is from)
- file name : pre-push.protect-master
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected_branch='push-test'
current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,')
if [ $protected_branch = $current_branch ]
then
read -p "You're about to push $protected_branch, is that what you intended? [y|n] " -n 1 -r < /dev/tty
echo
if echo $REPLY | grep -E '^[Yy]$' > /dev/null
then
exit 0
fi
exit 1
else
exit 0
fi
Don’t forget to execute : ln -s hook-chain pre-push.
Install hooks
In any git project, run git init will install the hooks under .git/hooks/ folder, however, the existing files won’t be overriden, meaning it is needed to delete the existing hooks in the git repository after updating the hook scripts.
1 | rm .git/hooks/* |
I suggest creating an alias command for doing that.
Copy the following in the ~/.profile file
1 | alias my_git_init='rm .git/hooks/*; git init' |
Then run my_git_init under the git project will always delete the existing hooks and install the latest ones.