diff --git a/taskfile/copy.go b/taskfile/copy.go new file mode 100644 index 00000000..10e1a017 --- /dev/null +++ b/taskfile/copy.go @@ -0,0 +1,23 @@ +package taskfile + +import "golang.org/x/exp/constraints" + +func deepCopySlice[T any](orig []T) []T { + if orig == nil { + return nil + } + c := make([]T, len(orig)) + copy(c, orig) + return c +} + +func deepCopyMap[K constraints.Ordered, V any](orig map[K]V) map[K]V { + if orig == nil { + return nil + } + c := make(map[K]V, len(orig)) + for k, v := range orig { + c[k] = v + } + return c +} diff --git a/taskfile/included_taskfile.go b/taskfile/included_taskfile.go index d24bb4e9..f344eb54 100644 --- a/taskfile/included_taskfile.go +++ b/taskfile/included_taskfile.go @@ -120,6 +120,23 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err return nil } +// DeepCopy creates a new instance of IncludedTaskfile and copies +// data by value from the source struct. +func (it *IncludedTaskfile) DeepCopy() *IncludedTaskfile { + if it == nil { + return nil + } + return &IncludedTaskfile{ + Taskfile: it.Taskfile, + Dir: it.Dir, + Optional: it.Optional, + Internal: it.Internal, + AdvancedImport: it.AdvancedImport, + Vars: it.Vars.DeepCopy(), + BaseDir: it.BaseDir, + } +} + // FullTaskfilePath returns the fully qualified path to the included taskfile func (it *IncludedTaskfile) FullTaskfilePath() (string, error) { return it.resolvePath(it.Taskfile) diff --git a/taskfile/merge.go b/taskfile/merge.go index 6454e8c7..5f05525a 100644 --- a/taskfile/merge.go +++ b/taskfile/merge.go @@ -39,19 +39,14 @@ func Merge(t1, t2 *Taskfile, includedTaskfile *IncludedTaskfile, namespaces ...s t1.Tasks = make(Tasks) } for k, v := range t2.Tasks { - // FIXME(@andreynering): Refactor this block, otherwise we can - // have serious side-effects in the future, since we're editing - // the original references instead of deep copying them. - task := v + // We do a deep copy of the task struct here to ensure that no data can + // be changed elsewhere once the taskfile is merged. + task := v.DeepCopy() // Set the task to internal if EITHER the included task or the included // taskfile are marked as internal task.Internal = task.Internal || includedTaskfile.Internal - // Deep copy the aliases so we can use them later - origAliases := make([]string, len(task.Aliases)) - copy(origAliases, task.Aliases) - // Add namespaces to dependencies, commands and aliases for _, dep := range task.Deps { dep.Task = taskNameWithNamespace(dep.Task, namespaces...) @@ -67,8 +62,8 @@ func Merge(t1, t2 *Taskfile, includedTaskfile *IncludedTaskfile, namespaces ...s // Add namespace aliases if includedTaskfile != nil { for _, namespaceAlias := range includedTaskfile.Aliases { - for _, alias := range origAliases { - task.Aliases = append(v.Aliases, taskNameWithNamespace(alias, namespaceAlias)) + for _, alias := range v.Aliases { + task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias)) } } } diff --git a/taskfile/task.go b/taskfile/task.go index 5e9f1008..fd5c3937 100644 --- a/taskfile/task.go +++ b/taskfile/task.go @@ -98,3 +98,35 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { t.Run = task.Run return nil } + +// DeepCopy creates a new instance of Task and copies +// data by value from the source struct. +func (t *Task) DeepCopy() *Task { + c := &Task{ + Task: t.Task, + Cmds: deepCopySlice(t.Cmds), + Deps: deepCopySlice(t.Deps), + Label: t.Label, + Desc: t.Desc, + Summary: t.Summary, + Aliases: deepCopySlice(t.Aliases), + Sources: deepCopySlice(t.Sources), + Generates: deepCopySlice(t.Generates), + Status: deepCopySlice(t.Status), + Preconditions: deepCopySlice(t.Preconditions), + Dir: t.Dir, + Vars: t.Vars.DeepCopy(), + Env: t.Env.DeepCopy(), + Silent: t.Silent, + Interactive: t.Interactive, + Internal: t.Internal, + Method: t.Method, + Prefix: t.Prefix, + IgnoreError: t.IgnoreError, + Run: t.Run, + IncludeVars: t.IncludeVars.DeepCopy(), + IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(), + IncludedTaskfile: t.IncludedTaskfile.DeepCopy(), + } + return c +} diff --git a/taskfile/var.go b/taskfile/var.go index 2693444f..65cc4fa4 100644 --- a/taskfile/var.go +++ b/taskfile/var.go @@ -34,6 +34,18 @@ func (vs *Vars) UnmarshalYAML(node *yaml.Node) error { return nil } +// DeepCopy creates a new instance of Vars and copies +// data by value from the source struct. +func (vs *Vars) DeepCopy() *Vars { + if vs == nil { + return nil + } + return &Vars{ + Keys: deepCopySlice(vs.Keys), + Mapping: deepCopyMap(vs.Mapping), + } +} + // Merge merges the given Vars into the caller one func (vs *Vars) Merge(other *Vars) { _ = other.Range(func(key string, value Var) error {