DEV Community

devTeaa
devTeaa

Posted on

How to use git-hooks to make better commit

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.

git hooks flow

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"
  }
}
Enter fullscreen mode Exit fullscreen mode
#!/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 $?
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
  }
}
Enter fullscreen mode Exit fullscreen mode
// 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')
})
Enter fullscreen mode Exit fullscreen mode

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.

LinkedIn

Github

Top comments (0)