Apply Spotless formatting with Git pre-commit hook

Michael Messell
3 min readSep 27, 2021

--

Tech stack: Java, Gradle, Git, Shell script

In your project you might want to ensure that team members only commit code that is properly formatted. There are multiple ways to do this. An approach could be to stop the build pipeline, if a Git repo does not pass a formatting analysis.

The approach presented in this article allows the formatting to be done automatically every time the programmer commits changes.

Prerequisites

Pre-commit script

The pre-commit script consists of 3 parts.

  1. Assign all staged files to a variable: This variable will contain the files, which the developer is about to commit. This is needed as all files with changes might not be staged.
  2. Apply the formatting: This is the part where files might be altered during the formatting. You could also run other plugin in addition to the formatting.
  3. Re-add staged files: All files that were identified in Part 1 are staged with git add. A test is needed to check if the file exists, as a staged file might have been deleted.
#!/bin/sh# Part 1
stagedFiles=$(git diff --staged --name-only)
# Part 2
echo "Running spotlessApply. Formatting code..."
./gradlew spotlessApply
# Part 3
for file in $stagedFiles; do
if test -f "$file"; then
git add $file
fi
done

It should be noted that it is not recommended to call ‘git add’ in a Git hook script, and especially not on the pre-commit. This could potentially add code changes to the commit, which the developer is not responsible for.

Install pre-commit script

Installing the pre-commit script file is as simple as putting a file named pre-commit into the .git/hooks folder.

The .git folder is not transferred when cloning, fetching or pushing code to your repo. This means that making sure that everyone on your team has the script installed is an issue. A manual process for copying the pre-commit script to the .git/hooks folder is error prone, and might result in unformatted code being pushed to the repo.

One way to automate this process is to rely on a Gradle task that you are often using.

  1. Have a checked-in version of the pre-commit script
    e.g. ./scripts/pre-commit
  2. Now have Gradle copy this file into the .git/hooks folder one every execution.
tasks.register('updateGitHooks', Copy) {
from './scripts/pre-commit'
into './.git/hooks'
}
compileJava.dependsOn updateGitHooks

Conclusion

This article presents a working approach of how formatting could be handled in a distributed manner. It is quite simple to set up and does not rely on your build pipeline.

Even though it is not recommended to call git add in a pre-commit script I think that with this use case, it can make some sense. The alterations to the committed code is purely formatting.

It should be said that calling ./gradlew spotlessApply adds additional overhead when committing. I have not tried the approach with an older repo, and do not know if it scales. Also if you belong to the people who only commit partial changes to a file, you might wanna use git stash and git stash pop to not commit unstaged changes.

I have tried the approach with a repo of a considerable size, and can say that I have been quite satisfied.

--

--