diff --git a/go.mod b/go.mod index 6e6861b4be..a9316af924 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/puzpuzpuz/xsync/v4 v4.4.0 + github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sajari/fuzzy v1.0.0 github.com/sebdah/goldie/v2 v2.8.0 github.com/spf13/pflag v1.0.10 diff --git a/go.sum b/go.sum index 00131950e7..e231960abb 100644 --- a/go.sum +++ b/go.sum @@ -220,6 +220,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= +github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= github.com/sajari/fuzzy v1.0.0 h1:+FmwVvJErsd0d0hAPlj4CxqxUtQY/fOoY0DwX4ykpRY= github.com/sajari/fuzzy v1.0.0/go.mod h1:OjYR6KxoWOe9+dOlXeiCJd4dIbED4Oo8wpS89o0pwOo= github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8rc= @@ -236,6 +238,7 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/u-root/u-root v0.15.1-0.20251208185023-2f8c7e763cf8 h1:cq+DjLAjz3ZPwh0+G571O/jMH0c0DzReDPLjQGL2/BA= @@ -311,6 +314,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mvdan.cc/sh/moreinterp v0.0.0-20260120230322-19def062a997 h1:3bbJwtPFh98dJ6lxRdR3eLHTH1CmR3BcU6TriIMiXjE= diff --git a/internal/fingerprint/gitignore.go b/internal/fingerprint/gitignore.go new file mode 100644 index 0000000000..62287f0d8a --- /dev/null +++ b/internal/fingerprint/gitignore.go @@ -0,0 +1,92 @@ +package fingerprint + +import ( + "bufio" + "os" + "path/filepath" + "strings" + + ignore "github.com/sabhiram/go-gitignore" +) + +type gitignoreRule struct { + dir string + matcher *ignore.GitIgnore +} + +// loadGitignoreRules walks up from dir collecting .gitignore files. +// Stops at the first .git (file or directory) found. +// Returns nil if no .git is found (not in a git repo). +func loadGitignoreRules(dir string) []gitignoreRule { + dir, _ = filepath.Abs(dir) + + var rules []gitignoreRule + foundGit := false + current := dir + + for { + lines := readGitignoreLines(filepath.Join(current, ".gitignore")) + if len(lines) > 0 { + rules = append(rules, gitignoreRule{ + dir: current, + matcher: ignore.CompileIgnoreLines(lines...), + }) + } + if _, err := os.Stat(filepath.Join(current, ".git")); err == nil { + foundGit = true + break + } + parent := filepath.Dir(current) + if parent == current { + break + } + current = parent + } + + if !foundGit { + return nil + } + + return rules +} + +func readGitignoreLines(path string) []string { + f, err := os.Open(path) + if err != nil { + return nil + } + defer f.Close() + + var lines []string + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := scanner.Text() + if line != "" && !strings.HasPrefix(line, "#") { + lines = append(lines, line) + } + } + return lines +} + +// filterGitignored removes entries from the file map that match gitignore rules. +func filterGitignored(files map[string]bool, dir string) map[string]bool { + rules := loadGitignoreRules(dir) + if len(rules) == 0 { + return files + } + + for path := range files { + for _, rule := range rules { + relPath, err := filepath.Rel(rule.dir, path) + if err != nil || strings.HasPrefix(relPath, "..") { + continue + } + if rule.matcher.MatchesPath(filepath.ToSlash(relPath)) { + files[path] = false + break + } + } + } + + return files +} diff --git a/internal/fingerprint/gitignore_test.go b/internal/fingerprint/gitignore_test.go new file mode 100644 index 0000000000..edef18b1af --- /dev/null +++ b/internal/fingerprint/gitignore_test.go @@ -0,0 +1,112 @@ +package fingerprint + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/go-task/task/v3/taskfile/ast" +) + +func initGitRepo(t *testing.T, dir string) { + t.Helper() + require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755)) +} + +func TestGlobsWithGitignore(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + initGitRepo(t, dir) + + require.NoError(t, os.WriteFile(filepath.Join(dir, "included.txt"), []byte("included"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "ignored.log"), []byte("ignored"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "also-included.txt"), []byte("also included"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644)) + + globs := []*ast.Glob{ + {Glob: "./*"}, + } + + filesWithout, err := Globs(dir, globs, false) + require.NoError(t, err) + + filesWith, err := Globs(dir, globs, true) + require.NoError(t, err) + + hasLog := false + for _, f := range filesWithout { + if filepath.Base(f) == "ignored.log" { + hasLog = true + break + } + } + assert.True(t, hasLog, "ignored.log should be present without gitignore filter") + + hasLog = false + for _, f := range filesWith { + if filepath.Base(f) == "ignored.log" { + hasLog = true + break + } + } + assert.False(t, hasLog, "ignored.log should be excluded with gitignore filter") + + txtCount := 0 + for _, f := range filesWith { + if filepath.Ext(f) == ".txt" { + txtCount++ + } + } + assert.Equal(t, 2, txtCount, "both .txt files should remain") +} + +func TestGlobsWithGitignoreNested(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + initGitRepo(t, dir) + + subDir := filepath.Join(dir, "sub") + require.NoError(t, os.MkdirAll(subDir, 0o755)) + + require.NoError(t, os.WriteFile(filepath.Join(subDir, "keep.txt"), []byte("keep"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(subDir, "build.out"), []byte("build"), 0o644)) + + require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(subDir, ".gitignore"), []byte("*.out\n"), 0o644)) + + globs := []*ast.Glob{ + {Glob: "./*"}, + } + + files, err := Globs(subDir, globs, true) + require.NoError(t, err) + + for _, f := range files { + assert.NotEqual(t, "build.out", filepath.Base(f), "build.out should be excluded by nested .gitignore") + } +} + +func TestGlobsWithGitignoreNoRepo(t *testing.T) { + t.Parallel() + + // Cannot use t.TempDir() here because it creates a dir inside the + // go-task repo which has a .git parent, defeating the "no repo" test. + dir, err := os.MkdirTemp("", "task-gitignore-norepo-*") //nolint:usetesting + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(dir) }) + + require.NoError(t, os.WriteFile(filepath.Join(dir, "file.txt"), []byte("content"), 0o644)) + + globs := []*ast.Glob{ + {Glob: "./*"}, + } + + files, err := Globs(dir, globs, true) + require.NoError(t, err) + assert.Len(t, files, 1) +} diff --git a/internal/fingerprint/glob.go b/internal/fingerprint/glob.go index fd3cafceaa..930a54a3b1 100644 --- a/internal/fingerprint/glob.go +++ b/internal/fingerprint/glob.go @@ -10,7 +10,7 @@ import ( "github.com/go-task/task/v3/taskfile/ast" ) -func Globs(dir string, globs []*ast.Glob) ([]string, error) { +func Globs(dir string, globs []*ast.Glob, useGitignore bool) ([]string, error) { resultMap := make(map[string]bool) for _, g := range globs { matches, err := glob(dir, g.Glob) @@ -21,6 +21,11 @@ func Globs(dir string, globs []*ast.Glob) ([]string, error) { resultMap[match] = !g.Negate } } + + if useGitignore { + resultMap = filterGitignored(resultMap, dir) + } + return collectKeys(resultMap), nil } diff --git a/internal/fingerprint/sources_checksum.go b/internal/fingerprint/sources_checksum.go index 16a98c1640..e30e8744a6 100644 --- a/internal/fingerprint/sources_checksum.go +++ b/internal/fingerprint/sources_checksum.go @@ -88,7 +88,7 @@ func (*ChecksumChecker) Kind() string { } func (c *ChecksumChecker) checksum(t *ast.Task) (string, error) { - sources, err := Globs(t.Dir, t.Sources) + sources, err := Globs(t.Dir, t.Sources, t.ShouldUseGitignore()) if err != nil { return "", err } diff --git a/internal/fingerprint/sources_timestamp.go b/internal/fingerprint/sources_timestamp.go index b1a6f299d5..e2c27575de 100644 --- a/internal/fingerprint/sources_timestamp.go +++ b/internal/fingerprint/sources_timestamp.go @@ -28,11 +28,11 @@ func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) { return false, nil } - sources, err := Globs(t.Dir, t.Sources) + sources, err := Globs(t.Dir, t.Sources, t.ShouldUseGitignore()) if err != nil { return false, nil } - generates, err := Globs(t.Dir, t.Generates) + generates, err := Globs(t.Dir, t.Generates, t.ShouldUseGitignore()) if err != nil { return false, nil } @@ -90,7 +90,7 @@ func (checker *TimestampChecker) Kind() string { // Value implements the Checker Interface func (checker *TimestampChecker) Value(t *ast.Task) (any, error) { - sources, err := Globs(t.Dir, t.Sources) + sources, err := Globs(t.Dir, t.Sources, t.ShouldUseGitignore()) if err != nil { return time.Now(), err } diff --git a/task_test.go b/task_test.go index a09e496f8b..d4ab2d9a35 100644 --- a/task_test.go +++ b/task_test.go @@ -555,6 +555,55 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in } } +func TestGitignoreChecksum(t *testing.T) { //nolint:paralleltest // cannot run in parallel + const dir = "testdata/gitignore" + + // Clean up + _ = os.RemoveAll(filepathext.SmartJoin(dir, ".task")) + _ = os.Remove(filepathext.SmartJoin(dir, "generated.txt")) + + var buff bytes.Buffer + tempDir := task.TempDir{ + Remote: filepathext.SmartJoin(dir, ".task"), + Fingerprint: filepathext.SmartJoin(dir, ".task"), + } + e := task.NewExecutor( + task.WithDir(dir), + task.WithStdout(&buff), + task.WithStderr(&buff), + task.WithTempDir(tempDir), + ) + require.NoError(t, e.Setup()) + + // First run - should execute + require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"})) + + // Second run - should be up to date + buff.Reset() + require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"})) + assert.Equal(t, "task: Task \"build\" is up to date\n", buff.String()) + + // Modify the ignored file - should still be up to date + require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, "ignored.txt"), []byte("modified\n"), 0o644)) + buff.Reset() + require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"})) + assert.Equal(t, "task: Task \"build\" is up to date\n", buff.String()) + + // Modify the source file - should re-execute + require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, "source.txt"), []byte("modified source\n"), 0o644)) + buff.Reset() + require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"})) + assert.NotEqual(t, "task: Task \"build\" is up to date\n", buff.String()) + + // Restore source file + require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, "source.txt"), []byte("source content\n"), 0o644)) + require.NoError(t, os.WriteFile(filepathext.SmartJoin(dir, "ignored.txt"), []byte("ignored content\n"), 0o644)) + + // Clean up + _ = os.RemoveAll(filepathext.SmartJoin(dir, ".task")) + _ = os.Remove(filepathext.SmartJoin(dir, "generated.txt")) +} + func TestStatusVariables(t *testing.T) { t.Parallel() diff --git a/taskfile/ast/task.go b/taskfile/ast/task.go index 0e9893943e..c9d2c20779 100644 --- a/taskfile/ast/task.go +++ b/taskfile/ast/task.go @@ -38,6 +38,7 @@ type Task struct { Method string Prefix string `hash:"ignore"` IgnoreError bool + UseGitignore *bool Run string Platforms []*Platform If string @@ -75,6 +76,12 @@ func (t *Task) IsSilent() bool { return t.Silent != nil && *t.Silent } +// ShouldUseGitignore returns true if the task has gitignore filtering explicitly enabled. +// Returns false if UseGitignore is nil (not set) or explicitly set to false. +func (t *Task) ShouldUseGitignore() bool { + return t.UseGitignore != nil && *t.UseGitignore +} + // WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values. func (t *Task) WildcardMatch(name string) (bool, []string) { names := append([]string{t.Task}, t.Aliases...) @@ -149,7 +156,8 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { Internal bool Method string Prefix string - IgnoreError bool `yaml:"ignore_error"` + IgnoreError bool `yaml:"ignore_error"` + UseGitignore *bool `yaml:"use_gitignore,omitempty"` Run string Platforms []*Platform If string @@ -190,6 +198,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { t.Method = task.Method t.Prefix = task.Prefix t.IgnoreError = task.IgnoreError + t.UseGitignore = deepcopy.Scalar(task.UseGitignore) t.Run = task.Run t.Platforms = task.Platforms t.If = task.If @@ -233,6 +242,7 @@ func (t *Task) DeepCopy() *Task { Method: t.Method, Prefix: t.Prefix, IgnoreError: t.IgnoreError, + UseGitignore: deepcopy.Scalar(t.UseGitignore), Run: t.Run, IncludeVars: t.IncludeVars.DeepCopy(), IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(), diff --git a/taskfile/ast/taskfile.go b/taskfile/ast/taskfile.go index 4e3a3e4255..9de0f90255 100644 --- a/taskfile/ast/taskfile.go +++ b/taskfile/ast/taskfile.go @@ -20,20 +20,21 @@ var ErrIncludedTaskfilesCantHaveDotenvs = errors.New("task: Included Taskfiles c // Taskfile is the abstract syntax tree for a Taskfile type Taskfile struct { - Location string - Version *semver.Version - Output Output - Method string - Includes *Includes - Set []string - Shopt []string - Vars *Vars - Env *Vars - Tasks *Tasks - Silent bool - Dotenv []string - Run string - Interval time.Duration + Location string + Version *semver.Version + Output Output + Method string + Includes *Includes + Set []string + Shopt []string + Vars *Vars + Env *Vars + Tasks *Tasks + Silent bool + Dotenv []string + Run string + Interval time.Duration + UseGitignore bool `yaml:"use_gitignore"` } // Merge merges the second Taskfile into the first @@ -76,19 +77,20 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error { switch node.Kind { case yaml.MappingNode: var taskfile struct { - Version *semver.Version - Output Output - Method string - Includes *Includes - Set []string - Shopt []string - Vars *Vars - Env *Vars - Tasks *Tasks - Silent bool - Dotenv []string - Run string - Interval time.Duration + Version *semver.Version + Output Output + Method string + Includes *Includes + Set []string + Shopt []string + Vars *Vars + Env *Vars + Tasks *Tasks + Silent bool + Dotenv []string + Run string + Interval time.Duration + UseGitignore bool `yaml:"use_gitignore"` } if err := node.Decode(&taskfile); err != nil { return errors.NewTaskfileDecodeError(err, node) @@ -106,6 +108,7 @@ func (tf *Taskfile) UnmarshalYAML(node *yaml.Node) error { tf.Dotenv = taskfile.Dotenv tf.Run = taskfile.Run tf.Interval = taskfile.Interval + tf.UseGitignore = taskfile.UseGitignore if tf.Includes == nil { tf.Includes = NewIncludes() } diff --git a/testdata/gitignore/.gitignore b/testdata/gitignore/.gitignore new file mode 100644 index 0000000000..f89d64dab6 --- /dev/null +++ b/testdata/gitignore/.gitignore @@ -0,0 +1 @@ +ignored.txt diff --git a/testdata/gitignore/Taskfile.yml b/testdata/gitignore/Taskfile.yml new file mode 100644 index 0000000000..93452867c6 --- /dev/null +++ b/testdata/gitignore/Taskfile.yml @@ -0,0 +1,25 @@ +version: '3' + +use_gitignore: true + +tasks: + build: + cmds: + - cp ./source.txt ./generated.txt + sources: + - ./*.txt + - exclude: ./generated.txt + generates: + - ./generated.txt + method: checksum + + build-no-use_gitignore: + use_gitignore: false + cmds: + - cp ./source.txt ./generated.txt + sources: + - ./*.txt + - exclude: ./generated.txt + generates: + - ./generated.txt + method: checksum diff --git a/testdata/gitignore/source.txt b/testdata/gitignore/source.txt new file mode 100644 index 0000000000..48763f0349 --- /dev/null +++ b/testdata/gitignore/source.txt @@ -0,0 +1 @@ +source content diff --git a/variables.go b/variables.go index c7c6cc8493..dad3ca9219 100644 --- a/variables.go +++ b/variables.go @@ -59,6 +59,7 @@ func (e *Executor) CompiledTaskForTaskList(call *Call) (*ast.Task, error) { Env: nil, Dotenv: origTask.Dotenv, Silent: deepcopy.Scalar(origTask.Silent), + UseGitignore: deepcopy.Scalar(origTask.UseGitignore), Interactive: origTask.Interactive, Internal: origTask.Internal, Method: origTask.Method, @@ -110,6 +111,11 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err } } + gitignore := origTask.ShouldUseGitignore() + if origTask.UseGitignore == nil { + gitignore = e.Taskfile.UseGitignore + } + new := ast.Task{ Task: origTask.Task, Label: templater.Replace(origTask.Label, cache), @@ -126,6 +132,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err Env: nil, Dotenv: templater.Replace(origTask.Dotenv, cache), Silent: deepcopy.Scalar(origTask.Silent), + UseGitignore: &gitignore, Interactive: origTask.Interactive, Internal: origTask.Internal, Method: templater.Replace(origTask.Method, cache), @@ -219,7 +226,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err continue } if cmd.For != nil { - list, keys, err := itemsFromFor(cmd.For, new.Dir, new.Sources, new.Generates, vars, origTask.Location, cache) + list, keys, err := itemsFromFor(cmd.For, new.Dir, new.Sources, new.Generates, gitignore, vars, origTask.Location, cache) if err != nil { return nil, err } @@ -268,7 +275,7 @@ func (e *Executor) compiledTask(call *Call, evaluateShVars bool) (*ast.Task, err continue } if dep.For != nil { - list, keys, err := itemsFromFor(dep.For, new.Dir, new.Sources, new.Generates, vars, origTask.Location, cache) + list, keys, err := itemsFromFor(dep.For, new.Dir, new.Sources, new.Generates, gitignore, vars, origTask.Location, cache) if err != nil { return nil, err } @@ -339,6 +346,7 @@ func itemsFromFor( dir string, sources []*ast.Glob, generates []*ast.Glob, + gitignore bool, vars *ast.Vars, location *ast.Location, cache *templater.Cache, @@ -361,7 +369,7 @@ func itemsFromFor( } // Get the list from the task sources if f.From == "sources" { - glist, err := fingerprint.Globs(dir, sources) + glist, err := fingerprint.Globs(dir, sources, gitignore) if err != nil { return nil, nil, err } @@ -375,7 +383,7 @@ func itemsFromFor( } // Get the list from the task generates if f.From == "generates" { - glist, err := fingerprint.Globs(dir, generates) + glist, err := fingerprint.Globs(dir, generates, gitignore) if err != nil { return nil, nil, err } diff --git a/watch.go b/watch.go index 8e7f7ccf7d..1a0dbee0c7 100644 --- a/watch.go +++ b/watch.go @@ -205,7 +205,7 @@ func (e *Executor) collectSources(calls []*Call) ([]string, error) { var sources []string err := e.traverse(calls, func(task *ast.Task) error { - files, err := fingerprint.Globs(task.Dir, task.Sources) + files, err := fingerprint.Globs(task.Dir, task.Sources, task.ShouldUseGitignore()) if err != nil { return err } diff --git a/website/src/public/schema.json b/website/src/public/schema.json index 2210952d62..61f7a843b7 100644 --- a/website/src/public/schema.json +++ b/website/src/public/schema.json @@ -177,6 +177,11 @@ "enum": ["none", "checksum", "timestamp"], "default": "none" }, + "use_gitignore": { + "description": "When set to true, files matching .gitignore rules will be excluded from sources and generates glob resolution. Overrides the global gitignore setting.", + "type": "boolean", + "default": false + }, "prefix": { "description": "Defines a string to prefix the output of tasks running in parallel. Only used when the output mode is `prefixed`.", "type": "string" @@ -687,6 +692,11 @@ "enum": ["none", "checksum", "timestamp"], "default": "checksum" }, + "use_gitignore": { + "description": "When set to true, files matching .gitignore rules will be excluded from sources and generates glob resolution for all tasks. Can be overridden per task.", + "type": "boolean", + "default": false + }, "includes": { "description": "Imports tasks from the specified taskfiles. The tasks described in the given Taskfiles will be available with the informed namespace.", "type": "object",