darcs-2.18.1: src/Darcs/Repository/Repair.hs
module Darcs.Repository.Repair ( replayRepository, checkIndex,
replayRepositoryInTemp,
RepositoryConsistency(..) )
where
import Darcs.Prelude
import Control.Monad ( when, unless )
import Control.Monad.Trans ( liftIO )
import Control.Exception ( catch, IOException )
import Data.List ( sort, (\\) )
import System.Directory
( createDirectoryIfMissing
, getCurrentDirectory
, setCurrentDirectory
, withCurrentDirectory
)
import Darcs.Patch.PatchInfoAnd ( PatchInfoAnd, hopefully, info )
import Darcs.Patch.Witnesses.Ordered
( FL(..)
, lengthFL
, mapFL
, nullFL
, reverseFL
, reverseRL
)
import Darcs.Patch.Witnesses.Sealed ( Sealed(..), unFreeLeft, unseal )
import Darcs.Patch.Apply( ApplyState )
import Darcs.Patch.Repair ( Repair(applyAndTryToFix) )
import Darcs.Patch.Info ( displayPatchInfo )
import Darcs.Patch.Set ( Origin, PatchSet(..), Tagged(..), patchSet2FL )
import Darcs.Patch ( RepoPatch, PrimOf, isInconsistent )
import Darcs.Repository.Diff( treeDiff )
import Darcs.Repository.Flags ( Verbosity(..), DiffAlgorithm )
import Darcs.Repository.Hashed ( readPatches, writeAndReadPatch )
import Darcs.Repository.InternalTypes ( Repository, repoCache, repoLocation )
import Darcs.Repository.Paths ( pristineDirPath )
import Darcs.Repository.Pending ( readPending )
import Darcs.Repository.Prefs ( filetypeFunction )
import Darcs.Repository.State
( readPristine
, readIndex
, readPristineAndPending
)
import Darcs.Util.Cache ( Cache, mkDirCache )
import Darcs.Util.Progress
( beginTedious
, endTedious
, finishedOneIO
, tediousSize
)
import Darcs.Util.Lock( withDelayedDir )
import Darcs.Util.Path( anchorPath, toFilePath )
import Darcs.Util.Printer ( putDocLn, text, renderString )
import Darcs.Util.Hash( showHash )
import Darcs.Util.Tree( Tree, emptyTree, list, restrict, expand, itemHash, zipTrees )
import Darcs.Util.Tree.Monad( TreeIO )
import Darcs.Util.Tree.Hashed( darcsUpdateHashes, hashedTreeIO )
import Darcs.Util.Tree.Plain( readPlainTree )
import Darcs.Util.Index( treeFromIndex )
applyAndFixPatchSet
:: forall rt p wU wR. (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wU wR
-> PatchSet p Origin wR
-> TreeIO (PatchSet p Origin wR, Bool)
applyAndFixPatchSet r s = do
liftIO $ beginTedious k
liftIO $ tediousSize k $ lengthFL $ patchSet2FL s
result <- case s of
PatchSet ts ps -> do
(ts', ts_ok) <- applyAndFixTagged (reverseRL ts)
(ps', ps_ok) <- applyAndFixPatches (reverseRL ps)
return (PatchSet (reverseFL ts') (reverseFL ps'), ts_ok && ps_ok)
liftIO $ endTedious k
return result
where
k = "Replaying patch"
applyAndFixTagged :: FL (Tagged p) wX wY -> TreeIO (FL (Tagged p) wX wY, Bool)
applyAndFixTagged NilFL = return (NilFL, True)
applyAndFixTagged (Tagged ps t _ :>: ts) = do
(ps', ps_ok) <- applyAndFixPatches (reverseRL ps)
(ts', ts_ok) <- applyAndFixTagged ts
return (Tagged (reverseFL ps') t Nothing :>: ts', ps_ok && ts_ok)
applyAndFixPatches
:: FL (PatchInfoAnd p) wX wY -> TreeIO (FL (PatchInfoAnd p) wX wY, Bool)
applyAndFixPatches NilFL = return (NilFL, True)
applyAndFixPatches (p :>: ps) = do
mp' <- applyAndTryToFix p
case isInconsistent . hopefully $ p of
Just err -> liftIO $ putDocLn err
Nothing -> return ()
liftIO $ finishedOneIO k $ renderString $ displayPatchInfo $ info p
(ps', ps_ok) <- applyAndFixPatches ps
case mp' of
Nothing -> return (p :>: ps', ps_ok)
Just (e, p') ->
liftIO $ do
putStrLn e
-- FIXME While this is okay semantically, it means we can't
-- run darcs check in a read-only repo
p'' <-
withCurrentDirectory (repoLocation r) $
writeAndReadPatch (repoCache r) p'
return (p'' :>: ps', False)
data RepositoryConsistency p wR = RepositoryConsistency
{ fixedPristine :: Maybe (Tree IO, Sealed (FL (PrimOf p) wR))
, fixedPatches :: Maybe (PatchSet p Origin wR)
, fixedPending :: Maybe (Sealed (FL (PrimOf p) wR))
}
hasDuplicate :: Ord a => [a] -> Maybe a
hasDuplicate li = hd $ sort li
where hd [_] = Nothing
hd [] = Nothing
hd (x1:x2:xs) | x1 == x2 = Just x1
| otherwise = hd (x2:xs)
replayRepository'
:: forall rt p wR wU. (RepoPatch p, ApplyState p ~ Tree)
=> DiffAlgorithm
-> Cache
-> Repository rt p wU wR
-> Verbosity
-> IO (RepositoryConsistency p wR)
replayRepository' dflag cache repo verbosity = do
let putVerbose s = when (verbosity == Verbose) $ putDocLn s
putInfo s = unless (verbosity == Quiet) $ putDocLn s
putVerbose $ text "Checking that patch names are unique..."
patches <- readPatches repo
case hasDuplicate $ mapFL info $ patchSet2FL patches of
Nothing -> return ()
Just pinf -> do
putInfo $ text "Error! Duplicate patch name:"
putInfo $ displayPatchInfo pinf
-- FIXME repair duplicates by re-generating their salt
fail "Duplicate patches found."
-- we have to read pristine before fixing patches as that updates pristine
pris <-
(readPristine repo >>= expand >>= darcsUpdateHashes)
`catch`
\(_ :: IOException) -> return emptyTree
putVerbose $ text "Checking content of recorded patches..."
((newpatches, patches_ok), newpris) <-
hashedTreeIO (applyAndFixPatchSet repo patches) emptyTree cache
putVerbose $ text "Checking pristine..."
ftf <- filetypeFunction
pristine_diff <- unFreeLeft `fmap` treeDiff dflag ftf pris newpris
let pristine_ok = unseal nullFL pristine_diff
putVerbose $ text "Checking pending patch..."
Sealed pend <- readPending repo
maybe_newpend <- fst <$> hashedTreeIO (applyAndTryToFix pend) newpris cache
(newpend, pending_ok) <- convertFixed pend maybe_newpend
return $ RepositoryConsistency
{ fixedPristine = if pristine_ok then Nothing else Just (newpris, pristine_diff)
, fixedPatches = if patches_ok then Nothing else Just newpatches
, fixedPending = if pending_ok then Nothing else Just (Sealed newpend)
}
where
convertFixed :: a -> Maybe (String, a) -> IO (a, Bool)
convertFixed x Nothing = return (x, True)
convertFixed _ (Just (e, x)) = do
unless (verbosity == Quiet) $ putStrLn e
return (x, False)
replayRepositoryInTemp
:: (RepoPatch p, ApplyState p ~ Tree)
=> DiffAlgorithm
-> Repository rt p wU wR
-> Verbosity
-> IO (RepositoryConsistency p wR)
replayRepositoryInTemp dflag r verb = do
repodir <- getCurrentDirectory
{- The reason we use withDelayedDir here, instead of withTempDir, is that
replayRepository' may return a new pristine that is read from the
temporary location and reading a Tree is done using lazy ByteStrings (for
file contents). Then we check if there is a difference to our stored
pristine, but when there are differences the check may terminate early
and not all of the new pristine was read/evaluated. This may then cause
does-not-exist-failures later on when the tree is evaluated further.
-}
withDelayedDir "darcs-check" $ \tmpDir -> do
setCurrentDirectory repodir
replayRepository' dflag (mkDirCache (toFilePath tmpDir)) r verb
replayRepository
:: (RepoPatch p, ApplyState p ~ Tree)
=> DiffAlgorithm
-> Repository rt p wU wR
-> Verbosity
-> (RepositoryConsistency p wR -> IO a)
-> IO a
replayRepository dflag r verb job = do
createDirectoryIfMissing False pristineDirPath
st <- replayRepository' dflag (repoCache r) r verb
job st
checkIndex
:: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wU wR
-> Bool
-> IO Bool
checkIndex repo quiet = do
index <- treeFromIndex =<< readIndex repo
pristine <- expand =<< readPristineAndPending repo
working <- expand =<< restrict pristine <$> readPlainTree "."
working_hashed <- darcsUpdateHashes working
let index_paths = [ p | (p, _) <- list index ]
working_paths = [ p | (p, _) <- list working ]
index_extra = index_paths \\ working_paths
working_extra = working_paths \\ index_paths
gethashes p (Just i1) (Just i2) = (p, itemHash i1, itemHash i2)
gethashes p (Just i1) Nothing = (p, itemHash i1, Nothing)
gethashes p Nothing (Just i2) = (p, Nothing, itemHash i2)
gethashes p Nothing Nothing = error $ "Bad case at " ++ show p
mismatches =
[miss | miss@(_, h1, h2) <- zipTrees gethashes index working_hashed, h1 /= h2]
format paths = unlines $ map ((" " ++) . anchorPath "") paths
mismatches_disp = unlines [ anchorPath "" p ++
"\n index: " ++ showHash h1 ++
"\n working: " ++ showHash h2
| (p, h1, h2) <- mismatches ]
unless (quiet || null index_extra) $
putStrLn $ "Extra items in index!\n" ++ format index_extra
unless (quiet || null working_extra) $
putStrLn $ "Missing items in index!\n" ++ format working_extra
unless (quiet || null mismatches) $
putStrLn $ "Hash mismatch(es)!\n" ++ mismatches_disp
return $ null index_extra && null working_extra && null mismatches