A Git Pre-Commit Hook for Running Checkstyle via Maven

At Black Pepper we use Checkstyle by default on any new Java project to enforce code style. Integrated with Maven, this will fail the build in the event of any violations. This frees up developers enormously, allowing them to focus on important decisions rather than arguing about where to put their opening braces, and prevents the intent of Git commits from being lost in whole-file IDE reformatting.

Unfortunately, unlike in Eclipse, Checkstyle integration with IDEA is pretty clunky. Violations are almost invisible unless you have the culprit file open in the editor, and as code refactorings often create violations in other files, there’s a good chance these won’t be spotted.

In fact, it sometimes seems that the majority of CI failures are caused by Checkstyle errors. Developers often have the discipline to run any tests they think may have broken before pushing back a new feature; what they’re less good at is visually verifying that the maximum line length hasn’t been exceeded in a file due to some refactoring operation, or manually running the IDEA Checkstyle tool.

Running Checkstyle Automatically

It would be nice to run Checkstyle automatically in our workflow somehow, to catch violations before they occur on CI, thus saving ourselves a tedious context switch when we break the build. I’ve got very simple requirements for what I’d like here:

  1. It must run before every commit. I don’t want any commits in my local repository with violations, or else I feel like I have to go back in history and edit them to prevent them being pushed back broken.
  2. It must be quick. Really quick. I’ve heard horror stories about extensive build jobs being run in Git hooks, and how these would inevitably get disabled to prevent developers’ utter exasperation. You should be committing all the time. Anything over a few seconds would be unacceptable.

A Git Hook for running Checkstyle

First off, the simplest possible content of a file at ${GIT_DIR}/hooks/pre-commit:

mvn checkstyle:check

This works, but completely fails on requirement 2):

  • It executes Maven even if Java files haven’t changed, and so no violations could possibly occur; and
  • It executes on every submodule of the top-level project POM, even if nothing has changed within them.

It takes about ten seconds on my current project, on every single commit. We can do better.

Doing Better

#!/bin/bash -e
function get_module() {
  local path=$1
  while true; do
    path=$(dirname $path)
    if [ -f "$path/pom.xml" ]; then
      echo "$path"
      return
    elif [[ "./" =~ "$path" ]]; then
      return
    fi
  done
}

modules=()

for file in $(git diff --name-only --cached \*.java); do
  module=$(get_module "$file")
  if [ "" != "$module" ] \
      && [[ ! " ${modules[@]} " =~ " $module " ]]; then
    modules+=("$module")
  fi
done

if [ ${#modules[@]} -eq 0 ]; then
  exit
fi

modules_arg=$(printf ",%s" "${modules[@]}")
modules_arg=${modules_arg:1}

export MAVEN_OPTS="-client
  -XX:+TieredCompilation
  -XX:TieredStopAtLevel=1
  -Xverify:none"

mvn -q -pl "$modules_arg" checkstyle:check

Now this is a big improvement:

  • We only consider staged .java files;
  • For each of these, we walk up the project filesystem tree looking for any pom.xml files, and if found, assume this is a module we need to run Checkstyle on; and
  • We add some JVM options to the Maven invocation to encourage the JVM to start up a bit quicker.

On my current project (greenfield, 8 weeks of development to date and 8 Maven submodules) this hook runs in 2-3 seconds for most commits – and it’s already saved me from breaking the build numerous times. Latest source on GitHub – a big thanks to Nick “*nix Wizard” Holloway for the code review.

comments powered by Disqus