Automate validating code changes with Git hooks

What could be more annoying than committing code changes to repository and noticing afterwards that formatting isn’t right or tests are failing? Your automated tests on Continuous Integration shows rain clouds and you need to get back to the code and fix minor issues with extra commits polluting the git history? Fortunately with small enhancements to your development workflow you can automatically prevent all the hassle and check your changes before committing them. The answer is to use Git hooks for example on pre-commit for running linters and tests.

Git Hooks

Git hooks are scripts that Git executes before or after events such as: commit, push, and receive. They’re a built-in feature and run locally. Hook scripts are only limited by a developer’s imagination. Some example hook scripts include:

  • pre-commit: Check the commit for linting errors.
  • pre-receive: Enforce project coding standards.
  • post-commit: Email team members of a new commit.
  • post-receive: Push the code to production.

Every Git repository has a .git/hooks folder with a script for each hook you can bind to. You’re free to change or update these scripts as necessary, and Git will execute them when those events occur.

Git hooks can greatly increase your productivity as a developer as you can automate tasks and ensure that your code is ready for commit or pushing to remote repository.

For more reading about Git hooks you can check missing Git hooks documentation, read the basics and check tutorial how to use Git hooks on local Git clients and Git servers.

Pre-commit

One productive way to use Git hooks is pre-commit framework for managing and maintaining multi-language pre-commit hooks. Read tips for using a pre-commit hook.

Pre-commit is nice for example running linters to ensure that your changes conform to coding standards. All you need is to install pre-commit and then add hooks.

Installing pre-commit, ktlint and pre-commit-hook on MacOS with Homebrew:

$ brew install pre-commit
$ brew install ktlint
$ ktlint --install-git-pre-commit-hook

For example the pre-commit hook to run ktlint with auto-correct option looks like the following in projects .git/hooks/pre-commit. The “export PATH=/usr/local/bin:$PATH” is for SourceTree to find git on MacOS.

#!/bin/sh
export PATH=/usr/local/bin:$PATH
# https://github.com/shyiko/ktlint pre-commit hook
git diff --name-only --cached --relative | grep '\.kt[s"]\?$' | xargs ktlint -F --relative .
if [ $? -ne 0 ]; then exit 1; else git add .; fi

The main disadvantage is using pre-commit and local git hooks is that hooks are kept within .git directory and it never comes to the remote repository. Each contributor will have to install them manually in his local repository which may be overlooked.

Maven projects

Githook Maven plugin deals with the problem of providing hook configuration to the repository and automates their installation. It binds to Maven projects build process and configures and installs local git hooks.

It keeps a mapping between the hook name and the script by creating a respective file in .git/hooks for each hook containing given script in Maven project’s initial lifecycle phase. It’s good to notice that the plugin rewrites hooks.

Usage Example:

<build>
    <plugins>
	<plugin>
	    <groupId>org.sandbox</groupId>
	    <artifactId>githook-maven-plugin</artifactId>
	    <version>1.0.0</version>
	    <executions>
	        <execution>
	            <goals>
	                <goal>install</goal>
	            </goals>
	            <configuration>
	                <hooks>
	                    <pre-commit>
	                         echo running validation build
	                         exec mvn clean install
	                    </pre-commit>
	                </hooks>
	            </configuration>
	        </execution>
	    </executions>
	</plugin>
    </plugins>
</build>

Git hooks for Node.js projects

On Node.js projects you can define scripts in package.json and run them with npm which enables an another approach to running Git hooks.

🐶 Husky is Git hooks made easy for Node.js projects. It keeps existing user hooks, supports GUI Git clients and all Git hooks.

Installing Husky is like any other npm library

npm install husky --save-dev

The following configuration on your package.json runs lint (e.g. eslint with –fix) command when you try to commit and runs lint and tests (e.g. mocha, jest) when you try to push to remote repository.

"husky": {
   "hooks": {
     "pre-commit": "npm run lint",
     "pre-push": "npm run lint && npm run test"
   }
}

Another useful tool is lint-staged which utilizes husky and runs linters against staged git files.

Summary

Make your development workflow easier by automating all the things. Check your changes before committing them with pre-commit, husky or Githook Maven plugin. You get better code and commit quality for free and your team is happier.

This article was originally published at 15.7.2019 on Gofore’s blog.

