Bug Description
The installation script incorrectly calculates core.hooksPath when running inside a git worktree where the common directory is not a child of the working tree. This causes hooks to silently not be found by git, even though they are correctly installed.
Root Cause
In modules/pre-commit.nix (around line 297-302), the install script does:
# Fetch the absolute path to the git common directory.
common_dir=$(git rev-parse --path-format=absolute --git-common-dir)
# Convert the absolute path to a path relative to the toplevel working directory.
common_dir=${common_dir#$GIT_WC/}
git config --local core.hooksPath "$common_dir/hooks"
The bash parameter expansion ${common_dir#$GIT_WC/} assumes the common directory is always a child of the working tree (e.g. $GIT_WC/.git). In a normal repo this works: stripping $GIT_WC/ from $GIT_WC/.git yields .git, so core.hooksPath becomes .git/hooks.
In a worktree, the common directory is typically not under the working tree at all. For example:
# Working tree (GIT_WC):
/repos/my-project/worktrees/feature-branch
# Common directory (git rev-parse --git-common-dir):
/repos/my-project/.bare
Since /repos/my-project/.bare does not start with /repos/my-project/worktrees/feature-branch/, the ${common_dir#$GIT_WC/} expansion silently fails (no match = no stripping), leaving common_dir as the full absolute path. Then core.hooksPath gets set to an absolute path like /repos/my-project/.bare/hooks.
Normally an absolute path would work fine, but the script first runs git config --local core.hooksPath "" (line ~280) to clear the value, then prek install sets up hooks in the common directory. The final git config --local core.hooksPath "$common_dir/hooks" writes the config to the worktree-local config, but because the --local flag in a worktree writes to the worktree's own config file (not the shared one), and prek installs hooks into the common hooks directory, there can be a mismatch in how git resolves the path.
The net result is that hooks are installed but git cannot find them, so pre-commit hooks silently don't run.
Reproduction
-
Create a bare repository with a worktree where the worktree path is not a parent of the bare directory:
mkdir -p /tmp/test-repo/.bare
git init --bare /tmp/test-repo/.bare
git -C /tmp/test-repo/.bare worktree add /tmp/test-worktree main
-
Set up a devenv/git-hooks.nix configuration in the worktree with any hook enabled
-
Enter the devenv shell (or run direnv allow) to trigger the installation script
-
Observe that hooks are installed in /tmp/test-repo/.bare/hooks/ but git doesn't execute them on commit
-
Verify with:
cd /tmp/test-worktree
git config --local core.hooksPath
# Shows an incorrect or absolute path that doesn't resolve to the hooks
Expected Behavior
core.hooksPath should correctly point to the hooks directory regardless of the repository layout (normal repo, worktree, bare repo with worktrees, etc.).
Suggested Fix
Instead of the fragile string-stripping approach, use realpath --relative-to or handle the case where the common directory is not under the working tree:
common_dir=$(git rev-parse --path-format=absolute --git-common-dir)
# Use realpath to correctly compute the relative path, even across directory trees
if command -v realpath &>/dev/null; then
common_dir=$(realpath --relative-to="$GIT_WC" "$common_dir")
else
# Fallback: strip prefix if possible, otherwise use absolute path
common_dir=${common_dir#$GIT_WC/}
fi
git config --local core.hooksPath "$common_dir/hooks"
This handles worktrees where the common directory might be ../../.bare relative to the working tree.
Environment
- git-hooks.nix: used via devenv integration
- Git version: 2.52.0
- OS: NixOS (Linux)
- Repository layout: bare repo with worktrees (megarepo / worktree-per-branch setup)
Bug Description
The installation script incorrectly calculates
core.hooksPathwhen running inside a git worktree where the common directory is not a child of the working tree. This causes hooks to silently not be found by git, even though they are correctly installed.Root Cause
In
modules/pre-commit.nix(around line 297-302), the install script does:The bash parameter expansion
${common_dir#$GIT_WC/}assumes the common directory is always a child of the working tree (e.g.$GIT_WC/.git). In a normal repo this works: stripping$GIT_WC/from$GIT_WC/.gityields.git, socore.hooksPathbecomes.git/hooks.In a worktree, the common directory is typically not under the working tree at all. For example:
Since
/repos/my-project/.baredoes not start with/repos/my-project/worktrees/feature-branch/, the${common_dir#$GIT_WC/}expansion silently fails (no match = no stripping), leavingcommon_diras the full absolute path. Thencore.hooksPathgets set to an absolute path like/repos/my-project/.bare/hooks.Normally an absolute path would work fine, but the script first runs
git config --local core.hooksPath ""(line ~280) to clear the value, thenprek installsets up hooks in the common directory. The finalgit config --local core.hooksPath "$common_dir/hooks"writes the config to the worktree-local config, but because the--localflag in a worktree writes to the worktree's own config file (not the shared one), and prek installs hooks into the common hooks directory, there can be a mismatch in how git resolves the path.The net result is that hooks are installed but git cannot find them, so pre-commit hooks silently don't run.
Reproduction
Create a bare repository with a worktree where the worktree path is not a parent of the bare directory:
Set up a devenv/git-hooks.nix configuration in the worktree with any hook enabled
Enter the devenv shell (or run direnv allow) to trigger the installation script
Observe that hooks are installed in
/tmp/test-repo/.bare/hooks/but git doesn't execute them on commitVerify with:
Expected Behavior
core.hooksPathshould correctly point to the hooks directory regardless of the repository layout (normal repo, worktree, bare repo with worktrees, etc.).Suggested Fix
Instead of the fragile string-stripping approach, use
realpath --relative-toor handle the case where the common directory is not under the working tree:This handles worktrees where the common directory might be
../../.barerelative to the working tree.Environment