Mandatory Use of Git Hooks
In my free time, I answer questions about Git on Stack Overflow, and I’ve seen a trend towards folks wanting to use hooks to enforce using various linting mechanisms or running test suites. This is generally a bad idea for a couple of reasons, mostly because it doesn’t achieve what you want to achieve and it makes advanced developers' lives needlessly difficult.
First, let me say that hooks are a great tool. They’re really flexible and let folks do a lot of cool stuff. I’ve used hooks in the past to automatically insert JIRA issues numbers into commit messages and otherwise format things in a way that makes my life much easier. So they’re a great option for helping developers do amazing things.
There are also a lot of cool tools you can use that are partially or mostly built on hooks, like Git LFS. Those hooks are a great way to extend Git and do things that wouldn’t otherwise be possible.
However, by definition, anything that runs on a developer’s machine can be
disabled. So a common concern folks have is, “How do I check if the developer
run git commit
with the --no-verify
option to disable the hook?” and the
answer is, you don’t. If you want to enforce policy, you do that on the server
side, usually in a CI system. Yes, the developer could disable the tests or
linting from working by editing the script, but if you have code review as well
(and you should), then you know the policy has been enforced.
That doesn’t mean that using a pre-commit hook can’t be a great idea if you have trouble remembering to run tests or the linter and want help. Hooks are there to help you, after all. They’re just not a good way to enforce policy.
All of this is well known, and as of recently, it’s documented in Git’s new FAQ as well. But let me also explain a workflow I use that pre-commit hooks interfere with.
Sometimes I need to work on a project where I’m going to have a lot of code that’s modified at once. Despite my interest in producing small, logical commits, sometimes I don’t even know what needs to be changed until I’ve made a change, run the test suite, and found yet more tests that now fail (and hence more code needing to be fixed). At this point, I may need to do some exploratory work: I have something that works for a particular subset of the problem, but I need to do some major refactoring to make more changes.
So I make a WIP (work-in-progress) commit. Now, this commit totally doesn’t pass the test suite or any linting rules, but it does work in one particular way that’s relevant to my work. By committing, I can roll back if my refactor turns out to be a disaster and easily switch to another approach. If it works, I can then make another commit, where more things work, and so on.
Finally, I get to a stage where everything works, and I can then look at my set of tiny, mostly broken commits and come up with a logical, sane, bisectable series out of them, usually something more in reverse order from my WIP commits. In the mean time, I get an easy way to store and checkpoint my work and roll back if things go south.
Now, I don’t necessarily claim that everyone does it this way, but it isn’t an
entirely uncommon flow among advanced developers. And this workflow is
completely broken if you enforce pre-commit checks on every commit. While it’s
possible to use the --no-verify
option and skip the checks, it also really
interrupts the flow of quickly committing the changes and getting back to
focusing on the work.
So that’s why I recommend that folks don’t enforce hooks on developer systems, or even install them by default. It’s much better to provide a common set for your project if folks want them and then let them use them as is, customize them, or omit them entirely.