38 comments

[ 3.0 ms ] story [ 62.0 ms ] thread
Not just faster than pre-commit, and totally compatible. Also with more features.
This has been such a breath of fresh air. It was seamless to drop into my projects.
I struggle to see value with git hooks. They're an opt-in, easily opt-out way of calling shell scripts from my understanding--you can't force folks to run them, and they don't integrate/display nicely with CI/CD.

Why not just call a shell script directly? How would you use these with a CI/CD platform?

I think of Git hooks as a useful guiderail for myself that I share with others. They sometimes help me reduce the length of iteration cycles for workflows (which I generally don't like) where one typically commits before running/deploying (e.g., most of my team's Terraform repos).

> you can't force folks to run them

I think it's useful to be able to disable things like this when debugging or reconfiguring them. I sometimes disable the ones I've set up for myself, then reenable them later.

> Why not just call a shell script directly?

Because it's manual; I have to remember to do it each time. But there's no reason not to have a script you can invoke in other ways, if that's something you want.

> How would you use these with a CI/CD platform?

The thing that sets up your environment also installs the git hooks configuration, and/or you can have it invoke specific hooks via CLI.

Am I alone in that I never have had an issue with performance with pre-commit? granted I don't work on projects the size of the Linux kernel, but I haven't had any complaints.
(comment deleted)
I think it was a massive mistake to build on the pre-commit plugin base. pre-commit is probably the most popular tool for pre-commit hooks but the platform is bad. My main critique is that it mixes tool installation with linting—when you will undoubtedly want to use linters _outside_ of hooks. The interface isn't built with parallelism in mind, it's sort of bolted on but not really something I think could work well in practice. It also uses a bunch of rando open source repos which is a supply chain nightmare even with pinning.

pre-commit considered harmful if you ask me. prek seems to largely be an improvement but I think it's improving on an already awful platform so you should not use it.

I know I am working on a competing tool, but I don't share the same criticism for lefthook or husky. I think those are fine and in some ways (like simplicity) better than hk.

> My main critique is that it mixes tool installation with linting

If you use a tool like this via Devenv instead of using its built-in mechanisms for installing tools:

  - you can add a linter without putting it on your path
  - you can put a linter on your path without enabling any git hooks for it
  - if you are already using a linter in a git hook, adding it to your environment will get you the exact same version as you're already using for your git hook at no additional storage cost
  - if you are already using a linter at the CLI, and you add a git hook for it, your hook will run with the exact same version that you are already using at the CLI at no additional storage cost
  - your configuration interface is isomorphic to the upstream one, so
    - any custom hooks you're already using can be added without modification beyond converting from one format to another;
    - any online documentation you find about adding a custom hook not distributed with the upstream framework still applies;
    - and you can configure new, custom, or modified hooks with a familiar interface.
  - any hook you write as a script or with substantial logic can also be plugged into a built-in task runner for use outside git hook contexts, where
    - you can express dependency relationships between tasks, so
    - every task runs concurrently unless dependency relations mandate otherwise.
Which imo solves that problem pretty well. My team uses that kind of setup in all of our projects.

> The interface isn't built with parallelism in mind, it's sort of bolted on but not really something I think could work well in practice.

I'm curious about what this means. Could you expand on it?

You might be interested in something like `treefmt`, which is designed around a standard interface for configuring formatters to run in parallel, but doesn't do any package management or installation at all:

https://github.com/numtide/treefmt

(That might address both of the issues I've replied to you about so far, to some extent.)

> It also uses a bunch of rando open source repos which is a supply chain nightmare even with pinning.

If the linters you're running are open source, isn't this what you're ultimately doing anyway? Nix gives a bit more control here, but I'm not sure whether it directly addresses your concern.

> I am working on a competing tool

Oh, dammit. Only after writing all of this out did I realize you're the author of mise. I'm sure you're well aware of Nix and Devenv. :)

Because I think your critiques make sense and might be shared by others, I'll post this comment anyway. I'm still interested in your replies, and your opinion of having some environment management tool plug into prek and supplant its software installation mechanisms.

And because I think mise likely gets many of these things right in the same way Devenv does, and reasonable people could prefer either, I'll include links to both Devenv and mise below:

https://devenv.sh/

https://mise.jdx.dev/

So if you are using multiple languages to have scripts that run off your pre-commit hook, this is like a package and language runtime management system for your pre-commit hook build system? Rather, I think this is a reimplementation of such a system in rust so it can be self contained and fast.

This is the kind of thing I see and I think to myself: is this solving a problem or is this solving a problem that the real problem created?

Why is your pre-commit so complicated that it needs all this? I wish I could say it could all be much simpler, but I’ve worked in big tech and the dynamics of large engineering workforces over time can make this sort of thing do more good than harm, but again I wonder if the real problem is very large engineering teams…

I have also been working on an alternative written in Rust, but in my version the hooks are WASI programs. They run on a virtual filesystem backed by the Git repo. That means a) there are no security issues (they have no network access, and no file access outside the repo), b) you can run them in parallel, c) you can choose whether to apply fixes or not without needing explicit support from the plugin, and most importantly d) they work reliably.

I'm sure this is more reliably than pre-commit, but you still have hooks building Python wheels and whatnot, which fails annoyingly often.

