133

Prek: A better, faster, drop-in pre-commit replacement, engineered in 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.

2 hours agodpc_01234

Yep, I think a watcher is better suited [0] to trigger on file changes.

I personally can't stand my git commit command to be slow or to fail.

[0]: such as https://github.com/watchexec/watchexec

an hour agojiehong

To myself: sometimes I think the background process should be committing for me automatically each time a new working set exists, and I should only rebase and squash before pushing.

That’s reversing the flow of control, but might be workable!

30 minutes agojiehong

jj already pretty much does that with the oplog. A consistent way of making new snapshots in the background would be nice though. (Currently you have to run a jj command — any jj command — to capture the working directory.)

9 minutes agowrs

That's a great idea, and I was just thinking about how it would pair with self hosted CI of some type.

Basically what I would want is write a commit (because I want to commit early and often) then run the lint (and tests) in a sandboxed environment. if they pass, great. if they fail and HERAD has moved ahead of the failing commit, create a "FIXME" branch off the failure. back on main or whatever branch head was pointed at, if tests start passing, you probably never need to revisit the failure.

I want to know about local test failures before I push to remote with full CI.

automatic branching and workflow stuff is optional. the core idea is great.

2 hours agopaddy_m

That looks really cool! I've been looking for a more thought-out approach to hooks on JJ, I'll dig into this. Do you have any other higher level architecture/overview documentation other than what is in that repo? It has a sense of "you should already know what this does" from the documentation as is.

Also, how do you like Radicle?

2 hours agodigdugdirk

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!

3 hours agotimhh

the second the hooks modify the code they've broken your sandbox

I think wasi is a cool way to handle this problem. I don't think security is a reason though.

3 hours agojdxcode

> the second the hooks modify the code they've broken your sandbox

Changes to code would obviously need to be reviewed before they are committed. That's still much better than with pre-commit, where e.g. to do simple things like banning tabs you pretty much give some guy you don't know full access to your machine. Even worse - almost everyone that uses pre-commit also uses tags instead of commit hashes so the hook can be modified retroactively.

One interesting attack would be for a hook to modify e.g. `.vscode/settings.json`... I should probably make the default config exclude those files. Is that what you meant? Even without that it's a lot more secure than pre-commit.

an hour agotimhh

I wouldn't want hooks modifying the code. They should be only approve/reject. Ideally landlock rules would give them only ro access to repo dir

an hour agoaccelbred

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.

4 hours agojdxcode

I think really they just need to implement some kind of plug-in or extension framework. Extensions are just not first class citizens but they really should be.

There should be a .gitextensions in the repo that the repo owners maintain just like .gitignores and . gitattributes etc etc. Everything can still be opt in by every user but at least all git clients would be able to know about, pull down, and install per user discretion.

It seems pretty basic in this day and age but it's still a gaping hole. You still need to manually call LFS install for goodness sake.

3 hours agojayd16

I use http://hk.jdx.dev/, which is based on https://pkl-lang.org/ and Rust, as it integrates with http://mise.jdx.dev/.

Is prek much better?

4 hours agoesafak

Love mise, didn't know about hk. Will check this out but don't think $WORK (or me) needs more than lefthook at the moment, which we're quite happy with. Wonder if there are comparisons/example projects that showcases the unique value propositions.

3 hours agosangeeth96

Correct me if I'm wrong but lefthook doesn't run its hooks exclusively on the staged changes IIRC. pre-commit, and prek by extension, have a process to stash the unstaged changes using git and running the code only on the staged files. Last I used it, lefthook ran on every file regardless of git status. This annoyed me because I'd have a few stray files that were not ready to be checked in or tracked that would trigger failures in lefthook. At the time this also made some hooks run slower since it would run on every single file but I think most linters have become significantly faster now.

2 hours agoaniforprez

Please look at the example that is literally on the front page of the lefthook website: https://lefthook.dev/

