Skip to content

Commit e806b64

Browse files
arwtyxouymzclaude
andcommitted
perf: use CoW (copy-on-write) cloning for directory copies
Add _fast_copy_dir() that leverages filesystem-level cloning (macOS APFS cp -c, Linux cp --reflink=auto) for near-instant directory copies on supported filesystems, with automatic fallback to standard cp -RP on ext4/NTFS/others. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4858fb6 commit e806b64

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

lib/copy.sh

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,32 @@ 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+
_fast_copy_dir() {
83+
local src="$1" dest="$2"
84+
local os
85+
os=$(detect_os)
86+
87+
case "$os" in
88+
darwin)
89+
# Try CoW clone first; if unsupported, fall back to regular copy
90+
if cp -cRP "$src" "$dest" 2>/dev/null; then
91+
return 0
92+
fi
93+
cp -RP "$src" "$dest"
94+
;;
95+
linux)
96+
cp --reflink=auto -RP "$src" "$dest"
97+
;;
98+
*)
99+
cp -RP "$src" "$dest"
100+
;;
101+
esac
102+
}
103+
78104
# Copy a single file to destination, handling exclusion, path preservation, and dry-run
79105
# Usage: _copy_pattern_file file dst_root excludes preserve_paths dry_run
80106
# Returns: 0 if file was copied (or would be in dry-run), 1 if skipped/failed
@@ -307,8 +333,8 @@ copy_directories() {
307333
dest_parent=$(dirname "$dest_dir")
308334
mkdir -p "$dest_parent"
309335

310-
# Copy directory (cp -RP preserves symlinks as symlinks)
311-
if cp -RP "$dir_path" "$dest_parent/" 2>/dev/null; then
336+
# Copy directory using CoW when available (preserves symlinks as symlinks)
337+
if _fast_copy_dir "$dir_path" "$dest_parent/"; then
312338
log_info "Copied directory $dir_path"
313339
copied_count=$((copied_count + 1))
314340
_apply_directory_excludes "$dest_parent" "$dir_path" "$excludes"

tests/copy_safety.bats

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
setup() {
44
load test_helper
5+
source "$PROJECT_ROOT/lib/platform.sh"
56
source "$PROJECT_ROOT/lib/copy.sh"
67
}
78

@@ -82,3 +83,41 @@ setup() {
8283
excludes=$(printf '%s\n' "*.log" "dist/*")
8384
! is_excluded "src/app.js" "$excludes"
8485
}
86+
87+
# --- _fast_copy_dir tests ---
88+
89+
@test "_fast_copy_dir copies directory contents" {
90+
local src dst
91+
src=$(mktemp -d)
92+
dst=$(mktemp -d)
93+
mkdir -p "$src/mydir/sub"
94+
echo "hello" > "$src/mydir/sub/file.txt"
95+
96+
_fast_copy_dir "$src/mydir" "$dst/"
97+
98+
[ -f "$dst/mydir/sub/file.txt" ]
99+
[ "$(cat "$dst/mydir/sub/file.txt")" = "hello" ]
100+
rm -rf "$src" "$dst"
101+
}
102+
103+
@test "_fast_copy_dir preserves symlinks" {
104+
local src dst
105+
src=$(mktemp -d)
106+
dst=$(mktemp -d)
107+
mkdir -p "$src/mydir"
108+
echo "target" > "$src/mydir/real.txt"
109+
ln -s real.txt "$src/mydir/link.txt"
110+
111+
_fast_copy_dir "$src/mydir" "$dst/"
112+
113+
[ -L "$dst/mydir/link.txt" ]
114+
[ "$(readlink "$dst/mydir/link.txt")" = "real.txt" ]
115+
rm -rf "$src" "$dst"
116+
}
117+
118+
@test "_fast_copy_dir fails on nonexistent source" {
119+
local dst
120+
dst=$(mktemp -d)
121+
! _fast_copy_dir "/nonexistent/path" "$dst/"
122+
rm -rf "$dst"
123+
}

0 commit comments

Comments
 (0)