Skip to content

Commit 6847a74

Browse files
committed
fix: handle tmutil errors gracefully instead of crashing
When tmutil addexclusion fails (e.g. Error -20 or -50 on paths inside app bundles or with permission issues), skip the path with a warning instead of crashing. This allows asimov to continue processing remaining directories. Addresses stevegrunwell#101 and stevegrunwell#86.
1 parent 5d4263d commit 6847a74

File tree

4 files changed

+55
-1
lines changed

4 files changed

+55
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
3434
* Added `scripts/uninstall.sh` to cleanly remove Asimov and its launchd schedule ([#35], props @sylver)
3535
* Added common interval reference comments to `com.stevegrunwell.asimov.plist` ([#35], props @sylver)
3636

37+
### Fixed
38+
39+
* Handle `tmutil` errors gracefully instead of crashing; paths that fail exclusion are skipped with a warning ([stevegrunwell/asimov#101], [stevegrunwell/asimov#86])
40+
3741
### Changed
3842

3943
* Skip directories already excluded from Time Machine backups for faster subsequent runs (inspired by [stevegrunwell/asimov#97], props @VladRassokhin)
@@ -129,5 +133,7 @@ Initial public release.
129133
[#56]: https://github.com/stevegrunwell/asimov/pull/56
130134
[stevegrunwell/asimov#64]: https://github.com/stevegrunwell/asimov/pull/64
131135
[stevegrunwell/asimov#69]: https://github.com/stevegrunwell/asimov/pull/69
136+
[stevegrunwell/asimov#86]: https://github.com/stevegrunwell/asimov/issues/86
132137
[stevegrunwell/asimov#87]: https://github.com/stevegrunwell/asimov/pull/87
133138
[stevegrunwell/asimov#97]: https://github.com/stevegrunwell/asimov/pull/97
139+
[stevegrunwell/asimov#101]: https://github.com/stevegrunwell/asimov/issues/101

asimov

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ exclude_file() {
122122
continue
123123
fi
124124

125-
tmutil addexclusion "${path}"
125+
if ! tmutil addexclusion "${path}" 2>/dev/null; then
126+
echo "! ${path}: failed to exclude (tmutil error), skipping." >&2
127+
continue
128+
fi
126129

127130
sizeondisk=$(du -hs "${path}" | cut -f1)
128131
echo "- ${path} has been excluded from Time Machine backups (${sizeondisk})."

tests/behavior.bats

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,41 @@ load test_helper
207207
second_count="$(count_exclusions)"
208208
[[ "$second_count" -eq 1 ]]
209209
}
210+
211+
# =============================================================================
212+
# Error handling
213+
# =============================================================================
214+
215+
@test "continues when tmutil fails for a path" {
216+
create_project "Code/Good-Project" "package.json" "node_modules"
217+
create_project "Code/Bad-Project" "Cargo.toml" "target"
218+
219+
# Mark the bad project as one that will cause tmutil to fail
220+
ASIMOV_TEST_TMUTIL_FAIL_PATHS="${TEST_TEMP_DIR}/.tmutil_fail_paths"
221+
export ASIMOV_TEST_TMUTIL_FAIL_PATHS
222+
echo "${HOME}/Code/Bad-Project/target" > "$ASIMOV_TEST_TMUTIL_FAIL_PATHS"
223+
224+
run_asimov
225+
226+
# The good project should still be excluded
227+
assert_excluded "${HOME}/Code/Good-Project/node_modules"
228+
# The bad project should NOT be in the exclusions list
229+
refute_excluded "${HOME}/Code/Bad-Project/target"
230+
[[ "$(count_exclusions)" -eq 1 ]]
231+
}
232+
233+
@test "prints warning when tmutil fails" {
234+
create_project "Code/Bad-Project" "package.json" "node_modules"
235+
236+
ASIMOV_TEST_TMUTIL_FAIL_PATHS="${TEST_TEMP_DIR}/.tmutil_fail_paths"
237+
export ASIMOV_TEST_TMUTIL_FAIL_PATHS
238+
echo "${HOME}/Code/Bad-Project/node_modules" > "$ASIMOV_TEST_TMUTIL_FAIL_PATHS"
239+
240+
run_asimov
241+
242+
# Script should succeed (exit 0) even though tmutil failed
243+
[[ "$status" -eq 0 ]]
244+
# Output should contain the warning
245+
[[ "$output" == *"failed to exclude"* ]]
246+
[[ "$(count_exclusions)" -eq 0 ]]
247+
}

tests/bin/tmutil

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ case "$subcommand" in
2424
fi
2525
;;
2626
addexclusion)
27+
# Simulate failure for paths listed in ASIMOV_TEST_TMUTIL_FAIL_PATHS
28+
if [[ -n "${ASIMOV_TEST_TMUTIL_FAIL_PATHS:-}" && -f "${ASIMOV_TEST_TMUTIL_FAIL_PATHS}" ]]; then
29+
if grep -Fxq "$path" "$ASIMOV_TEST_TMUTIL_FAIL_PATHS" 2>/dev/null; then
30+
echo "Error (-50) while attempting to change exclusion setting." >&2
31+
exit 1
32+
fi
33+
fi
2734
printf '%s\n' "$path" >> "$ASIMOV_TEST_EXCLUSIONS"
2835
;;
2936
*)

0 commit comments

Comments
 (0)