Skip to content

cachix/git-hooks.nix

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Jan 7, 2024
a44c73b · Jan 7, 2024
Jul 29, 2023
Jan 7, 2024
Dec 30, 2023
Oct 19, 2023
Nov 4, 2023
Feb 21, 2023
Feb 21, 2023
Nov 6, 2019
Sep 9, 2019
Jan 7, 2024
Nov 30, 2022
Jun 19, 2023
Jul 14, 2023
Jun 4, 2023
Aug 19, 2019
Nov 21, 2022

Repository files navigation

Seamless integration of pre-commit git hooks with Nix

pre-commit.png

The goal is to manage commit hooks with Nix and solve the following:

  • Trivial integration for Nix projects (wires up a few things behind the scenes)

  • Provide a low-overhead build of all the tooling available for the hooks to use (naive implementation of calling nix-shell does bring some latency when committing)

  • Common hooks for languages like Python, Haskell, Elm, etc.

  • Run hooks as part of development and on your CI

Getting started

devenv.sh

https://devenv.sh/pre-commit-hooks/

Nix

  1. (optional) Use binary caches to avoid compilation:

    nix-env -iA cachix -f https://cachix.org/api/v1/install
    cachix use pre-commit-hooks
  2. Integrate hooks to be built as part of default.nix:

     let
       nix-pre-commit-hooks = import (builtins.fetchTarball "https://github.com/cachix/pre-commit-hooks.nix/tarball/master");
     in {
       pre-commit-check = nix-pre-commit-hooks.run {
         src = ./.;
         # If your hooks are intrusive, avoid running on each commit with a default_states like this:
         # default_stages = ["manual" "push"];
         hooks = {
           elm-format.enable = true;
           ormolu.enable = true;
           shellcheck.enable = true;
         };
    
         # Some hooks offer custom settings that affect how they execute
         settings = {
           ormolu.defaultExtensions = [ "lhs" "hs" ];
         };
       };
     }

    Run $ nix-build -A pre-commit-check to perform the checks as a Nix derivation.

  3. Integrate hooks to prepare environment as part of shell.nix:

     (import <nixpkgs> {}).mkShell {
        shellHook = ''
         ${(import ./default.nix).pre-commit-check.shellHook}
       '';
     }

    Add /.pre-commit-config.yaml to .gitignore.

    Run $ nix-shell to execute shellHook which will:

    • build the tools and .pre-commit-config.yaml config file symlink which references the binaries, for speed and safe garbage collection
    • provide the pre-commit executable that git commit will invoke

Optional

Direnv

.envrc:

use nix

Hooks

Nix

Haskell

C/C++/C#/ObjC

Clojure

Elm

Elixir

OCaml

Purescript

Javascript/Typescript

  • denofmt: Runs deno fmt
  • denolint: Runs deno lint
  • eslint
  • rome

Python

PHP

Rust

Golang

Julia

Shell

LaTeX

Lua

HTML

Markdown

Terraform

  • terraform-format: built-in formatter
  • tflint

YAML

TOML

Typst

Fortran

Spell checkers

Other Formatters

You must configure which languages should be formatted by clang_format using clang-format.types_or. For example to check both C and C++ files:

clang-format = {
  enable = true;
  types_or = [ "c" "c++" ];
};

Git

Custom hooks

Sometimes it is useful to add a project specific command as an extra check that is not part of the pre-defined set of hooks provided by this project.

Example configuration:

 let
   nix-pre-commit-hooks = import (builtins.fetchTarball "https://github.com/cachix/pre-commit-hooks.nix/tarball/master");
 in {
   pre-commit-check = nix-pre-commit-hooks.run {
     hooks = {
       # ...

       # Example custom hook for a C project using Make:
       unit-tests = {
         enable = true;

         # The name of the hook (appears on the report table):
         name = "Unit tests";

         # The command to execute (mandatory):
         entry = "make check";

         # The pattern of files to run on (default: "" (all))
         # see also https://pre-commit.com/#hooks-files
         files = "\\.(c|h)$";

         # List of file types to run on (default: [ "file" ] (all files))
         # see also https://pre-commit.com/#filtering-files-with-types
         # You probably only need to specify one of `files` or `types`:
         types = [ "text" "c" ];

         # Exclude files that were matched by these patterns (default: [ ] (none)):
         excludes = [ "irrelevant\\.c" ];

         # The language of the hook - tells pre-commit
         # how to install the hook (default: "system")
         # see also https://pre-commit.com/#supported-languages
         language = "system";

         # Set this to false to not pass the changed files
         # to the command (default: true):
         pass_filenames = false;
       };
     };
   };
 }

Custom hooks are defined with the same schema as pre-defined hooks.

Nix Flakes support

Given the following flake.nix example:

{
  description = "An example project.";

  inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, pre-commit-hooks, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      {
        checks = {
          pre-commit-check = pre-commit-hooks.lib.${system}.run {
            src = ./.;
            hooks = {
              nixpkgs-fmt.enable = true;
            };
          };
        };
        devShell = nixpkgs.legacyPackages.${system}.mkShell {
          inherit (self.checks.${system}.pre-commit-check) shellHook;
        };
      }
    );
}

Add /.pre-commit-config.yaml to the .gitignore.

To run the all the hooks on CI:

nix flake check

To install pre-commit hooks developers would run:

nix develop

Contributing hooks

Everyone is encouraged to add new hooks.

Have a look at the existing hooks and the options.

There's no guarantee the hook will be accepted, but the general guidelines are:

  • Nix closure of the tool should be small e.g. < 50MB. A problematic example:
   $ du -sh $(nix-build -A go)
   463M	/nix/store/v4ys4lrjngf62lvvrdbs7r9kbxh9nqaa-go-1.18.6
  • The tool must not be very specific (e.g. language tooling is OK, but project specific tooling is not)
  • The tool needs to live in a separate repository (even if a simple bash script, unless it's a oneliner)