diff --git a/setup.go b/setup.go index 6e2034716..cb891d67b 100644 --- a/setup.go +++ b/setup.go @@ -51,6 +51,8 @@ func (e *Executor) Setup() error { } e.setupDefaults() e.setupConcurrencyState() + e.readTaskignore() + return nil } @@ -63,6 +65,28 @@ func (e *Executor) getRootNode() (taskfile.Node, error) { return node, err } +func (e *Executor) readTaskignore() { + // get only the tasks that have the sources defined + tasksWithSources := taskfile.GetTasksWithSources(e.Taskfile) + + if tasksWithSources == nil { + return + } + + ignoreGlobs := taskfile.ReadTaskignore(e.Logger, e.Dir, e.Timeout) + + if ignoreGlobs == nil { + return + } + + // apply .taskignore globs to each task + for _, t := range tasksWithSources { + for _, g := range ignoreGlobs { + t.Sources = append(t.Sources, &ast.Glob{Glob: g, Negate: true}) + } + } +} + func (e *Executor) readTaskfile(node taskfile.Node) error { reader := taskfile.NewReader( node, diff --git a/task_test.go b/task_test.go index afb40fa19..1c2a84b13 100644 --- a/task_test.go +++ b/task_test.go @@ -2839,6 +2839,23 @@ func TestReference(t *testing.T) { } } +func TestTaskignore(t *testing.T) { + var buff bytes.Buffer + e := task.Executor{ + Dir: "testdata/taskignore", + Stdout: &buff, + Stderr: &buff, + Force: true, + Silent: true, + } + + require.NoError(t, e.Setup()) + + err := e.Run(context.Background(), &ast.Call{Task: "txt"}) + assert.Equal(t, "dont_ignore.txt\n", buff.String()) + require.NoError(t, err) +} + // enableExperimentForTest enables the experiment behind pointer e for the duration of test t and sub-tests, // with the experiment being restored to its previous state when tests complete. // diff --git a/taskfile/taskignore.go b/taskfile/taskignore.go new file mode 100644 index 000000000..936c4b5f8 --- /dev/null +++ b/taskfile/taskignore.go @@ -0,0 +1,67 @@ +package taskfile + +import ( + "context" + "path" + "strings" + "time" + + "github.com/go-task/task/v3/internal/logger" + "github.com/go-task/task/v3/taskfile/ast" +) + +const taskignore = ".taskignore" + +func GetTasksWithSources(t *ast.Taskfile) []*ast.Task { + var tasksWithSources []*ast.Task + + for _, task := range t.Tasks.Values() { + if len(task.Sources) > 0 { + tasksWithSources = append(tasksWithSources, task) + } + } + + return tasksWithSources +} + +func ReadTaskignore(l *logger.Logger, dir string, timeout time.Duration) []string { + bytes := read(l, dir, timeout) + globs := filterGlobs(bytes) + + return globs +} + +func read(l *logger.Logger, dir string, timeout time.Duration) []byte { + fileNode, err := NewFileNode(l, "", path.Join(dir, taskignore)) + if err != nil { + return nil + } + + ctx, cf := context.WithTimeout(context.Background(), timeout) + defer cf() + + bytes, err := fileNode.Read(ctx) + if err != nil { + return nil + } + + return bytes +} + +func filterGlobs(bytes []byte) []string { + if len(bytes) == 0 { + return nil + } + + lines := strings.Split(string(bytes), "\n") + var validGlobs []string + + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if trimmed != "" && !strings.HasPrefix(trimmed, "#") { + validGlobs = append(validGlobs, trimmed) + } + } + + return validGlobs +} diff --git a/testdata/taskignore/.taskignore b/testdata/taskignore/.taskignore new file mode 100644 index 000000000..ca04abfd0 --- /dev/null +++ b/testdata/taskignore/.taskignore @@ -0,0 +1 @@ +./**/*.json diff --git a/testdata/taskignore/Taskfile.yml b/testdata/taskignore/Taskfile.yml new file mode 100644 index 000000000..a4a609a90 --- /dev/null +++ b/testdata/taskignore/Taskfile.yml @@ -0,0 +1,12 @@ +version: '3' + +tasks: + txt: + desc: Echo file names + sources: + - ./nested/ignore_me_too.json + - ./ignore_me.json + - ./dont_ignore.txt + cmds: + - for: sources + cmd: echo {{.ITEM}} diff --git a/testdata/taskignore/dont_ignore.txt b/testdata/taskignore/dont_ignore.txt new file mode 100644 index 000000000..e69de29bb diff --git a/testdata/taskignore/ignore_me.json b/testdata/taskignore/ignore_me.json new file mode 100644 index 000000000..e69de29bb diff --git a/testdata/taskignore/nested/ignore_me_too.json b/testdata/taskignore/nested/ignore_me_too.json new file mode 100644 index 000000000..e69de29bb diff --git a/website/docs/usage.mdx b/website/docs/usage.mdx index 2f5baa477..f58b802b9 100644 --- a/website/docs/usage.mdx +++ b/website/docs/usage.mdx @@ -789,6 +789,37 @@ tasks: - public/bundle.css ``` +It is also possible to exclude files from fingerprinting globally by creating `.taskignore` +file in the same directory where the `Taskfile` is located. + + + + + +```yaml +version: '3' + +tasks: + css: + sources: + - mysources/**/*.css + generates: + - public/bundle.css +``` + + + + +```yaml +mysources/ignoreme.css +``` + + + If you prefer these check to be made by the modification timestamp of the files, instead of its checksum (content), just set the `method` property to `timestamp`.