devTeaa
Posted on July 31, 2023
The following example will use nodejs tools, but you can use any similiar tools in your language. Also this post assumes you already have linting tools configured in your project.
Git hooks is one of git features which will trigger a custom scripts when a certain action occurs. There is multiple git-hooks actions, but today we will use 2 of client side hooks which will help you or your team writes better commit message and changes.
pre-commit
This hooks will be triggered before commiting the changes to git, we will use this hooks to lint/optimize our changes using below scripts. SVGO is an amazing npm package that will optimize your svg files, the same implementation on https://jakearchibald.github.io/svgomg/
{
"scripts": {
// This will allow the lint to automatically fix our staged files
"format-eslint": "eslint --fix",
"format-prettier-eslint": "prettier-eslint --log-level 'silent' --write --eslint-config-path .eslintrc.js",
"format-stylelint": "stylelint --config .stylelintrc.json --fix",
"optimize-svgo": "svgo"
}
}
#!/bin/bash
PASS=true
# 'git diff --diff-filter=d --cached --name-only' will output the staged file names
# with 'grep -E' we can capture the output and filter out the files we wanted to lint using RegExp
STAGED_FILES=$(git diff --diff-filter=d --cached --name-only | grep -E '.*\.(js|vue|ts|svg)$')
# This will exit the script if there is no files matching the pattern
if [[ "$STAGED_FILES" = "" ]]; then
exit 0
fi
# '\033[0;33m' is yellow bash color
# '\e[0m' means we reset the color back to normal
echo -e "\033[0;33m☐ Running pre-commmit-hooks:\e[0m"
for FILE in $STAGED_FILES
do
# This will capture the initial has of the file, we use this to detect if there is changes applied after linting
PRE_LINT_HASH=$(git hash-object "$FILE")
# Only run eslint/prettier/stylelint on vue/js/ts files
if [[ $FILE == *vue || $FILE == *js || $FILE == *ts ]];
then
# Only run stylelint on vue files since other file types doesn't contain stylesheet
if [[ $FILE == *vue ]];
then
npm run --silent format-stylelint "$FILE" || PASS=false
npm run --silent format-prettier-eslint "$FILE" || PASS=false
npm run --silent format-eslint "$FILE" || PASS=false
else
npm run --silent format-prettier-eslint "$FILE" || PASS=false
npm run --silent format-eslint "$FILE" || PASS=false
fi
# SVGO optimize svg files
elif [[ $FILE == *svg ]]
then
npm run optimize-svgo "$FILE"
fi
POST_LINT_HASH=$(git hash-object "$FILE")
# If there is changes detected, we will set the PASS flag to false to stop the commit from continuing
if [[ "$PRE_LINT_HASH" != "$POST_LINT_HASH" ]]; then
PASS=false
fi
done
# Stop the commit if there is file changes during above process
if ! $PASS; then
# '\033[0;31m' is red bash color
echo -e "\033[0;31m☒ pre-commmit-hooks failed, please check all the errors or stage all changes\e[0m"
# 'exit 1' will stop the process by throwing error
exit 1
fi
exit $?
The output will looks something like this
-> % git commit -m "foo"
☐ Running pre-commmit-hooks:
~/project/foo.js
82:11 error Identifier 'foo_fighter' is not in camel case camelcase
✖ 1 problem (1 error, 0 warnings)
☒ pre-commmit-hooks failed, please check all the errors or stage all changes
prepare-commit-msg
This hooks will be triggered before entering the commit message CLI. Sometimes we make silly or undescriptive commit message, using this hooks we will append our branch name into the beginning of the commit message so we can see the branch name during git blame. If you're using JIRA or any other issue tracker board, usually we create a branch name with their codes, example: git checkout -b PROD-34
.
#!/bin/bash
# This way you can customize which branches should be skipped when
# prepending commit message.
if [ -z "$BRANCHES_TO_SKIP" ]; then
BRANCHES_TO_SKIP=(master)
fi
# Get current commit branch name
BRANCH_NAME=$(git symbolic-ref --short HEAD)
BRANCH_NAME="${BRANCH_NAME##*/}"
BRANCH_EXCLUDED=$(printf "%s\n" "${BRANCHES_TO_SKIP[@]}" | grep -c "^$BRANCH_NAME$")
BRANCH_IN_COMMIT=$(grep -c "\[$BRANCH_NAME\]" $1)
# '-n' is a test pattern, we check if the branch name matches with the one we wanted to skip
if [ -n "$BRANCH_NAME" ] && ! [[ $BRANCH_EXCLUDED -eq 1 ]] && ! [[ $BRANCH_IN_COMMIT -ge 1 ]]; then
# This will append the commit message with '[branch-name]:'
sed -i.bak -e "1s/^/[$BRANCH_NAME]: /" $1
fi
With above script if we do
-> % git commit -m "foo"
[PROD-34 abc123456] [PROD-34]: foo
1 files changed, 2 insertions(+), 3 deletions(-)
create mode 100644 foo.js
But we can't install git-hooks on remote project, only locally
Yes, that's why we're going to use the postinstall
script. This will get triggered after running npm install
, we will create a simple nodejs
script to copy the script files we've prepared into .git/hooks
folder after user has finished installing the project node_modules
. Here I'm saving my hooks into git-hooks
folder.
{
"scripts": {
"postinstall": "node build/post-install.js",
}
}
// post-install.js
const fs = require('fs')
// destination will be created or overwritten by default.
fs.copyFile('./git-hooks/prepare-commit-msg', './.git/hooks/prepare-commit-msg', err => {
if (err) throw err
console.log('File was copied to destination')
})
// destination will be created or overwritten by default.
fs.copyFile('./git-hooks/pre-commit', './.git/hooks/pre-commit', err => {
if (err) throw err
console.log('File was copied to destination')
})
And that's how you can use git hooks to create some automation for your team code quality. You can check my dotfiles if you would like to see other helpful script that I used during development.
Github
Posted on July 31, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.