Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion lib/copy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<EOF
$(find . -path "./$pattern" -type f 2>/dev/null || true)
$_find_results
EOF
else
# Use native Bash glob expansion (supports ** if available)
Expand Down
57 changes: 57 additions & 0 deletions tests/copy_safety.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
}