{-# LANGUAGE CPP #-}
module Darcs.Repository.State
( restrictSubpaths, restrictBoring, TreeFilter(..), restrictDarcsdir
, unrecordedChanges
, readRecorded, readUnrecorded, readRecordedAndPending, readWorking
, readPendingAndWorking, readUnrecordedFiltered
, readIndex, updateIndex, invalidateIndex, UseIndex(..), ScanKnown(..)
, filterOutConflicts
, addPendingDiffToPending, addToPending
) where
import Darcs.Prelude
import Control.Monad ( when, foldM, forM )
import Control.Monad.State ( StateT, runStateT, get, put, liftIO )
import Control.Exception ( catch, IOException )
import Data.Maybe ( isJust )
import Data.Ord ( comparing )
import Data.List ( sortBy, union, delete )
import Text.Regex( matchRegex )
import System.Directory( removeFile, doesFileExist, doesDirectoryExist, renameFile )
import System.FilePath
( (</>)
#if mingw32_HOST_OS
, (<.>)
#endif
)
import System.IO ( hPutStrLn, stderr )
import System.IO.Error ( catchIOError )
import qualified Data.ByteString as B
( ByteString, readFile, writeFile, empty, concat )
import qualified Data.ByteString.Char8 as BC
( pack, unpack )
import qualified Data.ByteString.Lazy as BL ( toChunks )
import Darcs.Patch ( RepoPatch, PrimOf, sortCoalesceFL
, PrimPatch, maybeApplyToTree
, tokreplace, forceTokReplace, move )
import Darcs.Patch.Named ( anonymous )
import Darcs.Patch.Apply ( ApplyState, applyToTree, effectOnPaths )
import Darcs.Patch.Witnesses.Ordered ( FL(..), (+>+)
, (:>)(..), reverseRL, reverseFL
, mapFL, concatFL, toFL, nullFL )
import Darcs.Patch.Witnesses.Eq ( EqCheck(IsEq, NotEq) )
import Darcs.Patch.Witnesses.Unsafe ( unsafeCoerceP, unsafeCoercePEnd )
import Darcs.Patch.Witnesses.Sealed ( Sealed(Sealed), seal, unFreeLeft, mapSeal
, freeGap, emptyGap, joinGap, FreeLeft, Gap(..) )
import Darcs.Patch.Commute ( commuteFL )
import Darcs.Patch.Permutations ( partitionConflictingFL, genCommuteWhatWeCanRL )
import Darcs.Patch.PatchInfoAnd ( PatchInfoAnd, n2pia )
import Darcs.Patch.FileHunk ( FileHunk(..), IsHunk(..) )
import Darcs.Patch.TokenReplace ( breakToTokens, defaultToks )
import Darcs.Repository.Flags ( UseIndex(..), ScanKnown(..), DiffAlgorithm(..)
, UpdatePending(..), LookForMoves(..), LookForReplaces(..) )
import Darcs.Repository.InternalTypes ( Repository, repoFormat, repoLocation )
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.Repository.Inventory ( peekPristineHash, getValidHash )
import Darcs.Repository.Paths
( pristineDirPath
, hashedInventoryPath
, oldPristineDirPath
, oldCurrentDirPath
, patchesDirPath
, indexPath
, indexInvalidPath
)
import Darcs.Util.Path
( AnchoredPath
, anchorPath
, filterPaths
, inDarcsdir
, parents
, movedirfilename
)
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 qualified Darcs.Util.Tree.Plain as PlainTree ( 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 )
#define TEST_INDEX 0
#if TEST_INDEX
import Control.Monad ( unless )
import Darcs.Util.Path ( displayPath )
import Darcs.Util.Tree ( list )
#endif
newtype TreeFilter m = TreeFilter { applyTreeFilter :: forall tr . FilterTree tr m => tr m -> tr m }
restrictSubpaths :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wT -> [AnchoredPath]
-> IO (TreeFilter m)
restrictSubpaths repo paths = do
Sealed pending <- Pending.readPending repo
restrictSubpathsAfter pending repo paths
restrictSubpathsAfter :: (RepoPatch p, ApplyState p ~ Tree)
=> FL (PrimOf p) wR wP
-> Repository rt p wR wU wT
-> [AnchoredPath]
-> IO (TreeFilter m)
restrictSubpathsAfter pending _repo paths = do
let paths' = paths `union` effectOnPaths pending paths
restrictPaths :: FilterTree tree m => tree m -> tree m
restrictPaths = Tree.filter (filterPaths paths')
return (TreeFilter restrictPaths)
maybeRestrictSubpaths :: (RepoPatch p, ApplyState p ~ Tree)
=> FL (PrimOf p) wR wP
-> Repository rt p wR wU wT
-> Maybe [AnchoredPath]
-> IO (TreeFilter m)
maybeRestrictSubpaths pending repo =
maybe (return $ TreeFilter id) (restrictSubpathsAfter pending repo)
restrictBoring :: 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 :: TreeFilter m
restrictDarcsdir = TreeFilter $ Tree.filter $ \p _ -> not (inDarcsdir p)
unrecordedChanges :: (RepoPatch p, ApplyState p ~ Tree)
=> (UseIndex, ScanKnown, DiffAlgorithm)
-> LookForMoves
-> LookForReplaces
-> Repository rt p wR wU wR
-> Maybe [AnchoredPath] -> IO (FL (PrimOf p) wR wU)
unrecordedChanges dopts lfm lfr r paths = do
(pending :> working) <- readPendingAndWorking dopts lfm lfr r paths
return $ sortCoalesceFL (pending +>+ working)
readPendingAndWorking :: (RepoPatch p, ApplyState p ~ Tree)
=> (UseIndex, ScanKnown, DiffAlgorithm)
-> LookForMoves
-> LookForReplaces
-> Repository rt p wR wU wR
-> Maybe [AnchoredPath]
-> IO ((FL (PrimOf p) :> FL (PrimOf p)) wR wU)
readPendingAndWorking _ _ _ r _ | formatHas NoWorkingDir (repoFormat r) = do
IsEq <- return $ workDirLessRepoWitness r
return (NilFL :> NilFL)
readPendingAndWorking (useidx, scan, diffalg) lfm lfr repo mbpaths = do
(pending_tree, working_tree, (pending :> moves)) <-
readPendingAndMovesAndUnrecorded repo useidx scan lfm mbpaths
(pending_tree_with_replaces, Sealed replaces) <-
getReplaces lfr diffalg repo pending_tree working_tree
ft <- filetypeFunction
wrapped_diff <- treeDiff diffalg ft pending_tree_with_replaces working_tree
case unFreeLeft wrapped_diff of
Sealed diff -> do
return $ unsafeCoercePEnd $ pending :> (moves +>+ replaces +>+ diff)
readPendingAndMovesAndUnrecorded
:: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR
-> UseIndex
-> ScanKnown
-> LookForMoves
-> Maybe [AnchoredPath]
-> IO ( Tree IO
, Tree IO
, (FL (PrimOf p) :> FL (PrimOf p)) wR wU
)
readPendingAndMovesAndUnrecorded repo useidx scan lfm mbpaths = do
(pending_tree, Sealed pending) <- readPending repo
moves <- getMoves lfm repo mbpaths
let pending' = pending +>+ moves
relevant <- maybeRestrictSubpaths pending' repo mbpaths
pending_tree' <-
applyTreeFilter relevant <$> applyToTree moves pending_tree
let useidx' = if nullFL moves then useidx else IgnoreIndex
index <-
applyToTree moves =<< readIndexOrPlainTree repo useidx relevant pending_tree
working_tree <- filteredWorking repo useidx' scan relevant index pending_tree'
return (pending_tree', working_tree, unsafeCoercePEnd (pending :> moves))
filteredWorking :: Repository rt p wR wU wR
-> UseIndex
-> ScanKnown
-> TreeFilter IO
-> Tree IO
-> Tree IO
-> IO (Tree IO)
filteredWorking repo useidx scan relevant index pending_tree =
applyTreeFilter restrictDarcsdir <$> applyTreeFilter relevant <$> do
case useidx of
UseIndex ->
case scan of
ScanKnown -> return index
ScanAll -> do
nonboring <- restrictBoring index
plain <- applyTreeFilter nonboring <$> readPlainTree repo
return $ plain `overlay` index
ScanBoring -> do
plain <- readPlainTree repo
return $ plain `overlay` index
IgnoreIndex ->
case scan of
ScanKnown -> do
guide <- expand pending_tree
restrict guide <$> readPlainTree repo
ScanAll -> do
guide <- expand pending_tree
nonboring <- restrictBoring guide
applyTreeFilter nonboring <$> readPlainTree repo
ScanBoring -> readPlainTree repo
workDirLessRepoWitness :: Repository rt p wR wU wT -> EqCheck wU wT
workDirLessRepoWitness r
| formatHas NoWorkingDir (repoFormat r) = unsafeCoerceP IsEq
| otherwise = NotEq
readRecorded :: Repository rt p wR wU wT -> IO (Tree IO)
readRecorded _repo = do
hashed <- doesFileExist hashedInventoryPath
if hashed
then do inv <- B.readFile hashedInventoryPath
let pris = peekPristineHash inv
hash = decodeDarcsHash $ BC.pack $ getValidHash pris
size = decodeDarcsSize $ BC.pack $ getValidHash pris
when (hash == NoHash) $
fail $ "Bad pristine root: " ++ getValidHash pris
readDarcsHashed pristineDirPath (size, hash)
else do have_pristine <- doesDirectoryExist $ oldPristineDirPath
have_current <- doesDirectoryExist $ oldCurrentDirPath
case (have_pristine, have_current) of
(True, _) -> PlainTree.readPlainTree $ oldPristineDirPath
(False, True) -> PlainTree.readPlainTree $ oldCurrentDirPath
(_, _) -> fail "No pristine tree is available!"
readUnrecorded :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR
-> UseIndex
-> Maybe [AnchoredPath]
-> IO (Tree IO)
readUnrecorded repo useidx mbpaths = do
#if TEST_INDEX
t1 <- expand =<< readUnrecordedFiltered repo useidx ScanKnown NoLookForMoves mbpaths
(pending_tree, Sealed pending) <- readPending repo
relevant <- maybeRestrictSubpaths pending repo mbpaths
t2 <- readIndexOrPlainTree repo useidx relevant pending_tree
assertEqualTrees "indirect" t1 "direct" t2
return t1
#else
expand =<< readUnrecordedFiltered repo useidx ScanKnown NoLookForMoves mbpaths
#endif
#if TEST_INDEX
assertEqualTrees :: String -> Tree m -> String -> Tree m -> IO ()
assertEqualTrees n1 t1 n2 t2 =
unless (t1 `eqTree` t2) $
fail $ "Trees are not equal!\n" ++ showTree n1 t1 ++ showTree n2 t2
eqTree :: Tree m -> Tree m -> Bool
eqTree t1 t2 = map fst (list t1) == map fst (list t2)
showTree :: String -> Tree m -> String
showTree name tree = unlines (name : map ((" "++) . displayPath . fst) (list tree))
#endif
readIndexOrPlainTree :: (ApplyState p ~ Tree, RepoPatch p)
=> Repository rt p wR wU wR
-> UseIndex
-> TreeFilter IO
-> Tree IO
-> IO (Tree IO)
#if TEST_INDEX
readIndexOrPlainTree repo useidx treeFilter pending_tree = do
indexTree <-
I.updateIndex =<< applyTreeFilter treeFilter <$> readIndex repo
plainTree <- do
guide <- expand pending_tree
expand =<< applyTreeFilter treeFilter . restrict guide <$> readPlainTree repo
assertEqualTrees "index tree" indexTree "plain tree" plainTree
return $
case useidx of
UseIndex -> indexTree
IgnoreIndex -> plainTree
#else
readIndexOrPlainTree repo UseIndex treeFilter pending_tree =
(I.updateIndex =<< applyTreeFilter treeFilter <$> readIndex repo)
`catchIOError` \e -> do
hPutStrLn stderr ("Warning, cannot access the index:\n" ++ show e)
readIndexOrPlainTree repo IgnoreIndex treeFilter pending_tree
readIndexOrPlainTree repo IgnoreIndex treeFilter pending_tree = do
guide <- expand pending_tree
expand =<< applyTreeFilter treeFilter . restrict guide <$> readPlainTree repo
#endif
readUnrecordedFiltered :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR
-> UseIndex
-> ScanKnown
-> LookForMoves
-> Maybe [AnchoredPath] -> IO (Tree IO)
readUnrecordedFiltered repo useidx scan lfm mbpaths = do
(_, working_tree, _) <-
readPendingAndMovesAndUnrecorded repo useidx scan lfm mbpaths
return working_tree
readWorking :: TreeFilter IO -> IO (Tree IO)
readWorking relevant =
expand =<<
(applyTreeFilter relevant . applyTreeFilter restrictDarcsdir <$>
PlainTree.readPlainTree ".")
readRecordedAndPending :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR -> IO (Tree IO)
readRecordedAndPending repo = fst `fmap` readPending repo
readPending :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR
-> IO (Tree IO, Sealed (FL (PrimOf p) wR))
readPending repo = do
pristine <- readRecorded repo
Sealed pending <- Pending.readPending repo
catch ((\t -> (t, seal pending)) <$> applyToTree pending pristine) $
\(err :: IOException) -> do
putStrLn $ "Yikes, pending has conflicts! " ++ show err
putStrLn "Stashing the buggy pending as _darcs/patches/pending_buggy"
renameFile (patchesDirPath </> "pending")
(patchesDirPath </> "pending_buggy")
return (pristine, seal NilFL)
invalidateIndex :: t -> IO ()
invalidateIndex _ = B.writeFile indexInvalidPath B.empty
readIndex :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR -> IO I.Index
readIndex repo = do
(invalid, exists, formatValid) <- checkIndex
if not exists || invalid || not formatValid
then do pris <- readRecordedAndPending repo
idx <- I.updateIndexFrom indexPath darcsTreeHash pris
when invalid $ removeFile indexInvalidPath
return idx
else I.readIndex indexPath darcsTreeHash
updateIndex :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR -> IO ()
updateIndex repo = do
(invalid, _, _) <- checkIndex
pris <- readRecordedAndPending repo
_ <- I.updateIndexFrom indexPath darcsTreeHash pris
when invalid $ removeFile indexInvalidPath
checkIndex :: IO (Bool, Bool, Bool)
checkIndex = do
invalid <- doesFileExist $ indexInvalidPath
exists <- doesFileExist indexPath
formatValid <- if exists
then I.indexFormatValid indexPath
else return True
when (exists && not formatValid) $ do
#if mingw32_HOST_OS
renameFile indexPath (indexPath <.> "old")
#else
removeFile indexPath
#endif
return (invalid, exists, formatValid)
filterOutConflicts
:: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR
-> FL (PatchInfoAnd rt p) wX wR
-> FL (PatchInfoAnd rt p) wX wZ
-> IO (Bool, Sealed (FL (PatchInfoAnd rt p) wX))
filterOutConflicts repository us them
= do
unrec <- fmap n2pia . anonymous
=<< unrecordedChanges (UseIndex, ScanKnown, MyersDiff)
NoLookForMoves NoLookForReplaces repository Nothing
them' :> rest <-
return $ partitionConflictingFL them (us +>+ unrec :>: NilFL)
return (check rest, Sealed them')
where check :: FL p wA wB -> Bool
check NilFL = False
check _ = True
getMoves :: forall rt p wR wU wB prim.
(RepoPatch p, ApplyState p ~ Tree, prim ~ PrimOf p)
=> LookForMoves
-> Repository rt p wR wU wR
-> Maybe [AnchoredPath]
-> IO (FL prim wB wB)
getMoves NoLookForMoves _ _ = return NilFL
getMoves YesLookForMoves repository files =
mkMovesFL <$> getMovedFiles repository files
where
mkMovesFL [] = NilFL
mkMovesFL ((a,b,_):xs) = move a b :>: mkMovesFL xs
getMovedFiles :: Repository rt p wR wU wR
-> Maybe [AnchoredPath]
-> 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 repository)
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 paths ->
filter (\(f1, f2, _) -> any (`elem` selfiles) [f1, f2]) movedfiles
where selfiles = paths
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 (if1,if2,it) = (movedirfilename f1 f2 if1, if2, it)
getReplaces :: forall rt p wR wU wT
. (RepoPatch p, ApplyState p ~ Tree)
=> LookForReplaces
-> DiffAlgorithm
-> Repository rt p wR wU wT
-> Tree IO
-> Tree IO
-> IO (Tree IO,
Sealed (FL (PrimOf p) wU))
getReplaces NoLookForReplaces _ _ pending _ = return (pending, Sealed NilFL)
getReplaces YesLookForReplaces diffalg _repo pending working = do
ftf <- filetypeFunction
Sealed changes <- unFreeLeft <$> treeDiff diffalg ftf pending working
let allModifiedTokens = concat $ mapFL modifiedTokens changes
replaces = rmInvalidReplaces allModifiedTokens
(patches, new_pending) <-
flip runStateT pending $
forM replaces $ \(path, a, b) ->
doReplace defaultToks path (BC.unpack a) (BC.unpack b)
return (new_pending, mapSeal concatFL $ toFL patches)
where
modifiedTokens :: PrimOf p wX wY -> [(AnchoredPath, B.ByteString, B.ByteString)]
modifiedTokens p = case isHunk p of
Just (FileHunk f _ old new) ->
map (\(a,b) -> (f, a, b)) (concatMap checkModified $
filter (\(a,b) -> length a == length b)
$ zip (map breakToTokens old) (map breakToTokens new))
Nothing -> []
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 path old new = do
pend <- get
mpend' <- liftIO $ maybeApplyToTree replacePatch pend
case mpend' of
Nothing -> getForceReplace path toks old new
Just pend' -> do
put pend'
return $ joinGap (:>:) (freeGap replacePatch) (emptyGap NilFL)
where
replacePatch = tokreplace path toks old new
getForceReplace :: (PrimPatch prim, ApplyState prim ~ Tree)
=> AnchoredPath -> String -> String -> String
-> StateT (Tree IO) IO (FreeLeft (FL prim))
getForceReplace path toks old new = do
tree <- get
expandedTree <- liftIO $ expandPath tree path
content <- case findFile expandedTree path of
Just blob -> liftIO $ 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 <- liftIO $ filetypeFunction
normaliseNewTokPatch <- liftIO $ treeDiff diffalg ftf expandedTree tree'
patches <- return $ joinGap (+>+) normaliseNewTokPatch $ freeGap $
tokreplace path toks old new :>: NilFL
mtree'' <- case unFreeLeft patches of
Sealed ps -> liftIO $ maybeApplyToTree ps tree
case mtree'' of
Nothing -> error "getForceReplace: unable to apply detected force replaces"
Just tree'' -> do
put tree''
return patches
addPendingDiffToPending :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR
-> FreeLeft (FL (PrimOf p)) -> IO ()
addPendingDiffToPending repo newP = do
(_, Sealed toPend) <- readPending repo
invalidateIndex repo
case unFreeLeft newP of
(Sealed p) -> do
recordedState <- readRecorded repo
Pending.makeNewPending repo YesUpdatePending (toPend +>+ p) recordedState
addToPending :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository rt p wR wU wR
-> UseIndex -> FL (PrimOf p) wU wY -> IO ()
addToPending repo useidx p = do
(toPend :> toUnrec) <- readPendingAndWorking (useidx, ScanKnown, MyersDiff)
NoLookForMoves NoLookForReplaces repo Nothing
invalidateIndex repo
case genCommuteWhatWeCanRL commuteFL (reverseFL toUnrec :> p) of
(toP' :> p' :> _excessUnrec) -> do
recordedState <- readRecorded repo
Pending.makeNewPending repo YesUpdatePending
(toPend +>+ reverseRL toP' +>+ p') recordedState
readPlainTree :: Repository rt p wR wU wT -> IO (Tree IO)
readPlainTree repo = PlainTree.readPlainTree (repoLocation repo)