Development takes place on Tangled, however, I do maintain a GitHub mirror. Please make issues and PRs to the Tangled repository.
nixhooks #
A small, fast, drop-in replacement for git-hooks.nix.
No flake required, no flake-parts, no systems, no Python, no slop, just
plain ol' Nix and Bash.
Usage (non-flake) #
{ pkgs ? import <nixpkgs> { } }:
let
nixhooks = import (fetchTarball "https://next.tangled.org/xrpc/sh.tangled.repo.archive?repo=at%3A%2F%2Fdid%3Aplc%3Ayaryxzbwui6ejiegzysy6ipp%2Fsh.tangled.repo%2Fnixhooks&ref=main&format=tar.gz") { inherit pkgs; };
in
nixhooks.mkHooks {
hooks = {
shellcheck = {
entry = "${pkgs.shellcheck}/bin/shellcheck";
files = "\\.sh$";
stages = [ "pre-commit" "pre-push" ];
};
nixfmt-check = {
entry = "${pkgs.nixfmt}/bin/nixfmt";
args = [ "--check" ];
files = "\\.nix$";
};
};
}
$ nix-build -A install-hooks && ./result/bin/install-hooks
Or, wire it into a shellHook so hooks install automatically on entry (see the devShells output in flake.nix).
Usage (flake) #
nixhooks.lib.<system> is mkHooks/presets built from nixhooks' own
pinned nixpkgs for that system.
{
inputs.nixhooks.url = "git+https://tangled.org/poacher.dev/nixhooks";
outputs = { self, nixpkgs, nixhooks }: let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
hooks = nixhooks.lib.x86_64-linux.mkHooks {
hooks = {
shellcheck = {
entry = "${pkgs.shellcheck}/bin/shellcheck";
files = "\\.sh$";
};
};
};
in {
apps.x86_64-linux = hooks.apps;
devShells.x86_64-linux.default = pkgs.mkShell {
shellHook = ''
${hooks.install-hooks}/bin/install-hooks
'';
};
};
}
$ nix run .#install-hooks
lib.withHooks #
withHooks is sugar over mkHooks for flakes. Pass it your hook config
alongside your regular flake outputs and it merges the generated
apps/packages and wires install-hooks into devShells.<system>.default
Hooks are passed as a hooks argument keyed by system, kept separate from
your regular flake outputs. Each system's attrset may also carry its own
settings.
{
inputs.nixhooks.url = "git+https://tangled.org/poacher.dev/nixhooks";
outputs = { self, nixpkgs, nixhooks }: let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in nixhooks.lib.withHooks {
hooks.x86_64-linux = {
inherit (nixhooks.lib.x86_64-linux.presets) alejandra statix;
settings = {
parallel = true;
tangled = {
enable = true;
attr = "run-hooks";
flake = true;
};
};
};
packages.x86_64-linux = { /* ... */ };
apps.x86_64-linux = { /* ... */ };
devShells.x86_64-linux.default = pkgs.mkShell {
packages = [ pkgs.jq ];
};
};
}
Warning
Passing hooks via a top-level nixhooks = { hooks = ...; settings = ...; }
argument is deprecated and will be removed in a future release. Use
hooks.<system> instead.
Hook spec #
hooks.<name> = {
enable = true; # default true; set false to disable without deleting the entry
entry = "/nix/store/.../bin/tool"; # required: the command to run
args = [ ]; # extra args passed before matched filenames
files = ".*"; # ERE regex (bash [[ =~ ]]) tested against each candidate path
exclude = "^$"; # ERE regex; "^$" (default) excludes nothing
stages = [ "pre-commit" ]; # subset of [ "pre-commit" "pre-push" "commit-msg" ]
pass_filenames = true; # append matched files as trailing args
always_run = false; # run once with no file filtering/passing
path = [ ]; # packages whose bin/ dirs are prepended to PATH for entry
serial = true; # if false this hook will be run in parallel; see "Parallel execution" below
};
<name> must match [A-Za-z0-9_.-]+
path is for tools that internally dispatch to a sibling binary via PATH
(e.g. cargo finding cargo-clippy) -- entry itself is always invoked by
absolute path regardless of path.
A commit-msg-staged hook receives the path to the temporary file
containing the commit message as its sole file. files/exclude/
pass_filenames apply to that single path the same way they apply to real
filenames in pre-commit/pre-push.
Presets #
presets is a small built-in catalog of common tool configs. These are
functors which accept either no arguments, or allow for their options to be
overriden in an attrSet.
nixhooks.mkHooks {
hooks = {
shellcheck = nixhooksLib.presets.shellcheck;
alejandra = nixhooksLib.presets.alejandra { stages = [ "pre-push" ]; };
};
}
See lib/presets.nix for the full list of presets.
Parallel execution #
By default every hook runs sequentially. Passing settings.parallel = true to
mkHooks enables a two-phase model where every hook not marked serial runs
concurrently, then the remaining hooks run sequentially. Note that parallelism
can actually decrease your hook execution speed if its already fast. Only use
it when needed.
nixhooks.mkHooks {
hooks = {inherit (nixhooksLib.presets) shellcheck alejandra statix;};
settings.parallel = true;
}
Output from the concurrent batch is buffered per hook and flushed once the entire batch finishes in the declared order.
Only mark a hook serial = false if it doesn't mutate the files it's
matched against, or if it's provably safe to race against every other
serial = false hook in the same stage.
CI/CD #
nixhooks can also autogenerate CI workflows based on your selected hooks.
Hooks staged only for commit-msg (e.g. the commitlint preset) are
excluded from the generated CI pipeline's run-hooks invocation as CI has
no commit message to check. A hook declaring multiple stages including
commit-msg still runs in CI for its other stage(s).
Tangled pipelines #
nixhooks.mkHooks {
hooks = { /* ... */ };
settings.tangled = {
enable = true;
attr = "run-hooks";
flake = true;
engine = "microvm";
when = [
{
event = ["push" "pull_request"];
branch = ["main"];
}
];
};
}
$ nix-build -A gen-tangled-pipeline && ./result/bin/gen-tangled-pipeline
gen-tangled-pipeline: wrote .tangled/workflows/hooks.yml
This writes a .tangled/workflows/hooks.yml in the same manner as
install-hooks which you commit to the repo. Note that this isn't wired into
shellHook like install-hooks, so you'll need to trigger it manually.
GitHub Actions #
nixhooks.mkHooks {
hooks = { /* ... */ };
settings.githubActions = {
enable = true;
attr = "run-hooks";
flake = true;
runsOn = "ubuntu-latest";
cache = true; # nix-community/cache-nix-action
on = {
push = { branches = [ "main" ]; };
pull_request = { branches = [ "main" ]; };
};
};
}
$ nix-build -A gen-github-actions-workflow && ./result/bin/gen-github-actions-workflow
gen-github-actions-workflow: wrote .github/workflows/hooks.yml
Note that I don't actively used GitHub. If you find any bugs then please open an issue.