mirror of
https://github.com/tw93/Mole.git
synced 2026-03-24 08:25:06 +00:00
fix: dedupe cleanup previews by filesystem identity
This commit is contained in:
23
bin/clean.sh
23
bin/clean.sh
@@ -129,6 +129,7 @@ PROJECT_ARTIFACT_HINT_EXAMPLES=()
|
|||||||
PROJECT_ARTIFACT_HINT_ESTIMATED_KB=0
|
PROJECT_ARTIFACT_HINT_ESTIMATED_KB=0
|
||||||
PROJECT_ARTIFACT_HINT_ESTIMATE_SAMPLES=0
|
PROJECT_ARTIFACT_HINT_ESTIMATE_SAMPLES=0
|
||||||
PROJECT_ARTIFACT_HINT_ESTIMATE_PARTIAL=false
|
PROJECT_ARTIFACT_HINT_ESTIMATE_PARTIAL=false
|
||||||
|
declare -a DRY_RUN_SEEN_IDENTITIES=()
|
||||||
|
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
note_activity() {
|
note_activity() {
|
||||||
@@ -137,6 +138,20 @@ note_activity() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
register_dry_run_cleanup_target() {
|
||||||
|
local path="$1"
|
||||||
|
local identity
|
||||||
|
identity=$(mole_path_identity "$path")
|
||||||
|
|
||||||
|
if [[ ${#DRY_RUN_SEEN_IDENTITIES[@]} -gt 0 ]] && mole_identity_in_list "$identity" "${DRY_RUN_SEEN_IDENTITIES[@]}"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DRY_RUN_SEEN_IDENTITIES+=("$identity")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
CLEANUP_DONE=false
|
CLEANUP_DONE=false
|
||||||
# shellcheck disable=SC2329
|
# shellcheck disable=SC2329
|
||||||
cleanup() {
|
cleanup() {
|
||||||
@@ -380,7 +395,12 @@ safe_clean() {
|
|||||||
log_operation "clean" "SKIPPED" "$path" "whitelist"
|
log_operation "clean" "SKIPPED" "$path" "whitelist"
|
||||||
fi
|
fi
|
||||||
[[ "$skip" == "true" ]] && continue
|
[[ "$skip" == "true" ]] && continue
|
||||||
[[ -e "$path" ]] && existing_paths+=("$path")
|
if [[ -e "$path" ]]; then
|
||||||
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
|
register_dry_run_cleanup_target "$path" || continue
|
||||||
|
fi
|
||||||
|
existing_paths+=("$path")
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "$show_scan_feedback" == "true" ]]; then
|
if [[ "$show_scan_feedback" == "true" ]]; then
|
||||||
@@ -705,6 +725,7 @@ start_cleanup() {
|
|||||||
# Set current command for operation logging
|
# Set current command for operation logging
|
||||||
export MOLE_CURRENT_COMMAND="clean"
|
export MOLE_CURRENT_COMMAND="clean"
|
||||||
log_operation_session_start "clean"
|
log_operation_session_start "clean"
|
||||||
|
DRY_RUN_SEEN_IDENTITIES=()
|
||||||
|
|
||||||
if [[ -t 1 ]]; then
|
if [[ -t 1 ]]; then
|
||||||
printf '\033[2J\033[H'
|
printf '\033[2J\033[H'
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ project_cache_has_indicators() {
|
|||||||
# Discover candidate project roots without scanning the whole home directory.
|
# Discover candidate project roots without scanning the whole home directory.
|
||||||
discover_project_cache_roots() {
|
discover_project_cache_roots() {
|
||||||
local -a roots=()
|
local -a roots=()
|
||||||
|
local -a unique_roots=()
|
||||||
|
local -a seen_identities=()
|
||||||
local root
|
local root
|
||||||
|
|
||||||
for root in "${MOLE_PURGE_DEFAULT_SEARCH_PATHS[@]}"; do
|
for root in "${MOLE_PURGE_DEFAULT_SEARCH_PATHS[@]}"; do
|
||||||
@@ -147,7 +149,18 @@ discover_project_cache_roots() {
|
|||||||
|
|
||||||
[[ ${#roots[@]} -eq 0 ]] && return 0
|
[[ ${#roots[@]} -eq 0 ]] && return 0
|
||||||
|
|
||||||
printf '%s\n' "${roots[@]}" | LC_ALL=C sort -u
|
for root in "${roots[@]}"; do
|
||||||
|
local identity
|
||||||
|
identity=$(mole_path_identity "$root")
|
||||||
|
if [[ ${#seen_identities[@]} -gt 0 ]] && mole_identity_in_list "$identity" "${seen_identities[@]}"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
seen_identities+=("$identity")
|
||||||
|
unique_roots+=("$root")
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ ${#unique_roots[@]} -gt 0 ]] && printf '%s\n' "${unique_roots[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Scan a project root for supported build caches while pruning heavy subtrees.
|
# Scan a project root for supported build caches while pruning heavy subtrees.
|
||||||
|
|||||||
@@ -27,6 +27,45 @@ if [[ -f "$_MOLE_CORE_DIR/sudo.sh" ]]; then
|
|||||||
source "$_MOLE_CORE_DIR/sudo.sh"
|
source "$_MOLE_CORE_DIR/sudo.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Normalize a path for comparisons while preserving root.
|
||||||
|
mole_normalize_path() {
|
||||||
|
local path="$1"
|
||||||
|
local normalized="${path%/}"
|
||||||
|
[[ -n "$normalized" ]] && printf '%s\n' "$normalized" || printf '%s\n' "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Return a stable identity for an existing path. Prefer dev+inode so aliased
|
||||||
|
# paths on case-insensitive filesystems or symlinks collapse to one identity.
|
||||||
|
mole_path_identity() {
|
||||||
|
local path="$1"
|
||||||
|
local normalized
|
||||||
|
normalized=$(mole_normalize_path "$path")
|
||||||
|
|
||||||
|
if [[ -e "$normalized" || -L "$normalized" ]]; then
|
||||||
|
if command -v stat > /dev/null 2>&1; then
|
||||||
|
local fs_id=""
|
||||||
|
fs_id=$(stat -L -f '%d:%i' "$normalized" 2> /dev/null || stat -f '%d:%i' "$normalized" 2> /dev/null || true)
|
||||||
|
if [[ "$fs_id" =~ ^[0-9]+:[0-9]+$ ]]; then
|
||||||
|
printf 'inode:%s\n' "$fs_id"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'path:%s\n' "$normalized"
|
||||||
|
}
|
||||||
|
|
||||||
|
mole_identity_in_list() {
|
||||||
|
local needle="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
local existing
|
||||||
|
for existing in "$@"; do
|
||||||
|
[[ "$existing" == "$needle" ]] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Update via Homebrew
|
# Update via Homebrew
|
||||||
update_via_homebrew() {
|
update_via_homebrew() {
|
||||||
local current_version="$1"
|
local current_version="$1"
|
||||||
|
|||||||
@@ -125,6 +125,18 @@ PLIST
|
|||||||
[ -f "$HOME/Library/LaunchAgents/com.example.stale.plist" ]
|
[ -f "$HOME/Library/LaunchAgents/com.example.stale.plist" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "mo clean --dry-run does not export duplicate targets across sections" {
|
||||||
|
mkdir -p "$HOME/Library/Application Support/Code/CachedData"
|
||||||
|
echo "cache" > "$HOME/Library/Application Support/Code/CachedData/data.bin"
|
||||||
|
|
||||||
|
run env HOME="$HOME" MOLE_TEST_MODE=0 "$PROJECT_ROOT/mole" clean --dry-run
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
|
||||||
|
run grep -c "Application Support/Code/CachedData" "$HOME/.config/mole/clean-list.txt"
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[ "$output" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
@test "mo clean honors whitelist entries" {
|
@test "mo clean honors whitelist entries" {
|
||||||
mkdir -p "$HOME/Library/Caches/WhitelistedApp"
|
mkdir -p "$HOME/Library/Caches/WhitelistedApp"
|
||||||
echo "keep me" > "$HOME/Library/Caches/WhitelistedApp/data.tmp"
|
echo "keep me" > "$HOME/Library/Caches/WhitelistedApp/data.tmp"
|
||||||
|
|||||||
@@ -238,6 +238,25 @@ EOF
|
|||||||
rm -rf "$HOME/go"
|
rm -rf "$HOME/go"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "discover_project_cache_roots dedupes aliased roots by filesystem identity" {
|
||||||
|
mkdir -p "$HOME/code/demo/.dart_tool"
|
||||||
|
touch "$HOME/code/demo/pubspec.yaml"
|
||||||
|
mkdir -p "$HOME/.config/mole"
|
||||||
|
ln -s "$HOME/code" "$HOME/Code"
|
||||||
|
printf '%s\n' "$HOME/Code" > "$HOME/.config/mole/purge_paths"
|
||||||
|
|
||||||
|
run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/clean/caches.sh"
|
||||||
|
roots=$(discover_project_cache_roots)
|
||||||
|
printf '%s\n' "$roots"
|
||||||
|
printf 'COUNT=%s\n' "$(printf '%s\n' "$roots" | sed '/^$/d' | wc -l | tr -d ' ')"
|
||||||
|
EOF
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"COUNT=1"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
@test "clean_project_caches skips stalled root scans" {
|
@test "clean_project_caches skips stalled root scans" {
|
||||||
mkdir -p "$HOME/.config/mole"
|
mkdir -p "$HOME/.config/mole"
|
||||||
mkdir -p "$HOME/SlowProjects/app"
|
mkdir -p "$HOME/SlowProjects/app"
|
||||||
|
|||||||
Reference in New Issue
Block a user