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
8 changes: 7 additions & 1 deletion cmd/entire/cli/strategy/hook_managers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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"} {
Expand Down
140 changes: 140 additions & 0 deletions cmd/entire/cli/strategy/hook_managers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down