https://github.com/timmmm/nit

The VFS stuff is not quite finished yet though (it's really complicated). If anyone wants to help me with that it would be welcome!

I am a big fan of prek and have converted a couple of projects over from pre-commit

The main advantage for me is that prek has support for monorepo/workspaces, while staying compatible with existing pre-commit hooks.

So you can have additional .pre-commit-config.yaml files in each workspace under the root, and prek will find and run them all when you commit. The results are collated nicely. Just works.

Having the default hooks reimplemented in Rust is minor bonus (3rd party hooks won't be any faster) and also using uv as the package manager speeds up hook updates for python hooks.

I believe that was the main reason it was created - pre-commit author acknowledged the request for such support but said they wouldn't do (or merge) it in pre-commit.
Really enjoying using prek.

Dedicated a whole chapter to it in my latest book, Effective Testing.

The trend of fast core (with rust) and convenient wrapper is great while we are still writing code.

Another commenter is currently down voted for something similar, but I'll share my controversial take anyways: I hate pre-commit hooks.

I loathe UX flows where you get turned around. If I try to make a commit, it's because that I what I intend to do. I don't want to receive surprise errors. It's just more magic, more implicit behavior. Give me explicit tooling.

If you want to use pre-commit hooks, great! You do you. But don't force them on me, as so many projects do these days.

I don't understand. The whole point of pre-commit is it's a gateway to the operating system and also creating a ecosystem of pre integration continuous integration scripts. Scripts that are not rust.
BTW. Pre-commit hooks are the wrong way to go about this stuff.

I'm advocating for JJ to build a proper daemon that runs "checks" per change in the background. So you don't run pre-commit checks when committing. They just happen in the background, and when by the time you get to sharing your changes, you get all the things verified for you for each change/commit, effortlessly without you wasting time or needing to do anything special.

I have something a bit like that implemented in SelfCI (a minimalistic local-first Unix-philosophy-abiding CI) https://app.radicle.xyz/nodes/radicle.dpc.pw/rad%3Az2tDzYbAX... and it replaced my use of pre-commit hooks entirely. And users already told me that it does feel like commit hooks done right.

I had been eagerly moving over to using JJ when I discovered that 'hook' behavior was not present. Pre-push hooks for formatting and linting were very helpful for me because I needed to enforce these standards on others who were more junior. It would be great for JJ to incorporate it in some way if possible. I understand the structural differences and why that makes it hard but something about that pre-* hook just hits right
I think we built similar things: http://github.com/bjackman/limmat

From the docs I think Limmat is much more minimal. It doesn't have a merge queue or anything, "jobs" are just commands that run in a worktree.

I would be interested to try SelfCI coz I have actually gone back and forth on whether I want that merge queue feature in Limmat. Sometimes I think for that feature I no longer want it to be a local tool but actually I just want a "proper CI system" that isn't a huge headache to configure.

I see it as a layered system, each one slower than the last, but saving time in the long run.

* in-editor, real time linting / formatting / type checking. This handles whatever file you have open at the time.

* pre-commit, do quick checks for all affected code - linting, type checking, formatting, unit tests.

* CI server, async / slow tests. Also does all the above (because pre-commit / pre-push scripts are clientside and cannot be guaranteed to run), plus any slower checks like integration tests.

Basically "shift left", because it takes 100x as long to find and fix a typo (for example) if you find it in production compared to in your editor while writing.

Oh interesting. Checks sound similar to lix validation rules [1].

We were coming from a an application perspective where blocking the users intent is a no-go.

Do you have a link to a discussion where the JJ community is discussing checks?

[1] https://github.com/opral/lix/issues/239

What difference does it make that it's written in Rust? Why is that so much a selling that it made it into the title?
Because this is Hacker News, not a marketing website. People may not only be interested in the tool itself, but also in the implementation.
It doesn’t seem like this solves the main issues with pre-commit hooks. They are broken by design. Just to name 2, they run during rebase and aren’t compatible with commits that leave unstaged files in your tree.
Can people give examples of how they use pre-commit hooks that _cannot_ be replaced by a combination of the following?

* CI (I understand pre-commit shifts errors left)

* in editor/IDE live error callouts for stuff like type checking, and auto-formatting for things like "linters".

Do you run tests? How do you know _which_ tests to run, and not just run every test CI would run, which could be slow?

Anyone using on very big projects can vouch for the speed of things?
My big problem with pre-commit is that it doesn't have any way for you to have your own commit hoos that run in addition to the hooks that are part of the repo, and the author of it is hostile to any suggestion of supporting that. Heaven forbid that I want to run something on commit that other developers who work on the repo don't want to.
"...in Rust"

Is enough to don't even open the link! Everything right now seems to have an urgent need to be developed into Rust, like why???

Just like kubernetes, many companies followed the kubernetes hype even when it was not needed and added unnecessary complexity to a simple environment.

Now it is Rust time!!

I like pre-merge hooks. They're great.

Pre-commit and pre-push hooks serve the purpose of keeping code isolated to a developer's machine. This is a recipe for disaster. You will run into situations where important work isn't accessible since a developer couldn't commit/push their code and the machine was lost or damaged. I've seen it happen.