diff --git a/lib/copy.sh b/lib/copy.sh index 2a17b58..7c335db 100644 --- a/lib/copy.sh +++ b/lib/copy.sh @@ -163,12 +163,32 @@ _expand_and_copy_pattern() { if [ "$have_globstar" -eq 0 ] && echo "$pattern" | grep -q '\*\*'; then # Fallback to find for ** patterns on Bash 3.2 + # find -path doesn't treat ** as recursive glob; it's just a wildcard that + # won't match across the required '/' separator. For **/-prefixed patterns, + # also search with the suffix alone so root-level files are found. + local _find_results + _find_results=$(find . -path "./$pattern" -type f 2>/dev/null || true) + case "$pattern" in + \*\*/*) + local _suffix="${pattern#\*\*/}" + local _root_results + _root_results=$(find . -maxdepth 1 -path "./$_suffix" -type f 2>/dev/null || true) + if [ -n "$_root_results" ]; then + if [ -n "$_find_results" ]; then + _find_results="$_find_results"$'\n'"$_root_results" + else + _find_results="$_root_results" + fi + fi + ;; + esac while IFS= read -r file; do + [ -z "$file" ] && continue if _copy_pattern_file "$file" "$dst_root" "$excludes" "$preserve_paths" "$dry_run"; then count=$((count + 1)) fi done </dev/null || true) +$_find_results EOF else # Use native Bash glob expansion (supports ** if available) diff --git a/tests/copy_safety.bats b/tests/copy_safety.bats index 377045e..de31b87 100644 --- a/tests/copy_safety.bats +++ b/tests/copy_safety.bats @@ -124,3 +124,60 @@ teardown() { _test_tmpdir=$(mktemp -d) ! _fast_copy_dir "/nonexistent/path" "$_test_tmpdir/" } + +# --- _expand_and_copy_pattern find-fallback tests --- +# These test the Bash 3.2 fallback path (have_globstar=0) + +@test "find fallback: empty results don't cause failures" { + _test_tmpdir=$(mktemp -d) + local src="$_test_tmpdir/src" dst="$_test_tmpdir/dst" + mkdir -p "$src" "$dst" + + cd "$src" + local count + count=$(_expand_and_copy_pattern "**/.nonexistent*" "$dst" "" "true" "false" "0") + [ "$count" -eq 0 ] +} + +@test "find fallback: **/ pattern matches root-level files" { + _test_tmpdir=$(mktemp -d) + local src="$_test_tmpdir/src" dst="$_test_tmpdir/dst" + mkdir -p "$src" "$dst" + echo "secret" > "$src/.env" + echo "local" > "$src/.env.local" + + cd "$src" + local count + count=$(_expand_and_copy_pattern "**/.env*" "$dst" "" "true" "false" "0") + [ "$count" -eq 2 ] + [ -f "$dst/.env" ] + [ -f "$dst/.env.local" ] +} + +@test "find fallback: **/ pattern matches nested files" { + _test_tmpdir=$(mktemp -d) + local src="$_test_tmpdir/src" dst="$_test_tmpdir/dst" + mkdir -p "$src/subdir" "$dst" + echo "nested" > "$src/subdir/.env" + + cd "$src" + local count + count=$(_expand_and_copy_pattern "**/.env" "$dst" "" "true" "false" "0") + [ "$count" -eq 1 ] + [ -f "$dst/subdir/.env" ] +} + +@test "find fallback: **/ pattern matches both root and nested files" { + _test_tmpdir=$(mktemp -d) + local src="$_test_tmpdir/src" dst="$_test_tmpdir/dst" + mkdir -p "$src/config" "$dst" + echo "root" > "$src/CLAUDE.md" + echo "nested" > "$src/config/CLAUDE.md" + + cd "$src" + local count + count=$(_expand_and_copy_pattern "**/CLAUDE.md" "$dst" "" "true" "false" "0") + [ "$count" -eq 2 ] + [ -f "$dst/CLAUDE.md" ] + [ -f "$dst/config/CLAUDE.md" ] +}