feat: add prek support as pre-commit alternative (#1040)
prek (https://prek.j178.dev) is a faster Rust-based alternative to pre-commit that uses the same .pre-commit-config.yaml config files. Changes: - Add prek detection pattern in hookManagerPatterns (before pre-commit to ensure correct detection since prek hooks may contain 'pre-commit') - Handle prek in checkManagerBdIntegration using same config parser - Update init_git_hooks.go to recognize prek-installed hooks - Rename isPreCommit field to isPreCommitFramework for clarity - Use regex pattern matching all pre-commit/prek signatures consistently - Add test cases for prek run and prek hook-impl signatures Co-authored-by: Ismar Iljazovic <ismar@gmail.com>
This commit is contained in:
@@ -27,6 +27,8 @@ type hookManagerConfig struct {
|
||||
|
||||
// hookManagerConfigs defines external hook managers in priority order.
|
||||
// See https://lefthook.dev/configuration/ for lefthook config options.
|
||||
// Note: prek (https://prek.j178.dev) uses the same config files as pre-commit
|
||||
// but is a faster Rust-based alternative. We detect both from the same config.
|
||||
var hookManagerConfigs = []hookManagerConfig{
|
||||
{"lefthook", []string{
|
||||
// YAML variants
|
||||
@@ -38,6 +40,7 @@ var hookManagerConfigs = []hookManagerConfig{
|
||||
"lefthook.json", ".lefthook.json", ".config/lefthook.json",
|
||||
}},
|
||||
{"husky", []string{".husky"}},
|
||||
// pre-commit and prek share the same config files; we detect which is active from git hooks
|
||||
{"pre-commit", []string{".pre-commit-config.yaml", ".pre-commit-config.yml"}},
|
||||
{"overcommit", []string{".overcommit.yml"}},
|
||||
{"yorkie", []string{".yorkie"}},
|
||||
@@ -92,9 +95,12 @@ type hookManagerPattern struct {
|
||||
}
|
||||
|
||||
// hookManagerPatterns identifies which hook manager installed a git hook (in priority order).
|
||||
// Note: prek must come before pre-commit since prek hooks may also contain "pre-commit" in paths.
|
||||
var hookManagerPatterns = []hookManagerPattern{
|
||||
{"lefthook", regexp.MustCompile(`(?i)lefthook`)},
|
||||
{"husky", regexp.MustCompile(`(?i)(\.husky|husky\.sh)`)},
|
||||
// prek (https://prek.j178.dev) - faster Rust-based pre-commit alternative
|
||||
{"prek", regexp.MustCompile(`(?i)(prek\s+run|prek\s+hook-impl)`)},
|
||||
{"pre-commit", regexp.MustCompile(`(?i)(pre-commit\s+run|\.pre-commit-config|INSTALL_PYTHON|PRE_COMMIT)`)},
|
||||
{"simple-git-hooks", regexp.MustCompile(`(?i)simple-git-hooks`)},
|
||||
}
|
||||
@@ -470,8 +476,13 @@ func checkManagerBdIntegration(name, path string) *HookIntegrationStatus {
|
||||
return CheckLefthookBdIntegration(path)
|
||||
case "husky":
|
||||
return CheckHuskyBdIntegration(path)
|
||||
case "pre-commit":
|
||||
return CheckPrecommitBdIntegration(path)
|
||||
case "pre-commit", "prek":
|
||||
// prek uses the same config format as pre-commit
|
||||
status := CheckPrecommitBdIntegration(path)
|
||||
if status != nil {
|
||||
status.Manager = name // Use the actual detected manager name
|
||||
}
|
||||
return status
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -586,6 +586,16 @@ func TestDetectActiveHookManager(t *testing.T) {
|
||||
hookContent: "#!/usr/bin/env bash\n# PRE_COMMIT hook\npre-commit run --all-files\n",
|
||||
expected: "pre-commit",
|
||||
},
|
||||
{
|
||||
name: "prek run signature",
|
||||
hookContent: "#!/bin/sh\nprek run pre-commit\n",
|
||||
expected: "prek",
|
||||
},
|
||||
{
|
||||
name: "prek hook-impl signature",
|
||||
hookContent: "#!/usr/bin/env bash\nexec prek hook-impl --hook-type=pre-commit\n",
|
||||
expected: "prek",
|
||||
},
|
||||
{
|
||||
name: "simple-git-hooks signature",
|
||||
hookContent: "#!/bin/sh\n# simple-git-hooks\nnpm run lint\n",
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -12,6 +13,11 @@ import (
|
||||
"github.com/steveyegge/beads/internal/ui"
|
||||
)
|
||||
|
||||
// preCommitFrameworkPattern matches pre-commit or prek framework hooks.
|
||||
// Uses same patterns as hookManagerPatterns in doctor/fix/hooks.go for consistency.
|
||||
// Includes all detection patterns: pre-commit run, prek run/hook-impl, config file refs, and pre-commit env vars.
|
||||
var preCommitFrameworkPattern = regexp.MustCompile(`(?i)(pre-commit\s+run|prek\s+run|prek\s+hook-impl|\.pre-commit-config|INSTALL_PYTHON|PRE_COMMIT)`)
|
||||
|
||||
// hooksInstalled checks if bd git hooks are installed
|
||||
func hooksInstalled() bool {
|
||||
gitDir, err := git.GetGitDir()
|
||||
@@ -64,12 +70,12 @@ func hooksInstalled() bool {
|
||||
|
||||
// hookInfo contains information about an existing hook
|
||||
type hookInfo struct {
|
||||
name string
|
||||
path string
|
||||
exists bool
|
||||
isBdHook bool
|
||||
isPreCommit bool
|
||||
content string
|
||||
name string
|
||||
path string
|
||||
exists bool
|
||||
isBdHook bool
|
||||
isPreCommitFramework bool // true for pre-commit or prek
|
||||
content string
|
||||
}
|
||||
|
||||
// detectExistingHooks scans for existing git hooks
|
||||
@@ -91,10 +97,10 @@ func detectExistingHooks() []hookInfo {
|
||||
hooks[i].exists = true
|
||||
hooks[i].content = string(content)
|
||||
hooks[i].isBdHook = strings.Contains(hooks[i].content, "bd (beads)")
|
||||
// Only detect pre-commit framework if not a bd hook
|
||||
// Only detect pre-commit/prek framework if not a bd hook
|
||||
// Use regex for consistency with DetectActiveHookManager patterns
|
||||
if !hooks[i].isBdHook {
|
||||
hooks[i].isPreCommit = strings.Contains(hooks[i].content, "pre-commit run") ||
|
||||
strings.Contains(hooks[i].content, ".pre-commit-config")
|
||||
hooks[i].isPreCommitFramework = preCommitFrameworkPattern.MatchString(hooks[i].content)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,8 +114,8 @@ func promptHookAction(existingHooks []hookInfo) string {
|
||||
for _, hook := range existingHooks {
|
||||
if hook.exists && !hook.isBdHook {
|
||||
hookType := "custom script"
|
||||
if hook.isPreCommit {
|
||||
hookType = "pre-commit framework"
|
||||
if hook.isPreCommitFramework {
|
||||
hookType = "pre-commit/prek framework"
|
||||
}
|
||||
fmt.Printf(" - %s (%s)\n", hook.name, hookType)
|
||||
}
|
||||
|
||||
@@ -24,12 +24,12 @@ func TestDetectExistingHooks(t *testing.T) {
|
||||
hooksDir := filepath.Join(gitDirPath, "hooks")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setupHook string
|
||||
hookContent string
|
||||
wantExists bool
|
||||
wantIsBdHook bool
|
||||
wantIsPreCommit bool
|
||||
name string
|
||||
setupHook string
|
||||
hookContent string
|
||||
wantExists bool
|
||||
wantIsBdHook bool
|
||||
wantIsPreCommitFramework bool
|
||||
}{
|
||||
{
|
||||
name: "no hook",
|
||||
@@ -44,11 +44,11 @@ func TestDetectExistingHooks(t *testing.T) {
|
||||
wantIsBdHook: true,
|
||||
},
|
||||
{
|
||||
name: "pre-commit framework hook",
|
||||
setupHook: "pre-commit",
|
||||
hookContent: "#!/bin/sh\n# pre-commit framework\npre-commit run",
|
||||
wantExists: true,
|
||||
wantIsPreCommit: true,
|
||||
name: "pre-commit framework hook",
|
||||
setupHook: "pre-commit",
|
||||
hookContent: "#!/bin/sh\n# pre-commit framework\npre-commit run",
|
||||
wantExists: true,
|
||||
wantIsPreCommitFramework: true,
|
||||
},
|
||||
{
|
||||
name: "custom hook",
|
||||
@@ -90,8 +90,8 @@ func TestDetectExistingHooks(t *testing.T) {
|
||||
if found.isBdHook != tt.wantIsBdHook {
|
||||
t.Errorf("isBdHook = %v, want %v", found.isBdHook, tt.wantIsBdHook)
|
||||
}
|
||||
if found.isPreCommit != tt.wantIsPreCommit {
|
||||
t.Errorf("isPreCommit = %v, want %v", found.isPreCommit, tt.wantIsPreCommit)
|
||||
if found.isPreCommitFramework != tt.wantIsPreCommitFramework {
|
||||
t.Errorf("isPreCommitFramework = %v, want %v", found.isPreCommitFramework, tt.wantIsPreCommitFramework)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user