|
1 | 1 | #!/usr/bin/env bash |
2 | 2 | # File copying utilities with pattern matching |
3 | 3 |
|
| 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 | + |
4 | 27 | # Parse .gitignore-style pattern file |
5 | 28 | # Usage: parse_pattern_file file_path |
6 | 29 | # Returns: newline-separated patterns (comments and empty lines stripped) |
@@ -74,26 +97,8 @@ copy_patterns() { |
74 | 97 | # Remove leading ./ |
75 | 98 | file="${file#./}" |
76 | 99 |
|
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 | | - |
95 | 100 | # Skip if excluded |
96 | | - [ "$excluded" -eq 1 ] && continue |
| 101 | + is_excluded "$file" "$excludes" && continue |
97 | 102 |
|
98 | 103 | # Determine destination path |
99 | 104 | local dest_file |
|
132 | 137 | # Remove leading ./ |
133 | 138 | file="${file#./}" |
134 | 139 |
|
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 | | - |
153 | 140 | # Skip if excluded |
154 | | - [ "$excluded" -eq 1 ] && continue |
| 141 | + is_excluded "$file" "$excludes" && continue |
155 | 142 |
|
156 | 143 | # Determine destination path |
157 | 144 | local dest_file |
@@ -243,36 +230,8 @@ copy_directories() { |
243 | 230 | # Remove leading ./ |
244 | 231 | dir_path="${dir_path#./}" |
245 | 232 |
|
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 | | - |
274 | 233 | # Skip if excluded |
275 | | - [ "$excluded" -eq 1 ] && continue |
| 234 | + is_excluded "$dir_path" "$excludes" && continue |
276 | 235 |
|
277 | 236 | # Ensure source directory exists |
278 | 237 | [ ! -d "$dir_path" ] && continue |
|
0 commit comments