Repository: gruntwork-io/terragrunt (language: go)

Issue:
breaking change in 0.73.1 for terragrunt stack generate of local units using ${get_repo_root()}
## Describe the bug

terragrunt stack generate of local units using ${get_repo_root()} failed with error of 
```
15:05:40.754 ERROR  destination exists and is not a symlink
15:05:40.755 TRACE  *errors.errorString destination exists and is not a symlink
github.com/gruntwork-io/terragrunt/cli/commands/stack/generate.go:78 (0x102132481)
github.com/gruntwork-io/terragrunt/cli/commands/stack/generate.go:32 (0x102131f2c)
github.com/gruntwork-io/terragrunt/cli/commands/stack/action.go:29 (0x102130bac)
github.com/gruntwork-io/terragrunt/cli/commands/stack/command.go:68 (0x102131dac)
github.com/gruntwork-io/terragrunt/cli/app.go:335 (0x10251cae8)
golang.org/x/sync@v0.11.0/errgroup/errgroup.go:78 (0x101adb364)
runtime/asm_arm64.s:1223 (0x100b981b4)
```

## Steps To Reproduce

terragrunt --experiment stacks stack generate

```hcl
unit "net" {
  source = "${get_repo_root()}/units/net"
  #source = "../../../units/net"  --> this works, but not with get_repo_root
  path = "net"
}
```

## Expected behavior

properly generating unit..
15:00:38.853 INFO   Generating stack from ./terragrunt.stack.hcl
15:00:38.913 INFO   Processing unit net


## Versions

working version - 0.73.0
failing version - 0.73.1

issue likely introduced from this PR/commit -
https://github.com/gruntwork-io/terragrunt/pull/3865 
https://github.com/gruntwork-io/terragrunt/commit/cfdc2300d96dff5d0bd1cc0dcb4b3daf4af1d5a3


## Additional context

Add any other context about the problem here.



Discussion / hints:

