Fix Git hooks installation in linked worktrees and submodules - #360
Merged
Conversation
GitSupport copied the hooks into a hardcoded <projectRoot>/.git/hooks. That assumes .git is a directory, which is only true in a plain repository. In a linked worktree and in a submodule, .git is a regular file that references the real Git directory, so the copy failed with "Cannot create directory '<projectRoot>/.git/hooks'". Because the CLI aborts the whole run on a tool failure, no tool was installed or activated at all in such a project, not only the Git hooks. The target directory is now resolved from the referenced Git directory: - .git is a directory, or missing: <projectRoot>/.git/hooks, as before. - .git is a file and the referenced Git directory has a commondir file: the common Git directory, because a linked worktree shares its hooks with the main worktree. Verified against Git: a hook in the common directory is executed from a linked worktree, one in the per-worktree administrative directory is not. - .git is a file without a commondir file: the referenced Git directory, because a submodule holds its own hooks. A .git file that references a Git directory which does not exist now fails with a clear message instead of silently creating that path outside the project. Git rejects the same input. Paths are resolved through the file system rather than normalized lexically, so a relative reference stays correct when a symlink sits on the path. Resolution uses only java.io and java.nio, so no Git executable is required and the native image is unaffected. The new GitSupportTest covers all cases with file system fixtures and never invokes Git. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
project-env-clicannot install anything in a project whose.gitis a file instead of a directory. That is the case in a linked Git worktree and in a submodule.GitSupportcopied the hooks into a hardcoded<projectRoot>/.git/hooks, so the copy failed:The impact is larger than the hooks. Because the CLI aborts the whole run on a tool failure, no tool is installed or activated at all in such a project, and the CLI exits 1 without writing an activation script. Any project with a
[git]section is affected in every worktree.Fix
The target directory is resolved from the referenced Git directory:
<projectRoot>/.git<projectRoot>/.git/hooks, as beforecommondirfilecommondirfileA
.gitfile that references a Git directory which does not exist now fails with a clear message instead of silently creating that path outside the project. Git rejects the same input withfatal: not a git repository: ....Paths are resolved through the file system rather than normalised lexically.
Path.normalize()removes..segments lexically, which gives the wrong directory when a symlink sits at the traversed depth, and Git resolves such a path through the OS.Resolution uses only
java.ioandjava.nio. Nogitexecutable is required, so the native image is unaffected. The three added dependencies aretestscope only.Verification
git-supporthad no tests.GitSupportTestis new and covers every case with file system fixtures. It never invokesgit, so it is deterministic on CI and on Windows.Beyond the unit tests, the behaviour was checked against real Git in seven layouts: plain repository, worktree with an absolute gitdir, worktree created with
--relative-paths, submodule, submodule inside a linked worktree, bare repository with a worktree, andgit init --separate-git-dir. In each one the resolved target equals whatgit rev-parse --git-common-dirreports, and Git executed the installed hook.The central design decision was confirmed by experiment. With competing hooks in both
<main>/.git/hooksand<main>/.git/worktrees/<name>/hooks, committing from the linked worktree runs the one in the common directory.Finally the built CLI was run against a real linked worktree of a Maven project. Before: exit 1 and no activation script. After: exit 0, a complete activation script for the worktree's own JDK, Maven and Node, and the hooks in the common Git directory.
Not addressed
Two pre-existing limitations are untouched and out of scope:
core.hooksPathis still ignored, and a project root below the repository root still resolves to<projectRoot>/.git/hooks.🤖 Generated with Claude Code