Skip to content

Commit 42c153a

Browse files
committed
feat: skip already-excluded directories for faster subsequent runs
Use Spotlight metadata (mdfind) to identify directories already excluded from Time Machine and skip them during the find traversal. Also fixes a comment typo and removes duplicate Gradle sentinel entries. Inspired by stevegrunwell#97, props @VladRassokhin.
1 parent 64834be commit 42c153a

File tree

5 files changed

+48
-4
lines changed

5 files changed

+48
-4
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).
3232

3333
### Changed
3434

35+
* Skip directories already excluded from Time Machine backups for faster subsequent runs (inspired by [stevegrunwell/asimov#97], props @VladRassokhin)
3536
* Migrated test suite from PHP/PHPUnit to [Bats](https://github.com/bats-core/bats-core) (Bash Automated Testing System), removing the PHP dependency for contributors
3637
* Replaced Travis CI pipeline with GitHub Actions (macOS 14 + 15 matrix)
3738
* Replaced PHP `tmutil` mock with a pure bash implementation
3839
* Moved install script to `scripts/install.sh` with shared variables, now copies binary instead of symlinking ([#35], props @sylver)
3940

41+
### Fixed
42+
43+
* Fixed duplicate Gradle sentinel entries in the sentinels list
44+
* Fixed typo in comment ("decendents" → "descendants")
45+
4046
### Removed
4147

4248
* Removed PHP test infrastructure (`composer.json`, `phpunit.xml.dist`, and PHP test files)
@@ -117,3 +123,4 @@ Initial public release.
117123
[#55]: https://github.com/stevegrunwell/asimov/pull/55
118124
[#35]: https://github.com/stevegrunwell/asimov/pull/35
119125
[#56]: https://github.com/stevegrunwell/asimov/pull/56
126+
[stevegrunwell/asimov#97]: https://github.com/stevegrunwell/asimov/pull/97

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ test: ## Run Bats tests
99
@bats tests/sentinels.bats tests/behavior.bats
1010

1111
lint: ## Run Shellcheck on all shell scripts
12-
@shellcheck asimov scripts/install.sh scripts/uninstall.sh tests/bin/run-tests.sh tests/bin/tmutil
12+
@shellcheck asimov scripts/install.sh scripts/uninstall.sh tests/bin/run-tests.sh tests/bin/tmutil tests/bin/mdfind
1313

1414
check: test lint ## Run tests and linting
1515

asimov

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ set -Eeu -o pipefail
2020
readonly ASIMOV_ROOT=~
2121

2222
# Paths to unconditionally skip over. This prevents Asimov from modifying the
23-
# Time Machine exclusions for these paths (and decendents). It has an important
23+
# Time Machine exclusions for these paths (and descendants). It has an important
2424
# side-effect of speeding up the search.
2525
readonly ASIMOV_SKIP_PATHS=(
2626
~/.Trash
@@ -50,8 +50,6 @@ readonly ASIMOV_VENDOR_DIR_SENTINELS=(
5050
'Carthage Cartfile' # Carthage
5151
'Pods Podfile' # CocoaPods
5252
'bower_components bower.json' # Bower (JavaScript)
53-
'build build.gradle' # Gradle
54-
'build build.gradle.kts' # Gradle Kotlin Script
5553
'build pubspec.yaml' # Flutter (Dart)
5654
'build setup.py' # Python
5755
'dist setup.py' # PyPI Publishing (Python)
@@ -116,6 +114,13 @@ for d in "${ASIMOV_SKIP_PATHS[@]}"; do
116114
find_parameters_skip+=( -not \( -path "${d}" -prune \) )
117115
done
118116

117+
# Skip directories already excluded from Time Machine backups.
118+
# Uses Spotlight metadata which may not report all exclusions — any missed
119+
# directories are still handled by the tmutil isexcluded check in exclude_file().
120+
while IFS= read -r line; do
121+
[[ -n "$line" ]] && find_parameters_skip+=( -not \( -path "${line}" -prune \) )
122+
done < <(mdfind -onlyin "${ASIMOV_ROOT}" "com_apple_backup_excludeItem = 'com.apple.backupd'" 2>/dev/null | sort -u)
123+
119124
# Iterate over the directory/sentinel pairs to construct the `find` expression.
120125
declare -a find_parameters_vendor=()
121126
for i in "${ASIMOV_VENDOR_DIR_SENTINELS[@]}"; do

tests/behavior.bats

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,27 @@ load test_helper
127127
refute_excluded "${HOME}/Code/My-Project/node_modules/dep/node_modules"
128128
[[ "$(count_exclusions)" -eq 1 ]]
129129
}
130+
131+
# =============================================================================
132+
# Skip already-excluded directories (mdfind optimization)
133+
# =============================================================================
134+
135+
@test "skips directories already excluded from Time Machine" {
136+
create_project "Code/Already-Excluded" "package.json" "node_modules"
137+
create_project "Code/New-Project" "package.json" "node_modules"
138+
139+
# Pre-exclude the first project manually
140+
echo "${HOME}/Code/Already-Excluded/node_modules" > "$ASIMOV_TEST_EXCLUSIONS"
141+
142+
# Tell mock mdfind to report it as already excluded
143+
ASIMOV_TEST_MDFIND_RESULTS="${TEST_TEMP_DIR}/.mdfind_results"
144+
export ASIMOV_TEST_MDFIND_RESULTS
145+
echo "${HOME}/Code/Already-Excluded" > "$ASIMOV_TEST_MDFIND_RESULTS"
146+
147+
run_asimov
148+
149+
# The new project should be excluded
150+
assert_excluded "${HOME}/Code/New-Project/node_modules"
151+
# The already-excluded one should still only have 1 entry (not duplicated)
152+
[[ "$(count_exclusions)" -eq 2 ]]
153+
}

tests/bin/mdfind

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/usr/bin/env bash
2+
# Mock mdfind for testing — returns nothing by default.
3+
# Set ASIMOV_TEST_MDFIND_RESULTS to a file path to return its contents.
4+
set -euo pipefail
5+
6+
if [[ -n "${ASIMOV_TEST_MDFIND_RESULTS:-}" && -f "${ASIMOV_TEST_MDFIND_RESULTS}" ]]; then
7+
cat "${ASIMOV_TEST_MDFIND_RESULTS}"
8+
fi

0 commit comments

Comments
 (0)