Fix patch:
diff --git a/cli/commands/stack/generate.go b/cli/commands/stack/generate.go
index ffa9f2383e..a1e3872efd 100644
--- a/cli/commands/stack/generate.go
+++ b/cli/commands/stack/generate.go
@@ -85,6 +85,10 @@ func processStackFile(ctx context.Context, opts *options.TerragruntOptions, stac
 
 func isLocal(opts *options.TerragruntOptions, src string) bool {
 	// check initially if the source is a local file
+	if util.FileExists(src) {
+		return true
+	}
+
 	src = filepath.Join(opts.WorkingDir, src)
 	if util.FileExists(src) {
 		return true


Test patch:
diff --git a/test/fixtures/stacks/locals/terragrunt.stack.hcl b/test/fixtures/stacks/locals/terragrunt.stack.hcl
index e5175d0e98..0dab45f739 100644
--- a/test/fixtures/stacks/locals/terragrunt.stack.hcl
+++ b/test/fixtures/stacks/locals/terragrunt.stack.hcl
@@ -1,6 +1,7 @@
 locals {
 	chicken = "units/chicken"
 	chick = "units/chick"
+	repo_path = "${get_repo_root()}"
 }
 
 unit "mother" {
@@ -14,12 +15,12 @@ unit "father" {
 }
 
 unit "chick_1" {
-	source = local.chick
+	source = "${local.chick}"
 	path   = "chicks/chick-1"
 }
 
 unit "chick_2" {
-	source = local.chick
+	source = "${local.repo_path}/fixtures/stacks/locals/${local.chick}"
 	path   = "chicks/chick-2"
 }
 
diff --git a/test/helpers/package.go b/test/helpers/package.go
index 4367cffeec..baf08c70f2 100644
--- a/test/helpers/package.go
+++ b/test/helpers/package.go
@@ -19,6 +19,7 @@ import (
 	"net"
 	"net/http"
 	"net/url"
+	"os/exec"
 	"regexp"
 	"sort"
 	"strconv"
@@ -911,3 +912,10 @@ func IsTerragruntProviderCacheEnabled(t *testing.T) bool {
 
 	return false
 }
+
+func CreateGitRepo(t *testing.T, dir string) {
+	t.Helper()
+
+	commandOutput, err := exec.Command("git", "init", dir).CombinedOutput()
+	require.NoErrorf(t, err, "Error initializing git repo: %v\n%s", err, string(commandOutput))
+}
diff --git a/test/integration_destroy_test.go b/test/integration_destroy_test.go
index bbbd21f6af..e2d2d75ead 100644
--- a/test/integration_destroy_test.go
+++ b/test/integration_destroy_test.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"os/exec"
 	"path/filepath"
 	"strings"
 	"testing"
@@ -86,10 +85,7 @@ func TestDestroyDependentModule(t *testing.T) {
 	tmpEnvPath, _ := filepath.EvalSymlinks(helpers.CopyEnvironment(t, testFixtureDestroyDependentModule))
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureDestroyDependentModule)
 
-	commandOutput, err := exec.Command("git", "init", rootPath).CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error initializing git repo: %v\n%s", err, string(commandOutput))
-	}
+	helpers.CreateGitRepo(t, rootPath)
 	// apply each module in order
 	helpers.RunTerragrunt(t, "terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-working-dir "+util.JoinPath(rootPath, "a"))
 	helpers.RunTerragrunt(t, "terragrunt apply -auto-approve --terragrunt-non-interactive --terragrunt-working-dir "+util.JoinPath(rootPath, "b"))
@@ -102,7 +98,7 @@ func TestDestroyDependentModule(t *testing.T) {
 	stderr := bytes.Buffer{}
 
 	workingDir := util.JoinPath(rootPath, "c")
-	err = helpers.RunTerragruntCommand(t, "terragrunt destroy -auto-approve --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir "+workingDir, &stdout, &stderr)
+	err := helpers.RunTerragruntCommand(t, "terragrunt destroy -auto-approve --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir "+workingDir, &stdout, &stderr)
 	require.NoError(t, err)
 
 	output := stderr.String()
@@ -281,10 +277,7 @@ func TestTerragruntSkipConfirmExternalDependencies(t *testing.T) {
 	})
 	require.NoError(t, os.Mkdir(filepath.ToSlash("/tmp/external-46521694"), 0755))
 
-	output, err := exec.Command("git", "init", tmpEnvPath).CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error initializing git repo: %v\n%s", err, string(output))
-	}
+	helpers.CreateGitRepo(t, tmpEnvPath)
 
 	stdout := bytes.Buffer{}
 	stderr := bytes.Buffer{}
@@ -293,7 +286,7 @@ func TestTerragruntSkipConfirmExternalDependencies(t *testing.T) {
 	oldStdout := os.Stderr
 	os.Stderr = w
 
-	err = helpers.RunTerragruntCommand(t, "terragrunt destroy --terragrunt-working-dir "+testPath, &stdout, &stderr)
+	err := helpers.RunTerragruntCommand(t, "terragrunt destroy --terragrunt-working-dir "+testPath, &stdout, &stderr)
 	os.Stderr = oldStdout
 	require.NoError(t, w.Close())
 
diff --git a/test/integration_functions_test.go b/test/integration_functions_test.go
index 0936e7c81a..5d40d6b198 100644
--- a/test/integration_functions_test.go
+++ b/test/integration_functions_test.go
@@ -5,7 +5,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"os"
-	"os/exec"
 	"path"
 	"path/filepath"
 	"runtime"
@@ -178,10 +177,7 @@ func TestGetRepoRootCaching(t *testing.T) {
 	tmpEnvPath, _ := filepath.EvalSymlinks(helpers.CopyEnvironment(t, testFixtureGetRepoRoot))
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureGetRepoRoot)
 
-	gitOutput, err := exec.Command("git", "init", rootPath).CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error initializing git repo: %v\n%s", err, string(gitOutput))
-	}
+	helpers.CreateGitRepo(t, rootPath)
 
 	stdout, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run-all plan --terragrunt-non-interactive --terragrunt-log-level trace --terragrunt-working-dir "+rootPath)
 	require.NoError(t, err)
@@ -198,10 +194,7 @@ func TestGetRepoRoot(t *testing.T) {
 	tmpEnvPath, _ := filepath.EvalSymlinks(helpers.CopyEnvironment(t, testFixtureGetRepoRoot))
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureGetRepoRoot)
 
-	output, err := exec.Command("git", "init", rootPath).CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error initializing git repo: %v\n%s", err, string(output))
-	}
+	helpers.CreateGitRepo(t, rootPath)
 	helpers.RunTerragrunt(t, "terragrunt apply-all --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
 
 	// verify expected outputs are not empty
@@ -230,10 +223,7 @@ func TestGetWorkingDirBuiltInFunc(t *testing.T) {
 	tmpEnvPath, _ := filepath.EvalSymlinks(helpers.CopyEnvironment(t, testFixtureGetWorkingDir))
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureGetWorkingDir)
 
-	output, err := exec.Command("git", "init", rootPath).CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error initializing git repo: %v\n%s", err, string(output))
-	}
+	helpers.CreateGitRepo(t, rootPath)
 	helpers.RunTerragrunt(t, "terragrunt apply-all --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
 
 	// verify expected outputs are not empty
@@ -254,7 +244,7 @@ func TestGetWorkingDirBuiltInFunc(t *testing.T) {
 	expectedWorkingDir := filepath.Join(rootPath, util.TerragruntCacheDir)
 	curWalkStep := 0
 
-	err = filepath.Walk(expectedWorkingDir,
+	err := filepath.Walk(expectedWorkingDir,
 		func(path string, info os.FileInfo, err error) error {
 			if err != nil || !info.IsDir() {
 				return err
@@ -299,11 +289,7 @@ func TestPathRelativeFromInclude(t *testing.T) {
 	basePath := util.JoinPath(rootPath, "base")
 	clusterPath := util.JoinPath(rootPath, "cluster")
 
-	output, err := exec.Command("git", "init", tmpEnvPath).CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error initializing git repo: %v\n%s", err, string(output))
-	}
-
+	helpers.CreateGitRepo(t, tmpEnvPath)
 	helpers.RunTerragrunt(t, "terragrunt run-all apply -auto-approve --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
 
 	// verify expected outputs are not empty
@@ -331,11 +317,7 @@ func TestGetPathFromRepoRoot(t *testing.T) {
 	tmpEnvPath, _ := filepath.EvalSymlinks(helpers.CopyEnvironment(t, testFixtureGetPathFromRepoRoot))
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureGetPathFromRepoRoot)
 
-	output, err := exec.Command("git", "init", tmpEnvPath).CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error initializing git repo: %v\n%s", err, string(output))
-	}
-
+	helpers.CreateGitRepo(t, tmpEnvPath)
 	helpers.RunTerragrunt(t, "terragrunt apply-all --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
 
 	// verify expected outputs are not empty
@@ -364,10 +346,7 @@ func TestGetPathToRepoRoot(t *testing.T) {
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureGetPathToRepoRoot)
 	helpers.CleanupTerraformFolder(t, rootPath)
 
-	output, err := exec.Command("git", "init", tmpEnvPath).CombinedOutput()
-	if err != nil {
-		t.Fatalf("Error initializing git repo: %v\n%s", err, string(output))
-	}
+	helpers.CreateGitRepo(t, tmpEnvPath)
 	helpers.RunTerragrunt(t, "terragrunt apply-all --terragrunt-non-interactive --terragrunt-working-dir "+rootPath)
 
 	// verify expected outputs are not empty
diff --git a/test/integration_stacks_test.go b/test/integration_stacks_test.go
index 050594f317..81b449fa15 100644
--- a/test/integration_stacks_test.go
+++ b/test/integration_stacks_test.go
@@ -43,6 +43,7 @@ func TestStacksGenerateLocals(t *testing.T) {
 
 	helpers.CleanupTerraformFolder(t, testFixtureStacksLocals)
 	tmpEnvPath := helpers.CopyEnvironment(t, testFixtureStacksLocals)
+	helpers.CreateGitRepo(t, tmpEnvPath)
 	rootPath := util.JoinPath(tmpEnvPath, testFixtureStacksLocals)
 
 	helpers.RunTerragrunt(t, "terragrunt stack generate --experiment stacks --terragrunt-working-dir "+rootPath)
diff --git a/test/integration_strict_test.go b/test/integration_strict_test.go
index 8ff361c2b8..1d5624816d 100644
--- a/test/integration_strict_test.go
+++ b/test/integration_strict_test.go
@@ -28,7 +28,7 @@ func TestStrictMode(t *testing.T) {
 			name:           "plan-all",
 			controls:       []string{},
 			strictMode:     false,
-			expectedStderr: "The `plan-all` command is deprecated and will be removed in a future version. Use `terragrunt run-all plan` instead.",
+			expectedStderr: "The `plan-all` command is deprecated and will be removed in a future version of Terragrunt. Use `terragrunt run-all plan` instead.",
 			expectedError:  nil,
 		},
 		{