@@ -75,6 +75,41 @@ merge_copy_patterns() {
7575 fi
7676}
7777
78+ # Copy a directory using CoW (copy-on-write) when available, falling back to standard cp.
79+ # macOS APFS: cp -cRP (clone); Linux Btrfs/XFS: cp --reflink=auto -RP
80+ # Callers must guard the return value with `if` or `|| true` (set -e safe).
81+ # Usage: _fast_copy_dir src dest
82+ # Cached OS value for _fast_copy_dir; set on first call.
83+ _fast_copy_os=" "
84+
85+ _fast_copy_dir () {
86+ local src=" $1 " dest=" $2 "
87+ if [ -z " $_fast_copy_os " ]; then
88+ _fast_copy_os=$( detect_os)
89+ fi
90+ local os=" $_fast_copy_os "
91+
92+ case " $os " in
93+ darwin)
94+ # Try CoW clone first; if unsupported, fall back to regular copy
95+ if cp -cRP " $src " " $dest " 2> /dev/null; then
96+ return 0
97+ fi
98+ # Clean up any partial clone output before fallback
99+ local _clone_target
100+ _clone_target=" ${dest%/ } /$( basename " $src " ) "
101+ if [ -e " $_clone_target " ]; then rm -rf " $_clone_target " ; fi
102+ cp -RP " $src " " $dest "
103+ ;;
104+ linux)
105+ cp --reflink=auto -RP " $src " " $dest "
106+ ;;
107+ * )
108+ cp -RP " $src " " $dest "
109+ ;;
110+ esac
111+ }
112+
78113# Copy a single file to destination, handling exclusion, path preservation, and dry-run
79114# Usage: _copy_pattern_file file dst_root excludes preserve_paths dry_run
80115# Returns: 0 if file was copied (or would be in dry-run), 1 if skipped/failed
@@ -295,6 +330,13 @@ copy_directories() {
295330
296331 # Find directories matching the pattern
297332 # Use -path for patterns with slashes (e.g., vendor/bundle), -name for basenames
333+ # Note: case inside $() inside heredocs breaks Bash 3.2, so compute first
334+ local find_results
335+ case " $pattern " in
336+ * /* ) find_results=$( find . -type d -path " ./$pattern " 2> /dev/null) ;;
337+ * ) find_results=$( find . -type d -name " $pattern " 2> /dev/null) ;;
338+ esac
339+
298340 while IFS= read -r dir_path; do
299341 [ -z " $dir_path " ] && continue
300342 dir_path=" ${dir_path# ./ } "
@@ -307,16 +349,16 @@ copy_directories() {
307349 dest_parent=$( dirname " $dest_dir " )
308350 mkdir -p " $dest_parent "
309351
310- # Copy directory (cp -RP preserves symlinks as symlinks)
311- if cp -RP " $dir_path " " $dest_parent /" 2> /dev/null ; then
352+ # Copy directory using CoW when available ( preserves symlinks as symlinks)
353+ if _fast_copy_dir " $dir_path " " $dest_parent /" ; then
312354 log_info " Copied directory $dir_path "
313355 copied_count=$(( copied_count + 1 ))
314356 _apply_directory_excludes " $dest_parent " " $dir_path " " $excludes "
315357 else
316358 log_warn " Failed to copy directory $dir_path "
317359 fi
318360 done << EOF
319- $( case " $pattern " in * / * ) find . -type d -path "./ $pattern " 2>/dev/null ;; *) find . -type d -name " $pattern " 2>/dev/null ;; esac)
361+ $find_results
320362EOF
321363 done << EOF
322364$dir_patterns
0 commit comments