Many projects force their code to be formatted. We use spotless for this purpose. It can check for propper formatting, and also format the code for you. Then build pipeline checks if the code is properly formatted. Failing pipelines due to formatting errors are annoying and cost a lot of time and money. This blog proposes a solution.

Git pre-commit hook

Git has some hooks that you can install. Hooks are scripts that are run at specific events such as pre-commit, post-commit, pre-push, prepare-commit-msg, pre-rebase, etc. I created a script that formats the code in the pre-commit hook. It does:

  1. This scripts tracks any of the java or kotlin files in the staging area.

  2. Formats the code using spotless if there are any java/kotlin files.

  3. It adds the files from step 1 again to the staging area, to pick up the re-formatted changes.

The commit now contains formatted code. This saves a lot of time, instead of finding out that the build failed 5 minutes later.

The pre-commit hook looks like:

#!/bin/bash
set -e

# command taken from https://github.com/JLLeitschuh/ktlint-gradle  task addKtlintFormatGitPreCommitHook
filesToFormat="$(git --no-pager diff --name-status --no-color --cached | awk '$1 != "D" && $2 ~ /\.kts|\.java|\.kt/ { print $2}')"

echo "files to format $filesToFormat"
for sourceFilePath in $filesToFormat
do
  ./gradlew spotlessApply -PspotlessIdeHook="$(pwd)/$sourceFilePath"
  git add $sourceFilePath
done;

If you use another formatter, you can easily update the script.

You can install it by running the following command in your repository root:

curl -o .git/hooks/pre-commit https://gist.githubusercontent.com/toefel18/23c8927e28b7e7cf600cb5cdd4d980e1/raw/163337f2ae596d4b3a55936c2652b1e8227db5b7/pre-commit && chmod +x ./.git/hooks/pre-commit

The commit hook in action

Assume that our repository has the current status with 1 file staged, and another not staged:

$> git status
On branch demo-pre-commit-hook
Your branch is up to date with 'origin/master'.

Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified:   src/main/java/net/project/MyController.java

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified:   src/main/java/net/project/AnotherController.java

Now running a git commit would format our files and only add MyController.java back to staging, in order to include it in the commit.

$> git commit -m "Committing!"
files to format src/main/java/net/project/MyController.java

> Configure project :
Inferred project: blog-project, version: 1.0

BUILD SUCCESSFUL in 1s
7 actionable tasks: 1 executed, 6 up-to-date
re-adding src/main/java/net/project/MyController.java after formatting
[DBA-330-added-more-asserts 768387f] Test
 1 file changed, 1 insertion(+)



$> git status
On branch demo-pre-commit-hook
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified:   src/main/java/net/project/AnotherController.java

You can use git show HEAD to verify that only the staged files are part of the commit, and they are formatted.

Update 2020-12-05

The latest version only re-formats files in the staging area. It does not run spotless apply on the whole project anymore and then just add the formatted files.

shadow-left