[HN Gopher] Hk, a new Git hook manager
       ___________________________________________________________________
        
       Hk, a new Git hook manager
        
       Author : DrBenCarson
       Score  : 83 points
       Date   : 2025-02-17 16:23 UTC (6 hours ago)
        
 (HTM) web link (hk.jdx.dev)
 (TXT) w3m dump (hk.jdx.dev)
        
       | righthand wrote:
       | Git hooks are like make files for me. Copy/paste that `git staged
       | files` command and then add your linter commands (in
       | .githooks/pre-commit file). Add `set -e`. Set package manager
       | post-install hook to config git to the `.githooks` dir.
       | 
       | Sadly I think git hook managers remove the simplicity of the
       | whole design. Understandably since no one reads manuals anymore
       | and projects don't mind tacking on yet another module/plugin,
       | however easy.
        
         | regularfry wrote:
         | I can get behind a hook manager that's a single binary. Where I
         | have an issue is with (for instance) python hook managers where
         | you now need to have python interpreter management that's aware
         | of both the python app you're working on, and the tooling which
         | might have a non-intersecting version requirement.
        
           | whilenot-dev wrote:
           | While I understand your valid criticism with the overhead of
           | using an interpreted language here, I must point out that
           | using multiple versions in the same repository is quite an
           | antipattern that comes with various complications. Maybe you
           | should consider switching to a version manager of[0] your[1]
           | choice[2].
           | 
           | [0]: https://github.com/jdx/mise
           | 
           | [1]: https://github.com/asdf-vm/asdf
           | 
           | [2]: https://github.com/version-fox/vfox
        
         | cowsandmilk wrote:
         | The big difference with Makefiles is that git hooks can't be
         | committed to the repo. Hook managers allow hooks to be shared
         | and updated across a team.
        
           | goku12 wrote:
           | Committing git hooks to the repo is possible. These are the 2
           | ways in which they're commonly handled:
           | 
           | 1. Link the scripts from the worktree to the .git/hooks
           | directory - perhaps using a bootstrap script.
           | 
           | Ref: https://codeinthehole.com/tips/tips-for-using-a-git-pre-
           | comm...
           | 
           | 2. Declare the directory in the worktree to be the local git
           | hooks directory.
           | 
           | Ref: https://knpw.rs/blog/direnv-git-hooks
        
           | globnomulous wrote:
           | So do dev-environment start-up scripts, which in my
           | experience are always simpler, clearer, and more maintainable
           | than these managers.
           | 
           | Just put your hook logic in a script and copy it to the git
           | hooks folder on startup as necessary -- and then, voila,
           | you've avoided the nonsense of some well-intentioned package
           | whose author thinks Rust in git hooks is a selling point
           | rather than a head scratcher.
        
         | warp wrote:
         | 100%, I was using husky because we were using it at work. But
         | it turns out for my use-case all I needed was this in my
         | package.json:                   "scripts": {
         | "postinstall": "git config --local core.hooksPath etc/hooks"
         | },
        
           | rav wrote:
           | Ooh, core.hooksPath is quite nifty. I usually use something
           | like                        ln -sf ../../scripts/git-pre-
           | commit-hook .git/hooks/pre-commit
           | 
           | which simply adds a pre-commit symlink to a script in the
           | repo's scripts/ dir. But hooksPath seems better.
        
         | Klonoar wrote:
         | Sometimes I wonder if the death of blogging - which in turn
         | killed a lot of showing people how to use a tool where they'd
         | otherwise skip the manual - means we entered a world where
         | people jump immediately to building a tool for an otherwise
         | simple concept.
        
       | Chilinot wrote:
       | I like Rust as much as the next guy, but statements like this
       | makes me roll my eyes:
       | 
       | > hk is written in rust, pre-commit is written in python. hk will
       | be much faster.
       | 
       | I have no idea how fast hk or pre-commit is, i have never used
       | them. What matters to speed is the algorithms used and their
       | complexities. If you implement a shitty algorithm with
       | exponential complexity in Rust it's going to be slower than a
       | linear complexity algorithm in python.
        
         | jitl wrote:
         | This isn't true when it comes to CLI tools, where fixed costs
         | and warm-up time can easily dominate over algorithmic
         | complexity, especially comparing a scripting language to an
         | ahead-of-time language. Without careful construction, a tool
         | written in python with whatever O(log(n)) time complexity may
         | still be booting up by the time a Rust tool finishes the job in
         | O(n^2) time: frequently n is actually pretty small outside
         | whole-repo builds, and the cost of interpreter is high
         | especially with accompanying tooling -- python3 itself will run
         | `print("hello")` on my machine in 40ms, but when wrapped in
         | pyenv (which I think is typical?) it takes 400ms. The same goes
         | for node/npx and ruby/bundle. Compare to an ahead-of-time
         | compiled binary from go, which can boot and print hello in 3ms
         | on my machine -- it has between 10x to 100x the wallclock
         | budget the python program just spends booting up.
        
           | trallnag wrote:
           | These days I'd say tools pipx (and now also uv) are far more
           | common than pyenv for tool management
        
       | pimlottc wrote:
       | I appreciate the comparison to the leading existing tools, but it
       | also assume a familiarity with them. This doc could also use a
       | basic introduction for people who are new to this area and don't
       | already use a hook manager. Examples of hooks, what the benefit
       | is, etc.
        
         | goku12 wrote:
         | While your concern is valid, I recommend learning about git
         | hooks from their canonical source rather than from the
         | documentation of any hook manager. Hooks are features of Git
         | itself. Hook managers are an after-thought. Meanwhile, this
         | tool and its docs also appear to be works in progress. I use
         | pre-commit (a python program, not the hook itself) as a hook
         | manager. But nothing beats the git book and docs. Here are some
         | links:
         | 
         | 1. https://git-scm.com/docs/githooks
         | 
         | 2. https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
         | 
         | Here is another site dedicated to git-hooks. You'll find some
         | good examples and resources there:
         | 
         | https://githooks.com/
        
       | judofyr wrote:
       | > lefthook is written in go, hk is written in rust. This will
       | make hk faster but the advanced parallelism logic in hk should
       | make hk much faster. Because git hook managers are often limited
       | not by their own logic but the speed of the commands they run,
       | this should make hk significantly faster in real-world usage.
       | 
       | What a strange sentence. First Hk will be faster than Lefthook
       | because it will be written in Rust[1], but then later on it says
       | that it doesn't actually matter? But either way it will be faster
       | because it has "advanced parallelism"?
       | 
       | Looking at the comparison to Lefthook it's not clear to me why
       | this isn't a PR to Lefthook. It's already a well-established
       | solution which is present in many package managers. Surely the
       | distinction between "checks" and "fixes" would be possible to
       | introduce there as well?
       | 
       | Do we _yet_ another tool instead of working together on improving
       | the existing ones?
       | 
       | [1]: Which also is not a given. Programs which allocate a lot can
       | be faster in Go since the garbage collector only works on the
       | _live_ set of objects whereas Rust have to explicitly deallocate
       | _all_ memory. CLI tooling is actually kinda a sweet-spot of GCs:
       | You don 't want to spend time reclaiming memory _until_ you reach
       | a certain threshold of used memory. In scenarios where you use
       | less than the threshold you end up spending _zero_ cycles on
       | deallocation. (And completely leaking all memory is bound to
       | cause problem on bigger commands or smaller machines.)
        
         | searealist wrote:
         | It makes sense. If you have 4 hooks, and you run them serially,
         | it will be slower than if you ran them in parallel.
        
           | vlovich123 wrote:
           | I say this as a huge fan of Rust, Rust adds almost nothing to
           | this kind of parallelism.
        
             | searealist wrote:
             | It doesn't claim that.
             | 
             | It claims:
             | 
             | 1) Rust is fast, so that helps some.
             | 
             | 2) They run hooks in parallel, and that helps a lot.
        
           | rounce wrote:
           | That assumes the hooks themselves can be run in parallel.
           | Without a way to describe dependencies between them there's
           | big scope for race conditions.
        
             | goku12 wrote:
             | Hooks are generally not meant to modify the source code.
             | They should ideally just analyze the code and either
             | succeed (exit code 0) or fail. Race conditions won't happen
             | in such situations.
             | 
             | In practice though, hooks sometimes run tools that are were
             | not designed like that and instead modify the file (eg:
             | formatters). However, this is usually done on staged files
             | (in case of the pre-commit hook) and the modification is
             | applied to the working copy. This can cause race condition
             | in that one tool may overwrite the changes made by another
             | tool. But since the source is not modified, it won't end up
             | in a deadlock. It will also fail as desired. So the race is
             | not serious. It will resolve itself after a few runs,
             | unless something is done to prevent it in the first place.
        
             | thayne wrote:
             | Which is why it is best if you plan for parallelism from
             | the beginning, so that your configuration has a
             | straightforward way to indicate dependencies and making
             | sure certain hooks aren't run in parallel with conflicting
             | hooks
        
               | rounce wrote:
               | The subject program doesn't seem to provide a way in
               | which to specify the dependencies between hooks, hence
               | they will just race each other assuming they are all
               | fired off in parallel.
        
           | great_wubwub wrote:
           | Sure, but Go can do things in parallel too. How is Rust's
           | faster than Go's, especially if they're limited by the speed
           | of the commands they run?
        
             | unshavedyak wrote:
             | Imo this problem would be fine in either Go or Rust. I've
             | done both. For extreme cases you can, i imagine, make Rust
             | parallelism faster than Gos, but by default if you just
             | design them similarly -- eg: throwing Tokio at the problem
             | and not hyper optimizing Rust -- i imagine they'd perform
             | quite similarly.
             | 
             | As much as i'm a proponent of Rust, Go is very capable and
             | is generally a great language. Its warts _(as i see them)_
             | are not going to be apparent in the parallelism needed for
             | a Git hook manager lol.
        
             | Izkata wrote:
             | It mentions "lefthook" by name. I'm guessing that specific
             | go-based hook manager doesn't do this parallelism. So it's
             | saying "rust instead of go might give a small performance
             | boost, but that's not where most of the performance boost
             | comes from".
        
         | doug_durham wrote:
         | Why mention Rust at all? How is that possibly relevant to the
         | problem? There seems to be a lot of projects that are rewrites
         | of existing project in Rust where the fact that they are in
         | Rust seems to be their distinguishing feature. Languages are
         | less important than the solutions that a piece of software
         | provides.
        
           | codetrotter wrote:
           | > Why mention Rust at all? How is that possibly relevant to
           | the problem?
           | 
           | There are a lot of command line tools that are written in JS
           | and other scripting languages.
           | 
           | Having the tools that are involved in interactive use be
           | written in a compiled languages gives hope that they might be
           | fast enough to not be annoying.
           | 
           | Me personally I do not install nodejs on my machines. So
           | knowing that this tool is not written in JS is relevant for
           | me.
        
             | acheong08 wrote:
             | > There are a lot of command line tools that are written in
             | JS and other scripting languages.
             | 
             | ???
             | 
             | Also, this tool replaces another which was written in Go,
             | which I would put in a similar performance category as
             | Rust. It shouldn't make a difference in this scenario
        
           | unshavedyak wrote:
           | I prefer knowing what language things are written in. Not
           | only is it interesting, but i like knowing what sort of
           | installation i have to look forward to, if i'm interested in
           | contributing, etc.
        
           | jayd16 wrote:
           | If they didn't mention the language, it would be an immediate
           | question. The main focus is parallelism.
           | 
           | Could the Go solution add parallelism, sure. Did they? Not
           | yet. Does that mean no other improvement in any other
           | language can ever be written? No.
           | 
           | "Rust is faster" as an off the cuff comment that should have
           | been left out seeing it has triggered some folks to hyper
           | focus on that point.
        
             | judofyr wrote:
             | > Could the Go solution add parallelism, sure. Did they?
             | Not yet.
             | 
             | They did: Lefthook lets you define a "group" where you can
             | specify that every command should be done in parallel:
             | https://lefthook.dev/configuration/group.html. In addition,
             | it's possible to configure the whole hook to run in
             | parallel through another property:
             | https://lefthook.dev/configuration/parallel.html.
             | 
             | I'm assuming that Hk's innovation here is that it's a bit
             | smarter with what it runs in parallel. Maybe it uses the
             | globs to automatically run commands in parallel which
             | targets different files?
             | 
             | > "Rust is faster" as an off the cuff comment that should
             | have been left out seeing it has triggered some folks to
             | hyper focus on that point.
             | 
             | It's not a a hyper focus: This was the _first reason_ (out
             | of only three) that Hk itself presented as a reason to use
             | it over Lefthook. So yes, I agree: It should have been left
             | out if the intention wasn 't for people to focus on it. Put
             | it somewhere in a footnote if it's not so relevant.
        
           | dsff3f3f3f wrote:
           | > Why mention Rust at all?
           | 
           | I like knowing what language a tool is written in. If it's
           | written in Python or JavaScript and it isn't something that's
           | absolutely essential I can just immediately move on. It also
           | lets me know if it's something I'd be willing to contribute
           | to. It's odd that the authors mentioning the language is so
           | triggering for you.
        
         | Arech wrote:
         | Overall, the whole logic "A is written in X, but B is written
         | in Y, hence it is faster" is so deeply flawed, that one has to
         | consider a possibility that the author doesn't understand the
         | mere basics of computer program engineering. There are so much
         | nuances that I don't even want to start untangling it.
        
           | WhyNotHugo wrote:
           | Agreed. And for tools like this (which are light wrappers
           | around much heavier tools), the difference in performance is
           | insignificant. It's all the other factors that start to
           | matter.
        
           | sgarland wrote:
           | mise (nee rtx), by the same author, could confidently claim
           | this because the tool it replaced - asdf - is written in
           | bash. Bash is perfectly capable for a great many things, but
           | unless you're getting fairly deep into the weeds of what it
           | can do (e.g. eschewing external calls to sed et al. whenever
           | possible in favor of parameter substitution), you'll tank
           | performance from sub-shell spawn times. mise was objectively
           | and obviously faster out of the box, though nearly any
           | language that included basic text processing functionality
           | would've been acceptably fast.
           | 
           | In general though, I agree that the blanket statement of "X
           | is good because it's language Y" is absurd, though I
           | stubbornly cling to the opposite case for NodeJS, because I
           | despise the idea of a frontend language running anything but
           | a browser window. I have no objective defense.
        
             | dlisboa wrote:
             | `asdf` has now been rewritten in Go for anyone interested,
             | so choosing mise over it due to performance is less of a
             | concern now.
        
         | WhyNotHugo wrote:
         | It says _should_ be much faster, which makes me think they're
         | talking about performance but haven't actually compared it.
         | 
         | I find hk's choice of programming language for hooks pretty
         | exotic TBH.
         | 
         | This all made me look at lefthook, and it's pretty darn
         | interesting. I'd been missing out!
        
           | jdxcode wrote:
           | I haven't even built it, let alone benchmark it. I don't
           | think people are realizing this thing is like a 3 day old
           | project (in terms of actual work). The about page is a bunch
           | of scratch I wrote today (and didn't even finish that doc
           | before I moved onto something else).
        
         | jdxcode wrote:
         | This project is brand new. I've only spent 3 days on it and
         | this doc I half finished today. That said, I just did some
         | benchmarks and it certainly is much faster than its peers. 4.6x
         | faster than lefthook and 6.1x faster than pre-commit (in the
         | best case).
         | 
         | I'll put these benchmarks on that page in different scenarios.
         | 
         | Update:
         | https://github.com/jdx/hk/blob/main/docs/public/benchmark.pn...
         | 
         | As I said in the doc I think real-world performance in a large
         | codebase will show that hk is even faster still--though that
         | will depend on the project in question. It's really just a
         | matter of providing the right levers in the right places and
         | having good defaults.
         | 
         | Despite what everyone here says: yeah, just doing CLIs in Rust
         | _will_ be faster than Go and for CLIs like this milliseconds
         | matter.
        
       | autarch wrote:
       | I wrote a tool in this space as well, precious
       | (https://github.com/houseabsolute/precious). It's odd to me that
       | these tools are often talked about with such a focus on Git
       | hooks. Yes, that is definitely one use case, but I also want to
       | run this sort of thing in CI to check PRs, and I _also_ want to
       | run it locally to apply _pretty-printing_ to new code, rather
       | than just having it check the code that I wrote.
       | 
       | I think hk does do all those things, but it's a bit obscured by
       | the focus on Git hooks in the docs. But the docs are also still
       | in a super early state, so maybe that will be fleshed out more in
       | the future.
        
         | srid wrote:
         | I use https://github.com/cachix/git-hooks.nix which provides
         | all of it:
         | 
         | - Pre-commit hook setup
         | 
         | - Run locally anytime (`pre-commit run -a`)
         | 
         | - Check in CI (as Nix flake check)
         | 
         | Example repo: https://github.com/srid/haskell-template
         | 
         | The pre-commit configuration: https://github.com/srid/haskell-
         | template/blob/master/nix/mod...
        
       | joachimma wrote:
       | For me the issue to be solved is how to easily get the team to
       | run the tool. Some focus should be on integrating build tools.
       | 
       | When I or a team member do gradle build, npm build or cargo build
       | or similar on a fresh checkout the tool should ask to be
       | installed.
        
         | autarch wrote:
         | That is what mise from the same author is for. It lets you
         | define per-project sets of tools with specific versions
         | (including languages). I find it quite useful and I'm planning
         | to get my team at work to adopt it once I get some round tuits.
        
       | timhh wrote:
       | Interesting. I've been working on a pre-commit replacement too,
       | written in Rust but using WASI for all plugins (no exceptions!).
       | I haven't got very far but I think this will have huge advantages
       | over pre-commit, mostly in reliability.
       | 
       | Me and my colleagues have had numerous issues setting up pre-
       | commit because it inherits Python's atrocious infrastructure.
       | 
       | I'm curious how this is going to deal with actually running
       | plugins? Will it take the same approach as pre-commit and add
       | dedicated not-very-good support for a load of different
       | languages?
        
         | Spivak wrote:
         | Not gonna say Python isn't a mess but I'm surprised a self-
         | contained application is so bad. All the pieces are there to
         | have it work-- give it its own venv, use wrappers or the
         | shebang so transparently uses it, and plug-ins get installed
         | into that venv. All should be happy.
        
         | trallnag wrote:
         | Is it that difficult to use `pipx install` or `uv tool
         | install`?
        
           | timhh wrote:
           | Yes. Those are both more effort and less reliable than what I
           | am planning (a static binary with WASI plugins).
        
         | hv42 wrote:
         | You will be able to use mise to set up any tools required for
         | the linters. See https://mise.jdx.dev/dev-tools/
        
         | jdxcode wrote:
         | I also looked into WASI as well as lua until I ultimately
         | decided that I think I can get rid of the concept of plugins
         | altogether in favor of pkl. We'll see how well that works out
         | but right now I don't have any use-cases that would require
         | plugins and if I can stick to that it will definitely help
         | performance.
        
           | timhh wrote:
           | I don't really understand. Pkl seems to be a configuration
           | language? How are you going to run clang-format, rustfmt, go
           | fmt, pyright, etc. using Pkl?
        
             | jdxcode wrote:
             | it just needs to define shell script
        
               | timhh wrote:
               | So you have to already have all the linters installed? Ok
               | I am planning to solve that problem too, but we'll have
               | to see whether it's actually possible to compile e.g.
               | rustfmt to WASI. In my experience so far WASI is pretty
               | alpha quality.
        
               | jdxcode wrote:
               | mise is my solution to that problem. I don't think it
               | should be the job of a hook manager to install things
               | developers should have installed for other reasons
               | anyways.
        
         | pkmx wrote:
         | Pre-commit has like the worst DX to me, so I'm really looking
         | forward to a replacement.
         | 
         | My biggest gripes are:
         | 
         | 1. Pre-commit hooks are designed to modify files in-place,
         | which turns `git commit` into something that can alter your
         | work tree. IMO those tools should never ever modify files
         | (unless explicitly asked by the user) and only output a diff
         | and exit code that signifies if the check failed.
         | 
         | 2. It manages the tools with its own environment but never
         | exposes it. I always have to dig around its cache directory to
         | find out which executable it is running if I have to reproduce
         | the problem after something goes wrong.
        
       | azeirah wrote:
       | I don't know if this is well-known or not, but nix flakes are
       | _amazing_ for managing and configuring cross-platform hooks.
       | 
       | In less than 40 lines of nix, most of which is boilerplate, you
       | have fully cross-platform automatically installed and
       | declaratively managed hooks, as part of your repo.
       | 
       | Need to run it in CI too? Well, no problem! Nix runs equally well
       | in CI as it does locally.
       | 
       | Check this flake for example:
       | 
       | https://github.com/Azeirah/remarks/blob/main/flake.nix
       | 
       | See line 62 in the shell script.
       | 
       | This flake:
       | 
       | 1. Manages ALL my dev env dependencies. Including even the
       | specific version of bash that the script is running on, also the
       | specific python dependencies down to the bit.
       | 
       | 2. It's (posix-compliant) cross-platform, so it runs on MacOS,
       | WSL, Linux, NixOS, ARM, x86 etc. Also docker.
       | 
       | 3. It uses no specialized tools other than Nix, which is now a 20
       | year old Linux project which is quickly gaining even more
       | traction. Nix is a programming language for reproducible,
       | reliable and declarative dependency management (think docker but
       | with a pure and functional programming language, rather than a
       | recipe with installation instructions)
       | 
       | 4. It also creates binaries as well as docker images from the
       | binary. The docker image is like 2 lines of extra code.
       | 
       | Highly highly recommended. It's a bit difficult to wrap your head
       | around initially, but holy damn if all dependency management
       | problems don't just magically disappear forever once you learn
       | Nix.
       | 
       | And I mean ALL of them. Whether it's Linux packages, docker
       | containers, development environments, CI, virtual machines,
       | containers, compilers, C headers from a 1970 Bell Labs project.
       | No matter the architecture or OS (except Windows! :D).
       | 
       | Oh, did I mention it has a lockfile? So you can rollback or
       | upgrade piecewise? Whenever you want?
       | 
       | It's like a programmable version of the superset of npm, cargo,
       | pip, apt, brew, composer, gem, make, cmake, docker, git, Jenkins
       | and even Linux itself (if you dare go the way of NixOS)
        
         | guipsp wrote:
         | Nix is slow enough that having it in your pre-commit gets
         | annoying
        
           | azeirah wrote:
           | Haven't run into that yet, but can imagine that it gets worse
           | if your devenv is large.
           | 
           | I'm sure it's something that can be engineered away and
           | optimized in the Nix core though. Because yeah it isn't a
           | hyperspecialized tool for specifically git hooks alone
           | written in Rust. No.
           | 
           | It's not a saw nor a hammer. It's an entire workshop.
           | 
           | Flakes are less of a workshop, but more like a bus with tools
           | or a heavy-duty toolbox.
        
           | justinrubek wrote:
           | When combined with direnv, the nix portion is almost entirely
           | cached, so there's nothing to make it slow. I use pre-commit
           | hooks via devShell in many projects, and they're nearly
           | instant. The only exception is when the hook itself is slow.
        
         | Arcuru wrote:
         | And since this thread is talking about git hooks, there's also
         | this project for managing git hooks inside Nix
         | https://github.com/cachix/git-hooks.nix
        
       | Saphyel wrote:
       | My git pre-commits are usually calling other executables or other
       | tools, so I'm not quite sure how much it matters how fast is the
       | git pre-commit tool. Can we have a benchmark of a normal use case
       | of how much time are we saving with this new tool?
       | 
       | or is this "Yet Another Rust Rewrite" (YARR)?
        
         | thayne wrote:
         | Being written in rust instead of python or go probably doesn't
         | matter that much. But being able to run multiple tasks in
         | parallel, which hk claims to do, _could_ make a big difference.
        
       | thayne wrote:
       | The most compelling thing about this for me is that it uses pkl
       | instead of yaml or json.
        
         | jdxcode wrote:
         | pkl is so good I don't think I need plugins for hk
        
       ___________________________________________________________________
       (page generated 2025-02-17 23:01 UTC)