Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions cmd/entire/cli/strategy/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,25 +344,25 @@ fi

// hookCmdPrefix returns the command prefix for hook scripts and warning messages.
// Returns "go run ./cmd/entire/main.go" when local_dev is enabled.
// When absolutePath is true, resolves the full binary path via os.Executable()
// and returns an error if resolution fails. This is needed for GUI git clients
// (Xcode, Tower, etc.) that don't source shell profiles.
func hookCmdPrefix(localDev, absolutePath bool) (string, error) {
// By default, resolves the full binary path via os.Executable() so that hooks
// work in GUI git clients (Xcode, Tower, etc.) that don't source shell profiles.
// Falls back to bare "entire" only if binary path resolution fails.
func hookCmdPrefix(localDev, _ bool) (string, error) {
if localDev {
return "go run ./cmd/entire/main.go", nil
}
if absolutePath {
exe, err := os.Executable()
if err != nil {
return "", fmt.Errorf("--absolute-git-hook-path: failed to resolve binary path: %w", err)
}
resolved, err := filepath.EvalSymlinks(exe)
if err != nil {
return "", fmt.Errorf("--absolute-git-hook-path: failed to resolve symlinks for %s: %w", exe, err)
}
return shellQuote(resolved), nil
// Always resolve the absolute path so hooks work in environments
// that don't source shell profiles (GUI git clients, IDEs, cron).
exe, err := os.Executable()
if err != nil {
// Fall back to bare command name if resolution fails
return "entire", nil //nolint:nilerr // Graceful fallback to PATH-based lookup
}
resolved, err := filepath.EvalSymlinks(exe)
if err != nil {
return "entire", nil //nolint:nilerr // Graceful fallback to PATH-based lookup
}
return "entire", nil
return shellQuote(resolved), nil
}

// shellQuote wraps a string in single quotes for safe use in #!/bin/sh scripts.
Expand Down
19 changes: 10 additions & 9 deletions cmd/entire/cli/strategy/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ func TestInstallGitHook_LocalDevCommandPrefix(t *testing.T) {
}
}

// Reinstall with localDev=false hooks should update to use "entire" prefix
// Reinstall with localDev=false - hooks should update to use absolute path
count, err = InstallGitHook(context.Background(), true, false, false)
if err != nil {
t.Fatalf("InstallGitHook(localDev=false) error = %v", err)
Expand All @@ -617,22 +617,23 @@ func TestInstallGitHook_LocalDevCommandPrefix(t *testing.T) {
if strings.Contains(content, "go run") {
t.Errorf("hook %s should not use 'go run' prefix when localDev=false, got:\n%s", hook, content)
}
if !strings.Contains(content, "\nentire ") {
t.Errorf("hook %s should use bare 'entire' prefix when localDev=false", hook)
// Should contain an absolute path (resolved via os.Executable), not bare "entire"
if !strings.Contains(content, "/") {
t.Errorf("hook %s should use absolute path when localDev=false, got:\n%s", hook, content)
}
}
}

func TestInstallGitHook_AbsoluteGitHookPath(t *testing.T) {
func TestInstallGitHook_AbsolutePathByDefault(t *testing.T) {
_, hooksDir := initHooksTestRepo(t)

// Install with absolutePath=true
count, err := InstallGitHook(context.Background(), true, false, true)
// Install with default settings (localDev=false) - should use absolute path
count, err := InstallGitHook(context.Background(), true, false, false)
if err != nil {
t.Fatalf("InstallGitHook(absolutePath=true) error = %v", err)
t.Fatalf("InstallGitHook() error = %v", err)
}
if count == 0 {
t.Fatal("InstallGitHook(absolutePath=true) should install hooks")
t.Fatal("InstallGitHook() should install hooks")
}

// Get the expected absolute path (shell-quoted)
Expand All @@ -656,7 +657,7 @@ func TestInstallGitHook_AbsoluteGitHookPath(t *testing.T) {
t.Errorf("hook %s should contain shell-quoted absolute path %q, got:\n%s", hook, quoted, content)
}
if strings.Contains(content, "\nentire ") {
t.Errorf("hook %s should not use bare 'entire' prefix when absolutePath=true", hook)
t.Errorf("hook %s should not use bare 'entire' prefix", hook)
}
}
}
Expand Down