Danny Krämer
Philosopher gone Frontend Developer

Using Git Hooks With Husky.js

Written by Danny Krämer

Published on: 24.01.2024

What are git hooks anyway?

Everybody knows this situation: You set up linters and formatting, you make sure the unit tests are running and all is green. You check out the development branch and the first thing you see is the linter screaming at you. Someone pushed to the development branch without pleasing the allmighty linter overloard. You jump onto slack and ask if the person could fix their code, because you know, we are caring about code quality here. Or more often than not you fix it yourself.

Here git hooks come in handy. Git hooks are a built in feature of git. Hooks are scripts that can be run in most of the stages of your git workflow (pre-commit, pre-push etc). All you have to do is put your scripts (they must be executable, so you can write them in whatever language you like) in the $GIT_DIR/hooks directory. But there is a problem: The .git directoy is not part of the repository. You cannot commit them and share them with your team easily. Here husky.js comes in handy.

TIPP: Your scripts must be executable. So make sure that the access permissions are set right. On Linux machines you can do this with chmod +x <script-file>

Husky.js for managing Git Hooks

Husky.js is a npm package that helps you to organize and share your git hooks. In theory you could just let every developer change the core.hooksPath property in their local git repository to a directory with the shared scripts. Husky helps you to automate this process. It adds a prepare script to your package.json that sets the core.hooksPath property to a .husky folder in the repository and builds some scaffolding around your hooks. All a new Dev in your project has to do is checkout the repository, run npm install and npm run prepare and the hooks will work for her. She can also add some of her own hooks for example with npx husky add .husky/pre-commit "npm test".

TIPP: Here is a basic setup for a new project.

  1. npm install husky --save-dev
  2. If your package.json is in the same directory as your .git directory run npx husky install otherwise you can install husky in a custom directory npx husky install ~/project/.husky
  3. You can add the prepare lifecycle script into your package.json. The script will be run after npm install. If you don’t want to auto install husky you can just give it another name and run it manually.
  4. Add a hook with npm husky add .husky/pre-commit "npm test"

So Husky is a lightweight package that takes an annoying task of setting up the shared git hooks of your team and basically wraps it into one terminal command.

Useful Scripts

Automate all the things meme

But what are some useful scripts to run in your git workflow?

Prepare-commit-msg check for Jira ID

This hook will run if you git commit right after preparing the default log message, and before the editor is started. Here is a bash script that checks if the branch name contains a Jira ID. If so it checks if the Jira ID of the branch name and the ID of the commit message are the same. If the branch name contains a Jira ID and the commit message does not, it adds the Jira ID of the branch name to the commit mesasge.

#!/usr/bin/env bash
. "$(dirname -- "$3")/_/husky.sh"

# Git Hook for JIRA_TASK_ID
# Adds to the top of your commit message `JIRA_TASK_ID`, based on the prefix of the current branch `feature/AWG-562-add-linter`
# Example: `Add SwiftLint -> `AWG-562 Add SwiftLint

if [ -z "$BRANCHES_TO_SKIP" ]; then
  BRANCHES_TO_SKIP='master feature'
fi

CURRENT_BRANCH=$(git branch --show-current)
for BRANCH in $BRANCHES_TO_SKIP; do
  if [ "$BRANCH" = "$CURRENT_BRANCH" ]; then
    echo "Info, skipping Jira ID check since current branch is included in the 'BRANCHES_TO_SKIP' variable"
    exit 0
  fi
done

COMMIT_FILE=$1
COMMIT_MSG=$(cat "$1")
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
JIRA_ID_REGEX="[A-Z0-9]{1,10}-?[A-Z0-9]+"
JIRA_ID_IN_CURRENT_BRANCH_NAME=$(echo "$CURRENT_BRANCH" | { grep -Eo "$JIRA_ID_REGEX" || true; })
JIRA_ID_IN_COMMIT_MESSAGE=$(echo "$COMMIT_MSG" | { grep -Eo "$JIRA_ID_REGEX" || true; })

if [ -n "$JIRA_ID_IN_COMMIT_MESSAGE" ]; then
  if [ "$JIRA_ID_IN_COMMIT_MESSAGE" != "$JIRA_ID_IN_CURRENT_BRANCH_NAME" ]; then
    echo "Error, your commit message JIRA_TASK_ID='$JIRA_ID_IN_COMMIT_MESSAGE' is not equal to current branch JIRA_TASK_ID='$JIRA_ID_IN_CURRENT_BRANCH_NAME'"
    exit 1
  fi
elif [ -n "$JIRA_ID_IN_CURRENT_BRANCH_NAME" ]; then
  echo "$JIRA_ID_IN_CURRENT_BRANCH_NAME $COMMIT_MSG" > "$COMMIT_FILE"
  echo "JIRA ID '$JIRA_ID_IN_CURRENT_BRANCH_NAME', matched in current branch name, prepended to commit message. (Use --no-verify to skip)"
fi

Pre-push run unit tests and linters

This script runs before you push to remote. It goes to the directory of the package.json and runs the unit tests and linting scripts. If one of those ends with an exit code other than 0 the push is aborted.

#!/usr/bin/env sh
. "$(dirname -- "$3")/_/husky.sh"

# runs frontend unit tests and linters
cd ~/project/ && npm run test:prod && npm run lint:js && npm run lint:style

If you want to see some more scripts that could be useful check out this repository: https://github.com/aitemr/awesome-git-hooks

TIPP: You can skip all the git hooks if you add the flag --no-verify to your git command.

git

software development

tooling