Writing Githooks in Kotlin
Well, turns out you can! Here’s how you do it…
What do I need?
Git will basically run whatever script you drop on the
.git/hooks directory. In their words:
To enable a hook script, put a file in the hooks subdirectory of your
.gitdirectory that is named appropriately (without any extension) and is executable
So all we need is to be able to execute Kotlin files as scripts. There is a Kotlin Scripting Support KEEP under definition. But for the time being we’ll stick with the awesome KScript library (by @holgerbrandl) that enables Kotlin scripting on *nix-based systems.
I’ll also be using Gradle to automatically install the githook and run the proper validation, but the same can be done with Maven.
As an example I’m going to show how to do a pre-push client hook that aborts the push if
grade check task is not successful. For this I’ve created a file named
The first line is all the magic incantation we need to execute the script. By setting the shebang to
#!/usr/bin/env kscript we get to use
kscript as interpreter for the script.
The code after the import is the actual script. Those are the lines that are going to be executed as soon as somebody calls the script. Just as you’d expect with any regular shell script.
In a nutshell this is what the script does:
- Stash uncommitted changes if any1
- Run code validation (in this case
- Unstash possible changes stashed on step 1
- Log outcome and set the proper exit value
The last step is important because if the script exits to anything other than 0 then git aborts the action (in this case the push).
How do I call things from a script?
To do anything useful on your script you’ll probably have to call some external tool at some point. In this particular case for example a mix of git commands and gradle tasks.
There are 2 ways you can go about this:
- Either use a Kotlin/Java library for the task you’re trying to accomplish (in this example we could use JGit and Gradle tooling API)
- Or call a shell command directly
While the first approach is more portable, it will introduce some dependencies to your script (which fortunately KScript has great support for). On the other hand the second option is probably easier to implement because it’s just using the same commands we use everyday on our workflow.
Since I can assume everybody in my team has
gradle installed and in their path I went for option 2.
Running shell commands from Kotlin
We can run shell commands on Kotlin using
ProcessBuilder, just like we’d do from Java.
In this case I’ve created a
runCommandWithRedirect extension function that looks like this:
This function can be called on any String like this:
This function will:
- Redirect the standard and error output to the one for the current process, in our case that means the output of the command will be visible on the terminal when the githook is executed.
- Set the directory to the passed
dirparameter, or use the current directory if no parameter is provided.
- Execute the command, wait for it to finish and return the
You can play around with the different
ProcessBuilder options. In my script above for example I’ve another version of this function called
runCommand that executes the command and returns it’s output as a
Githooks are great to enforce code quality practices (i.e. ”You can’t push if your coverage is less than 80% “ 👮). But for the client-side githook to be execute it needs to be in the
.git/hooks folder which is not versioned. That means that each developer on your team has to manually install the hook, which means that you are again, relying on the good memory of your teammates to enforce code quality.
Instead we could use this trick. We can create a gradle task called “copy” that copies the githook from the
src folder to the
git/hooks and removes the file extension in the process.
Then we can make the “build” task depend on this new ”copy” task. The next time the developer runs
gradle build the githook will be installed. And as a bonus: the githook script is now versioned too! 2
Here’s how this would look like (using Kotlin DSL for Gradle)
⚠️ Don’t forget to do
chmod u+x Pre-Push.kts to make the script runnable, otherwise it won’t work.
What about performance?
Kotlin is a compiled language, so at some point your script will have to be compiled. Fortunately thanks to KScript this only happens the first time you run the script and it’s only compiled again if the script changes.
Other than that there’s the JVM startup time which adds around 200ms of overhead. Maybe in the future we’ll be able to use Kotlin Native to compile to native binaries directly and avoid this overhead.
If you want to read more about performance comparison between Python and Kotlin scripts check the KScript documentation.
Bonus track: testing
Testing Kotlin scripts turned out not to be so straight forward.
Neither approach convinced me. I was just looking for a way of individually test the functions in my script using the same tools I use to test the other parts of my code.
So what I ended up doing was moving all the Pre-Push logic to a regular
*.kt file. And then simply creating a
*kts Kotlin script that calls my class using the
//INCLUDE KScript directive.
The downside is that I know have 2 files for my githook (a
*.kt and a
*.kts) but that seems a small price to pay for being able to easily test my code.
Writing githooks in Kotlin is possible and not that hard thanks to KScript. You’ll be glad you have tried it out the next time you have to refactor that pre-push hook.
You can find an example repository containing all the code for this blogpost here: https://github.com/jivimberg/kotlin-githook