Skip to content

Commit 3405ab6

Browse files
authored
refactor(copy): extract is_excluded helper for pattern matching (#114)
Consolidates the exclude-checking loop that was duplicated 3 times in copy_patterns (find path + glob path) and copy_directories into a single is_excluded() function.
1 parent 3298796 commit 3405ab6

1 file changed

Lines changed: 26 additions & 67 deletions

File tree

lib/copy.sh

Lines changed: 26 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
#!/usr/bin/env bash
22
# File copying utilities with pattern matching
33

4+
# Check if a path matches any exclude pattern
5+
# Usage: is_excluded "path" "excludes_newline_separated"
6+
# Returns: 0 if excluded, 1 if not
7+
is_excluded() {
8+
local path="$1"
9+
local excludes="$2"
10+
11+
[ -z "$excludes" ] && return 1
12+
13+
while IFS= read -r exclude_pattern; do
14+
[ -z "$exclude_pattern" ] && continue
15+
# Intentional glob pattern matching for exclusion
16+
# shellcheck disable=SC2254
17+
case "$path" in
18+
$exclude_pattern) return 0 ;;
19+
esac
20+
done <<EOF
21+
$excludes
22+
EOF
23+
24+
return 1
25+
}
26+
427
# Parse .gitignore-style pattern file
528
# Usage: parse_pattern_file file_path
629
# Returns: newline-separated patterns (comments and empty lines stripped)
@@ -74,26 +97,8 @@ copy_patterns() {
7497
# Remove leading ./
7598
file="${file#./}"
7699

77-
# Check if file matches any exclude pattern
78-
local excluded=0
79-
if [ -n "$excludes" ]; then
80-
while IFS= read -r exclude_pattern; do
81-
[ -z "$exclude_pattern" ] && continue
82-
# Intentional glob pattern matching for file exclusion
83-
# shellcheck disable=SC2254
84-
case "$file" in
85-
$exclude_pattern)
86-
excluded=1
87-
break
88-
;;
89-
esac
90-
done <<EOF
91-
$excludes
92-
EOF
93-
fi
94-
95100
# Skip if excluded
96-
[ "$excluded" -eq 1 ] && continue
101+
is_excluded "$file" "$excludes" && continue
97102

98103
# Determine destination path
99104
local dest_file
@@ -132,26 +137,8 @@ EOF
132137
# Remove leading ./
133138
file="${file#./}"
134139

135-
# Check if file matches any exclude pattern
136-
local excluded=0
137-
if [ -n "$excludes" ]; then
138-
while IFS= read -r exclude_pattern; do
139-
[ -z "$exclude_pattern" ] && continue
140-
# Intentional glob pattern matching for file exclusion
141-
# shellcheck disable=SC2254
142-
case "$file" in
143-
$exclude_pattern)
144-
excluded=1
145-
break
146-
;;
147-
esac
148-
done <<EOF
149-
$excludes
150-
EOF
151-
fi
152-
153140
# Skip if excluded
154-
[ "$excluded" -eq 1 ] && continue
141+
is_excluded "$file" "$excludes" && continue
155142

156143
# Determine destination path
157144
local dest_file
@@ -243,36 +230,8 @@ copy_directories() {
243230
# Remove leading ./
244231
dir_path="${dir_path#./}"
245232

246-
# Check if directory matches any exclude pattern
247-
local excluded=0
248-
if [ -n "$excludes" ]; then
249-
while IFS= read -r exclude_pattern; do
250-
[ -z "$exclude_pattern" ] && continue
251-
252-
# Security: reject absolute paths and parent directory traversal in excludes
253-
case "$exclude_pattern" in
254-
/*|*/../*|../*|*/..|..)
255-
log_warn "Skipping unsafe exclude pattern: $exclude_pattern"
256-
continue
257-
;;
258-
esac
259-
260-
# Match full path (supports glob patterns like node_modules/.cache or */cache)
261-
# Intentional glob pattern matching for directory exclusion
262-
# shellcheck disable=SC2254
263-
case "$dir_path" in
264-
$exclude_pattern)
265-
excluded=1
266-
break
267-
;;
268-
esac
269-
done <<EOF
270-
$excludes
271-
EOF
272-
fi
273-
274233
# Skip if excluded
275-
[ "$excluded" -eq 1 ] && continue
234+
is_excluded "$dir_path" "$excludes" && continue
276235

277236
# Ensure source directory exists
278237
[ ! -d "$dir_path" ] && continue

0 commit comments

Comments
 (0)