Code quality metrics for Kotlin project on SonarQube

Code quality in software development projects is important and a good metric to follow. Code coverage, technical debt, vulnerabilities in dependencies and conforming to code style rules are couple of things you should follow. There are some de facto tools you can use to visualize things and one of them is SonarQube. Here’s a short technical note of how to setup it on Kotlin project and visualize metrics from different tools.

Including what analysis SonarQube’s default plugins provide we are also using Detekt for static source code analysis and OWASP Dependency-Check to detect publicly disclosed vulnerabilities contained within project dependencies.

Visualizing Kotlin project metrics on SonarQube

SonarQube is nice graphical tool to visualize different metrics of your project. Lately it has started to support also Kotlin with SonarKotlin plugin and sonar-kotlin plugin. From typical Java project you need some extra settings to get things working. It’s also good to notice that the support for Kotlin isn’t quite yet there and sonar-kotlin provides better information i.e. what comes to code coverage

Steps to integrate reporting to Sonar with maven build:

  • Add configuration in project pom.xml: Surefire, Failsafe, jaCoCo, Detekt, Dependency-Check
  • Run Sonar in Docker
  • Maven build with sonar:sonar option
  • Check Sonar dashboard
SonarQube overview
SonarQube project overview

Configure Kotlin project

Configure your Kotlin project built with Maven to have test reporting and static analysis. We are using Surefire to run unit tests, Failsafe for integration tests and JaCoCo generates reports for e.g. SonarQube. See the full pom.xml from example project (coming soon).

Test results reporting

pom.xml

<properties> 
<sonar.coverage.jacoco.xmlReportPaths>${project.build.directory}/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths> 
</properties> 

<build> 
    <plugins>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>default-prepare-agent</id>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <execution>
                    <id>pre-integration-test</id>
                    <goals>
                        <goal>prepare-agent-integration</goal>
                    </goals>
                </execution>
                <execution>
                    <id>jacoco-site</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <skipTests>${unit-tests.skip}</skipTests>
                <excludes>
                    <exclude>**/*IT.java</exclude>
                    <exclude>**/*IT.kt</exclude>
                    <exclude>**/*IT.class</exclude>
                </excludes>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-failsafe-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <skipTests>${integration-tests.skip}</skipTests>
                <includes>
                    <include>**/*IT.class</include>
                </includes>
                <runOrder>alphabetical</runOrder>
            </configuration>
        </plugin>
    </plugins> 

    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.22.1</version>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.3</version>
            </plugin>
        </plugins>
    </pluginManagement>

... 
</build> 

Static code analysis with Detekt

Detekt static code analysis configuration as AntRun. There’s also unofficial Maven plugin for Detekt. It’s good to notice that there are some “false positive” findings on Detekt and you can either customize detekt rules or suppress findings if they are intentional such as @Suppress(“MagicNumber”).

Detekt code smells
Detekt code smells

pom.xml

<properties> 
    <sonar.kotlin.detekt.reportPaths>${project.build.directory}/detekt.xml</sonar.kotlin.detekt.reportPaths> 
</properties> 

<build> 
... 
<plugins> 
<plugin> 
    <groupId>org.apache.maven.plugins</groupId> 
    <artifactId>maven-antrun-plugin</artifactId> 
    <version>1.8</version> 
    <executions> 
        <execution> 
            <!-- This can be run separately with mvn antrun:run@detekt --> 
            <id>detekt</id> 
            <phase>verify</phase> 
            <configuration> 
                <target name="detekt"> 
                    <java taskname="detekt" dir="${basedir}" 
                          fork="true" 
                          failonerror="false" 
                          classname="io.gitlab.arturbosch.detekt.cli.Main" 
                          classpathref="maven.plugin.classpath"> 
                        <arg value="--input"/> 
                        <arg value="${basedir}/src"/> 
                        <arg value="--filters"/> 
                        <arg value=".*/target/.*,.*/resources/.*"/> 
                        <arg value="--report"/> 
                        <arg value="xml:${project.build.directory}/detekt.xml"/> 
                    </java> 
                </target> 
            </configuration> 
            <goals> 
                <goal>run</goal> 
            </goals> 
        </execution> 
    </executions> 
    <dependencies> 
        <dependency> 
            <groupId>io.gitlab.arturbosch.detekt</groupId> 
            <artifactId>detekt-cli</artifactId> 
            <version>1.0.0-RC14</version> 
        </dependency> 
    </dependencies> 
