The recent release of npm@7 broke husky, my git hook management tool of choice. When I was looking into why my git hooks had stopped working I came across the docs for the next version of husky and saw that git hooks will now be stored in a committed directory rather than inside package.json
. This got me thinking; do I really need husky? Surely there must be a simple method to manage a directory of git hooks inside your repository. I want something that's (mostly) automatic, safe, easy, and preferably uses tooling I'm already familiar with (installing a global dependency just to manage git hooks on a per-project basis is somewhat less than ideal).
TL;DR
As the title suggests, I am now using direnv
to manage my git hooks. What I landed on was adding the following line to a .envrc
file in the root of my repositories:
git config --local core.hooksPath $PWD/.githooks
Now I just run direnv allow
and place whatever hooks I want in the .githooks
directory at the root of my project, e.g., an executable file at .githooks/commit-msg
.
Check out knpwrs/instant.bible or knpwrs/listenator on Github to see this setup in action.
How does this work?
I already use direnv
for other things. It's a really handy tool for managing environment variables in a way that isn't tied to any specific framework or implementation, and additionally makes environment modifications available to all of your tooling. The trick is that .envrc
is more than just a file which specifies variables for direnv
to export -- it's a shell script which direnv
executes inside of bash
(even if you use another shell such as zsh
). Our git config
call configures the current git repository to look for hooks inside the .githooks
directory inside our repository. Whereas most solutions involve symlinking or copying files into .git/hooks
, we just reconfigure each individual git repository to tell it where to look for hooks.
direnv
works automatically, so that makes this setup easy to use. It also won't execute any .envrc
files without explicit permission, so it's safe. Since direnv
isn't tied to any specific framework or implementation, this setup for git hooks works great for polyglot monorepos (e.g., several microservices in a single git repository or multiple app implementations in a single repository).
What I really would have liked is some sort of environment variable to configure where git
should look for hooks. This works just as well, though.
Note that passing --local
isn't actually necessary, as git config
is --local
by default, and --global
only if specified. That said, for this purpose I do like passing --local
so our intentions are explicitly clear to anyone reading our code.
I want it to be less automated!
I hear you. Running non-exports in your .envrc
isn't for everyone. If you would prefer manual hook initialization for your project, or if you would just like hooks to be opt-in, you can place the git config
call above into a shell script or even a Makefile
like such:
.PHONY: hooks
hooks:
git config --local core.hooksPath $(shell pwd)/.githooks
Now just have people run make hooks
and you're good to go!
Conclusion
direnv
just may now be my favorite way to manage git hooks now! It's simple, safe, framework-agnostic, and automatic. I haven't seen the git config
approach discussed much elsewhere, so even if you prefer something more manual, hopefully this article was still helpful to you.
This post originally appeared at https://knpw.rs/blog/direnv-git-hooks
Top comments (0)