-
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. See all hook options
-
Run hooks as part of development and during CI
-
Support for alternative
pre-commitimplementations, like prek.
{ inputs, ... }:
{
git-hooks.hooks = {
# Format Nix code
nixfmt.enable = true;
# Format Python code
black.enable = true;
# Lint shell scripts
shellcheck.enable = true;
# Execute shell examples in Markdown files
mdsh.enable = true;
# Override a package with a different version
ormolu.enable = true;
ormolu.package = pkgs.haskellPackages.ormolu;
# Some hooks have more than one package, like clippy:
clippy.enable = true;
clippy.packageOverrides.cargo = pkgs.cargo;
clippy.packageOverrides.clippy = pkgs.clippy;
# Some hooks provide settings
clippy.settings.allFeatures = true;
# Define your own custom hooks
my-custom-hook = {
name = "My own hook";
exec = "on-pre-commit.sh";
};
};
# Use alternative pre-commit implementations
git-hooks.package = pkgs.prek;
}See getting started.
Given the following flake.nix example:
{
description = "An example project";
inputs = {
systems.url = "github:nix-systems/default";
git-hooks.url = "github:cachix/git-hooks.nix";
};
outputs =
{
self,
systems,
nixpkgs,
...
}@inputs:
let
forEachSystem = nixpkgs.lib.genAttrs (import systems);
in
{
# Run the hooks with `nix fmt`.
formatter = forEachSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
config = self.checks.${system}.pre-commit-check.config;
inherit (config) package configFile;
script = ''
${pkgs.lib.getExe package} run --all-files --config ${configFile}
'';
in
pkgs.writeShellScriptBin "pre-commit-run" script
);
# Run the hooks in a sandbox with `nix flake check`.
# Read-only filesystem and no internet access.
checks = forEachSystem (system: {
pre-commit-check = inputs.git-hooks.lib.${system}.run {
src = ./.;
hooks = {
nixfmt.enable = true;
};
};
});
# Enter a development shell with `nix develop`.
# The hooks will be installed automatically.
# Or run pre-commit manually with `nix develop -c pre-commit run --all-files`
devShells = forEachSystem (system: {
default =
let
pkgs = nixpkgs.legacyPackages.${system};
inherit (self.checks.${system}.pre-commit-check) shellHook enabledPackages;
in
pkgs.mkShell {
inherit shellHook;
buildInputs = enabledPackages;
};
});
};
}Add /.pre-commit-config.yaml to .gitignore.
This file is auto-generated from the Nix configuration and doesn't need to be committed.
Enter a development shell with pre-commit hooks enabled:
nix developRun all hooks sandboxed:
nix flake checkKeep in mind that nix flake check runs in a sandbox.
It doesn't have access to the internet and cannot modify files.
This makes it a poor choice for formatting hooks that attempt to fix files automatically, or hooks that cannot easily be packaged to avoid impure access to the internet.
A better alternative in such cases is to run pre-commit through nix develop:
nix develop -c pre-commit run -aOr configure a formatter like in the example above and use nix fmt:
nix fmtIf your flake uses flake-parts, we provide a flake-parts module as well. Checkout ./template/flake.nix for an example.
-
Optionally use binary caches to avoid compilation:
nix-env -iA cachix -f https://cachix.org/api/v1/install cachix use pre-commit-hooks
-
Integrate hooks to be built as part of
default.nix:let nix-pre-commit-hooks = import (builtins.fetchTarball "https://github.com/cachix/git-hooks.nix/tarball/master"); in { # Configured with the module options defined in `modules/pre-commit.nix`: 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" "pre-push"]; hooks = { elm-format.enable = true; # override a package with a different version ormolu.enable = true; ormolu.package = pkgs.haskellPackages.ormolu; ormolu.settings.defaultExtensions = [ "lhs" "hs" ]; # some hooks have more than one package, like clippy: clippy.enable = true; clippy.packageOverrides.cargo = pkgs.cargo; clippy.packageOverrides.clippy = tools.clippy; # some hooks provide settings clippy.settings.allFeatures = true; }; }; }
Run
$ nix-build -A pre-commit-checkto perform the checks as a Nix derivation. -
Integrate hooks to prepare environment as part of
shell.nix:let pre-commit = import ./default.nix; in (import <nixpkgs> {}).mkShell { shellHook = '' ${pre-commit.pre-commit-check.shellHook} ''; buildInputs = pre-commit.pre-commit-check.enabledPackages; }
Add
/.pre-commit-config.yamlto.gitignore.Run
$ nix-shellto executeshellHookwhich will:- build the tools and
.pre-commit-config.yamlconfig file symlink which references the binaries, for speed and safe garbage collection - provide the
pre-commitexecutable thatgit commitwill invoke
- build the tools and
.envrc:
use nix
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/git-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", or "unsupported" for pre-commit >= 4.4.0)
# see also https://pre-commit.com/#supported-languages
language = "unsupported";
# Set this to false to not pass the changed files
# to the command (default: true):
pass_filenames = false;
# Which git hooks the command should run for (default: [ "pre-commit" ]):
stages = ["pre-push"];
};
};
};
}Custom hooks are defined with the same schema as pre-defined hooks.
Note on
language = "system"vs"unsupported":Pre-commit >= 4.4.0 renamed
"system"to"unsupported".This does not mean deprecated —
"unsupported"is just a new name to reflect that when using this language, pre-commit does not provision the tools, and using externally managed tools (e.g. via Nix) is not an officially supported workflow.Both values are functionally equivalent. The default is set automatically based on the pre-commit package version.
We recommend switching to
prek.
-
clang-format.
You may restrict which languages should be formatted byclang-formatusingclang-format.types_or. For example to check only C and C++ files:clang-format = { enable = true; types_or = lib.mkForce [ "c" "c++" ]; };
Otherwise, the default internal list is used which includes everything that clang-format supports.
- annex
- check-merge-conflicts
- commitizen
- convco
- forbid-new-submodules
- gitlint
- gptcommit
- no-commit-to-branch
- gofmt: Runs
go fmt - golangci-lint
- golines
- gotest: Runs
go test - govet
- revive
- staticcheck
- alejandra
- deadnix
- flake-checker
- nil
- nixf-diagnose
- nixfmt (supports
nixfmt>=v1.0) - nixfmt-classic
- nixfmt-rfc-style
- nixpkgs-fmt
- statix
- autoflake
- black
- check-builtin-literals
- check-docstring-first
- check-python
- fix-encoding-pragma
- flake8
- flynt
- isort
- mypy
- name-tests-test
- poetry-check: Run
poetry check. - poetry-lock: Run
poetry lock. - pylint
- pyright
- python-debug-statements
- pyupgrade
- ruff
- ruff-format
- single-quoted-strings
- sort-requirements-txt
- uv
- pre-commit-ensure-sops
- ripsecrets
- trufflehog: Secret scanner
terraform-format: built-in formatter (using OpenTofu'sfmtor Terraform'sfmt)terraform-validate: built-in validator (using OpenTofu'svalidateTerraform'svalidate)- tflint
- actionlint
- action-validator
- chart-testing
- check-added-large-files
- check-case-conflicts
- check-executables-have-shebangs
- checkmake
- check-shebang-scripts-are-executable
- check-symlinks
- check-vcs-permalinks
- check-xml
- circleci
- conform
- detect-aws-credentials
- detect-private-keys
- end-of-file-fixer
- fix-byte-order-marker
- headache
- hledger-fmt
- keep-sorted
- mixed-line-endings
- mkdocs-linkcheck
- openapi-spec-validator
- prettier
- reuse
- sort-file-contents
- tagref
- topiary
- treefmt
- trim-trailing-whitespace
- woodpecker-cli-lint
- zizmor
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)
