Skip to content

Fix post-checkout hook installation failing in git worktrees#57

Merged
albertodebortoli merged 1 commit intomainfrom
fix-worktree-hook-installation
Apr 10, 2026
Merged

Fix post-checkout hook installation failing in git worktrees#57
albertodebortoli merged 1 commit intomainfrom
fix-worktree-hook-installation

Conversation

@albertodebortoli
Copy link
Copy Markdown
Member

@albertodebortoli albertodebortoli commented Apr 8, 2026

Description

Fixes luca install failing when run inside a git worktree.

  • What is changing and why? See detailed explanation below.
  • No related issue.

Root Cause

Git worktrees do not have a .git directory. Instead, each worktree gets a .git file containing a pointer like gitdir: /path/to/main/.git/worktrees/<name>. The actual hooks directory exists only in the main repository at <main>/.git/hooks/.

GitHookInstaller.installPostCheckoutHook() used fileExists(atPath:) to detect whether the current directory was a git repository. FileManager.fileExists(atPath:) returns true for both files and directories, so in a worktree the guard passed even though .git was a file. The code then tried to copy the hook to .git/hooks/post-checkout — a path that is unreachable when .git is a file — causing an error.

This also triggered a cascading problem: git shares hooks across all worktrees (there is only one hooks directory per repository, in the main repo). So when the post-checkout hook fires in a worktree after a branch switch, it calls luca install, which re-enters the same failing code path.

The issue was reported by a colleague who found luca install and any tool that calls it (e.g. Swiftlane) would fail when working in a worktree setup. The only workaround at the time was passing --no-install-post-checkout-git-hook to every luca install invocation.

Fix

After confirming .git exists, check that it is a directory using attributesOfItem(atPath:)[.type]. If it is a file (worktree), return early — the hook is owned by the main repository and does not need to be installed per-worktree.

attributesOfItem(atPath:) was added to GitHookInstallerFileManaging (it was already part of FileManaging / FileManagerWrapper / FileManagerWrapperMock).

Type of Change

  • Bug fix

How Has This Been Tested?

  • Added / updated unit tests — test_installPostCheckoutHook_whenGitWorktree_doesNothing creates a .git file (simulating a worktree) and asserts the installer silently returns without printing anything or modifying the filesystem.
  • Tested on macOS (arch: arm64)
swift test --filter GitHookInstallerTests
# 7 tests, 0 failures
swift test
# 352 tests, 0 failures

Checklist

  • Swift code builds locally (swift build)
  • Tests pass locally (swift test)
  • Code style / formatting respected
  • Documentation updated (README / comments)

Breaking Changes?

  • No

Additional Notes

A fully complete fix LucaTools/LucaScripts#12 updates the post-checkout shell script (in the LucaScripts repo) to pass --no-install-post-checkout-git-hook when calling luca install. That prevents the hook from needlessly attempting to reinstall itself on every checkout. With this fix in place that is harmless (the attempt just returns early), but it is a nice follow-up.

In a git worktree, `.git` is a regular file (containing a gitdir
pointer) rather than a directory. The previous code used
`fileExists(atPath:)` to detect whether the current directory was
a git repository, but that method returns true for both files and
directories. As a result, `GitHookInstaller` would proceed past the
guard and attempt to copy the hook to `.git/hooks/post-checkout` —
a path that cannot exist when `.git` is a file — causing an error.

Additionally, when the post-checkout hook fires in a worktree context
(git shares the main repo's hooks across all worktrees), it invokes
`luca install`, which would re-trigger the same failing installation.

Fix: after confirming `.git` exists, check that it is a directory
using `attributesOfItem(atPath:)`. If it is a file (worktree), return
early — hooks are owned by the main repository.

Add `attributesOfItem(atPath:)` to `GitHookInstallerFileManaging` and
a new test covering the worktree case.
@albertodebortoli albertodebortoli added the bugfix Something isn't working label Apr 8, 2026
@albertodebortoli albertodebortoli added this to the 0.14.0 milestone Apr 8, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@albertodebortoli albertodebortoli merged commit 4d6e2c7 into main Apr 10, 2026
3 checks passed
@albertodebortoli albertodebortoli deleted the fix-worktree-hook-installation branch April 10, 2026 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant