One of the nicer tools a developer has is the linter. The linter helps you conform to a clean, unified and easy to read format of your code. When I join a project that has no automated lint set up, adding lint to the flow is one of the first steps I take.
In the past, I used to set up lint on the build server. That way, no pull request (PR) would get merged which breaks the formatting rules of the project. However, this had a few flaws. First, unless squashed, your git history had lint fix commits all over. Second, it would really slow you down as a developer. You’d create a PR, and then wait for a few minutes – only for the build server to tell you your formatting is wrong.
It was only fairly recently that I learned about git hooks. In a project I joined, they were using them in the pre-push step to prevent you from pushing code that was not properly linted. Pretty cool. But we could do better. If we moved linting to the pre-commit step, you couldn’t even commit a file that was not linted. Now, you don’t even need a separate commit for the lint fixes.
The linting script on that project was also scanning the whole project. This works really well when your project is already linted. However, if you’re setting this up on an existing project, you could never commit anything without fixing the whole project. To fix that, I rewrote the lint script to only iterate just over the newly staged files. For Macs, the script looks like this:
#!/bin/bash echo "Running lint..." git diff --name-only --cached | grep "\.kt" | while read fn; do ktlint "${fn}"; done RESULT_KTLINT=$? [ $RESULT_KTLINT -ne 0 ] && exit 1 exit 0
For Windows, it looks like this:
#!C:/Program\ Files/Git/usr/bin/sh.exe echo "Running lint..." git diff --name-only --cached | grep "\.kt" | while read fn; do ktlint "${fn}"; done RESULT_KTLINT=$? [ $RESULT_KTLINT -ne 0 ] && exit 1 exit 0
The above examples run ktlint, for Kotlin projects. You should be able to port them to any other language fairly easily.
There’s still one problem. Every developer has to copy the relevant script manually to their .git/hooks folder (and name them “pre-commit”). This can be solved by automating the copying task. In gradle, it can be done by updating your app build.gradle like so:
task installGitHook(type: Copy) {
def suffix = "macos"
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
suffix = "windows"
}
from new File(rootProject.rootDir, "automation/scripts/pre-commit-$suffix")
into { new File(rootProject.rootDir, '.git/hooks') }
rename("pre-commit-$suffix", 'pre-commit')
fileMode 0775
}
tasks.getByPath(':app:preBuild').dependsOn installGitHook
And voila! The first time a developer builds the project, the appropriate git hook gets installed. Don’t you just love automation?
Grateful for shharing this
LikeLiked by 1 person