-- Copyright (C) 2002-2004,2007-2008 David Roundy -- Copyright (C) 2005 Juliusz Chroboczek -- -- This program is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; either version 2, or (at your option) -- any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program; see the file COPYING. If not, write to -- the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -- Boston, MA 02110-1301, USA. module Darcs.Repository.Pending ( readPending , siftForPending , tentativelyRemoveFromPending , finalizePending , makeNewPending , tentativelyAddToPending , setTentativePending , prepend -- deprecated interface: , pendingName ) where import Prelude () import Darcs.Prelude import Control.Applicative import qualified Data.ByteString as B ( empty ) import Control.Exception ( catch, IOException ) import Data.Maybe ( fromJust, fromMaybe ) import Darcs.Util.Printer ( errorDoc ) import Darcs.Util.Global ( darcsdir ) import Darcs.Util.Lock ( writeDocBinFile , removeFileMayNotExist ) import Darcs.Repository.InternalTypes ( Repository, withRepoLocation ) import Darcs.Repository.Flags ( UpdateWorking (..)) import Darcs.Patch ( readPatch, RepoPatch, PrimOf, tryToShrink , primIsHunk, primIsBinary, commute, invert , primIsAddfile, primIsAdddir, commuteFLorComplain , effect, primIsSetpref, applyToTree ) import Darcs.Patch.PatchInfoAnd ( PatchInfoAnd ) import Darcs.Patch.Progress (progressFL) import Darcs.Patch.Permutations ( commuteWhatWeCanFL , removeFL ) import Darcs.Patch.Prim ( tryShrinkingInverse , PrimPatch ) import Darcs.Patch.Read ( ReadPatch(..), bracketedFL ) import Darcs.Patch.ReadMonads ( ParserM ) import Darcs.Patch.Show ( ShowPatchBasic(..), ShowPatchFor(ForStorage) ) import Darcs.Patch.Apply ( ApplyState ) import Darcs.Util.Tree ( Tree ) import Darcs.Util.Exception ( catchall ) import Darcs.Util.Workaround ( renameFile ) import Darcs.Patch.Witnesses.Eq ( EqCheck(..) ) import Darcs.Patch.Witnesses.Sealed ( Sealed(Sealed), mapSeal, seal , FlippedSeal(FlippedSeal) , flipSeal ) import Darcs.Patch.Witnesses.Unsafe ( unsafeCoerceP, unsafeCoercePStart ) import Darcs.Patch.Witnesses.Ordered ( FL(..), RL(..), (:>)(..), (+>+) , lengthFL, allFL, filterOutFLFL , reverseFL, mapFL ) import Darcs.Util.ByteString ( gzReadFilePS ) import Darcs.Util.Printer ( Doc, ($$), text, vcat, (<+>) ) import Darcs.Util.Progress ( debugMessage ) pendingName :: String pendingName = darcsdir ++ "/patches/pending" newSuffix, tentativeSuffix :: String newSuffix = ".new" tentativeSuffix = ".tentative" -- | Read the contents of pending. -- The return type is currently incorrect as it refers to the tentative -- state rather than the recorded state. readPending :: RepoPatch p => Repository rt p wR wU wT -> IO (Sealed (FL (PrimOf p) wT)) readPending = readPendingFile "" -- |Read the contents of tentative pending. readTentativePending :: RepoPatch p => Repository rt p wR wU wT -> IO (Sealed (FL (PrimOf p) wT)) readTentativePending = readPendingFile tentativeSuffix -- |Read the contents of tentative pending. readNewPending :: RepoPatch p => Repository rt p wR wU wT -> IO (Sealed (FL (PrimOf p) wT)) readNewPending = readPendingFile newSuffix -- |Read the pending file with the given suffix. CWD should be the repository -- directory. readPendingFile :: ReadPatch prim => String -> Repository rt p wR wU wT -> IO (Sealed (FL prim wX)) readPendingFile suffix _ = do pend <- gzReadFilePS (pendingName ++ suffix) `catchall` return B.empty return . maybe (Sealed NilFL) (mapSeal unFLM) . readPatch $ pend -- Wrapper around FL where printed format uses { } except around singletons. -- Now that the Show behaviour of FL p can be customised (using -- showFLBehavior (*)), we could instead change the general behaviour of FL Prim; -- but since the pending code can be kept nicely compartmentalised, it's nicer -- to do it this way. -- (*) bf: This function does not exist. newtype FLM p wX wY = FLM { unFLM :: FL p wX wY } instance ReadPatch p => ReadPatch (FLM p) where readPatch' = mapSeal FLM <$> readMaybeBracketedFL readPatch' '{' '}' instance ShowPatchBasic p => ShowPatchBasic (FLM p) where showPatch f = showMaybeBracketedFL (showPatch f) '{' '}' . unFLM readMaybeBracketedFL :: forall m p wX . ParserM m => (forall wY . m (Sealed (p wY))) -> Char -> Char -> m (Sealed (FL p wX)) readMaybeBracketedFL parser pre post = bracketedFL parser pre post <|> (mapSeal (:>:NilFL) <$> parser) showMaybeBracketedFL :: (forall wX wY . p wX wY -> Doc) -> Char -> Char -> FL p wA wB -> Doc showMaybeBracketedFL _ pre post NilFL = text [pre] $$ text [post] showMaybeBracketedFL printer _ _ (p :>: NilFL) = printer p showMaybeBracketedFL printer pre post ps = text [pre] $$ vcat (mapFL printer ps) $$ text [post] -- |Write the contents of tentative pending. writeTentativePending :: RepoPatch p => Repository rt p wR wU wT -> FL (PrimOf p) wT wY -> IO () writeTentativePending = writePendingFile tentativeSuffix -- |Write the contents of new pending. CWD should be the repository directory. writeNewPending :: RepoPatch p => Repository rt p wR wU wT -> FL (PrimOf p) wT wY -> IO () writeNewPending = writePendingFile newSuffix -- Write a pending file, with the given suffix. CWD should be the repository -- directory. writePendingFile :: ShowPatchBasic prim => String -> Repository rt p wR wU wT -> FL prim wX wY -> IO () writePendingFile suffix _ = writePatch name . FLM where name = pendingName ++ suffix writePatch :: ShowPatchBasic p => FilePath -> p wX wY -> IO () writePatch f p = writeDocBinFile f $ showPatch ForStorage p <> text "\n" -- | @siftForPending ps@ simplifies the candidate pending patch @ps@ -- through a combination of looking for self-cancellations -- (sequences of patches followed by their inverses), coalescing, -- and getting rid of any hunk/binary patches we can commute out -- the back -- -- The visual image of sifting can be quite helpful here. We are -- repeatedly tapping (shrinking) the patch sequence and -- shaking it (sift). Whatever falls out is the pending we want -- to keep. We do this until the sequence looks about as clean as -- we can get it siftForPending :: forall prim wX wY . PrimPatch prim => FL prim wX wY -> Sealed (FL prim wX) siftForPending simple_ps = if allFL (\p -> primIsAddfile p || primIsAdddir p) oldps then seal oldps else fromJust $ do Sealed x <- return $ sift NilFL $ reverseFL oldps return $ case tryToShrink x of ps | lengthFL ps < lengthFL oldps -> siftForPending ps | otherwise -> seal ps where oldps = fromMaybe simple_ps $ tryShrinkingInverse $ crudeSift simple_ps -- get rid of any hunk/binary patches that we can commute out the -- back (ie. we work our way backwards, pushing the patches down -- to the very end and popping them off; so in (addfile f :> hunk) -- we can nuke the hunk, but not so in (hunk :> replace) sift :: FL prim wA wB -> RL prim wC wA -> Sealed (FL prim wC) sift sofar NilRL = seal sofar sift sofar (ps:<:p) | primIsHunk p || primIsBinary p = case commuteFLorComplain (p :> sofar) of Right (sofar' :> _) -> sift sofar' ps Left _ -> sift (p:>:sofar) ps sift sofar (ps:<:p) = sift (p:>:sofar) ps -- | 'crudeSift' can be seen as a first pass approximation of 'siftForPending' -- that works without having to do any commutation. It either returns a -- sifted pending (if the input is simple enough for this crude approach) -- or has no effect. crudeSift :: forall prim wX wY . PrimPatch prim => FL prim wX wY -> FL prim wX wY crudeSift xs = if isSimple xs then filterOutFLFL ishunkbinary xs else xs where ishunkbinary :: prim wA wB -> EqCheck wA wB ishunkbinary x | primIsHunk x || primIsBinary x = unsafeCoerceP IsEq | otherwise = NotEq -- | @tentativelyRemoveFromPending p@ is used by Darcs whenever it -- adds a patch to the repository (eg. with apply or record). -- Think of it as one part of transferring patches from pending to -- somewhere else. -- -- Question (Eric Kow): how do we detect patch equivalence? tentativelyRemoveFromPending :: forall rt p wR wU wT wX wY. (RepoPatch p) => Repository rt p wR wU wT -> UpdateWorking -> PatchInfoAnd rt p wX wY -> IO () tentativelyRemoveFromPending _ NoUpdateWorking _ = return () tentativelyRemoveFromPending repo YesUpdateWorking p = do Sealed pend <- readTentativePending repo -- Question (Eric Kow): why does pending being all simple matter for -- changepref patches in p? isSimple includes changepref, so what do -- adddir/etc have to do with it? Why don't we we systematically -- crudeSift/not? let effectp = if isSimple pend then crudeSift $ effect p else effect p Sealed newpend <- return $ rmpend (progressFL "Removing from pending:" effectp) (unsafeCoercePStart pend) writeTentativePending repo (unsafeCoercePStart newpend) where -- @rmpend effect pending@ removes as much of @effect@ from @pending@ -- as possible -- -- Note that @effect@ and @pending@ must start from the same context -- This is not a bad thing to assume because @effect@ is a patch we want to -- add to the repository anyway so it'd kind of have to start from wR anyway -- -- Question (Eric Kow), ok then why not -- @PatchInfoAnd p wR wY@ in the type signature above? rmpend :: FL (PrimOf p) wA wB -> FL (PrimOf p) wA wC -> Sealed (FL (PrimOf p) wB) rmpend NilFL x = Sealed x rmpend _ NilFL = Sealed NilFL rmpend (x:>:xs) xys | Just ys <- removeFL x xys = rmpend xs ys rmpend (x:>:xs) ys = case commuteWhatWeCanFL (x:>xs) of a:>x':>b -> case rmpend a ys of Sealed ys' -> case commute (invert (x':>:b) :> ys') of Just (ys'' :> _) -> seal ys'' Nothing -> seal $ invert (x':>:b)+>+ys' -- DJR: I don't think this last case should be -- reached, but it also shouldn't lead to corruption. -- | A sequence of primitive patches (candidates for the pending patch) -- is considered simple if we can reason about their continued status as -- pending patches solely on the basis of them being hunk/binary patches. -- -- Simple here seems to mean that all patches are either hunk/binary -- patches, or patches that cannot (indirectly) depend on hunk/binary -- patches. For now, the only other kinds of patches in this category -- are changepref patches. -- -- It might be tempting to add, say, adddir patches but it's probably not a -- good idea because Darcs also inverts patches a lot in its reasoning so an -- innocent addir may be inverted to a rmdir which in turn may depend on -- a rmfile, which in turn depends on a hunk/binary. Likewise, we would -- not want to add move patches to this category for similar reasons of -- a potential dependency chain forming. isSimple :: PrimPatch prim => FL prim wX wY -> Bool isSimple = allFL isSimp where isSimp x = primIsHunk x || primIsBinary x || primIsSetpref x -- | @makeNewPending repo YesUpdateWorking pendPs@ verifies that the -- @pendPs@ could be applied to pristine if we wanted to, and if so -- writes it to disk. If it can't be applied, @pendPs@ must -- be somehow buggy, so we save it for forensics and crash. makeNewPending :: (RepoPatch p, ApplyState p ~ Tree) => Repository rt p wR wU wT -> UpdateWorking -> FL (PrimOf p) wT wY -> Tree IO -- ^recorded state of the repository, to check if pending can be applied -> IO () makeNewPending _ NoUpdateWorking _ _ = return () makeNewPending repo YesUpdateWorking origp recordedState = withRepoLocation repo $ do let newname = pendingName ++ ".new" debugMessage $ "Writing new pending: " ++ newname Sealed sfp <- return $ siftForPending origp writeNewPending repo sfp Sealed p <- readNewPending repo -- We don't ever use the resulting tree. _ <- catch (applyToTree p recordedState) $ \(err :: IOException) -> do let buggyname = pendingName ++ "_buggy" renameFile newname buggyname errorDoc $ text ("There was an attempt to write an invalid pending! " ++ show err) $$ text "If possible, please send the contents of" <+> text buggyname $$ text "along with a bug report." renameFile newname pendingName debugMessage $ "Finished writing new pending: " ++ newname -- | Replace the pending patch with the tentative pending. -- If @NoUpdateWorking@, this merely deletes the tentative pending -- without replacing the current one. -- -- Question (Eric Kow): shouldn't this also delete the tentative -- pending if @YesUpdateWorking@? I'm just puzzled by the seeming -- inconsistency of the @NoUpdateWorking@ doing deletion, but -- @YesUpdateWorking@ not bothering. finalizePending :: (RepoPatch p, ApplyState p ~ Tree) => Repository rt p wR wU wT -> UpdateWorking -> Tree IO -> IO () finalizePending repo NoUpdateWorking _ = withRepoLocation repo $ removeFileMayNotExist pendingName finalizePending repo updateWorking@YesUpdateWorking recordedState = withRepoLocation repo $ do Sealed tpend <- readTentativePending repo Sealed new_pending <- return $ siftForPending tpend makeNewPending repo updateWorking new_pending recordedState -- | @tentativelyAddToPending repo NoDryRun YesUpdateWorking pend ps@ -- appends @ps@ to the pending patch. -- -- It has no effect with @NoUpdateWorking@. -- -- This fuction is unsafe because it accepts a patch that works on the -- tentative pending and we don't currently track the state of the -- tentative pending. tentativelyAddToPending :: forall rt p wR wU wT wX wY. RepoPatch p => Repository rt p wR wU wT -> UpdateWorking -> FL (PrimOf p) wX wY -> IO () tentativelyAddToPending _ NoUpdateWorking _ = return () tentativelyAddToPending repo YesUpdateWorking patch = withRepoLocation repo $ do Sealed pend <- readTentativePending repo FlippedSeal newpend_ <- return $ newpend (unsafeCoerceP pend :: FL (PrimOf p) wA wX) patch writeTentativePending repo (unsafeCoercePStart newpend_) where newpend :: FL prim wA wB -> FL prim wB wC -> FlippedSeal (FL prim) wC newpend NilFL patch_ = flipSeal patch_ newpend p patch_ = flipSeal $ p +>+ patch_ -- | setTentativePending is basically unsafe. It overwrites the pending -- state with a new one, not related to the repository state. setTentativePending :: forall rt p wR wU wT wX wY. RepoPatch p => Repository rt p wR wU wT -> UpdateWorking -> FL (PrimOf p) wX wY -> IO () setTentativePending _ NoUpdateWorking _ = return () setTentativePending repo YesUpdateWorking patch = do Sealed prims <- return $ siftForPending patch withRepoLocation repo $ writeTentativePending repo (unsafeCoercePStart prims) -- | @prepend repo YesUpdateWorking ps@ prepends @ps@ to the pending patch -- It's used right before removing @ps@ from the repo. This ensures that -- the pending patch can still be applied on top of the recorded state. -- -- This function is basically unsafe. It overwrites the pending state -- with a new one, not related to the repository state. prepend :: forall rt p wR wU wT wX wY. RepoPatch p => Repository rt p wR wU wT -> UpdateWorking -> FL (PrimOf p) wX wY -> IO () prepend _ NoUpdateWorking _ = return () prepend repo YesUpdateWorking patch = do Sealed pend <- readTentativePending repo Sealed newpend_ <- return $ newpend (unsafeCoerceP pend) patch writeTentativePending repo (unsafeCoercePStart $ crudeSift newpend_) where newpend :: FL prim wB wC -> FL prim wA wB -> Sealed (FL prim wA) newpend NilFL patch_ = seal patch_ newpend p patch_ = seal $ patch_ +>+ p