Repository: go-task/task
Pull request #831 (2022-08-08 18:02:46)

Attempt to fix Task not following symlinks
Please check if this could be a good solution for the described bug.


Patch:
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 60a03fc0..320e729d 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -17,9 +17,6 @@ build:
       goarch: 386
   env:
     - CGO_ENABLED=0
-  mod_timestamp: '{{ .CommitTimestamp }}'
-  flags:
-    - -trimpath
   ldflags:
     - -s -w # Don't set main.version.
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd19f876..85bca8ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,5 @@
 # Changelog
 
-## Unreleased
-
-- Improvements and fixes to Bash completion
-  ([#835](https://github.com/go-task/task/pull/835)).
-
 ## v3.14.1 - 2022-08-03
 
 - Always resolve relative include paths relative to the including Taskfile
diff --git a/completion/bash/task.bash b/completion/bash/task.bash
index feaaca4d..46e3a452 100644
--- a/completion/bash/task.bash
+++ b/completion/bash/task.bash
@@ -1,48 +1,26 @@
-# vim: set tabstop=2 shiftwidth=2 expandtab:
+#!/bin/bash
 
-_GO_TASK_COMPLETION_LIST_OPTION='--list-all'
+GO_TASK_PROGNAME=task
 
-function _task()
+_go_task_completion()
 {
-  local cur prev words cword
-  _init_completion -n : || return
+  local cur
+  _get_comp_words_by_ref -n : cur
 
-  # Handle special arguments of options.
-  case "$prev" in
-    -d|--dir)
-      _filedir -d
-      return $?
-    ;;
-    -t|--taskfile)
-      _filedir yaml || return $?
-      _filedir yml
-      return $?
-    ;;
-    -o|--output)
-      COMPREPLY=( $( compgen -W "interleaved group prefixed" -- $cur ) )
-      return 0
-    ;;
-  esac
-
-  # Handle normal options.
   case "$cur" in
-    -*)
-      COMPREPLY=( $( compgen -W "$(_parse_help $1)" -- $cur ) )
-      return 0
+  --*)
+    local options
+    options="$(_parse_help task)"
+    mapfile -t COMPREPLY < <(compgen -W "$options" -- "$cur")
+    ;;
+  *)
+    local tasks
+    tasks="$($GO_TASK_PROGNAME --list-all 2> /dev/null | awk 'NR>1 { sub(/:$/,"",$2); print $2 }')"
+    mapfile -t COMPREPLY < <(compgen -W "$tasks" -- "$cur")
     ;;
   esac
 
-  # Get task names.
-  local line tasks=()
-  while read line; do
-    if [[ "${line}" =~ ^\*[[:space:]]+([[:alnum:]_:]+): ]]; then
-      tasks+=( ${BASH_REMATCH[1]} )
-    fi
-  done < <("${COMP_WORDS[@]}" $_GO_TASK_COMPLETION_LIST_OPTION 2> /dev/null)
-
-  # Prepare task completions and post-process due to colons.
-  COMPREPLY=( $( compgen -W "${tasks[*]}" -- "$cur" ) )
   __ltrim_colon_completions "$cur"
 }
 
-complete -F _task task
+complete -F _go_task_completion $GO_TASK_PROGNAME
diff --git a/docs/docs/releasing.md b/docs/docs/releasing.md
index 573d6cb6..d8c1f8bf 100644
--- a/docs/docs/releasing.md
+++ b/docs/docs/releasing.md
@@ -31,9 +31,9 @@ the [Snapcraft dashboard][snapcraftdashboard].
 
 # Scoop
 
