darcs-2.18.1: src/Darcs/Repository/Pristine.hs
{-# LANGUAGE OverloadedStrings #-}
module Darcs.Repository.Pristine
( applyToTentativePristine
, readHashedPristineRoot
, pokePristineHash
, peekPristineHash
, createPristineDirectoryTree
, readPristine
, writePristine
, convertSizePrefixedPristine
) where
import Darcs.Prelude
import Control.Exception ( catch, IOException, throwIO )
import System.Directory ( withCurrentDirectory )
import System.FilePath.Posix ( (</>) )
import System.IO ( hPutStrLn, stderr )
import System.IO.Error ( catchIOError )
import Darcs.Patch ( PatchInfoAnd, RepoPatch, description )
import Darcs.Patch.Apply ( Apply(..) )
import Darcs.Patch.Invertible ( Invertible )
import Darcs.Patch.Show ( ShowPatch )
import Darcs.Patch.Witnesses.Ordered ( FL )
import Darcs.Repository.Flags ( WithWorkingDir(..) )
import Darcs.Repository.Format ( RepoProperty(HashedInventory), formatHas )
import Darcs.Repository.Inventory
import Darcs.Repository.InternalTypes
( Repository
, AccessType(..)
, SAccessType(..)
, repoAccessType
, repoCache
, repoFormat
, repoLocation
, withRepoDir
)
import Darcs.Repository.Old ( oldRepoFailMsg )
import Darcs.Repository.Paths
( hashedInventoryPath
, tentativePristinePath
)
import Darcs.Util.ByteString ( gzReadFilePS )
import Darcs.Util.Cache ( Cache )
import Darcs.Util.Lock ( writeDocBinFile )
import Darcs.Util.Printer ( ($$), renderString, text )
import Darcs.Util.Tree ( Tree )
import Darcs.Util.Tree.Hashed
( darcsAddMissingHashes
, darcsTreeHash
, hashedTreeIO
, readDarcsHashed
, readDarcsHashedNosize
, writeDarcsHashed
)
import Darcs.Util.Tree.Plain ( writePlainTree )
import Darcs.Util.ValidHash ( fromHash, getSize )
-- | Apply a patch to the 'Tree' identified by the given root 'PristineHash',
-- then return the root hash of the result. The 'ApplyDir' argument says
-- whether to add or remove the changes. The 'Cache' argument specifies the
-- possible locations for hashed files.
applyToHashedPristine :: (Apply p, ApplyState p ~ Tree, ShowPatch p)
=> Cache
-> PristineHash
-> p wX wY
-> IO PristineHash
applyToHashedPristine cache root patch = tryApply `catchIOError` annotateError
where
tryApply :: IO PristineHash
tryApply = do
-- Read a non-size-prefixed pristine, failing if we encounter one.
tree <- readDarcsHashedNosize cache root
(_, updatedTree) <- hashedTreeIO (apply patch) tree cache
return $ fromHash $ darcsTreeHash updatedTree
annotateError e =
throwIO $
userError $
renderString $
"Cannot apply patch to pristine:" $$ (description patch) $$
"You may want to run 'darcs repair' on the repository containing this patch." $$
"Reason: " <> text (show e)
convertSizePrefixedPristine :: Cache -> PristineHash -> IO PristineHash
convertSizePrefixedPristine cache ph = do
case getSize ph of
Nothing -> return ph
Just _ -> do
hPutStrLn stderr "Converting pristine..."
-- Read the old size-prefixed pristine tree
old <- readDarcsHashed cache ph
-- Write out the pristine tree as a non-size-prefixed pristine
-- and return the new root hash.
writeDarcsHashed old cache
-- | Apply an 'FL' of 'Invertible' patches tentative pristine tree, and update
-- the tentative pristine hash. The patches need to be 'Invertible' so that we
-- can use it when removing patches from the repository, too.
applyToTentativePristine :: (ApplyState p ~ Tree, RepoPatch p)
=> Repository 'RW p wU wR
-> Invertible (FL (PatchInfoAnd p)) wR wY
-> IO ()
applyToTentativePristine r p = do
tentativePristine <- gzReadFilePS tentativePristinePath
-- Extract the pristine hash from the tentativePristine file, using
-- peekPristineHash (this is valid since we normally just extract the hash
-- from the first line of an inventory file; we can pass in a one-line file
-- that just contains said hash).
let tentativePristineHash = peekPristineHash tentativePristine
newPristineHash <- applyToHashedPristine (repoCache r) tentativePristineHash p
writeDocBinFile tentativePristinePath $
pokePristineHash newPristineHash tentativePristine
readHashedPristineRoot :: Repository rt p wU wR -> IO PristineHash
readHashedPristineRoot r =
withRepoDir r $
case repoAccessType r of
SRO -> getHash hashedInventoryPath
SRW -> getHash tentativePristinePath -- note the asymmetry!
where
getHash path =
peekPristineHash <$>
gzReadFilePS path `catch` (\(_ :: IOException) -> fail oldRepoFailMsg)
-- | Write the pristine tree into a plain directory at the given path.
createPristineDirectoryTree ::
Repository rt p wU wR -> FilePath -> WithWorkingDir -> IO ()
createPristineDirectoryTree r _ NoWorkingDir = do
tree <- readPristine r
-- evaluate the tree to force copying of pristine files
_ <- darcsAddMissingHashes tree
return ()
createPristineDirectoryTree r dir WithWorkingDir = do
tree <- readPristine r
writePlainTree tree dir
-- | Obtains a Tree corresponding to the "recorded" state of the repository:
-- this is the same as the pristine cache, which is the same as the result of
-- applying all the repository's patches to an empty directory.
readPristine :: Repository rt p wU wR -> IO (Tree IO)
readPristine repo
| formatHas HashedInventory (repoFormat repo) =
case repoAccessType repo of
SRO -> do
inv <- gzReadFilePS $ repoLocation repo </> hashedInventoryPath
let root = peekPristineHash inv
readDarcsHashed (repoCache repo) root
SRW -> do
hash <-
peekPristineHash <$>
gzReadFilePS (repoLocation repo </> tentativePristinePath)
readDarcsHashedNosize (repoCache repo) hash
| otherwise = fail oldRepoFailMsg
-- | Replace the existing pristine with a new one (loaded up in a Tree object).
-- Warning: If @rt ~ 'RO@ this overwrites the recorded state, use only when
-- creating a new repo!
writePristine :: Repository rt p wU wR -> Tree IO -> IO PristineHash
writePristine repo tree =
withCurrentDirectory (repoLocation repo) $ do
tree' <- darcsAddMissingHashes tree
root <- writeDarcsHashed tree' (repoCache repo)
-- now update the current pristine hash
case repoAccessType repo of
SRO -> putHash root hashedInventoryPath
SRW -> putHash root tentativePristinePath -- note the asymmetry!
where
putHash root path = do
content <- gzReadFilePS path
writeDocBinFile path $ pokePristineHash root content
return root