Skip to content

core.hooksPath incorrectly calculated for git worktrees #688

@schickling

Description

@schickling

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

  1. 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
  2. Set up a devenv/git-hooks.nix configuration in the worktree with any hook enabled

  3. Enter the devenv shell (or run direnv allow) to trigger the installation script

  4. Observe that hooks are installed in /tmp/test-repo/.bare/hooks/ but git doesn't execute them on commit

  5. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions