diff --git a/ralph-loop.sh b/ralph-loop.sh index 596d602..63b9558 100755 --- a/ralph-loop.sh +++ b/ralph-loop.sh @@ -185,8 +185,20 @@ normalize_memory_max() { rotate_logs() { local log_dir=$1 local max_logs=$2 - - ls -t "$log_dir"/*.log 2>/dev/null | tail -n +"$((max_logs + 1))" | xargs -r rm -f -- + local -a logs=() + local line + + # Newest first. NUL-delimited so workspace paths with spaces (or other + # whitespace) stay one entry; xargs would split them into separate paths. + while IFS= read -r -d '' line; do + logs+=("${line#*$'\t'}") + done < <(find "$log_dir" -maxdepth 1 -type f -name '*.log' -printf '%T@\t%p\0' 2>/dev/null | sort -z -t $'\t' -k1,1nr) + + # Only ever remove entries inside $log_dir, and only the ones past max_logs. + local f + for f in "${logs[@]:max_logs}"; do + rm -f -- "$f" + done } find_stop_file() { diff --git a/tests/ralph-loop.bats b/tests/ralph-loop.bats index a306b7e..3d73006 100644 --- a/tests/ralph-loop.bats +++ b/tests/ralph-loop.bats @@ -71,3 +71,49 @@ teardown() { # The stop file must NOT be deleted. [ -f stop.md ] } + +# Log rotation runs once per iteration. These tests use a fake CLI so the loop +# can run to completion without any real tool installed. +write_env() { + local ws=$1 max_logs=$2 + mkdir -p "$ws/.ralph" + cat > "$ws/.ralph/.env" < "$ws/p.md" +} + +setup_fake_tool() { + cat > "$WORK/faketool" <<'SH' +#!/usr/bin/env bash +cat >/dev/null +echo "faketool $*" +SH + chmod +x "$WORK/faketool" +} + +@test "log rotation still trims when the workspace path contains a space" { + setup_fake_tool + ws="$WORK/my ws" + write_env "$ws" 1 + cd "$ws" + run bash "$SCRIPT" 3 p.md + [ "$(find "$ws/.ralph/logs" -name '*.log' | wc -l)" -eq 1 ] +} + +@test "log rotation never removes a file outside the log directory" { + setup_fake_tool + ws="$WORK/my ws" + write_env "$ws" 1 + : > "$WORK/my" + cd "$ws" + run bash "$SCRIPT" 3 p.md + [ -e "$WORK/my" ] +}