</plugin> 
</plugins> 
... 
</build> 

Dependency checks

Dependency check with OWASP Dependency-Check Maven plugin

OWASP Dependency-Check
OWASP Dependency-Check

pom.xml

<properties> 
    <dependency.check.report.dir>${project.build.directory}/dependency-check</dependency.check.report.dir> 
    <sonar.host.url>http://localhost:9000/</sonar.host.url> 
    <sonar.dependencyCheck.reportPath>${dependency.check.report.dir}/dependency-check-report.xml</sonar.dependencyCheck.reportPath>
    <sonar.dependencyCheck.htmlReportPath>${dependency.check.report.dir}/dependency-check-report.html</sonar.dependencyCheck.htmlReportPath>
</properties> 

<build> 
... 
<plugins> 
<plugin> 
    <groupId>org.owasp</groupId> 
    <artifactId>dependency-check-maven</artifactId> 
    <version>4.0.2</version> 
    <configuration> 
        <format>ALL</format> 
        <skipProvidedScope>true</skipProvidedScope> 
        <skipRuntimeScope>true</skipRuntimeScope> 
        <outputDirectory>${dependency.check.report.dir}</outputDirectory> 
    </configuration> 
    <executions> 
        <execution> 
            <goals> 
                <goal>check</goal> 
            </goals> 
        </execution> 
    </executions> 
</plugin> 
</plugins> 
... 
</build>

Sonar scanner to run with Maven

pom.xml

<build> 
... 
    <pluginManagement> 
        <plugins> 
            <plugin> 
                <groupId>org.sonarsource.scanner.maven</groupId> 
                <artifactId>sonar-maven-plugin</artifactId> 
                <version>3.6.0.1398</version> 
            </plugin> 
        </plugins> 
    </pluginManagement> 
... 
</build> 

Running Sonar with Kotlin plugin

Create a SonarQube server with Docker

$ docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube 

There’s also OWASP docker image for SonarQube which adds several community plugins to enable SAST. But for our purposes the “plain” SonarQube works nicely.

Use the Kotlin plugin which comes with SonarQube (SonarKotlin) or install the sonar-kotlin plugin which shows information differently. If you want to use sonar-kotlin and are using the official Docker image for SonarQube then you’ve to first remove the SonarKotlin plugin.

Using sonar-kotlin

$ git clone https://github.com/arturbosch/sonar-kotlin 
$ cd sonar-kotlin 
$ mvn package  
$ docker exec -it sonarqube sh -c "ls /opt/sonarqube/extensions/plugins" 
$ docker exec -it sonarqube sh -c "rm /opt/sonarqube/extensions/plugins/sonar-kotlin-plugin-1.5.0.315.jar" 
$ docker cp target/sonar-kotlin-0.5.2.jar sonarqube:/opt/sonarqube/extensions/plugins 
$ docker stop sonarqube 
$ docker start sonarqube 

Adding dependency-check-sonar-plugin to SonarQube

$ curl -JLO https://github.com/SonarSecurityCommunity/dependency-check-sonar-plugin/releases/download/1.2.1/sonar-dependency-check-plugin-1.2.1.jar 
$ docker cp sonar-dependency-check-plugin-1.2.1.jar sonarqube:/opt/sonarqube/extensions/plugins 
$ docker stop sonarqube 
$ docker start sonarqube 

Run test on project and scan with Sonar

The verify phase runs your tests and should generate i.a. jacoco.xml under target/site/jacoco and detekt.xml.

$ mvn clean verify sonar:sonar

Access Sonar via http://localhost:9000/

Code quality metrics? So what?

You now have metrics on Sonar to show to stakeholders but what should you do with those numbers?

One use case is to set quality gates on SonarQube to check that a set of conditions must be met before project can be released into production. Ensuring code quality of “new” code while fixing existing ones is one good way to maintain a good codebase over time. The Quality Gate facilitates setting up rules for validating every new code added to the codebase on subsequent analysis. By default the rules are: coverage on new code < 80%; percentage of duplicated lines on new code > 3; maintainability, reliability or security rating is worse than A.