Conditioning Yourself With Git Hooks

What are Git Hooks?

Git hooks allow users to execute scripts before or after specific Git events. These can be executed either locally or server-side on a git repository. The Git documentation and Atlassian Git Tutorial provide more in-depth explanations about git hooks and how to use them.

In this post, I will cover some of the more common use cases for git hooks and how I use them to condition myself over time with some snippets of code included. There are no limits as to what can be done with git hooks besides your own imagination.

git hooks

Enforcing Best Practices and Standards

When contributing to any project, there tends to be a set of guidelines outlining the procedure one must follow before having their contributions merged. There may be many other factors involved, but I believe that it is to ensure that the code style remains consistent, maintainable, and is of quality work.

While there are plenty of linter plugins out there for many of the editors we use daily, how often do we actually pay attention to the warnings shown? There may also be times when we don’t notice them at all. They simply don’t force us to address them immediately or stop us from committing them. We usually end up being alerted later on by an external service or during the review process. Instead, you can pre-emptively stop that from happening before you end up heading into a downwards spiral. Git hooks help.

Increased Error Visibility

The pre-commit hook can be used to force us to address issues before allowing us to pass through the gate and commit the changes we’ve made. This allows us to minimize the amounts of commits made to address simple code styling issues while keeping our git history and code both clean and readable.

#!/bin/bash
# This obtains the list of files staged for the current commit and executes an
# additional script that runs the correct linter depending on the filename and
# would then output the list of files that have errors.

staged_files=`git diff --cached --name-only`
/usr/bin/node /path/to/linter-processing-script.js $staged_files

Preventing Mistakes Beforehand

There are always a number of issues that can happen unintentionally due to a variety of reasons. This can range from simple merge conflicts to quick hotfixes to mental exhaustion. There isn’t a perfect way to prevent some of these issues from happening, but we can always try to minimize when it occurs.

Common Pitfalls

While linters can help us locate syntax issues and debugger statements accidentally left in the code, we can also use the pre-commit hook to check for unresolved merge conflicts, secret keys, private information, or are you working on the master branch? There are plenty of actions that can be done to help prevent these common mistakes from happening.

#!/bin/bash

branch=$(git rev-parse --abbrev-ref HEAD | grep -e 'master')
if [ $? -eq 0 ]; then
  echo "!!!WARNING!! You should not be working on the master branch."
  echo "Are you sure you want to continue? [y/N]"
  read confirmation
  if [ "$confirmation" != "Y" ] || [ "$confirmation" != "y" ]; then
    exit 1
  fi
fi

conflicts=`git diff --cached --name-only -S '<<<<<< HEAD'`
if [ -n "$conflicts" ]; then
  echo "!!COMMIT REJECTED!! You have merge conflicts in the following file(s):"
  echo "$conflicts"
  exit 1
fi

stashed=`git diff --cached --name-only -S '<<<<<<< Updated upstream'`
if [ -n "$stashed" ]; then
  echo "!!COMMIT REJECTED!! You have stashed conflicts in the following file(s):"
  echo "$stashed"
  exit 1
fi

Protect Yourself and Everyone

The pre-push hook can be used to “protect” a branch from being pushed to for various reasons. Is it the end of the day? Are you exhausted mentally and/or physically? This is usually around the time you begin to wrap things up. If there isn’t a good reason to start pushing everything up, you can make sure that your sanity is still intact before allowing yourself to push up commits that may potentially break something. Usually, it can wait.

#!/bin/bash
# We want to make sure that we still have our sanity and are sure we want to
# continue working after 6PM. This can be adjusted accordingly.

sanity_time='18'
hour_of_day=`date +%H`

if [ "$hour_of_date" -ge "$sanity_time"]; then
  echo "It looks like it's close to the end of the day. Do you still have your sanity? Are you really sure you want to push these commits? [y/N]"
  read $confirmation
  if [ "$confirmation" != "Y" ] || [ "$confirmation" != "y" ]; then
    exit 1
  fi
fi

Being a Team Player

I believe that it is very important that commit messages contain meaningful information. This makes it extremely easy for anyone to traverse the git history to find when a bug was introduced and locate the root cause of the issue. We also want to make sure that the code review process remains fast and simple. Cleaning up many of these issues allows everyone to save even more time in the end.

Proper Commit Messages

The commit-msg hook can be used in combination with a spellchecker to ensure that your commit message is readable and doesn’t contain any errors. If there is an error, we can always use git commit --amend to fix it before pushing it.

#!/bin/bash

aspell_path="/usr/bin/aspell"

errors=$($aspell_path list < "#1")
if [ -n "$errors" ]; then
  echo "There are some possible spelling errors found in the commit message."
  echo "Use git commit --amend to verify that your commit message is correct."
fi

The End Game

The main goal is to condition myself to address any possible issues early and to reinforce any coding standards I’m working with so that it becomes second nature. What were the results? The amount of time required to review my code for a merge, looking up code style rules, and making mistakes is shrinking. You aren’t the only one that benefits. So does everyone you work with.

More Posts by Hung Tran: