Using Husky with Your Global Git Hooks
Husky 🐶 is an awesome JavaScript tool that
gives you the power to run pre-defined scripts as a git
hook with minimal
effort. When used together with
lint-staged
, you can really create a
nice developer experience for yourself. I've been using both on most of my
recent projects and I highly recommend them.
I use husky
primarily to run a pre-commit
hook, where I do some linting and
formatting, and even run some tests on changed files. This was working great up
until a few weeks ago when I started to notice that the script defined in the
pre-commit
hook was not running.
The problem
I spent several frustrating hours trying to debug the issue to no avail. This
led to some anger-induced Googling and a freshly created
GitHub issue. The lone commenter
in that issue suggested taking a look at a git
configuration option,
init.templateDir
,
to potentially solve my problem.
This was helpful for two reasons. For one, it was a spot on suggestion, but
we'll get to that. Second, it forced me to jump into the git
documentation,
which I should've done in the first place.
After digging through the docs I realized my problem. I had recently created a
directory to hold my "global" git
hooks. I then defined the path to that
directory using the core.hooksPath
option in my .gitconfig
.
~/.gitconfig
...
[core]
hookspath = /path/to/global/git-hooks
This option,
according to the docs,
tells git
where to find my hooks. What I didn't realize, was that if such an
option existed in my .gitconfig
, then those hooks would be used in favor of
any defined at the project level, i.e. in my project's .git/hooks
directory.
This includes the hooks that husky
creates when you install the package.
So, having a global
git
hooks directory affects my usage ofhusky
Frustrating. In my ideal world git
would manage this better. 🤷🏼♂
The solution
I needed to find a way to use both my global git
hook and the husky
hooks
without creating some complex workaround. Taking the
suggestion of the commenter,
here's what I came up with.
Create a template directory
First, I created a git
template directory.
mkdir ~/path/to/git-template-dir
The contents of this directory, as long as they are not prefixed with a dot,
will be copied to the $GIT_DIR
after it's created. The $GIT_DIR
, in my
case, is just my project's .git
directory, which is created when running
git init
.
In order to tell git
where to find this newly created directory, I defined its
location inside of .gitconfig
using the init.templateDir
option.
~/.gitconfig
...
[init]
templateDir = /path/to/git-template-dir
I also removed the core.hooksPath
definition inside of .gitconfig
because
I no longer need it.
Add your global hooks to the template directory
At this point, you'll need to move any globally defined git
hooks to the new
template directory underneath a folder named hooks
.
cd /path/to/git-template-dir
mkdir hooks
I ended up re-creating my lone hook instead of "moving" it. If you did the same, make sure to set the proper permissions on the hook so that it is executable:
chmod a+x /hooks/<hook-file>
Now, whenever you run git init
, the contents of your
/path/to/git-template-dir/hooks
folder will be copied over to the .git/hooks
folder in your project.
Supporting existing projects
The only caveat here is that this solution won't work with existing projects.
This is because git
will only copy over the contents of your template
directory when you run git init
.
If you have an existing project that you want to support, and you don't mind
re-initializing git
, you can remove the .git
directory and run git init
again. This isn't really ideal, especially if you value your git
history. In
that case, I would suggest manually creating your hooks in the .git/hooks
directory of the project as a one-off type of thing.
Summary
To wrap this post up, in order to solve my issue I created a template directory
and defined its location using the init.templateDir
option inside of
.gitconfig
. From there, I created a hooks
folder inside of the template
directory, where I can now define my global git
hooks.
git
will then copy the contents of this template directory any time I run
git init
. This means my global hooks will be accessible inside of any future
projects. I can then install husky
as normal, and the package will create its
own git
hooks alongside the ones I've defined globally.
I hope this helps any of you who have run into the same problem. Reach out to me on Twitter if you have any questions. Happy coding!