Skip to content

Commit 1fea6ed

Browse files
committed
shallow copy
1 parent 17db3ed commit 1fea6ed

File tree

3 files changed

+59
-27
lines changed

3 files changed

+59
-27
lines changed

internal/exec/copy_glob.go

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -234,14 +234,20 @@ func copyDirRecursiveWithPrefix(srcDir, dstDir, globalBase, prefix string, exclu
234234

235235
// getMatchesForPattern returns files/directories matching a pattern relative to sourceDir.
236236
// If no matches are found, it logs a debug message and returns an empty slice.
237-
// When the pattern ends with "/*", it retries with a recursive "/**" variant.
237+
// For patterns ending with "/*" (shallow copy indicator) the function does not fallback to a recursive variant.
238238
func getMatchesForPattern(sourceDir, pattern string) ([]string, error) {
239239
fullPattern := filepath.Join(sourceDir, pattern)
240240
matches, err := u.GetGlobMatches(fullPattern)
241241
if err != nil {
242242
return nil, fmt.Errorf("error getting glob matches for %q: %w", fullPattern, err)
243243
}
244244
if len(matches) == 0 {
245+
// If the pattern ends with "/*" (and not "/**"), do not fallback.
246+
if strings.HasSuffix(pattern, "/*") && !strings.HasSuffix(pattern, "/**") {
247+
l.Debug("No matches found for shallow pattern; target directory will be empty", "pattern", fullPattern)
248+
return []string{}, nil
249+
}
250+
// Fallback for patterns ending with "/*" (non-shallow) or others.
245251
if strings.HasSuffix(pattern, "/*") {
246252
recursivePattern := strings.TrimSuffix(pattern, "/*") + "/**"
247253
fullRecursivePattern := filepath.Join(sourceDir, recursivePattern)
@@ -287,25 +293,25 @@ func copyToTargetWithPatterns(
287293
return cp.Copy(sourceDir, targetPath)
288294
}
289295

290-
// If inclusion patterns are provided, use them to determine which files to copy.
291-
if len(s.IncludedPaths) > 0 {
292-
filesToCopy := make(map[string]struct{})
293-
for _, pattern := range s.IncludedPaths {
294-
matches, err := getMatchesForPattern(sourceDir, pattern)
295-
if err != nil {
296-
l.Debug("Warning: error getting matches for pattern", "pattern", pattern, "error", err)
297-
continue
298-
}
299-
for _, match := range matches {
300-
filesToCopy[match] = struct{}{}
301-
}
296+
// If inclusion patterns are provided, process each pattern individually.
297+
for _, pattern := range s.IncludedPaths {
298+
// Determine if the pattern indicates shallow copy.
299+
shallow := false
300+
if strings.HasSuffix(pattern, "/*") && !strings.HasSuffix(pattern, "/**") {
301+
shallow = true
302+
}
303+
304+
matches, err := getMatchesForPattern(sourceDir, pattern)
305+
if err != nil {
306+
l.Debug("Warning: error getting matches for pattern", "pattern", pattern, "error", err)
307+
continue
302308
}
303-
if len(filesToCopy) == 0 {
304-
l.Debug("No files matched the inclusion patterns - target directory will be empty")
305-
return nil
309+
if len(matches) == 0 {
310+
l.Debug("No files matched the inclusion pattern", "pattern", pattern)
311+
continue
306312
}
307-
for file := range filesToCopy {
308-
// Retrieve file information early so that we can adjust exclusion checks if this is a directory.
313+
for _, file := range matches {
314+
// Retrieve file information.
309315
info, err := os.Stat(file)
310316
if err != nil {
311317
return fmt.Errorf("stating file %q: %w", file, err)
@@ -315,8 +321,9 @@ func copyToTargetWithPatterns(
315321
return fmt.Errorf("computing relative path for %q: %w", file, err)
316322
}
317323
relPath = filepath.ToSlash(relPath)
324+
325+
// Check exclusion patterns (for directories, try both plain and trailing slash).
318326
skip := false
319-
// For directories, check both the plain relative path and with a trailing slash.
320327
for _, ex := range s.ExcludedPaths {
321328
if info.IsDir() {
322329
matched, err := u.PathMatch(ex, relPath)
@@ -327,7 +334,6 @@ func copyToTargetWithPatterns(
327334
skip = true
328335
break
329336
}
330-
// Also try matching with a trailing slash.
331337
matched, err = u.PathMatch(ex, relPath+"/")
332338
if err != nil {
333339
l.Debug("Error matching exclusion pattern with trailing slash", "pattern", ex, "path", relPath+"/", "error", err)
@@ -337,7 +343,6 @@ func copyToTargetWithPatterns(
337343
break
338344
}
339345
} else {
340-
// For files, just check the plain relative path.
341346
matched, err := u.PathMatch(ex, relPath)
342347
if err != nil {
343348
l.Debug("Error matching exclusion pattern", "pattern", ex, "path", relPath, "error", err)
@@ -355,19 +360,26 @@ func copyToTargetWithPatterns(
355360
// Build the destination path.
356361
dstPath := filepath.Join(targetPath, relPath)
357362
if info.IsDir() {
358-
// Instead of resetting the base for relative paths,
359-
// use the new recursive function that preserves the global relative path.
360-
if err := copyDirRecursiveWithPrefix(file, dstPath, sourceDir, relPath, s.ExcludedPaths); err != nil {
361-
return err
363+
if shallow {
364+
// Use shallow copy: copy only immediate file entries.
365+
l.Debug("Directory is not copied becasue it is a shallow copy", "directory", relPath)
366+
} else {
367+
// Use the existing recursive copy with prefix.
368+
if err := copyDirRecursiveWithPrefix(file, dstPath, sourceDir, relPath, s.ExcludedPaths); err != nil {
369+
return err
370+
}
362371
}
363372
} else {
364373
if err := copyFile(file, dstPath); err != nil {
365374
return err
366375
}
367376
}
368377
}
369-
} else {
370-
// No inclusion patterns defined; copy everything except those matching excluded items.
378+
}
379+
380+
// If no inclusion patterns are defined; copy everything except those matching excluded items.
381+
// (This branch is preserved from the original logic.)
382+
if len(s.IncludedPaths) == 0 {
371383
if err := copyDirRecursive(sourceDir, targetPath, sourceDir, s.ExcludedPaths, s.IncludedPaths); err != nil {
372384
return fmt.Errorf("error copying from %q to %q: %w", sourceDir, targetPath, err)
373385
}

tests/fixtures/scenarios/vendor/vendor.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,16 @@ spec:
7272
- "components/library/"
7373
tags:
7474
- demo
75+
76+
- component: "test shallow globs and folder exclusion"
77+
source: "github.com/cloudposse/atmos.git"
78+
included_paths:
79+
- "**/demo-localstack/*"
80+
- "**/demo-library/**"
81+
excluded_paths:
82+
- "**/demo-library/**/stargazers/**"
83+
- "**/demo-library/**/*.tf"
84+
targets:
85+
- "components/globs/"
86+
tags:
87+
- demo

tests/test-cases/demo-stacks.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,11 @@ tests:
193193
- "./components/library/examples/demo-stacks/components/terraform/myapp/variables.tf"
194194
- "./components/library/examples/demo-stacks/components/terraform/myapp/versions.tf"
195195
- "./components/library/weather/README.md"
196+
- "./components/globs/examples/demo-library/ipinfo/README.md"
197+
- "./components/globs/examples/demo-library/weather/README.md"
198+
- "./components/globs/examples/demo-library/README.md"
199+
- "./components/globs/examples/demo-localstack/.gitignore"
200+
- "./components/globs/examples/demo-localstack/atmos.yaml"
201+
- "./components/globs/examples/demo-localstack/docker-compose.yml"
202+
- "./components/globs/examples/demo-localstack/README.md"
196203
exit_code: 0

0 commit comments

Comments
 (0)