From f692bb59689324c500e860838b8c94781e9e0d18 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 21 Jan 2025 10:30:21 +0300 Subject: [PATCH] feat: allow providing a list of globs (#937) --- docs/mdbook/configuration/glob.md | 25 ++++++++++++---- internal/config/command.go | 2 +- internal/config/job.go | 8 ++--- internal/config/load_test.go | 6 ++-- internal/lefthook/runner/filters/filters.go | 30 ++++++++++++++----- .../lefthook/runner/filters/filters_test.go | 19 +++++++----- internal/lefthook/runner/jobs/jobs.go | 2 +- internal/lefthook/runner/run_jobs.go | 6 ++-- internal/lefthook/runner/runner_test.go | 12 ++++---- schema.json | 26 ++++++++++++++-- testdata/dump.txt | 9 ++++-- testdata/job_merging.txt | 6 ++-- testdata/many_extends_levels.txt | 3 +- testdata/remotes.txt | 9 ++++-- 14 files changed, 112 insertions(+), 51 deletions(-) diff --git a/docs/mdbook/configuration/glob.md b/docs/mdbook/configuration/glob.md index 3ad70bb1..e0f0da87 100644 --- a/docs/mdbook/configuration/glob.md +++ b/docs/mdbook/configuration/glob.md @@ -8,12 +8,25 @@ You can set a glob to filter files for your command. This is only used if you us # lefthook.yml pre-commit: - commands: - lint: - glob: "*.{js,ts,jsx,tsx}" + jobs: + - name: lint run: yarn eslint {staged_files} + glob: "*.{js,ts,jsx,tsx}" ``` +> **Note:** from lefthook version `1.10.10` you can also provide a list of globs: +> +> ```yml +> # lefthook.yml +> +> pre-commit: +> jobs: +> - run: yarn lint {staged_files} +> glob: +> - "*.ts" +> - "*.js" +> ``` + **Notes** For patterns that you can use see [this](https://tldp.org/LDP/GNU-Linux-Tools-Summary/html/x11655.htm) reference. We use [glob](https://github.com/gobwas/glob) library. @@ -24,8 +37,8 @@ If you've specified `glob` but don't have a files template in [`run`](./run.md) # lefthook.yml pre-commit: - commands: - lint: - glob: "*.js" + jobs: + - name: lint run: npm run lint # skipped if no .js files staged + glob: "*.js" ``` diff --git a/internal/config/command.go b/internal/config/command.go index 63495eb4..cda7706b 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -17,7 +17,7 @@ type Command struct { FileTypes []string `json:"file_types,omitempty" koanf:"file_types" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty"` - Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` + Glob []string `json:"glob,omitempty" jsonschema:"oneof_type=string;array" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` Exclude interface{} `json:"exclude,omitempty" jsonschema:"oneof_type=string;array" mapstructure:"exclude" toml:"exclude,omitempty" yaml:",omitempty"` diff --git a/internal/config/job.go b/internal/config/job.go index 287f7307..bf8d7c02 100644 --- a/internal/config/job.go +++ b/internal/config/job.go @@ -6,10 +6,10 @@ type Job struct { Script string `json:"script,omitempty" jsonschema:"oneof_required=Run a script" mapstructure:"script" toml:"script,omitempty" yaml:",omitempty"` Runner string `json:"runner,omitempty" mapstructure:"runner" toml:"runner,omitempty" yaml:",omitempty"` - Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` - Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` - Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` - FailText string `json:"fail_text,omitempty" koanf:"fail_text" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty"` + Glob []string `json:"glob,omitempty" jsonschema:"oneof_type=string;array" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` + Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` + Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` + FailText string `json:"fail_text,omitempty" koanf:"fail_text" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty"` Tags []string `json:"tags,omitempty" mapstructure:"tags" toml:"tags,omitempty" yaml:",omitempty"` FileTypes []string `json:"file_types,omitempty" koanf:"file_types" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty"` diff --git a/internal/config/load_test.go b/internal/config/load_test.go index 6a60c94c..78c19261 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -201,7 +201,7 @@ pre-push: Tags: []string{"backend", "test"}, }, "lint": { - Glob: "*.rb", + Glob: []string{"*.rb"}, Run: "docker exec -it ruby:2.7 bundle exec rubocop", Tags: []string{"backend", "linter"}, }, @@ -649,7 +649,7 @@ pre-commit: "global-lint": { Run: "bundle exec rubocop", Tags: []string{"backend", "linter"}, - Glob: "*.rb", + Glob: []string{"*.rb"}, }, "global-other": { Run: "bundle exec rubocop", @@ -783,7 +783,7 @@ pre-commit: Jobs: []*Job{ { Name: "group 1", - Glob: "*.rb", + Glob: []string{"*.rb"}, Group: &Group{ Parallel: true, Jobs: []*Job{ diff --git a/internal/lefthook/runner/filters/filters.go b/internal/lefthook/runner/filters/filters.go index 1df64b63..09b50f98 100644 --- a/internal/lefthook/runner/filters/filters.go +++ b/internal/lefthook/runner/filters/filters.go @@ -29,7 +29,7 @@ const ( ) type Params struct { - Glob string + Glob []string Root string FileTypes []string Exclude interface{} @@ -52,19 +52,33 @@ func Apply(fs afero.Fs, files []string, params Params) []string { return files } -func byGlob(vs []string, matcher string) []string { - if matcher == "" { +func byGlob(vs []string, matchers []string) []string { + if len(matchers) == 0 { return vs } - g := glob.MustCompile(strings.ToLower(matcher)) - + var hasNonEmpty bool vsf := make([]string, 0) - for _, v := range vs { - if res := g.Match(strings.ToLower(v)); res { - vsf = append(vsf, v) + for _, matcher := range matchers { + if len(matcher) == 0 { + continue + } + + hasNonEmpty = true + + g := glob.MustCompile(strings.ToLower(matcher)) + + for _, v := range vs { + if res := g.Match(strings.ToLower(v)); res { + vsf = append(vsf, v) + } } } + + if !hasNonEmpty { + return vs + } + return vsf } diff --git a/internal/lefthook/runner/filters/filters_test.go b/internal/lefthook/runner/filters/filters_test.go index 6995c813..d72a6c58 100644 --- a/internal/lefthook/runner/filters/filters_test.go +++ b/internal/lefthook/runner/filters/filters_test.go @@ -28,31 +28,31 @@ func slicesEqual(a, b []string) bool { func TestByGlob(t *testing.T) { for i, tt := range [...]struct { source, result []string - glob string + glob []string }{ { source: []string{"folder/subfolder/0.rb", "1.txt", "2.RB", "3.rbs"}, - glob: "", + glob: []string{}, result: []string{"folder/subfolder/0.rb", "1.txt", "2.RB", "3.rbs"}, }, { source: []string{"folder/subfolder/0.rb", "1.txt", "2.RB", "3.rbs"}, - glob: "*.rb", + glob: []string{"*.rb"}, result: []string{"folder/subfolder/0.rb", "2.RB"}, }, { source: []string{"folder/subfolder/0.rb", "1.rbs"}, - glob: "**/*.rb", + glob: []string{"**/*.rb"}, result: []string{"folder/subfolder/0.rb"}, }, { source: []string{"folder/0.rb", "1.rBs", "2.rbv"}, - glob: "*.rb?", + glob: []string{"*.rb?"}, result: []string{"1.rBs", "2.rbv"}, }, { source: []string{"f.a", "f.b", "f.c", "f.cn"}, - glob: "*.{a,b,cn}", + glob: []string{"*.{a,b,cn}"}, result: []string{"f.a", "f.b", "f.cn"}, }, } { @@ -68,7 +68,7 @@ func TestByGlob(t *testing.T) { func TestByExclude(t *testing.T) { for i, tt := range [...]struct { source, result []string - exclude string + exclude interface{} }{ { source: []string{"folder/subfolder/0.rb", "1.txt", "2.RB", "3.rb"}, @@ -95,6 +95,11 @@ func TestByExclude(t *testing.T) { exclude: ".*\\.(a|b|cn)$", result: []string{"f.c"}, }, + { + source: []string{"f.a", "f.b", "f.c", "f.cn"}, + exclude: []interface{}{"*.a", "*.b", "*.cn"}, + result: []string{"f.c"}, + }, } { t.Run(fmt.Sprintf("%d:", i), func(t *testing.T) { res := byExclude(tt.source, tt.exclude) diff --git a/internal/lefthook/runner/jobs/jobs.go b/internal/lefthook/runner/jobs/jobs.go index a53198eb..84414206 100644 --- a/internal/lefthook/runner/jobs/jobs.go +++ b/internal/lefthook/runner/jobs/jobs.go @@ -19,10 +19,10 @@ type Params struct { Root string Runner string Script string - Glob string Files string FileTypes []string Tags []string + Glob []string Templates map[string]string Exclude interface{} Only interface{} diff --git a/internal/lefthook/runner/run_jobs.go b/internal/lefthook/runner/run_jobs.go index 150c002a..43d37194 100644 --- a/internal/lefthook/runner/run_jobs.go +++ b/internal/lefthook/runner/run_jobs.go @@ -26,7 +26,7 @@ var ( type domain struct { failed *atomic.Bool - glob string + glob []string root string exclude interface{} onlyJobs []string @@ -95,7 +95,7 @@ func (r *Runner) runJob(ctx context.Context, domain *domain, id string, job *con if job.Group != nil { inheritedDomain := *domain - inheritedDomain.glob = first(job.Glob, domain.glob) + inheritedDomain.glob = slices.Concat(inheritedDomain.glob, job.Glob) inheritedDomain.root = first(job.Root, domain.root) switch list := job.Exclude.(type) { case []interface{}: @@ -131,7 +131,7 @@ func (r *Runner) runSingleJob(ctx context.Context, domain *domain, id string, jo name := job.PrintableName(id) root := first(job.Root, domain.root) - glob := first(job.Glob, domain.glob) + glob := slices.Concat(domain.glob, job.Glob) exclude := join(job.Exclude, domain.exclude) executionJob, err := jobs.New(name, &jobs.Params{ Repo: r.Repo, diff --git a/internal/lefthook/runner/runner_test.go b/internal/lefthook/runner/runner_test.go index 6b8952da..e5d4b08d 100644 --- a/internal/lefthook/runner/runner_test.go +++ b/internal/lefthook/runner/runner_test.go @@ -618,12 +618,12 @@ func TestRunAll(t *testing.T) { "ok": { Run: "success", StageFixed: true, - Glob: "*.md", + Glob: []string{"*.md"}, }, "fail": { Run: "fail", StageFixed: true, - Glob: "*.txt", + Glob: []string{"*.txt"}, }, }, }, @@ -648,12 +648,12 @@ func TestRunAll(t *testing.T) { "ok": { Run: "success", StageFixed: true, - Glob: "*.md", + Glob: []string{"*.md"}, }, "fail": { Run: "fail", StageFixed: true, - Glob: "*.sh", + Glob: []string{"*.sh"}, }, }, }, @@ -703,12 +703,12 @@ func TestRunAll(t *testing.T) { "ok": { Run: "success", StageFixed: true, - Glob: "*.md", + Glob: []string{"*.md"}, }, "fail": { Run: "fail", StageFixed: true, - Glob: "*.sh", + Glob: []string{"*.sh"}, }, }, }, diff --git a/schema.json b/schema.json index db95a9c0..88087842 100644 --- a/schema.json +++ b/schema.json @@ -56,7 +56,17 @@ "type": "array" }, "glob": { - "type": "string" + "oneOf": [ + { + "type": "string" + }, + { + "type": "array" + } + ], + "items": { + "type": "string" + } }, "root": { "type": "string" @@ -214,7 +224,17 @@ "type": "string" }, "glob": { - "type": "string" + "oneOf": [ + { + "type": "string" + }, + { + "type": "array" + } + ], + "items": { + "type": "string" + } }, "root": { "type": "string" @@ -392,7 +412,7 @@ "type": "object" } }, - "$comment": "Last updated on 2025.01.16.", + "$comment": "Last updated on 2025.01.21.", "properties": { "min_version": { "type": "string", diff --git a/testdata/dump.txt b/testdata/dump.txt index f3c8ec19..c380b457 100644 --- a/testdata/dump.txt +++ b/testdata/dump.txt @@ -61,7 +61,8 @@ pre-commit: test: run: yarn test skip: merge - glob: '*.js' + glob: + - '*.js' scripts: my-script.sh: runner: bash @@ -95,7 +96,9 @@ pre-commit: "test": { "run": "yarn test", "skip": "merge", - "glob": "*.js" + "glob": [ + "*.js" + ] } }, "scripts": { @@ -130,7 +133,7 @@ interactive = true [pre-commit.commands.test] run = 'yarn test' skip = 'merge' -glob = '*.js' +glob = ['*.js'] [pre-commit.scripts] [pre-commit.scripts.'my-script.sh'] diff --git a/testdata/job_merging.txt b/testdata/job_merging.txt index 6e4f5641..e4c8bc4f 100644 --- a/testdata/job_merging.txt +++ b/testdata/job_merging.txt @@ -103,7 +103,8 @@ extends: pre-commit: jobs: - name: group - glob: '*.rb' + glob: + - '*.rb' group: jobs: - name: child @@ -115,7 +116,8 @@ pre-commit: - run: 3 no-name - name: echo run: echo 2 - glob: "3" + glob: + - "3" tags: - backend skip: true diff --git a/testdata/many_extends_levels.txt b/testdata/many_extends_levels.txt index 1edc6c58..0543face 100644 --- a/testdata/many_extends_levels.txt +++ b/testdata/many_extends_levels.txt @@ -77,4 +77,5 @@ pre-commit: skip: true tags: - backend - glob: "3" + glob: + - "3" diff --git a/testdata/remotes.txt b/testdata/remotes.txt index a9262a7e..05b84bea 100644 --- a/testdata/remotes.txt +++ b/testdata/remotes.txt @@ -23,13 +23,15 @@ pre-commit: commands: js-lint: run: npx eslint --fix {staged_files} && git add {staged_files} - glob: '*.{js,ts}' + glob: + - '*.{js,ts}' ping: run: echo pong ruby-lint: run: bundle exec rubocop --force-exclusion --parallel '{files}' files: git diff-tree -r --name-only --diff-filter=CDMR HEAD origin/master - glob: '*.rb' + glob: + - '*.rb' ruby-test: run: bundle exec rspec skip: @@ -44,7 +46,8 @@ pre-push: spelling: run: npx yaspeller {files} files: git diff --name-only HEAD @{push} - glob: '*.md' + glob: + - '*.md' remotes: - git_url: https://github.com/evilmartians/lefthook ref: v1.4.0