2 hours agomm263

Ah ok the home page actually reminded me what the actual issue was. It can pass the list of staged files to the command but since it doesn't actually stash anything, it's not compatible with commands that don't accept a list of files. golangci-lint for example doesn't accept a list of files like this and will run on every single file in the repo. I don't know if this behaviour has changed in lefthook or golangci-lint now.

an hour agoaniforprez

in hk you can not only have a mix of staged/unstaged files but it even deals with staged/unstaged HUNKS in the same file (best it can at least)

an hour agojdxcode

prek is compatible with pre-commit so any hooks that can be used for pre-commit can be used with prek including the repo config file. Depending on if you're interested in buying into the existing pre-commit ecosystem, which is pretty extensive, then prek is a really good alternative

3 hours agoaniforprez

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.

3 hours agoanentropic

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.

3 hours ago__mharrison__

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?

14 minutes agonsm

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.

4 hours agofishgoesblub

I've used pre-commit very sparingly but it has happened and I also have no idea why this project need to exist? Why would pre-commit ever lead to performance problems? I get that the processes that are hooked in can be long running but the pre-commit itself? Why would it take any time at all?

3 hours agoworldsayshi

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…

3 hours agoaaronblohowiak

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?

2 hours agoegorfine

For a lot of the the "$THING in Rust" posts on HN it seems that the in Rust part is usually the interesting bit.

I don't think it's true in this case, but it's been a useful heuristic for me.

an hour agocriddell

To entice people who are fluent in said language, or those who are looking for something compiled and performant. If I see a project written in (java|type)script, I know to avoid it.

an hour agofishgoesblub

I always just disable pre-commit

3 hours agosemiinfinitely
[deleted]
3 hours ago

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.

3 hours agoxyzzy_plugh

Client-side pre-commit hooks are there to help you in the same way that type checking (or a powerful compiler) is there to help you avoid bugs. In particular with git, you can skip the hooks when committing.

Now, if the server enforces checks on push, that's a project policy that should be respected.

3 hours agonitnelave

The problem is that pre-commit hooks are much slower with a much higher false-positive rate than type checking.

Pre-commit checks should be opt-in with CI as the gate. It's useful to be able to commit code in a failing state.

an hour agosa46

I use exactly one such hook, and that's to add commit signoff because of a checklist-compliance item called DCO that fails all PRs unless they have the sign-off trailer added by `git commit -s`. I've long argued that we should be enforcing actual signed commits instead, but compliance has never been about doing the sensible thing.

It's as simple as a script with a cp command that I run after any clone of a repo that requires it; certainly doesn't require anything as elaborate as a hook manager.

an hour agochuckadams

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?

4 hours agocandiddevmike

I tend to work the other way around - what is defined in CI steps gets added to pre-commit. Several tools have already existing configurations or you can use local mode. Sure, I can't force people to use it but it saves them time as CI would fail anyway.

4 hours agoszenrom

You can obviously bypass them, but having precommit hooks to run scripts locally, to make sure certain checks pass, can save them from failing in your pipeline, which can save time and money.

From an org standpoint you can have them (mandate?) as part of the developer experience.

