diff --git a/.golangci.yml b/.golangci.yml index ce406f26..6b6f1b0f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -45,7 +45,6 @@ linters: - govet - ineffassign - misspell - - nakedret - nestif - noctx - nolintlint diff --git a/CHANGELOG.md b/CHANGELOG.md index 5269a2ca..733953d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## master (unreleased) +- feat: support json configs ([#489](https://github.com/evilmartians/lefthook/pull/489)) by @mrexox + ## 1.4.1 (2024-05-22) - fix: add win32 binary to artifacts (by @mrexox) diff --git a/internal/config/load_test.go b/internal/config/load_test.go index e4f560f1..1fc14c1b 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -466,4 +466,79 @@ pre-push: } }) } + + for i, tt := range [...]struct { + name string + yaml, json string + result *Config + }{ + { + name: "simple configs", + yaml: ` +pre-commit: + commands: + echo: + run: echo 1 +`, + json: ` +{ + "pre-commit": { + "commands": { + "echo": { "run": "echo 1" } + } + } +}`, + result: &Config{ + SourceDir: DefaultSourceDir, + SourceDirLocal: DefaultSourceDirLocal, + Hooks: map[string]*Hook{ + "pre-commit": { + Commands: map[string]*Command{ + "echo": { + Run: "echo 1", + }, + }, + }, + }, + }, + }, + } { + fs := afero.Afero{Fs: afero.NewMemMapFs()} + repo := &git.Repository{ + Fs: fs, + RootPath: root, + InfoPath: filepath.Join(root, ".git", "info"), + } + + t.Run(fmt.Sprintf("%d: %s", i, tt.name), func(t *testing.T) { + yamlConfig := filepath.Join(root, "lefthook.yml") + if err := fs.WriteFile(yamlConfig, []byte(tt.yaml), 0o644); err != nil { + t.Errorf("unexpected error: %s", err) + } + + checkConfig, err := Load(fs.Fs, repo) + if err != nil { + t.Errorf("should parse configs without errors: %s", err) + } else if !cmp.Equal(checkConfig, tt.result, cmpopts.IgnoreUnexported(Hook{})) { + t.Errorf("configs should be equal") + t.Errorf("(-want +got):\n%s", cmp.Diff(tt.result, checkConfig)) + } + + if err = fs.Remove(yamlConfig); err != nil { + t.Errorf("unexpected error: %s", err) + } + + if err = fs.WriteFile(filepath.Join(root, "lefthook.json"), []byte(tt.json), 0o644); err != nil { + t.Errorf("unexpected error: %s", err) + } + + checkConfig, err = Load(fs.Fs, repo) + if err != nil { + t.Errorf("should parse configs without errors: %s", err) + } else if !cmp.Equal(checkConfig, tt.result, cmpopts.IgnoreUnexported(Hook{})) { + t.Errorf("configs should be equal") + t.Errorf("(-want +got):\n%s", cmp.Diff(tt.result, checkConfig)) + } + }) + } } diff --git a/internal/lefthook/install.go b/internal/lefthook/install.go index 88d828c0..d48772d2 100644 --- a/internal/lefthook/install.go +++ b/internal/lefthook/install.go @@ -4,12 +4,15 @@ import ( "bufio" "crypto/md5" "encoding/hex" + "fmt" "io" + "os" "path/filepath" "regexp" "strconv" "strings" + "github.com/gobwas/glob" "github.com/spf13/afero" "github.com/evilmartians/lefthook/internal/config" @@ -21,12 +24,15 @@ const ( configFileMode = 0o666 checksumFileMode = 0o644 hooksDirMode = 0o755 - configGlob = "lefthook.y*ml" timestampBase = 10 timestampBitsize = 64 ) -var lefthookChecksumRegexp = regexp.MustCompile(`(\w+)\s+(\d+)`) +var ( + lefthookChecksumRegexp = regexp.MustCompile(`(\w+)\s+(\d+)`) + configGlob = glob.MustCompile("lefthook.{json,yaml,yml}") + errNoConfig = fmt.Errorf("no lefthook config found") +) type InstallArgs struct { Force, Aggressive bool @@ -69,7 +75,6 @@ func (l *Lefthook) readOrCreateConfig() (*config.Config, error) { if !l.configExists(l.repo.RootPath) { log.Info("Config not found, creating...") - if err := l.createConfig(l.repo.RootPath); err != nil { return nil, err } @@ -79,13 +84,13 @@ func (l *Lefthook) readOrCreateConfig() (*config.Config, error) { } func (l *Lefthook) configExists(path string) bool { - paths, err := afero.Glob(l.Fs, filepath.Join(path, configGlob)) + paths, err := afero.ReadDir(l.Fs, path) if err != nil { return false } - for _, config := range paths { - if ok, _ := afero.Exists(l.Fs, config); ok { + for _, file := range paths { + if ok := configGlob.Match(file.Name()); ok { return true } } @@ -199,27 +204,46 @@ func (l *Lefthook) hooksSynchronized() bool { } func (l *Lefthook) configLastUpdateTimestamp() (timestamp int64, err error) { - m, err := afero.Glob(l.Fs, filepath.Join(l.repo.RootPath, configGlob)) + paths, err := afero.ReadDir(l.Fs, l.repo.RootPath) if err != nil { return } + var config os.FileInfo + for _, file := range paths { + if ok := configGlob.Match(file.Name()); ok { + config = file + break + } + } - info, err := l.Fs.Stat(m[0]) - if err != nil { + if config == nil { + err = errNoConfig return } - timestamp = info.ModTime().Unix() + timestamp = config.ModTime().Unix() return } func (l *Lefthook) configChecksum() (checksum string, err error) { - m, err := afero.Glob(l.Fs, filepath.Join(l.repo.RootPath, configGlob)) + paths, err := afero.ReadDir(l.Fs, l.repo.RootPath) if err != nil { return } - file, err := l.Fs.Open(m[0]) + var config string + for _, file := range paths { + if ok := configGlob.Match(file.Name()); ok { + config = file.Name() + break + } + } + if len(config) == 0 { + err = errNoConfig + return + } + + file, err := l.Fs.Open(filepath.Join(l.repo.RootPath, config)) if err != nil { return }