Addicted to hooks
By Maxime Bréhin • Published on Apr 14, 2017

This post is also available in French.

Beyond Git commands and standard revisions cycle, we can use hooks around specific Git commands to help users automate daily tasks.

This article complements the official documentation and the the manual page.

Principles

Git hooks let you trigger scripts (Bash, Node.js, Perl, Python, PHP…) around existing commands. Using these, we can automate some of the user-side work to make it more reliable (and some server-side work, too.).

  • By default you'll find these hooks in each project in the .git/hooks directory.
  • They follow naming conventions & must be executable (chmod +x .git/hooks/…).
  • Because of their location they can be deleted or disabled by the user.

Therefore user-side hooks are more of an optional safeguard than an absolute barrier.

Alos note that users can circumvent a few hooks with using --no-verify option (available only for pre-commit and commit-msg hooks).

On each project initialization Git injects a collection of sample hooks, marked with the .sample file extension (e.g. .git/hooks/pre-commit.sample). Remove that extension to enable them (e.g. .git/hooks/pre-commit).

Blocking … or not!

A hook can be blocking. That means it can stop the command he's linked to.

By convention, every hook run before its associated command blocks (hooks named pre-[command]). Still, two hooks can be bypassed by using the --no-verify CLI option: pre-commit and commit-msg.

Always-blocking/non-bypassable hooks are:

  • on the client/developer side : prepare-commit-msg, pre-rebase, pre-apply-patch, pre-push, pre-auto-gc;
  • on the server side: pre-receive, update.

Git knows whether to stop or continue by looking at the script's exit code. Standard exit codes are expected. These boil down to:

  • 0 (zero): everything's fine, keep going;
  • ≥1: an error occured, abort the current Git operation.

For instance if we use a pre-commit script that ends with exit 1, then our commit won't be created/completed.

A real-world use case

We've got a script that stops each commit as long as the relevant files…

  • retain conflict markers;
  • contain instances of: TODO or FIXME.
#! /bin/bash
# If you encounter any error like `declare: -A: invalid option`
# then you'll have to upgrade bash version to v4.
# For Mac OS, see http://clubmate.fi/upgrade-to-bash-4-in-mac-os-x/

# Hash using the search regex as keys, and the matching error messages as values
declare -A PATTERNS;
PATTERNS['^[<>|=]{4,}']="You've got leftover conflict markers";
PATTERNS['TODO|FIXME']="You've got work to finish (TODO/FIME)";

# Declare empty errors array
declare -a errors;

# Loop over staged files and check for any specific pattern listed in PATTERNS keys
# Filter only added (A), copied (C), modified (M) files
for file in $(git diff --staged --name-only --diff-filter=ACM); do
  for elem in ${!PATTERNS[*]} ; do
    { git show :0:"$file" | grep -Eq ${elem}; } || continue;
    errors+=("${PATTERNS[${elem}]} in ${file}…");
  done
done

# Print errors
author=$(git config --get user.name)
for error in "${errors[@]}"; do
  echo -e "\[\033[1;31m\]${error}\[\033[0m\]"
  # Mac OS only: use auditable speech
  which -s say && say -v Samantha -r 250 "$author $error"
done

# If there is any error, then stop commit creation
if [ ${#errors[@]} -ne 0 ]; then
  exit 1
fi

Client-side hooks

These hooks are only available and triggered on the user side/machine. They're not shared within a project but we can achieve this in several ways:

Initializing a project using a template

When you're initializing a Git repository, you can tell Git that you'd like to use a project template using the --template=<template directory> CLI option:

  • when creating the project: git init --template=<template directory>;
  • when cloning a project: git clone --template=<template directory>.

This lets you manage multiple project templates and load the one you like on clone or init.

Using an external hooks directory

Since Git 2.9 we can set a global or local configuration setting to tell Git where our hooks live: core.hooksPath=<hooks directory>.

We can then manage a dedicated Git project for hooks that we'll be able to share and enhance. This is useful for reducing errors and maintenance. We don't have to copy/paste our hooks anymore from project to project, machine to machine!

Available hooks

  • Around commits:
    • pre-commit: before commit creation, even before message editing (e.g. linting, unit tests);
    • prepare-commit-msg: before commit creation, when everything's ready to start editing the message (e.g. pre-calculated message injection);
    • commit-msg: before commit creation, but after message editing (e.g. message content control and override);
    • post-commit: when the commit is done (e.g. notification);
  • Around patches (git am):
    • applypatch-msg: before the patch (e.g. check patch message);
    • pre-applypatch: after the patch is applied, but before the commit is created (e.g. patch content validation);
    • post-applypatch: when the path is applied and the commit is done (e.g. notify patch author);
  • Other actions:
    • pre-rebase: before starting git rebase (e.g. stop rebase of master branch);
    • post-checkout: after git checkout execution, for instance on rebase or at the end of a git clone (e.g. setting up a branch-associated configuration);
    • post-merge: after a successful git merge (e.g. check if there are conflict markers left after a "bad" merge);
    • post-rewrite: called by "rewriting" commands (git commit --amend, git rebase);
    • pre-auto-gc: on garbage collection (e.g. stop references clean-up if we have to use old ones);
    • pre-push: just before pushing revisions and objects to a remote repository (e.g. running unit tests and stop push if they they fail).

Server-side

When using SaaS like GitHub, GitLab or BitBucket, you can’t manually manage your server-side hooks. You'll have to use their APIs or plugins.

Otherwise, if you're hosting your own remote repositories or have access to the server, you just have to put your scripts on the server as you'd like.

Available server hooks

  • pre-receive: before receiving references/objects (e.g. check user rights on a project);
  • update: before receiving references/objects on a branch (e.g. check user rights on a specific branch);
  • post-update: after receiving references/objects on a branch (e.g. notify some users and ask for code review);
  • post-receive: after receiving all references (e.g. launch continuous integration, continuous deployment).

We recommend you use post-receive instead of post-update because you'll have access to previous references and push options that were used (this is what GitHub uses for push notifications, for instance).

Typical hooks schema

Here is a visual summary of frequent hooks and where they chime in, both client- and server-side:

Hooks like cycle example

Hook-based workflow example

When developing software, we'd like to enforce a few rules:

  • Commits quality:
    • Code must follow conventions (e.g. code linting): pre-commit;
    • We must ensure that there is no conflict marker left after a conflicting merge: pre-commit;
    • Unit tests have to pass without error: pre-commit;
    • Commit messages must follow conventions: prepare-commit-msg, commit-msg;
  • Repository and branch access: update;
  • Build chain:
    • Before merging branches, we want to ensure:
      • automatic validations (Continuous Integration): update;
      • no-regression validations (e.g. minimum percentage of test coverage we expect): update;
      • manual validations (code review, fonctional testing): post-receive;
    • We want to automate deployment on stable environments (staging, production): post-update ou post-receive.

TL;DR

Git hooks help us automate and improve our workflow.

Many tools build upon the hook system to automate common needs already.

Do not forget: your workflows can and should be improved regularly, so stay curious and check what others suggest.

Learn more about hooks…

To go further with hooks, read the following links for complementary documentations and tools:

Check out our video courses! 🖥

Our screencasts are the ideal, affordable complement to our tech articles and in-room training courses, tackling Git, JavaScript and other topics. Check out these high-quality, affordable courses that are specially crafted to take on your biggest pain points and roadblocks.