(Our team doesn't use them, but I can see the potential value)

4 hours agothoughtpalette

I never understood this argument.

The checks in those pre-commit hooks would need to be very fast - otherwise they'd be too slow to run on every commit.

Then why would it save time and money if they only get run at the pipeline stage? That would only save substantial time if the pipepline is architected in a suboptimal way: Those checks should get run immediately on push, and first in the pipeline so the make the pipeline fail fast if they don't pass. Instant Slack notification on fail.

But the fastest feedback is obviously in the editor, where such checks like linting / auto-formatting belong, IMHO. There I can see what gets changed, and react to it.

Pre-commit hooks sit in such a weird place between where I author my code (editor) and the last line of defense (CI).

31 minutes agolukasgraf

They're very commonly used in CI. There are dedicated GitHub actions for pre-commit and prek, but most commonly people just invoke something like `prek run --all-files` or `pre-commit run --all-files` in their typical lint CI jobs.

The prek documentation has a list of many large projects (such as CPython and FastAPI, to name a few) who use it; each link is a PR of how they integrated it into CI if you want to see more: https://prek.j178.dev/#who-is-using-prek

4 hours agofortuitous-frog

I think there's value in git hooks, but pre-commit is the wrong hook. This belongs in a hook that runs on attempted push, not on commit.

3 hours agoJoshTriplett

formatting should definitely be in pre-commit though, otherwise you'll destroy diffs.

3 hours agoanttihaapala

Run the light ones on commit, the heavy ones on push.

3 hours agoesafak

The value is in finding out something is going to fail locally before pushing it. Useful for agents and humans alike.

4 hours agoesafak

They integrate well with CI.

You run the same hooks in CI as locally so it's DRY and pushes people to use the hooks locally to get the early feedback instead of failing in CI.

Hooks without CI are less useful since they will be constantly broken.

4 hours agoBeeOnRope

Why wouldn't I just call the same shell script in CI and locally though? What's the benefit here? All I'm seeing is circular logic.

3 hours agocandiddevmike

The point is enforcement. If there's a newcomer to developing your repo, you can ask them to install the hooks and from thereon everything they commit will be compatible with the processes in your CI. You don't need to manually run the scripts they'll run automatically as part of the commit or push or whatever process

3 hours agoaniforprez

Yes, you can run the CI script locally so you detect errors faster.

2 hours agoesafak

Besides during commit, pre-commit/prek can run all hooks with `run`. So in CI/CD you can replace all discrete lint/format tool calls with one to pre-commit/prek. E.g. https://github.com/python/cpython/blob/main/.github/workflow....

4 hours agoforgotpwd16

This just seems like calling a shell script with extra steps.

I have a shell utility similar to make that CI/CD calls for each step (like for step build, run make build) that abstracts stuff. I'd have Prek call this tool, I guess, but then I don't get what benefit there is here.

4 hours agocandiddevmike

This might be a me problem but I extensively manipulate the git history all the time which makes me loathe git hooks. A commit should take milliseconds, not a minute.

3 hours agoschindlabua

it’s not just you.

i regularly edit history of PRs for a variety of reasons and avoid pre-commit when possible.

put it all in CI thank you please — gimme a big red X on my pipeline publicly telling me i’ve forgotten to do something considered important.

38 minutes agodijksterhuis

You do seem to be doing it wrong. Extensive manipulation of the record and slow hooks are both undesirable.

3 hours agoesafak

I would reckon cleaning up your branch before opening a pull request is good practice. I also rebase a lot, aswell as git reset, and I use wip commits.

Slow hooks are also not a problem in projects I manage as I don't use them.

3 hours agoschindlabua

No, I would not and don't do that. It is better to leave the PR commits separate and atomic so reviewers can digest them more easily. You just squash on merge.

> Slow hooks are also not a problem in projects I manage as I don't use them.

You bypass the slow hooks you mentioned? Why even have hooks then?

2 hours agoesafak

I do leave PR commits separate. In my teams I don't set up pre-commit hooks altogether, unless others feel strongly otherwise. In projects where they are forced upon me I frequently --no-verify hooks if they are slow, as the linter runs on save and I run tests during development. CI failing unintentionally is usually not a problem for me.

2 hours agoschindlabua
[deleted]
2 hours ago

I like my pre-receive hooks.

4 hours agothrow20251220

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.

3 hours agoiFire

Not just faster than pre-commit, and totally compatible. Also with more features.

4 hours agorurban

Voting and comment rings are against HN guidelines

4 hours agoverdverm

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.

an hour agoteaearlgraycold
[deleted]
4 hours ago

This has been such a breath of fresh air. It was seamless to drop into my projects.