diff --git a/src/lib/cfg/lefthook.md b/src/lib/cfg/lefthook.md index 803582a0..626b42ff 100644 --- a/src/lib/cfg/lefthook.md +++ b/src/lib/cfg/lefthook.md @@ -11,3 +11,62 @@ ```nix {{#include ../../src/lib/cfg/lefthook.nix}} ``` + +#### Explanation of the `in { ... }` block + +This `in { ... }` block is a **Nixago configuration record**, it tells the +Nixago block type how to generate and manage the `lefthook.yml` file. + +`data = {};` is The actual lefthook config content. It's empty here because this +is just the template/scaffold, the real config data gets merged in by consumers +(e.g. in `src/local/configs.nix` or wherever this is used). Nixago deep-merges +`data` from the caller. + +`hook.extra` is a Nixago hook hat runs whenever the generated `lefthook.yml` is +materialized. It receives the final merged `config` (the full lefthook YAML +content as a Nix attrset) and produces extra shell commands. Here's the pipeline +step by step: + +```nix +hook.extra = config: + let + commands = lib.pipe config [ + # 1. Strip non-stage keys (colors, extends, etc.) + # e.g. { pre-commit = {...}; commit-msg = {...}; colors = true; } + # becomes { pre-commit = {...}; commit-msg = {...}; } + toStagesConfig + + # 2. Extract just the attribute names → ["pre-commit" "commit-msg"] + lib.attrNames + + # 3. For each stage, create a symlink command: + # ln -sf "/nix/store/...-lefthook-pre-commit" ".git/hooks/pre-commit" + # The target is a Nix-built script (mkScript) that runs: + # lefthook run "pre-commit" "$@" + # (unless $LEFTHOOK == "0", which disables it) + (lib.map (stage: ''ln -sf "${mkScript stage}" ".git/hooks/${stage}"'')) + + # 4. Prepend "mkdir -p .git/hooks" IF there are any stages + # ["mkdir -p .git/hooks" "ln -sf ..." "ln -sf ..."] + (stages: + lib.optional (stages != []) "mkdir -p .git/hooks" + ++ stages) + + # 5. Join into a single newline-separated shell script string + (lib.concatStringsSep "\n") + ]; + in '' + # Only install hooks in the main repo, not in worktrees. + # In worktrees, .git is a file pointing to the main repo's .git dir, + # so mkdir -p .git/hooks would fail. + if test "$(git rev-parse --git-dir)" = "$(git rev-parse --git-common-dir)"; then + ${commands} + fi + ''; +``` + +When you run the Nixago hook (e.g. via `direnv allow` or `std` commands), it: + +1. Writes `lefthook.yml` to your project root +2. Runs this `hook.extra` script, which symlinks Nix-built wrapper scripts into + `.git/hooks/` (only in the main repo, not in worktrees) diff --git a/src/lib/cfg/lefthook.nix b/src/lib/cfg/lefthook.nix index c34180fd..6b8e9cd8 100644 --- a/src/lib/cfg/lefthook.nix +++ b/src/lib/cfg/lefthook.nix @@ -21,9 +21,10 @@ in { format = "yaml"; output = "lefthook.yml"; packages = [nixpkgs.lefthook]; - # Add an extra hook for adding required stages whenever the file changes - hook.extra = config: - lib.pipe config [ + # Add an extra hook for adding required stages whenever the file changes. + # Skip hook installation in git worktrees where .git is a file, not a directory. + hook.extra = config: let + commands = lib.pipe config [ toStagesConfig lib.attrNames (lib.map (stage: ''ln -sf "${mkScript stage}" ".git/hooks/${stage}"'')) @@ -32,4 +33,9 @@ in { ++ stages) (lib.concatStringsSep "\n") ]; + in '' + if test "$(${lib.getExe nixpkgs.git} rev-parse --git-dir 2>/dev/null)" = "$(${lib.getExe nixpkgs.git} rev-parse --git-common-dir 2>/dev/null)"; then + ${commands} + fi + ''; }