mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 17:24:45 +00:00
refactor: clean_local_snapshots now uses an array for snapshot processing and includes a read_key fallback, with new tests.
This commit is contained in:
@@ -280,33 +280,29 @@ clean_local_snapshots() {
|
|||||||
local total_cleaned_size=0 # Estimation not possible without thin
|
local total_cleaned_size=0 # Estimation not possible without thin
|
||||||
local newest_ts=0
|
local newest_ts=0
|
||||||
local newest_name=""
|
local newest_name=""
|
||||||
|
local -a snapshots=()
|
||||||
# Find the most recent snapshot to keep at least one version
|
# Find the most recent snapshot to keep at least one version
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
# Format: com.apple.TimeMachine.2023-10-25-120000
|
# Format: com.apple.TimeMachine.2023-10-25-120000
|
||||||
if [[ "$line" =~ com\.apple\.TimeMachine\.([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{6}) ]]; then
|
if [[ "$line" =~ com\.apple\.TimeMachine\.([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{6}) ]]; then
|
||||||
|
local snap_name="${BASH_REMATCH[0]}"
|
||||||
|
snapshots+=("$snap_name")
|
||||||
local date_str="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]} ${BASH_REMATCH[4]:0:2}:${BASH_REMATCH[4]:2:2}:${BASH_REMATCH[4]:4:2}"
|
local date_str="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]} ${BASH_REMATCH[4]:0:2}:${BASH_REMATCH[4]:2:2}:${BASH_REMATCH[4]:4:2}"
|
||||||
local snap_ts=$(date -j -f "%Y-%m-%d %H:%M:%S" "$date_str" "+%s" 2> /dev/null || echo "0")
|
local snap_ts=$(date -j -f "%Y-%m-%d %H:%M:%S" "$date_str" "+%s" 2> /dev/null || echo "0")
|
||||||
# Skip if parsing failed
|
# Skip if parsing failed
|
||||||
[[ "$snap_ts" == "0" ]] && continue
|
[[ "$snap_ts" == "0" ]] && continue
|
||||||
if [[ "$snap_ts" -gt "$newest_ts" ]]; then
|
if [[ "$snap_ts" -gt "$newest_ts" ]]; then
|
||||||
newest_ts="$snap_ts"
|
newest_ts="$snap_ts"
|
||||||
newest_name="${BASH_REMATCH[0]}"
|
newest_name="$snap_name"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done <<< "$snapshot_list"
|
done <<< "$snapshot_list"
|
||||||
|
|
||||||
|
[[ ${#snapshots[@]} -eq 0 ]] && return 0
|
||||||
[[ -z "$newest_name" ]] && return 0
|
[[ -z "$newest_name" ]] && return 0
|
||||||
|
|
||||||
local deletable_count=0
|
local deletable_count=$(( ${#snapshots[@]} - 1 ))
|
||||||
while IFS= read -r line; do
|
[[ $deletable_count -le 0 ]] && return 0
|
||||||
if [[ "$line" =~ com\.apple\.TimeMachine\.([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{6}) ]]; then
|
|
||||||
if [[ "${BASH_REMATCH[0]}" != "$newest_name" ]]; then
|
|
||||||
((deletable_count++))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done <<< "$snapshot_list"
|
|
||||||
|
|
||||||
[[ $deletable_count -eq 0 ]] && return 0
|
|
||||||
|
|
||||||
if [[ "$DRY_RUN" != "true" ]]; then
|
if [[ "$DRY_RUN" != "true" ]]; then
|
||||||
if [[ ! -t 0 ]]; then
|
if [[ ! -t 0 ]]; then
|
||||||
@@ -318,7 +314,14 @@ clean_local_snapshots() {
|
|||||||
echo -e " ${GRAY}The most recent snapshot will be kept.${NC}"
|
echo -e " ${GRAY}The most recent snapshot will be kept.${NC}"
|
||||||
echo -ne " ${PURPLE}${ICON_ARROW}${NC} Remove all local snapshots except the most recent one? ${GREEN}Enter${NC} continue, ${GRAY}Space${NC} skip: "
|
echo -ne " ${PURPLE}${ICON_ARROW}${NC} Remove all local snapshots except the most recent one? ${GREEN}Enter${NC} continue, ${GRAY}Space${NC} skip: "
|
||||||
local choice
|
local choice
|
||||||
choice=$(read_key)
|
if type read_key > /dev/null 2>&1; then
|
||||||
|
choice=$(read_key)
|
||||||
|
else
|
||||||
|
IFS= read -r -s -n 1 choice || choice=""
|
||||||
|
if [[ -z "$choice" || "$choice" == $'\n' || "$choice" == $'\r' ]]; then
|
||||||
|
choice="ENTER"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
if [[ "$choice" == "ENTER" ]]; then
|
if [[ "$choice" == "ENTER" ]]; then
|
||||||
printf "\r\033[K" # Clear the prompt line
|
printf "\r\033[K" # Clear the prompt line
|
||||||
else
|
else
|
||||||
@@ -327,16 +330,12 @@ clean_local_snapshots() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while IFS= read -r line; do
|
local snap_name
|
||||||
|
for snap_name in "${snapshots[@]}"; do
|
||||||
# Format: com.apple.TimeMachine.2023-10-25-120000
|
# Format: com.apple.TimeMachine.2023-10-25-120000
|
||||||
if [[ "$line" =~ com\.apple\.TimeMachine\.([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{6}) ]]; then
|
if [[ "$snap_name" =~ com\.apple\.TimeMachine\.([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]{6}) ]]; then
|
||||||
local date_str="${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]} ${BASH_REMATCH[4]:0:2}:${BASH_REMATCH[4]:2:2}:${BASH_REMATCH[4]:4:2}"
|
|
||||||
local snap_ts=$(date -j -f "%Y-%m-%d %H:%M:%S" "$date_str" "+%s" 2> /dev/null || echo "0")
|
|
||||||
# Skip if parsing failed
|
|
||||||
[[ "$snap_ts" == "0" ]] && continue
|
|
||||||
# Remove all but the most recent snapshot
|
# Remove all but the most recent snapshot
|
||||||
if [[ "${BASH_REMATCH[0]}" != "$newest_name" ]]; then
|
if [[ "${BASH_REMATCH[0]}" != "$newest_name" ]]; then
|
||||||
local snap_name="${BASH_REMATCH[0]}"
|
|
||||||
if [[ "$DRY_RUN" == "true" ]]; then
|
if [[ "$DRY_RUN" == "true" ]]; then
|
||||||
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Local snapshot: $snap_name ${YELLOW}dry-run${NC}"
|
echo -e " ${YELLOW}${ICON_DRY_RUN}${NC} Local snapshot: $snap_name ${YELLOW}dry-run${NC}"
|
||||||
((cleaned_count++))
|
((cleaned_count++))
|
||||||
@@ -353,7 +352,7 @@ clean_local_snapshots() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done <<< "$snapshot_list"
|
done
|
||||||
if [[ $cleaned_count -gt 0 && "$DRY_RUN" != "true" ]]; then
|
if [[ $cleaned_count -gt 0 && "$DRY_RUN" != "true" ]]; then
|
||||||
log_success "Cleaned $cleaned_count local snapshots, kept latest"
|
log_success "Cleaned $cleaned_count local snapshots, kept latest"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -108,6 +108,104 @@ EOF
|
|||||||
[[ "$output" == *"No incomplete backups found"* ]]
|
[[ "$output" == *"No incomplete backups found"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "clean_local_snapshots skips in non-interactive mode" {
|
||||||
|
run bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/clean/system.sh"
|
||||||
|
|
||||||
|
tmutil() {
|
||||||
|
if [[ "$1" == "listlocalsnapshots" ]]; then
|
||||||
|
printf '%s\n' \
|
||||||
|
"com.apple.TimeMachine.2023-10-25-120000" \
|
||||||
|
"com.apple.TimeMachine.2023-10-24-120000"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
start_section_spinner(){ :; }
|
||||||
|
stop_section_spinner(){ :; }
|
||||||
|
|
||||||
|
DRY_RUN="false"
|
||||||
|
clean_local_snapshots
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"skipping non-interactive mode"* ]]
|
||||||
|
[[ "$output" != *"Removed snapshot"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "clean_local_snapshots keeps latest in dry-run" {
|
||||||
|
run bash --noprofile --norc <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/clean/system.sh"
|
||||||
|
|
||||||
|
tmutil() {
|
||||||
|
if [[ "$1" == "listlocalsnapshots" ]]; then
|
||||||
|
printf '%s\n' \
|
||||||
|
"com.apple.TimeMachine.2023-10-25-120000" \
|
||||||
|
"com.apple.TimeMachine.2023-10-25-130000" \
|
||||||
|
"com.apple.TimeMachine.2023-10-24-120000"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
start_section_spinner(){ :; }
|
||||||
|
stop_section_spinner(){ :; }
|
||||||
|
note_activity(){ :; }
|
||||||
|
|
||||||
|
DRY_RUN="true"
|
||||||
|
clean_local_snapshots
|
||||||
|
EOF
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Local snapshot: com.apple.TimeMachine.2023-10-25-120000"* ]]
|
||||||
|
[[ "$output" == *"Local snapshot: com.apple.TimeMachine.2023-10-24-120000"* ]]
|
||||||
|
[[ "$output" != *"Local snapshot: com.apple.TimeMachine.2023-10-25-130000"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "clean_local_snapshots uses read fallback when read_key missing" {
|
||||||
|
if ! command -v script > /dev/null 2>&1; then
|
||||||
|
skip "script not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmp_script="$BATS_TEST_TMPDIR/clean_local_snapshots_fallback.sh"
|
||||||
|
cat > "$tmp_script" <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
source "$PROJECT_ROOT/lib/core/common.sh"
|
||||||
|
source "$PROJECT_ROOT/lib/clean/system.sh"
|
||||||
|
|
||||||
|
tmutil() {
|
||||||
|
if [[ "$1" == "listlocalsnapshots" ]]; then
|
||||||
|
printf '%s\n' \
|
||||||
|
"com.apple.TimeMachine.2023-10-25-120000" \
|
||||||
|
"com.apple.TimeMachine.2023-10-24-120000"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
start_section_spinner(){ :; }
|
||||||
|
stop_section_spinner(){ :; }
|
||||||
|
note_activity(){ :; }
|
||||||
|
|
||||||
|
unset -f read_key
|
||||||
|
|
||||||
|
CALL_LOG="$HOME/snapshot_calls.log"
|
||||||
|
> "$CALL_LOG"
|
||||||
|
sudo() { echo "sudo:$*" >> "$CALL_LOG"; return 0; }
|
||||||
|
|
||||||
|
DRY_RUN="false"
|
||||||
|
clean_local_snapshots
|
||||||
|
cat "$CALL_LOG"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
run bash --noprofile --norc -c "printf '\n' | script -q /dev/null bash \"$tmp_script\""
|
||||||
|
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
[[ "$output" == *"Skipped"* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "clean_homebrew skips when cleaned recently" {
|
@test "clean_homebrew skips when cleaned recently" {
|
||||||
run bash --noprofile --norc <<'EOF'
|
run bash --noprofile --norc <<'EOF'
|
||||||
@@ -1088,5 +1186,3 @@ EOF
|
|||||||
[ "$status" -eq 0 ]
|
[ "$status" -eq 0 ]
|
||||||
[[ "$output" == *"Spotlight index already optimal"* ]]
|
[[ "$output" == *"Spotlight index already optimal"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user