Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/Database.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ module Database (clearRedoTempDirectory, initializeTargetDatabase, hasAlwaysDep,
doesDatabaseExist, storeIfChangeDep, storeAlwaysDep, getBuiltTargetPath, isDirty,
initializeSourceDatabase, isClean, getDoFile, getStamp, isSource, getKey, getTempKey,
TempKey(..), Key(..), initializeSession, getTargetLockFile, getJobServerPipe,
getStdoutFile, getTempFile, markBuilt, isBuilt, markErrored, isErrored) where
getStdoutFile, getTempFile, markBuilt, isBuilt, markErrored, isErrored,
redoReset) where

import Control.Exception (catch, SomeException(..))
import Control.Monad (when)
import qualified Data.ByteString.Char8 as BS
import Crypto.Hash (hashWith, MD5(..), Digest)
import qualified Data.ByteArray
Expand All @@ -20,6 +22,8 @@ import System.Exit (exitFailure)
import System.Environment (getEnv, setEnv, lookupEnv)
import System.Posix.User (getEffectiveUserID)
import System.Random (randomRIO)
import System.Process (callProcess)
import System.IO (hFlush, stdout)

import DatabaseEntry
import PrettyPrint
Expand Down Expand Up @@ -529,3 +533,35 @@ markDirty key = withDatabaseLock' key func
-- Store a stamp for the target in the database:
storeStamp :: Key -> Stamp -> IO ()
storeStamp key stamp = withDatabaseLock key (storeStamp' key stamp)

-- redoReset - remove all redo state
redoReset :: IO ()
redoReset = do
putStr "Resetting redo state... " >> hFlush stdout

-- Gather paths to remove
metaDir <- redoMetaDirectory
user <- getUsername
base <- getTemporaryDirectory
let lockDir = base </> "redo-" ++ user ++ "-locks"
let tempBaseDir = base </> "redo-" ++ user

-- Check existence
exists1 <- doesDirectoryExist metaDir
exists2 <- doesDirectoryExist lockDir
exists3 <- doesDirectoryExist tempBaseDir

if exists1 || exists2 || exists3
then do -- Fast bulk removal via rm -rf (faster than Haskell's
-- removeDirectoryRecursive which does per-file stat+unlink)
let dirs = [metaDir | exists1] ++ [lockDir | exists2] ++ [tempBaseDir | exists3]
catch (callProcess "rm" ("-rf" : dirs))
(\(_ :: SomeException) ->
-- Fallback to Haskell removal if rm not available
mapM_ safeRemoveDirectoryRecursive dirs)
putStrLn "done."
when exists1 $ putStrLn $ " Removed " ++ metaDir ++ " (build state)"
when exists2 $ putStrLn $ " Removed " ++ lockDir ++ " (lock files)"
when exists3 $ putStrLn $ " Removed " ++ tempBaseDir ++ " (temp/cache)"
putStrLn "Redo state reset. Next build will start fresh."
else putStrLn "nothing to reset. Redo state is already clean."
20 changes: 19 additions & 1 deletion src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,29 @@ printVersion _ = do putStrLn versionString
-- Canonical usage line per command:
usageLine :: String -> String
usageLine "redo-done" = "usage: redo-done [OPTION...] target [dep ...]"
usageLine "redo-reset" = "usage: redo-reset"
usageLine name = "usage: " ++ name ++ " [OPTION...] target..."

printHelp :: String -> [OptDescr a] -> [String] -> IO b
printHelp programName opts errs = if null errs then do putStrLn $ helpStr opts
printHelp programName opts errs = if null errs then do putStr $ helpStr opts
when (programName == "redo") $ putStr relatedCommands
putStrLn ""
exitSuccess
else ioError (userError (concat errs ++ helpStr opts))
where helpStr = usageInfo (usageLine programName)

relatedCommands :: String
relatedCommands = unlines
[ ""
, "Related commands:"
, " redo-ifchange Rebuild targets only if dependencies changed"
, " redo-ifcreate Rebuild if a source file is created"
, " redo-always Always rebuild (mark target as never up-to-date)"
, " redo-ood List out-of-date targets"
, " redo-done Mark an external target as up-to-date"
, " redo-reset Remove all redo state and start fresh"
]

-- Helper function to get parse through commandline arguments and return options:
getOptions :: IO (Options, [String])
getOptions = do
Expand Down Expand Up @@ -189,6 +204,9 @@ main = do
let debug2 = fromMaybe "" debug2Flag
unless (null debug2) (putUnformattedStrLn $ progName ++ " " ++ unwords (map unTarget targetsToRun') )

-- Handle redo-reset early, before session initialization
when (progName == "redo-reset") $ do redoReset >> exitSuccess

-- Run the main:
mainToRun' <- mainToRun jobs'
mainToRun' progName targetsToRun'
Expand Down
2 changes: 1 addition & 1 deletion src/Version.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import BuildInfo (gitCommitHash, buildTimestamp)

-- Version string with platform, commit, and build time
versionString :: String
versionString = "redo 0.2.2 (" ++ System.Info.os ++ "-" ++ System.Info.arch ++ ")\n" ++
versionString = "redo 0.2.3 (" ++ System.Info.os ++ "-" ++ System.Info.arch ++ ")\n" ++
"https://github.com/dinkelk/redo\n" ++
"commit: " ++ $(gitCommitHash) ++ "\n" ++
"built: " ++ $(buildTimestamp)
2 changes: 1 addition & 1 deletion src/install.do
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Dependencies:
binaries="redo redo-ifchange redo-ifcreate redo-always redo-ood redo-done"
binaries="redo redo-ifchange redo-ifcreate redo-always redo-ood redo-done redo-reset"
redo-ifchange $binaries

# Create bin directory and copy over built files:
Expand Down
168 changes: 168 additions & 0 deletions test/420-reset/all.do
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
exec >&2
. ../skip-if-minimal-do.sh

# redo-reset refuses to run inside a .do file (it detects REDO_SESSION).
# So we unset REDO_SESSION before calling it, and use an isolated HOME
# to avoid interfering with the parent redo session's ~/.redo state.
TEST_HOME=$(mktemp -d)
TEST_DIR=$(mktemp -d)
TEST_TMPDIR=$(mktemp -d)
cleanup() { rm -rf "$TEST_HOME" "$TEST_DIR" "$TEST_TMPDIR"; }
trap cleanup EXIT

# Create a simple .do file for test builds
cat > "$TEST_DIR/target1.do" << 'DOEOF'
echo "built" > "$3"
DOEOF

# Helper to run redo commands in the isolated environment.
# Unsets REDO_KEY so redo doesn't think it's inside a .do build.
# Uses isolated HOME so ~/.redo doesn't conflict with parent session.
iredo() {
env -u REDO_KEY -u REDO_SESSION -u REDO_INIT_PATH HOME="$TEST_HOME" TMPDIR="$TEST_TMPDIR" "$@"
}

# Helper: get the redo meta directory for isolated env
redo_meta_dir() {
echo "$TEST_HOME/.redo"
}

# Test 1: redo-reset with no prior state
echo "Test 1: redo-reset with no prior state"
rm -rf "$(redo_meta_dir)"
output=$(iredo redo-reset 2>&1)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "FAIL: redo-reset exited with $rc: $output" >&2
exit 1
fi
echo " PASS"

# Test 2: redo-reset removes state after a build
echo "Test 2: redo-reset removes state after build"
(cd "$TEST_DIR" && iredo redo target1)
[ -e "$TEST_DIR/target1" ] || exit 3
[ -d "$(redo_meta_dir)" ] || { echo "FAIL: meta dir missing after build" >&2; exit 4; }
output=$(iredo redo-reset 2>&1)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "FAIL: redo-reset exited with $rc" >&2
exit 5
fi
case "$output" in
*"Redo state reset"*) ;; # good
*) echo "FAIL: expected 'Redo state reset', got: $output" >&2; exit 6 ;;
esac
echo " PASS"

# Test 3: redo-reset removes ~/.redo
echo "Test 3: redo-reset removes meta directory"
(cd "$TEST_DIR" && rm -f target1 && iredo redo target1)
[ -d "$(redo_meta_dir)" ] || { echo "FAIL: meta dir missing after build" >&2; exit 7; }
iredo redo-reset >/dev/null 2>&1
if [ -d "$(redo_meta_dir)" ]; then
echo "FAIL: meta dir still exists after reset" >&2
exit 8
fi
echo " PASS"

# Test 4: Build works correctly after reset (clean slate)
echo "Test 4: build works after reset"
(cd "$TEST_DIR" && rm -f target1 && iredo redo target1)
iredo redo-reset >/dev/null 2>&1
(cd "$TEST_DIR" && rm -f target1 && iredo redo target1)
[ -e "$TEST_DIR/target1" ] || { echo "FAIL: target1 not built after reset" >&2; exit 9; }
content=$(cat "$TEST_DIR/target1")
if [ "$content" != "built" ]; then
echo "FAIL: expected 'built', got: $content" >&2
exit 10
fi
echo " PASS"

# Test 5: redo-reset does not remove build outputs (only state)
echo "Test 5: redo-reset preserves build outputs"
(cd "$TEST_DIR" && rm -f target1 && iredo redo target1)
[ -e "$TEST_DIR/target1" ] || exit 11
iredo redo-reset >/dev/null 2>&1
if [ ! -e "$TEST_DIR/target1" ]; then
echo "FAIL: target1 removed by reset (should only remove state)" >&2
exit 12
fi
echo " PASS"

# Test 6: redo-reset reports what was removed
echo "Test 6: redo-reset reports removed directories"
(cd "$TEST_DIR" && rm -f target1 && iredo redo target1)
output=$(iredo redo-reset 2>&1)
case "$output" in
*"Removed"*) ;; # good
*) echo "FAIL: expected 'Removed' in output, got: $output" >&2; exit 13 ;;
esac
echo " PASS"

