module Darcs.Repository.State
( restrictSubpaths, restrictBoring, TreeFilter(..), restrictDarcsdir
, maybeRestrictSubpaths
, unrecordedChanges, unrecordedChangesWithPatches, readPending
, readRecorded, readUnrecorded, readRecordedAndPending, readWorking
, readPendingAndWorking, readUnrecordedFiltered
, readIndex, updateIndex, invalidateIndex, UseIndex(..), ScanKnown(..)
, filterOutConflicts
, getMovesPs, getReplaces
) where
import Prelude ()
import Darcs.Prelude
import Control.Monad( when, foldM )
import Control.Exception ( catch, IOException )
import Data.Maybe ( isJust, fromJust )
import Data.Ord ( comparing )
import Data.List ( sortBy, union, delete )
import Text.Regex( matchRegex )
import System.Directory( removeFile, doesFileExist, doesDirectoryExist, renameFile )
import System.FilePath ( (</>) )
import qualified Data.ByteString as B
( readFile, drop, writeFile, empty, concat )
import qualified Data.ByteString.Char8 as BC
( pack, unpack, split )
import qualified Data.ByteString.Lazy as BL ( toChunks )
import Darcs.Patch ( effect, RepoPatch, PrimOf, sortCoalesceFL, fromPrim, fromPrims
, PrimPatch, primIsHunk, maybeApplyToTree
, tokreplace, forceTokReplace, move )
import Darcs.Patch.Named.Wrapped ( anonymous )
import Darcs.Patch.Apply ( ApplyState, applyToTree, effectOnFilePaths )
import Darcs.Patch.Witnesses.Ordered ( RL(..), FL(..), (+>+), mapFL_FL
, (:>)(..), reverseRL, reverseFL
, mapFL, concatFL, toFL )
import Darcs.Patch.Witnesses.Eq ( EqCheck(IsEq, NotEq) )
import Darcs.Patch.Witnesses.Unsafe ( unsafeCoerceP )
import Darcs.Patch.Witnesses.Sealed ( Sealed(Sealed), seal, unFreeLeft, mapSeal
, freeGap, emptyGap, joinGap, FreeLeft, Gap(..) )
import Darcs.Patch.Commute ( selfCommuter )
import Darcs.Patch.CommuteFn ( commuterIdRL )
import Darcs.Patch.Permutations ( partitionConflictingFL, partitionRL )
import Darcs.Patch.PatchInfoAnd ( PatchInfoAnd, n2pia )
import Darcs.Patch.Prim.V1 ()
import Darcs.Patch.Prim.V1.Core ( FilePatchType( Hunk ), Prim(..) )
import Darcs.Patch.Prim.Class ( PrimConstruct, PrimCanonize )
import Darcs.Patch.TokenReplace ( breakToTokens, defaultToks )
import Darcs.Repository.Flags ( UseIndex(..), ScanKnown(..), DiffAlgorithm(..) )
import Darcs.Util.Global ( darcsdir )
import Darcs.Repository.InternalTypes ( Repository(..) )
import Darcs.Repository.Format(formatHas, RepoProperty(NoWorkingDir))
import qualified Darcs.Repository.Pending as Pending
import Darcs.Repository.Prefs ( filetypeFunction, boringRegexps )
import Darcs.Repository.Diff ( treeDiff )
import Darcs.Util.Path( AnchoredPath(..), anchorPath, floatPath, Name(..), fn2fp,
SubPath, sp2fn, filterPaths
, parents, replacePrefixPath, anchoredRoot
, toFilePath, simpleSubPath, normPath, floatSubPath )
import Darcs.Util.Hash( Hash( NoHash ) )
import Darcs.Util.Tree( Tree, restrict, FilterTree, expand, emptyTree, overlay, find
, ItemType(..), itemType, readBlob, modifyTree, findFile, TreeItem(..)
, makeBlobBS, expandPath )
import Darcs.Util.Tree.Plain( readPlainTree )
import Darcs.Util.Tree.Hashed( darcsTreeHash, readDarcsHashed, decodeDarcsHash, decodeDarcsSize )
import qualified Darcs.Util.Index as I
import qualified Darcs.Util.Tree as Tree
import Darcs.Util.Index ( listFileIDs, getFileID )
newtype TreeFilter m = TreeFilter { applyTreeFilter :: forall tr . FilterTree tr m => tr m -> tr m }
readPendingLL :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wT -> IO (Sealed ((FL p) wT))
readPendingLL repo = mapSeal (mapFL_FL fromPrim) `fmap` Pending.readPending repo
restrictSubpaths :: forall rt p m wR wU wT. (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wT -> [SubPath]
-> IO (TreeFilter m)
restrictSubpaths repo subpaths = do
Sealed pending <- readPendingLL repo
let paths = map (fn2fp . sp2fn) subpaths
paths' = paths `union` effectOnFilePaths pending paths
anchored = map floatPath paths'
restrictPaths :: FilterTree tree m => tree m -> tree m
restrictPaths = Tree.filter (filterPaths anchored)
return (TreeFilter restrictPaths)
maybeRestrictSubpaths :: forall rt p m wR wU wT. (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wT
-> Maybe [SubPath]
-> IO (TreeFilter m)
maybeRestrictSubpaths repo = maybe (return $ TreeFilter id) (restrictSubpaths repo)
inDarcsDir :: AnchoredPath -> Bool
inDarcsDir (AnchoredPath (Name x:_)) | x == BC.pack darcsdir = True
inDarcsDir _ = False
restrictBoring :: forall m . Tree m -> IO (TreeFilter m)
restrictBoring guide = do
boring <- boringRegexps
let boring' p | inDarcsDir p = False
boring' p = not $ any (\rx -> isJust $ matchRegex rx p') boring
where p' = anchorPath "" p
restrictTree :: FilterTree t m => t m -> t m
restrictTree = Tree.filter $ \p _ -> case find guide p of
Nothing -> boring' p
_ -> True
return (TreeFilter restrictTree)
restrictDarcsdir :: forall m . TreeFilter m
restrictDarcsdir = TreeFilter $ Tree.filter $ \p _ -> not (inDarcsDir p)
unrecordedChanges :: forall rt p wR wU wT . (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree)
=> (UseIndex, ScanKnown, DiffAlgorithm) -> Repository rt p wR wU wT
-> Maybe [SubPath] -> IO (FL (PrimOf p) wT wU)
unrecordedChanges = unrecordedChangesWithPatches NilFL NilFL
unrecordedChangesWithPatches :: forall rt p wR wU wT wX. (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree)
=> FL (PrimOf p) wX wT
-> FL (PrimOf p) wT wT
-> (UseIndex, ScanKnown, DiffAlgorithm) -> Repository rt p wR wU wT
-> Maybe [SubPath]
-> IO (FL (PrimOf p) wT wU)
unrecordedChangesWithPatches movPs replPs opts r paths = do
(pending :> working) <- readPendingAndWorkingWithPatches movPs replPs opts r paths
return $ sortCoalesceFL (pending +>+ unsafeCoerceP (movPs +>+ replPs) +>+ working)
readPendingAndWorkingWithPatches :: forall rt p wR wU wT wZ. (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree)
=> FL (PrimOf p) wZ wT
-> FL (PrimOf p) wT wT
-> (UseIndex, ScanKnown, DiffAlgorithm)
-> Repository rt p wR wU wT
-> Maybe [SubPath]
-> IO ((FL (PrimOf p) :> FL (PrimOf p)) wT wU)
readPendingAndWorkingWithPatches _ _ _ r@(Repo _ rf _ _) _ | formatHas NoWorkingDir rf = do
IsEq <- return $ workDirLessRepoWitness r
return (NilFL :> NilFL)
readPendingAndWorkingWithPatches movPs replPs (useidx', scan, dflag) repo mbpaths = do
let allPatches = movPs +>+ replPs
let useidx = case allPatches of
NilFL -> useidx'
_ -> IgnoreIndex
(all_current, Sealed (pending :: FL p wT wX)) <- readPending repo
all_current_with_patches <- applyToTree allPatches all_current
relevant <- maybeRestrictSubpaths repo mbpaths
let getIndex = applyToTree movPs =<< I.updateIndex =<< (applyTreeFilter relevant <$> readIndex repo)
current = applyTreeFilter relevant all_current_with_patches
working <- filteredWorking useidx scan relevant getIndex current
ft <- filetypeFunction
Sealed (diff :: FL (PrimOf p) wX wY) <- (unFreeLeft `fmap` treeDiff dflag ft current working) :: IO (Sealed (FL (PrimOf p) wX))
IsEq <- return (unsafeCoerceP IsEq) :: IO (EqCheck wY wU)
return (effect pending :> diff)
readPendingAndWorking :: forall rt p wR wU wT . (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree)
=> (UseIndex, ScanKnown, DiffAlgorithm)
-> Repository rt p wR wU wT
-> Maybe [SubPath]
-> IO ((FL (PrimOf p) :> FL (PrimOf p)) wT wU)
readPendingAndWorking = readPendingAndWorkingWithPatches NilFL NilFL
filteredWorking :: UseIndex
-> ScanKnown
-> TreeFilter IO
-> IO (Tree IO)
-> Tree IO
-> IO (Tree IO)
filteredWorking useidx scan relevant getIndex pending_tree = do
index <- getIndex
applyTreeFilter restrictDarcsdir <$> case scan of
ScanKnown -> case useidx of
UseIndex -> getIndex
IgnoreIndex -> do
guide <- expand pending_tree
applyTreeFilter relevant . restrict guide <$> readPlainTree "."
ScanAll -> do
nonboring <- restrictBoring index
plain <- applyTreeFilter relevant . applyTreeFilter nonboring <$> readPlainTree "."
return $ case useidx of
UseIndex -> plain `overlay` index
IgnoreIndex -> plain
ScanBoring -> do
plain <- applyTreeFilter relevant <$> readPlainTree "."
return $ case useidx of
UseIndex -> plain `overlay` index
IgnoreIndex -> plain
workDirLessRepoWitness :: Repository rt p wR wU wT -> EqCheck wU wT
workDirLessRepoWitness (Repo _ rf _ _)
| formatHas NoWorkingDir rf = unsafeCoerceP IsEq
| otherwise = NotEq
readRecorded :: (RepoPatch p, ApplyState p ~ Tree) => Repository rt p wR wU wT -> IO (Tree IO)
readRecorded _repo = do
let h_inventory = darcsdir </> "hashed_inventory"
hashed <- doesFileExist h_inventory
if hashed
then do inv <- B.readFile h_inventory
let linesInv = BC.split '\n' inv
case linesInv of
[] -> return emptyTree
(pris_line:_) -> do
let hash = decodeDarcsHash $ B.drop 9 pris_line
size = decodeDarcsSize $ B.drop 9 pris_line
when (hash == NoHash) $ fail $ "Bad pristine root: " ++ show pris_line
readDarcsHashed (darcsdir </> "pristine.hashed") (size, hash)
else do have_pristine <- doesDirectoryExist $ darcsdir </> "pristine"
have_current <- doesDirectoryExist $ darcsdir </> "current"
case (have_pristine, have_current) of
(True, _) -> readPlainTree $ darcsdir </> "pristine"
(False, True) -> readPlainTree $ darcsdir </> "current"
(_, _) -> fail "No pristine tree is available!"
readUnrecorded :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wT -> Maybe [SubPath] -> IO (Tree IO)
readUnrecorded repo mbpaths = do
relevant <- maybeRestrictSubpaths repo mbpaths
readIndex repo >>= I.updateIndex . applyTreeFilter relevant
readUnrecordedFiltered :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wT
-> UseIndex
-> ScanKnown
-> Maybe [SubPath] -> IO (Tree IO)
readUnrecordedFiltered repo useidx scan mbpaths = do
(all_current, _) <- readPending repo
relevant <- maybeRestrictSubpaths repo mbpaths
let getIndex = I.updateIndex =<< (applyTreeFilter relevant <$> readIndex repo)
current = applyTreeFilter relevant all_current
filteredWorking useidx scan relevant getIndex current
readWorking :: IO (Tree IO)
readWorking = expand =<< (nodarcs `fmap` readPlainTree ".")
where nodarcs = Tree.filter (\(AnchoredPath (Name x:_)) _ -> x /= BC.pack darcsdir)
readRecordedAndPending :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wT -> IO (Tree IO)
readRecordedAndPending repo = fst `fmap` readPending repo
readPending :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wT -> IO (Tree IO, Sealed (FL p wT))
readPending repo =
do Sealed pending <- readPendingLL repo
pristine <- readRecorded repo
catch ((\t -> (t, seal pending)) `fmap` applyToTree pending pristine) $ \ (err :: IOException) -> do
putStrLn $ "Yikes, pending has conflicts! " ++ show err
putStrLn "Stashing the buggy pending as _darcs/patches/pending_buggy"
renameFile (darcsdir </> "patches" </> "pending")
(darcsdir </> "patches" </> "pending_buggy")
return (pristine, seal NilFL)
invalidateIndex :: t -> IO ()
invalidateIndex _ = B.writeFile (darcsdir </> "index_invalid") B.empty
readIndex :: (RepoPatch p, ApplyState p ~ Tree) => Repository rt p wR wU wT -> IO I.Index
readIndex repo = do
invalid <- doesFileExist $ darcsdir </> "index_invalid"
exists <- doesFileExist $ darcsdir </> "index"
formatValid <- if exists
then I.indexFormatValid $ darcsdir </> "index"
else return True
when (exists && not formatValid) $ removeFile $ darcsdir </> "index"
if not exists || invalid || not formatValid
then do pris <- readRecordedAndPending repo
idx <- I.updateIndexFrom (darcsdir </> "index") darcsTreeHash pris
when invalid $ removeFile $ darcsdir </> "index_invalid"
return idx
else I.readIndex (darcsdir </> "index") darcsTreeHash
updateIndex :: (RepoPatch p, ApplyState p ~ Tree) => Repository rt p wR wU wT -> IO ()
updateIndex repo = do
invalid <- doesFileExist $ darcsdir </> "index_invalid"
exists <- doesFileExist $ darcsdir </> "index"
formatValid <- if exists
then I.indexFormatValid $ darcsdir </> "index"
else return True
when (exists && not formatValid) $ removeFile $ darcsdir </> "index"
pris <- readRecordedAndPending repo
_ <- I.updateIndexFrom (darcsdir </> "index") darcsTreeHash pris
when invalid $ removeFile $ darcsdir </> "index_invalid"
filterOutConflicts :: (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree)
=> RL (PatchInfoAnd rt p) wX wT
-> Repository rt p wR wU wT
-> FL (PatchInfoAnd rt p) wX wZ
-> IO (Bool, Sealed (FL (PatchInfoAnd rt p) wX))
filterOutConflicts us repository them
= do let commuter = commuterIdRL selfCommuter
unrec <- fmap n2pia . anonymous . fromPrims
=<< unrecordedChanges (UseIndex, ScanKnown, MyersDiff) repository Nothing
them' :> rest <- return $ partitionConflictingFL commuter them (us :<: unrec)
return (check rest, Sealed them')
where check :: FL p wA wB -> Bool
check NilFL = False
check _ = True
getMovesPs :: forall rt p wR wU wB prim.
(PrimConstruct prim, PrimCanonize prim, RepoPatch p,
ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree)
=> Repository rt p wR wU wR
-> Maybe [SubPath]
-> IO (FL prim wB wB)
getMovesPs repository files = mkMovesFL <$> getMovedFiles repository files
where
mkMovesFL [] = NilFL
mkMovesFL ((a,b,_):xs) = move (anchorPath "" a) (anchorPath "" b) :>: mkMovesFL xs
getMovedFiles :: (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree)
=> Repository rt p wR wU wR
-> Maybe [SubPath]
-> IO [(AnchoredPath, AnchoredPath, ItemType)]
getMovedFiles repo fs = do
old <- sortBy (comparing snd) <$> (listFileIDs =<< readIndex repo)
nonboring <- restrictBoring emptyTree
let addIDs = foldM (\xs (p, it)-> do mfid <- getFileID p
return $ case mfid of
Nothing -> xs
Just fid -> ((p, it), fid):xs) []
new <- sortBy (comparing snd) <$>
(addIDs . map (\(a,b) -> (a, itemType b)) . Tree.list =<<
expand =<< applyTreeFilter nonboring <$> readPlainTree ".")
let match (x:xs) (y:ys) | snd x > snd y = match (x:xs) ys
| snd x < snd y = match xs (y:ys)
| snd (fst x) /= snd (fst y) = match xs ys
| otherwise = (fst (fst x), fst (fst y), snd (fst x)):match xs ys
match _ _ = []
movedfiles = match old new
fmovedfiles = case fs of
Nothing -> movedfiles
Just subpath -> filter (\(f1,f2,_) -> any (`elem` selfiles) [f1,f2]) movedfiles
where selfiles = map (floatPath . toFilePath) subpath
return (resolve fmovedfiles)
resolve :: [(AnchoredPath, AnchoredPath, ItemType)] -> [(AnchoredPath, AnchoredPath, ItemType)]
resolve xs = fixPaths $ sortMoves $ deleteCycles xs
where
deleteCycles [] = []
deleteCycles whole@( x@(start,_,_):rest)
= if hasCycle start whole start
then deleteCycles (deleteFrom start whole [])
else x:deleteCycles rest
where hasCycle current ((a',b',_):rest') first
| a' == current = b' == first || hasCycle b' whole first
| otherwise = hasCycle current rest' first
hasCycle _ [] _ = False
deleteFrom a (y@(a',b',_):ys) seen
| a == a' = deleteFrom b' (seen++ys) []
| otherwise = deleteFrom a ys (y:seen)
deleteFrom _ [] seen = seen
sortMoves [] = []
sortMoves whole@(current@(_,dest,_):_) =
smallest:sortMoves (delete smallest whole)
where
smallest = follow dest whole current
follow prevDest (y@(s,d,_):ys) currentSmallest
| prevDest == s = follow d whole y
| d `elem` parents prevDest = follow d whole y
| otherwise = follow prevDest ys currentSmallest
follow _ [] currentSmallest = currentSmallest
fixPaths [] = []
fixPaths (y@(f1,f2,t):ys)
| f1 == f2 = fixPaths ys
| TreeType <- t = y:fixPaths (map replacepp ys)
| otherwise = y:fixPaths ys
where replacepp i@(if1,if2,it) | nfst == anchoredRoot = i
| otherwise = (nfst, if2, it)
where nfst = replacePrefixPath f1 f2 if1
getReplaces :: forall rt p wR wU wT wX. (RepoPatch p, ApplyState p ~ Tree,
ApplyState (PrimOf p) ~ Tree, wX ~ wR)
=> (UseIndex, ScanKnown, DiffAlgorithm)
-> Repository rt p wR wU wT
-> Maybe [SubPath]
-> IO (Sealed (FL (PrimOf p) wX))
getReplaces (useindex, scan, dopts) repo files = do
relevant <- maybeRestrictSubpaths repo files
working <- readUnrecordedFiltered repo useindex scan files
pending <- applyTreeFilter relevant <$> readRecordedAndPending repo
ftf <- filetypeFunction
Sealed changes <- unFreeLeft <$> treeDiff dopts ftf pending working
_ :> hunks <- return $ partitionRL primIsHunk $ reverseFL changes
let allModifiedTokens = concat $ mapFL modifiedTokens (reverseRL hunks)
replaces = rmInvalidReplaces allModifiedTokens
mapSeal concatFL . toFL <$>
mapM (\(f,a,b) -> doReplace defaultToks pending
(fromJust $ simpleSubPath $ fn2fp $ normPath f)
(BC.unpack a) (BC.unpack b)) replaces
where
modifiedTokens (FP f (Hunk _ old new)) =
map (\(a,b) -> (f, a, b)) (concatMap checkModified $
filter (\(a,b) -> length a == length b)
$ zip (map breakToTokens old) (map breakToTokens new))
modifiedTokens _ = error "modifiedTokens: Not Hunk patch"
checkModified = filter (\(a,b) -> a/=b) . uncurry zip
rmInvalidReplaces [] = []
rmInvalidReplaces ((f,old,new):rs)
| any (\(f',a,b) -> f' == f && old == a && b /= new) rs =
rmInvalidReplaces $ filter (\(f'',a',_) -> f'' /= f || a' /= old) rs
rmInvalidReplaces (r:rs) = r:rmInvalidReplaces (filter (/=r) rs)
doReplace toks pend f old new = do
let maybeReplace p = isJust <$> maybeApplyToTree replacePatch p
pendReplaced <- maybeReplace pend
if pendReplaced
then return $ joinGap (:>:) (freeGap replacePatch) (emptyGap NilFL)
else getForceReplace f toks pend old new
where
replacePatch = tokreplace (toFilePath f) toks old new
getForceReplace :: PrimPatch prim => SubPath -> String -> Tree IO -> String -> String
-> IO (FreeLeft (FL prim))
getForceReplace f toks tree old new = do
let path = floatSubPath f
expandedTree <- expandPath tree path
content <- case findFile expandedTree path of
Just blob -> readBlob blob
Nothing -> error $ "getForceReplace: not in tree: " ++ show path
let newcontent = forceTokReplace toks (BC.pack new) (BC.pack old)
(B.concat $ BL.toChunks content)
tree' = modifyTree expandedTree path . Just . File $ makeBlobBS newcontent
ftf <- filetypeFunction
normaliseNewTokPatch <- treeDiff dopts ftf expandedTree tree'
return . joinGap (+>+) normaliseNewTokPatch $ freeGap $
tokreplace (toFilePath f) toks old new :>: NilFL