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.
|
// hookManagerConfigs defines external hook managers in priority order.
|
||||||
// See https://lefthook.dev/configuration/ for lefthook config options.
|
// 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{
|
var hookManagerConfigs = []hookManagerConfig{
|
||||||
{"lefthook", []string{
|
{"lefthook", []string{
|
||||||
// YAML variants
|
// YAML variants
|
||||||
@@ -38,6 +40,7 @@ var hookManagerConfigs = []hookManagerConfig{
|
|||||||
"lefthook.json", ".lefthook.json", ".config/lefthook.json",
|
"lefthook.json", ".lefthook.json", ".config/lefthook.json",
|
||||||
}},
|
}},
|
||||||
{"husky", []string{".husky"}},
|
{"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"}},
|
{"pre-commit", []string{".pre-commit-config.yaml", ".pre-commit-config.yml"}},
|
||||||
{"overcommit", []string{".overcommit.yml"}},
|
{"overcommit", []string{".overcommit.yml"}},
|
||||||
{"yorkie", []string{".yorkie"}},
|
{"yorkie", []string{".yorkie"}},
|
||||||
@@ -92,9 +95,12 @@ type hookManagerPattern struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// hookManagerPatterns identifies which hook manager installed a git hook (in priority order).
|
// 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{
|
var hookManagerPatterns = []hookManagerPattern{
|
||||||
{"lefthook", regexp.MustCompile(`(?i)lefthook`)},
|
{"lefthook", regexp.MustCompile(`(?i)lefthook`)},
|
||||||
{"husky", regexp.MustCompile(`(?i)(\.husky|husky\.sh)`)},
|
{"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)`)},
|
{"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`)},
|
{"simple-git-hooks", regexp.MustCompile(`(?i)simple-git-hooks`)},
|
||||||
}
|
}
|
||||||
@@ -470,8 +476,13 @@ func checkManagerBdIntegration(name, path string) *HookIntegrationStatus {
|
|||||||
return CheckLefthookBdIntegration(path)
|
return CheckLefthookBdIntegration(path)
|
||||||
case "husky":
|
case "husky":
|
||||||
return CheckHuskyBdIntegration(path)
|
return CheckHuskyBdIntegration(path)
|
||||||
case "pre-commit":
|
case "pre-commit", "prek":
|
||||||
return CheckPrecommitBdIntegration(path)
|
// 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:
|
default:
|
||||||
return nil
|
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",
|
hookContent: "#!/usr/bin/env bash\n# PRE_COMMIT hook\npre-commit run --all-files\n",
|
||||||
expected: "pre-commit",
|
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",
|
name: "simple-git-hooks signature",
|
||||||
hookContent: "#!/bin/sh\n# simple-git-hooks\nnpm run lint\n",
|
hookContent: "#!/bin/sh\n# simple-git-hooks\nnpm run lint\n",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -12,6 +13,11 @@ import (
|
|||||||
"github.com/steveyegge/beads/internal/ui"
|
"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
|
// hooksInstalled checks if bd git hooks are installed
|
||||||
func hooksInstalled() bool {
|
func hooksInstalled() bool {
|
||||||
gitDir, err := git.GetGitDir()
|
gitDir, err := git.GetGitDir()
|
||||||
@@ -64,12 +70,12 @@ func hooksInstalled() bool {
|
|||||||
|
|
||||||
// hookInfo contains information about an existing hook
|
// hookInfo contains information about an existing hook
|
||||||
type hookInfo struct {
|
type hookInfo struct {
|
||||||
name string
|
name string
|
||||||
path string
|
path string
|
||||||
exists bool
|
exists bool
|
||||||
isBdHook bool
|
isBdHook bool
|
||||||
isPreCommit bool
|
isPreCommitFramework bool // true for pre-commit or prek
|
||||||
content string
|
content string
|
||||||
}
|
}
|
||||||
|
|
||||||
// detectExistingHooks scans for existing git hooks
|
// detectExistingHooks scans for existing git hooks
|
||||||
@@ -91,10 +97,10 @@ func detectExistingHooks() []hookInfo {
|
|||||||
hooks[i].exists = true
|
hooks[i].exists = true
|
||||||
hooks[i].content = string(content)
|
hooks[i].content = string(content)
|
||||||
hooks[i].isBdHook = strings.Contains(hooks[i].content, "bd (beads)")
|
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 {
|
if !hooks[i].isBdHook {
|
||||||
hooks[i].isPreCommit = strings.Contains(hooks[i].content, "pre-commit run") ||
|
hooks[i].isPreCommitFramework = preCommitFrameworkPattern.MatchString(hooks[i].content)
|
||||||
strings.Contains(hooks[i].content, ".pre-commit-config")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,8 +114,8 @@ func promptHookAction(existingHooks []hookInfo) string {
|
|||||||
for _, hook := range existingHooks {
|
for _, hook := range existingHooks {
|
||||||
if hook.exists && !hook.isBdHook {
|
if hook.exists && !hook.isBdHook {
|
||||||
hookType := "custom script"
|
hookType := "custom script"
|
||||||
if hook.isPreCommit {
|
if hook.isPreCommitFramework {
|
||||||
hookType = "pre-commit framework"
|
hookType = "pre-commit/prek framework"
|
||||||
}
|
}
|
||||||
fmt.Printf(" - %s (%s)\n", hook.name, hookType)
|
fmt.Printf(" - %s (%s)\n", hook.name, hookType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ func TestDetectExistingHooks(t *testing.T) {
|
|||||||
hooksDir := filepath.Join(gitDirPath, "hooks")
|
hooksDir := filepath.Join(gitDirPath, "hooks")
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupHook string
|
setupHook string
|
||||||
hookContent string
|
hookContent string
|
||||||
wantExists bool
|
wantExists bool
|
||||||
wantIsBdHook bool
|
wantIsBdHook bool
|
||||||
wantIsPreCommit bool
|
wantIsPreCommitFramework bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no hook",
|
name: "no hook",
|
||||||
@@ -44,11 +44,11 @@ func TestDetectExistingHooks(t *testing.T) {
|
|||||||
wantIsBdHook: true,
|
wantIsBdHook: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "pre-commit framework hook",
|
name: "pre-commit framework hook",
|
||||||
setupHook: "pre-commit",
|
setupHook: "pre-commit",
|
||||||
hookContent: "#!/bin/sh\n# pre-commit framework\npre-commit run",
|
hookContent: "#!/bin/sh\n# pre-commit framework\npre-commit run",
|
||||||
wantExists: true,
|
wantExists: true,
|
||||||
wantIsPreCommit: true,
|
wantIsPreCommitFramework: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "custom hook",
|
name: "custom hook",
|
||||||
@@ -90,8 +90,8 @@ func TestDetectExistingHooks(t *testing.T) {
|
|||||||
if found.isBdHook != tt.wantIsBdHook {
|
if found.isBdHook != tt.wantIsBdHook {
|
||||||
t.Errorf("isBdHook = %v, want %v", found.isBdHook, tt.wantIsBdHook)
|
t.Errorf("isBdHook = %v, want %v", found.isBdHook, tt.wantIsBdHook)
|
||||||
}
|
}
|
||||||
if found.isPreCommit != tt.wantIsPreCommit {
|
if found.isPreCommitFramework != tt.wantIsPreCommitFramework {
|
||||||
t.Errorf("isPreCommit = %v, want %v", found.isPreCommit, tt.wantIsPreCommit)
|
t.Errorf("isPreCommitFramework = %v, want %v", found.isPreCommitFramework, tt.wantIsPreCommitFramework)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user