# Test 7: Multiple resets are idempotent
echo "Test 7: multiple resets are idempotent"
(cd "$TEST_DIR" && rm -f target1 && iredo redo target1)
iredo redo-reset >/dev/null 2>&1
output=$(iredo redo-reset 2>&1)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "FAIL: second reset exited with $rc" >&2
exit 14
fi
echo " PASS"

# Test 8: redo-reset --help shows usage
echo "Test 8: redo-reset --help"
output=$(iredo redo-reset --help 2>&1)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "FAIL: --help exited with $rc" >&2
exit 16
fi
case "$output" in
*"usage: redo-reset"*) ;; # good
*) echo "FAIL: expected usage line, got: $output" >&2; exit 17 ;;
esac
echo " PASS"

# Test 9: After reset, removing target and rebuilding works
echo "Test 9: rebuild from scratch after reset"
(cd "$TEST_DIR" && rm -f target1 && iredo redo target1)
[ -e "$TEST_DIR/target1" ] || exit 18
iredo redo-reset >/dev/null 2>&1
# After reset, existing target is treated as source (no DB entry).
# Remove it to get a clean rebuild.
rm -f "$TEST_DIR/target1"
(cd "$TEST_DIR" && iredo redo target1)
[ -e "$TEST_DIR/target1" ] || { echo "FAIL: target1 not rebuilt after reset+rm" >&2; exit 19; }
content=$(cat "$TEST_DIR/target1")
if [ "$content" != "built" ]; then
echo "FAIL: expected 'built', got: $content" >&2
exit 20
fi
echo " PASS"

# Test 10: redo-reset --version works
echo "Test 10: redo-reset --version"
output=$(iredo redo-reset --version 2>&1)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "FAIL: --version exited with $rc" >&2
exit 21
fi
case "$output" in
*redo*) ;; # good
*) echo "FAIL: expected version info, got: $output" >&2; exit 22 ;;
esac
echo " PASS"

# Test 11: redo-reset output mentions redo state
echo "Test 11: output mentions redo state"
(cd "$TEST_DIR" && rm -f target1 && iredo redo target1)
output=$(iredo redo-reset 2>&1)
case "$output" in
*"Redo state"*) ;; # good
*) echo "FAIL: expected 'Redo state' in output, got: $output" >&2; exit 23 ;;
esac
echo " PASS"
1 change: 1 addition & 0 deletions test/420-reset/clean.do
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rm -f target1 target1.do target1.do.tmp
Loading