diff --git a/cmd/entire/cli/strategy/hook_managers.go b/cmd/entire/cli/strategy/hook_managers.go index c81d8f316..0ecdce2e4 100644 --- a/cmd/entire/cli/strategy/hook_managers.go +++ b/cmd/entire/cli/strategy/hook_managers.go @@ -13,7 +13,7 @@ import ( // hookManager describes an external hook manager detected in a repository. type hookManager struct { - Name string // "Husky", "Lefthook", "pre-commit", "Overcommit" + Name string // "Husky", "Lefthook", "pre-commit", "Overcommit", "hk" ConfigPath string // relative path that triggered detection (e.g., ".husky/") OverwritesHooks bool // true if the tool will overwrite Entire's hooks on reinstall } @@ -29,6 +29,12 @@ func detectHookManagers(repoRoot string) []hookManager { {"Overcommit", ".overcommit.yml", false}, } + // hk: explicit precedence order matching hk's discovery + // (hk.local.pkl > .config/hk.local.pkl > hk.pkl > .config/hk.pkl) + for _, name := range []string{"hk.local.pkl", ".config/hk.local.pkl", "hk.pkl", ".config/hk.pkl"} { + checks = append(checks, hookManager{"hk", name, false}) + } + // Lefthook supports {.,}lefthook{,-local}.{yml,yaml,json,toml} for _, prefix := range []string{"", "."} { for _, variant := range []string{"", "-local"} { diff --git a/cmd/entire/cli/strategy/hook_managers_test.go b/cmd/entire/cli/strategy/hook_managers_test.go index b53decab7..e12720a0b 100644 --- a/cmd/entire/cli/strategy/hook_managers_test.go +++ b/cmd/entire/cli/strategy/hook_managers_test.go @@ -147,6 +147,146 @@ func TestDetectHookManagers_LefthookDedup(t *testing.T) { } } +func TestDetectHookManagers_Hk(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + if err := os.WriteFile(filepath.Join(tmpDir, "hk.pkl"), []byte(""), 0o644); err != nil { + t.Fatalf("failed to create hk.pkl: %v", err) + } + + managers := detectHookManagers(tmpDir) + if len(managers) != 1 { + t.Fatalf("expected 1 manager, got %d", len(managers)) + } + if managers[0].Name != "hk" { + t.Errorf("expected hk, got %s", managers[0].Name) + } + if managers[0].ConfigPath != "hk.pkl" { + t.Errorf("expected hk.pkl, got %s", managers[0].ConfigPath) + } + if managers[0].OverwritesHooks { + t.Error("hk should have OverwritesHooks=false") + } +} + +func TestDetectHookManagers_HkConfigDir(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + if err := os.MkdirAll(filepath.Join(tmpDir, ".config"), 0o755); err != nil { + t.Fatalf("failed to create .config/: %v", err) + } + if err := os.WriteFile(filepath.Join(tmpDir, ".config", "hk.pkl"), []byte(""), 0o644); err != nil { + t.Fatalf("failed to create .config/hk.pkl: %v", err) + } + + managers := detectHookManagers(tmpDir) + if len(managers) != 1 { + t.Fatalf("expected 1 manager, got %d", len(managers)) + } + if managers[0].Name != "hk" { + t.Errorf("expected hk, got %s", managers[0].Name) + } + if managers[0].ConfigPath != ".config/hk.pkl" { + t.Errorf("expected .config/hk.pkl, got %s", managers[0].ConfigPath) + } +} + +func TestDetectHookManagers_HkLocal(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + if err := os.WriteFile(filepath.Join(tmpDir, "hk.local.pkl"), []byte(""), 0o644); err != nil { + t.Fatalf("failed to create hk.local.pkl: %v", err) + } + + managers := detectHookManagers(tmpDir) + if len(managers) != 1 { + t.Fatalf("expected 1 manager, got %d", len(managers)) + } + if managers[0].Name != "hk" { + t.Errorf("expected hk, got %s", managers[0].Name) + } + if managers[0].ConfigPath != "hk.local.pkl" { + t.Errorf("expected hk.local.pkl, got %s", managers[0].ConfigPath) + } +} + +func TestDetectHookManagers_HkConfigDirLocal(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + if err := os.MkdirAll(filepath.Join(tmpDir, ".config"), 0o755); err != nil { + t.Fatalf("failed to create .config/: %v", err) + } + if err := os.WriteFile(filepath.Join(tmpDir, ".config", "hk.local.pkl"), []byte(""), 0o644); err != nil { + t.Fatalf("failed to create .config/hk.local.pkl: %v", err) + } + + managers := detectHookManagers(tmpDir) + if len(managers) != 1 { + t.Fatalf("expected 1 manager, got %d", len(managers)) + } + if managers[0].Name != "hk" { + t.Errorf("expected hk, got %s", managers[0].Name) + } + if managers[0].ConfigPath != ".config/hk.local.pkl" { + t.Errorf("expected .config/hk.local.pkl, got %s", managers[0].ConfigPath) + } +} + +func TestDetectHookManagers_HkDedup(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + // Both hk.pkl and hk.local.pkl present — should only report once, with highest precedence + if err := os.WriteFile(filepath.Join(tmpDir, "hk.pkl"), []byte(""), 0o644); err != nil { + t.Fatalf("failed to create hk.pkl: %v", err) + } + if err := os.WriteFile(filepath.Join(tmpDir, "hk.local.pkl"), []byte(""), 0o644); err != nil { + t.Fatalf("failed to create hk.local.pkl: %v", err) + } + + managers := detectHookManagers(tmpDir) + if len(managers) != 1 { + t.Fatalf("expected 1 manager (dedup), got %d", len(managers)) + } + if managers[0].Name != "hk" { + t.Errorf("expected hk, got %s", managers[0].Name) + } + if managers[0].ConfigPath != "hk.local.pkl" { + t.Errorf("expected hk.local.pkl (highest precedence), got %s", managers[0].ConfigPath) + } +} + +func TestDetectHookManagers_HkDedupConfigDir(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + if err := os.MkdirAll(filepath.Join(tmpDir, ".config"), 0o755); err != nil { + t.Fatalf("failed to create .config/: %v", err) + } + // Both .config/hk.pkl and .config/hk.local.pkl present + if err := os.WriteFile(filepath.Join(tmpDir, ".config", "hk.pkl"), []byte(""), 0o644); err != nil { + t.Fatalf("failed to create .config/hk.pkl: %v", err) + } + if err := os.WriteFile(filepath.Join(tmpDir, ".config", "hk.local.pkl"), []byte(""), 0o644); err != nil { + t.Fatalf("failed to create .config/hk.local.pkl: %v", err) + } + + managers := detectHookManagers(tmpDir) + if len(managers) != 1 { + t.Fatalf("expected 1 manager (dedup), got %d", len(managers)) + } + if managers[0].Name != "hk" { + t.Errorf("expected hk, got %s", managers[0].Name) + } + if managers[0].ConfigPath != ".config/hk.local.pkl" { + t.Errorf("expected .config/hk.local.pkl (highest precedence), got %s", managers[0].ConfigPath) + } +} + func TestDetectHookManagers_PreCommit(t *testing.T) { t.Parallel()