• Skip to main content

JS

Git Hooks for Mortals

July 18, 2022 by Jason Short

Continuing my series of opinionated local development environment implementations, today I will document my git hooks configuration.

The two main contenders are pre-commit and git-hooks. I’ve settled on git-hooks, mostly because I prefer to assemble my own wrappers.

Installation

brew install git-hooks-go

Configuration

Hook scripts should be installed into each workspace that requires hooks:

git-hooks install

Templates can also be set for future clone or init use:

git config --global init.templatedir '<path>'

Where <path> is a copy of the git-hooks scripts. ~/.git-templates is a popular location. Existing repositories (without hooks) can have these template hooks applied via git init.

Hooks

Hooks can exist at several scopes:

  • Local hooks are executed from $REPO/githooks/<hook>
  • Global hooks are executed from ~/.githooks/<hook>

where <hook> is a git action, such as pre-commit

Entrypoints in these paths must be executable. Here’s an example git diff-index whitespace check from my global pre-commit hooks:

$ cat ~/.githooks/pre-commit/whitespace.sh
#!/bin/sh

if git rev-parse --verify HEAD >/dev/null 2>&1
then
  against=HEAD
else
  # Initial commit: diff against an empty tree object
  against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

git diff-index --check --cached $against --

Here’s a similarly convoluted example for wrapping tools that are not git-aware:

$ cat githooks/pre-commit/shellcheck.sh
#!/usr/bin/env bash

set -efu -o pipefail

if git rev-parse --verify HEAD &> /dev/null
then
  against=HEAD
else
  against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
fi

( git diff-index --name-only $against | grep '\.sh$' || true ) | while read -r file ; do
  if [[ -f "${file}" ]] ; then
    shellcheck "${file}"
  fi
done

Or something simpler, like golangci-lint which is significantly more context-aware out of the box than shellcheck:

$ cat githooks/pre-commit/go
#!/bin/sh
golangci-lint run

In both of the above cases, the paths referenced are symlinks to a common hook storage path, rather than copies.

Filed Under: Technical