lein-githooks

The Problem

Loop

Tight feedback loops are awesome.

For me, tight feedback cycles are right at the heart of every methodological advance that the software industry has made over the twenty years.

CI, CD, TDD and lean and agile methods all work because they leverage the exponential gains that we get by amplifying learning through feedback. The tighter the loop and higher the information content the faster we can move and adapt.

Reducing the time and friction involved in QA and testing processes means we can increase the scope of what can be tested on developers’ own machines bringing this valuable feedback right into developers’ own development cycle.

With in-memory databases, embedded web servers and suchlike we can make fairly realistic end-to-end automated testing practical right within the development cycle, and sometimes, with a just a little more ceremony, we can even spin up reasonable simlacra of deployment architectures using tools like Vagrant.

However it only takes a momentary lapse to lose valuable time. Forgetting to run tests before a svn commit or git push can lead to failed builds with knock-on effects and delays.

Client-side git hooks solve this for projects in git. You can easily set up git to automatically run all your checks every time you push (or even at each commit). Never again will you commit and see a wave of builds kicking off on your CI servers only to suddenly remember you missed running the unit tests after your last little tweak.

However git hooks are private to your repository so it takes a little effort to share them amongst developers. In many scenarios (particularly fairly centralised development models that are typical in commercial closed-source development), it is appropriate to enforce client-side hooks at a project level.

I’ve wanted to do that in the past but haven’t found a particularly satisfying way to do it. Then while working on a node.js project this week, a colleague of mine pointed me at an effective solution in javascript-land (git-pre-hooks).

It’s a simple approach: define the hooks you want in your project metadata and allow the build tool to deploy and manage your git hooks.

There may be many implementations of this same approach out there but I want one for Leiningen and I couldn’t find an equivalent, so I wrote it.

lein-githooks

Right now, it’s crude but effective. The project is here.

Simply declare the commands you’d like to call in project.clj as follows.

(defproject my-project "..."

  ;; ...
  {:profiles {:dev {:plugins [[lein-githooks "0.1.0"]]
                    :githooks {:auto-install true
                               :pre-push ["lein test"]}}}})

Then when you next run any leiningen task (this is the optional :auto-install bit), the hooks will be installed or updated. The hook scripts themselves simply invoke leiningen to run the specified commands so there are currently a lot of JVMs spinning up in the process so it’s not the zippiest.

You can use it more manually if you prefer (:auto-install off and you’ll need lein githooks install to manage them) and you can override project settings in your own profiles.clj.

Full instructions in the README.

…and feedback is, of course, welcome.

Continuous Testing

Hooks are not the only approach to having tests run frequently inside the development cycle. Continuous testing runs tests repeatedly or on every file change. This has similar or even better benefits. Midje’s autotest is an example in Clojure.

However, even if you are using continuous testing you might want to run other tasks such as static code analysis (from Eastwood for example) on pre-push.

Comments