Skip to content

Commit c74f843

Browse files
authored
fix(copy): match root-level files for ** glob patterns on Bash 3.2 (#133)
1 parent 60d3bf2 commit c74f843

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

lib/copy.sh

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,32 @@ _expand_and_copy_pattern() {
163163

164164
if [ "$have_globstar" -eq 0 ] && echo "$pattern" | grep -q '\*\*'; then
165165
# Fallback to find for ** patterns on Bash 3.2
166+
# find -path doesn't treat ** as recursive glob; it's just a wildcard that
167+
# won't match across the required '/' separator. For **/-prefixed patterns,
168+
# also search with the suffix alone so root-level files are found.
169+
local _find_results
170+
_find_results=$(find . -path "./$pattern" -type f 2>/dev/null || true)
171+
case "$pattern" in
172+
\*\*/*)
173+
local _suffix="${pattern#\*\*/}"
174+
local _root_results
175+
_root_results=$(find . -maxdepth 1 -path "./$_suffix" -type f 2>/dev/null || true)
176+
if [ -n "$_root_results" ]; then
177+
if [ -n "$_find_results" ]; then
178+
_find_results="$_find_results"$'\n'"$_root_results"
179+
else
180+
_find_results="$_root_results"
181+
fi
182+
fi
183+
;;
184+
esac
166185
while IFS= read -r file; do
186+
[ -z "$file" ] && continue
167187
if _copy_pattern_file "$file" "$dst_root" "$excludes" "$preserve_paths" "$dry_run"; then
168188
count=$((count + 1))
169189
fi
170190
done <<EOF
171-
$(find . -path "./$pattern" -type f 2>/dev/null || true)
191+
$_find_results
172192
EOF
173193
else
174194
# Use native Bash glob expansion (supports ** if available)

tests/copy_safety.bats

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,60 @@ teardown() {
124124
_test_tmpdir=$(mktemp -d)
125125
! _fast_copy_dir "/nonexistent/path" "$_test_tmpdir/"
126126
}
127+
128+
# --- _expand_and_copy_pattern find-fallback tests ---
129+
# These test the Bash 3.2 fallback path (have_globstar=0)
130+
131+
@test "find fallback: empty results don't cause failures" {
132+
_test_tmpdir=$(mktemp -d)
133+
local src="$_test_tmpdir/src" dst="$_test_tmpdir/dst"
134+
mkdir -p "$src" "$dst"
135+
136+
cd "$src"
137+
local count
138+
count=$(_expand_and_copy_pattern "**/.nonexistent*" "$dst" "" "true" "false" "0")
139+
[ "$count" -eq 0 ]
140+
}
141+
142+
@test "find fallback: **/ pattern matches root-level files" {
143+
_test_tmpdir=$(mktemp -d)
144+
local src="$_test_tmpdir/src" dst="$_test_tmpdir/dst"
145+
mkdir -p "$src" "$dst"
146+
echo "secret" > "$src/.env"
147+
echo "local" > "$src/.env.local"
148+
149+
cd "$src"
150+
local count
151+
count=$(_expand_and_copy_pattern "**/.env*" "$dst" "" "true" "false" "0")
152+
[ "$count" -eq 2 ]
153+
[ -f "$dst/.env" ]
154+
[ -f "$dst/.env.local" ]
155+
}
156+
157+
@test "find fallback: **/ pattern matches nested files" {
158+
_test_tmpdir=$(mktemp -d)
159+
local src="$_test_tmpdir/src" dst="$_test_tmpdir/dst"
160+
mkdir -p "$src/subdir" "$dst"
161+
echo "nested" > "$src/subdir/.env"
162+
163+
cd "$src"
164+
local count
165+
count=$(_expand_and_copy_pattern "**/.env" "$dst" "" "true" "false" "0")
166+
[ "$count" -eq 1 ]
167+
[ -f "$dst/subdir/.env" ]
168+
}
169+
170+
@test "find fallback: **/ pattern matches both root and nested files" {
171+
_test_tmpdir=$(mktemp -d)
172+
local src="$_test_tmpdir/src" dst="$_test_tmpdir/dst"
173+
mkdir -p "$src/config" "$dst"
174+
echo "root" > "$src/CLAUDE.md"
175+
echo "nested" > "$src/config/CLAUDE.md"
176+
177+
cd "$src"
178+
local count
179+
count=$(_expand_and_copy_pattern "**/CLAUDE.md" "$dst" "" "true" "false" "0")
180+
[ "$count" -eq 2 ]
181+
[ -f "$dst/CLAUDE.md" ]
182+
[ -f "$dst/config/CLAUDE.md" ]
183+
}

0 commit comments

Comments
 (0)