-Scoop is a command-line package manager for the Windows operating system.
-Scoop package manifests are maintained by the community.
-Scoop owners usually take care of updating versions there by editing [this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
+Scoop is a community owned installation method. Scoop owners usually take care
+of updating versions there by editing
+[this file](https://github.com/lukesampson/scoop-extras/blob/master/bucket/task.json).
 If you think its Task version is outdated, open an issue to let us know.
 
 # Nix
diff --git a/errors.go b/errors.go
index 483ad565..e13ad9b3 100644
--- a/errors.go
+++ b/errors.go
@@ -17,7 +17,7 @@ type taskNotFoundError struct {
 }
 
 func (err *taskNotFoundError) Error() string {
-	return fmt.Sprintf(`task: Task %q not found`, err.taskName)
+	return fmt.Sprintf(`task: Task "%s" not found`, err.taskName)
 }
 
 type TaskRunError struct {
@@ -26,7 +26,7 @@ type TaskRunError struct {
 }
 
 func (err *TaskRunError) Error() string {
-	return fmt.Sprintf(`task: Failed to run task %q: %v`, err.taskName, err.err)
+	return fmt.Sprintf(`task: Failed to run task "%s": %v`, err.taskName, err.err)
 }
 
 func (err *TaskRunError) ExitCode() int {
@@ -46,7 +46,7 @@ type MaximumTaskCallExceededError struct {
 
 func (e *MaximumTaskCallExceededError) Error() string {
 	return fmt.Sprintf(
-		`task: maximum task call exceeded (%d) for task %q: probably an cyclic dep or infinite loop`,
+		`task: maximum task call exceeded (%d) for task "%s": probably an cyclic dep or infinite loop`,
 		MaximumTaskCall,
 		e.task,
 	)
diff --git a/init.go b/init.go
index f3ec2aa8..0521af6b 100644
--- a/init.go
+++ b/init.go
@@ -30,7 +30,7 @@ func InitTaskfile(w io.Writer, dir string) error {
 		return ErrTaskfileAlreadyExists
 	}
 
-	if err := os.WriteFile(f, []byte(defaultTaskfile), 0o644); err != nil {
+	if err := os.WriteFile(f, []byte(defaultTaskfile), 0644); err != nil {
 		return err
 	}
 	fmt.Fprintf(w, "Taskfile.yaml created in the current directory\n")
diff --git a/internal/execext/exec.go b/internal/execext/exec.go
index d696bc25..d7107ca1 100644
--- a/internal/execext/exec.go
+++ b/internal/execext/exec.go
@@ -73,7 +73,7 @@ func IsExitError(err error) bool {
 // if available.
 func Expand(s string) (string, error) {
 	s = filepath.ToSlash(s)
-	s = strings.ReplaceAll(s, " ", `\ `)
+	s = strings.Replace(s, " ", `\ `, -1)
 	fields, err := shell.Fields(s, nil)
 	if err != nil {
 		return "", err
diff --git a/internal/status/checksum.go b/internal/status/checksum.go
index 6a03e147..17163a99 100644
--- a/internal/status/checksum.go
+++ b/internal/status/checksum.go
@@ -45,8 +45,8 @@ func (c *Checksum) IsUpToDate() (bool, error) {
 	}
 
 	if !c.Dry {
-		_ = os.MkdirAll(filepathext.SmartJoin(c.TempDir, "checksum"), 0o755)
-		if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0o644); err != nil {
+		_ = os.MkdirAll(filepathext.SmartJoin(c.TempDir, "checksum"), 0755)
+		if err = os.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
 			return false, err
 		}
 	}
diff --git a/internal/status/glob.go b/internal/status/glob.go
index 7d1ca1f0..d04214be 100644
--- a/internal/status/glob.go
+++ b/internal/status/glob.go
@@ -32,7 +32,7 @@ func Glob(dir string, g string) ([]string, error) {
 		return nil, err
 	}
 
-	fs, err := zglob.Glob(g)
+	fs, err := zglob.GlobFollowSymlinks(g)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/templater/funcs.go b/internal/templater/funcs.go
index 8cbe96bb..20e9ed9b 100644
--- a/internal/templater/funcs.go
+++ b/internal/templater/funcs.go
@@ -19,11 +19,11 @@ func init() {
 		"OS":   func() string { return runtime.GOOS },
 		"ARCH": func() string { return runtime.GOARCH },
 		"catLines": func(s string) string {
-			s = strings.ReplaceAll(s, "\r\n", " ")
-			return strings.ReplaceAll(s, "\n", " ")
+			s = strings.Replace(s, "\r\n", " ", -1)
+			return strings.Replace(s, "\n", " ", -1)
 		},
 		"splitLines": func(s string) []string {
-			s = strings.ReplaceAll(s, "\r\n", "\n")
+			s = strings.Replace(s, "\r\n", "\n", -1)
 			return strings.Split(s, "\n")
 		},
 		"fromSlash": func(path string) string {
diff --git a/signals.go b/signals.go
index c38e1292..09a23c04 100644
--- a/signals.go
+++ b/signals.go
@@ -9,7 +9,7 @@ import (
 )
 
 // NOTE(@andreynering): This function intercepts SIGINT and SIGTERM signals
-// so the Task process is not killed immediately and processes running have
+// so the Task process is not killed immediatelly and processes running have
 // time to do cleanup work.
 func (e *Executor) InterceptInterruptSignals() {
 	ch := make(chan os.Signal, 3)
diff --git a/task.go b/task.go
index 02c8e383..90991c30 100644
--- a/task.go
+++ b/task.go
@@ -181,7 +181,7 @@ func (e *Executor) mkdir(t *taskfile.Task) error {
 	defer mutex.Unlock()
 
 	if _, err := os.Stat(t.Dir); os.IsNotExist(err) {
-		if err := os.MkdirAll(t.Dir, 0o755); err != nil {
+		if err := os.MkdirAll(t.Dir, 0755); err != nil {
 			return err
 		}
 	}


Test patch:
diff --git a/task_test.go b/task_test.go
index cd6b8b1c..bef7b22f 100644
--- a/task_test.go
+++ b/task_test.go
@@ -970,7 +970,7 @@ func TestSummary(t *testing.T) {
 
 	expectedOutput := string(data)
 	if runtime.GOOS == "windows" {
-		expectedOutput = strings.ReplaceAll(expectedOutput, "\r\n", "\n")
+		expectedOutput = strings.Replace(expectedOutput, "\r\n", "\n", -1)
 	}
 
 	assert.Equal(t, expectedOutput, buff.String())
@@ -1338,3 +1338,36 @@ func TestErrorCode(t *testing.T) {
 	assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
 	assert.Equal(t, 42, casted.ExitCode(), "unexpected exit code from task")
 }
+
+func TestEvaluateSymlinksInPaths(t *testing.T) {
+	const dir = "testdata/evaluate_symlinks_in_paths"
+	var buff bytes.Buffer
+	e := &task.Executor{
+		Dir:    dir,
+		Stdout: &buff,
+		Stderr: &buff,
+		Silent: false,
+	}
+	assert.NoError(t, e.Setup())
+	err := e.Run(context.Background(), taskfile.Call{Task: "default"})
+	assert.NoError(t, err)
+	assert.NotEqual(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
+	buff.Reset()
+	err = e.Run(context.Background(), taskfile.Call{Task: "test-sym"})
+	assert.NoError(t, err)
+	assert.NotEqual(t, `task: Task "test-sym" is up to date`, strings.TrimSpace(buff.String()))
+	buff.Reset()
+	err = e.Run(context.Background(), taskfile.Call{Task: "default"})
+	assert.NoError(t, err)
+	assert.NotEqual(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
+	buff.Reset()
+	err = e.Run(context.Background(), taskfile.Call{Task: "default"})
+	assert.NoError(t, err)
+	assert.Equal(t, `task: Task "default" is up to date`, strings.TrimSpace(buff.String()))
+	buff.Reset()
+	err = e.Run(context.Background(), taskfile.Call{Task: "reset"})
+	assert.NoError(t, err)
+	buff.Reset()
+	err = os.RemoveAll(dir + "/.task")
+	assert.NoError(t, err)
+}
diff --git a/testdata/evaluate_symlinks_in_paths/Taskfile.yaml b/testdata/evaluate_symlinks_in_paths/Taskfile.yaml
new file mode 100644
index 00000000..4940c9af
--- /dev/null
+++ b/testdata/evaluate_symlinks_in_paths/Taskfile.yaml
@@ -0,0 +1,14 @@
+version: '3'
+tasks:
+  default:
+    sources:
+      - src/**/*
+    cmds:
+      - echo "some job"
+  test-sym:
+    cmds:
+      - echo "shared file source changed" > src/shared/b
+  reset:
+    cmds:
+      - echo "shared file source" > src/shared/b
+      - echo "file source" > src/a
diff --git a/testdata/evaluate_symlinks_in_paths/shared/b b/testdata/evaluate_symlinks_in_paths/shared/b
new file mode 100644
index 00000000..41a3e917
--- /dev/null
+++ b/testdata/evaluate_symlinks_in_paths/shared/b
@@ -0,0 +1,1 @@
+shared file source
diff --git a/testdata/evaluate_symlinks_in_paths/shared/inner_shared/c b/testdata/evaluate_symlinks_in_paths/shared/inner_shared/c
new file mode 100644
index 00000000..a55874db
--- /dev/null
+++ b/testdata/evaluate_symlinks_in_paths/shared/inner_shared/c
@@ -0,0 +1,1 @@
+inner shared file source
diff --git a/testdata/evaluate_symlinks_in_paths/src/a b/testdata/evaluate_symlinks_in_paths/src/a
new file mode 100644
index 00000000..526a0fc7
--- /dev/null
+++ b/testdata/evaluate_symlinks_in_paths/src/a
@@ -0,0 +1,1 @@
+file source
diff --git a/testdata/evaluate_symlinks_in_paths/src/shared b/testdata/evaluate_symlinks_in_paths/src/shared
new file mode 120000
index 00000000..8fba6b66
--- /dev/null
+++ b/testdata/evaluate_symlinks_in_paths/src/shared
@@ -0,0 +1,1 @@
+../shared
\ No newline at end of file