You don't need husky for git-hooks!

June 11, 2022

The title sounds click-baity alright. And I may be wrong and husky is actually doing more than I think. But when I found the following, I really though: “Why do I need this package at all?“.

When I converted Gachou to a mono-repo, I wondered what I should to about the pre-commit hooks and lint-staged for linting and formatting. Since I had to run it in multiple sub-repositories, with only one commit hook, I thought about installing husky in the root repository as well.

I did not like this idea. Maybe some Java-developer wants to do something in the backend. Even if she does not touch anything in the Node.js repositories, she has to install Node.js. So I went and I tried to figure out what husky actually does.

What does husky do?

Usually, git looks for hooks in the .git/hooks directory, but of course, this is not committed to the repository, so you cannot use it to share your hooks. Husky configures git to use a different directory for this repository. If you look at .git/config, you will see

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        hooksPath = .husky    <---- This is it!

The hooksPath is set to the .husky directory, and that is where a pre-commit-script is placed by husky.

Do it yourself!

For my mono-repo, I added a simple script bin/init-repo.sh with the following content.

#!/bin/bash

git config core.hooksPath .git-hooks

Then I added a file .git-hooks/pre-commit to the repository:

#!/bin/bash

set -e

( cd e2e-testing && npx lint-staged )
( cd web-ui && npx lint-staged )

I also had to make this file executable, otherwise git will just ignore it with a warning.

chmod a+x .git-hooks/pre-commit

When a new user clones the mono-repo, she can run init-repo.sh and the hooks will be setup just fine. But she still needs Node.js to run the hook itself, because npx lint-staged does not work otherwise.

Restrict pre-commit checks to relevant sub-directories

As long as my potential Java-dev does not do changes in web-ui or e2e-testing, she does not need to execute lint-staged, so how can we check that?

git diff --quiet --cached e2e-testing

will look for staged (“cached”) differences in the repository inside e2e-testing. It will not print anything (“quiet”), but it will return with a non-zero exit-code if changes are found. We can use that in our pre-commit hook:

#!/bin/bash

set -e

if ! git diff --quiet --cached e2e-testing ; then
  ( cd e2e-testing && npx lint-staged )
fi

if ! git diff --quiet --cached web-ui ; then
  ( cd web-ui && npx lint-staged )
fi

This will only run lint-staged if there are changes in the given sub-diretories.

Replacing husky

Assuming, you don’t have a mono-repo, but just a simple Node.js project. I think, instead of husky, you can also do the following:

  • create a file .git-hooks/pre-commit

  • make it executable

  • add a script to your package.json

    {
      "scripts": {
        "prepare": "git config core.hooksPath .git-hooks"
      }
    }

This should have the same effect.

Conclusion / Disclaimer

I haven’t had a thorough look at the husky source-code. Maybe there other things that I don’t know. And if you are using Node.js anyway, you might as well use it. It has no dependencies after all.

But I was glad to find out, that I could very simply have the same thing without making the root-project in my mono-repo a Node.js project.


Profile picture

Written by Nils Knappmeier who has been a full-time web-developer since 2006, programming since he was 10 years old.