How to use Git Hooks to automate development tasks?

4 minute read

Introduction

If you frequently clone new repositories, it can be a challenge to adhere to the specific development policy. New project, new development team, new software development policy. This doesn’t sound familiar to you? Then perhaps one of the following:

  • “It would be cool if I could do X automatically befor pushing my Git commits.”
  • “I still keep forgetting running my unit tests in between each Git commit.”
  • “We have to prefix our Git commit message with the project ticket number.” (see Examples)

So.. Git hooks to the rescue! They allow us to customize the behavior of Git and trigger actions that are automatically executed when a specific Git event occurs. Development teams can enforce commit policies and implement CI workflows. You can use Git hooks to automate almost any aspect of your development workflow.1

I’ll cover only local hooks and not server-side hooks, and show two small examples and since the .git folder is always ignored, we can not share those scripts out-of-the-box within our development team, so let’s have a look at how we can share our Git hooks aswell.

Prerequisites

  • Git v2.9.02 as a minimum

Getting started

After executing the git init command, Git creates a new directory with the following structure:

local repository
├── .git
|   ├── hooks
|   |   ├── commit-msg.sample
|   |   ├── pre-commit.sample
|   |   ├── prepare-commit-msg.sample
|   |   ├── pre-push.sample
|   |   └── ...
|   ├── ...
|   ├── config
|   ├── description
|   └── HEAD
├── .gitignore
└── README.md

One of the folders is called hooks and contains only files with the extension .sample. The comment section of these files states that these pre-defined Git hooks become active when the filename extension .sample is removed.

For a full list of all available Git hooks, see: https://git-scm.com/docs/githooks#_hooks

Make sure that the Git hook is executable, otherwise it won’t run.

To change the script interpreter, you can change the first line of the file (Shebang #!/bin/sh) to your preferred scripting language.

Configuration

Since we cannot share the Git hooks from the .git directory, we create a new folder with the name .git-hooks and put all Git hooks into this folder.

Advise your development team that they will need to change the default path to the new Git hooks folder for this to take effect. To change the path of the hooks for the current local repository, run the command:

git config core.hooksPath ./.git-hooks

It’s a good idea to add this step to the Getting Started / Setup section of your repository’s README.md file. This is a common place that every developer should read after cloning the repository.

If you want to take this one step further and copy Git hooks to the new repository you create/clone, see Git Template: https://git-scm.com/docs/git-init#_template_directory

Examples

If you decide that it fits well into your development process to add the ticket number to every single commit message, perhaps if you decide not to squash commits after you have completed the pull request, it can become very tedious for developers to add the ticket number every time, especially for those who commit in small increments and therefore a lot.

Let’s take a look at two examples. One where we trigger a validation step and another where a prefix is added to the commit message. Both examples come from the boring execution of adding the ticket number as prefix to the commit message. Normally we already name the branch with the ticket number, so why bother and just add it automatically.

commit-msg: Validate Commit Message

If you do not name your branch after a specific ticket number, but still want to check the commit message prefixed with a ticket number, you can use the following script.

Content of the commit-msg file:

#!/bin/sh

FILE_COMMIT_EDITMSG=$1
COMMIT_MSG=`head -n 1 $FILE_COMMIT_EDITMSG`
REGEX_RESULT=$(echo $COMMIT_MSG | grep -Eo '(\[[A-Z]+-[0-9]+\]\s)')
if [[ $REGEX_RESULT == "" ]]; then
  echo "Git commit message '$COMMIT_MSG' is missing a ticket number. Example '[OTTER-1234] add component'"
  exit 1
fi

prepare-commit-msg: Prefix Commit Message

Usually, the name of the branch already contains the ticket number. Therefore, we can automatically insert the ticket number into the commit message based on the name of the current branch.

Content of the prepare-commit-msg file:

#!/bin/sh

FILE_COMMIT_EDITMSG=$1
COMMIT_MSG=`head -n 1 $FILE_COMMIT_EDITMSG`
BRANCH_NAME=`git rev-parse --abbrev-ref HEAD`
REGEX_RESULT=$(echo $BRANCH_NAME | grep -Eo '([A-Z][A-Z0-9]+-[0-9]+)')
if [ $REGEX_RESULT == "" ]; then
  echo "The current branch name does not contain a valid ticket number. Example 'feature/OTTER-1234-new-feature'"
  exit 1;
fi

echo "$REGEX_RESULT $COMMIT_MSG" > $FILE_COMMIT_EDITMSG

Further Reading

A few additional sources you can read to dive a bit deeper into the Git hooks topic:

Conclusion

I hope these two practical examples will inspire you to get creative and create your own Git hooks. You can basically add every step you want, you are only limited by your skills and imagination. So.. automate the boring stuff and enforce local development policies! Please don’t bloat it too much to ensure fast and smooth development.

Hope you learned something new.

See you around 🦦

Tags:

Categories:

Updated: