-- 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. {-# LANGUAGE CPP, ScopedTypeVariables, Rank2Types, RankNTypes #-} {-# LANGUAGE ForeignFunctionInterface #-} module Darcs.Repository.Job ( RepoJob(..) , withRepoLock , withRepoLockCanFail , withRepository , withRepositoryDirectory ) where import Darcs.Util.Global ( darcsdir ) import Darcs.Patch.Apply ( ApplyState ) import Darcs.Patch.V1 ( Patch ) import Darcs.Patch.V2 ( RealPatch ) import Darcs.Patch.Named ( Named ) import Darcs.Patch.Prim.V1 ( Prim ) import Darcs.Patch.Prim ( PrimOf ) import Darcs.Patch.Rebase ( Rebasing ) import Darcs.Patch.RepoPatch ( RepoPatch ) import Darcs.Repository.Flags ( UseCache(..), UpdateWorking(..), DryRun(..), UMask (..) , Compression, Verbosity ) import Darcs.Repository.Format ( RepoProperty( Darcs2 , RebaseInProgress ) , formatHas , writeProblem ) import Darcs.Repository.Internal ( identifyRepository , revertRepositoryChanges ) import Darcs.Repository.InternalTypes ( Repository(..) ) import Darcs.Repository.Rebase ( repoJobOnRebaseRepo , startRebaseJob , rebaseJob ) import Darcs.Repository.Lock ( withLock, withLockCanFail ) import Darcs.Util.Progress ( debugMessage ) import Control.Monad ( when ) import Control.Exception ( bracket_ ) import Foreign.C.String ( CString, withCString ) import Foreign.C.Error ( throwErrno ) import Foreign.C.Types ( CInt(..) ) import Storage.Hashed.Tree ( Tree ) #include "impossible.h" getUMask :: UMask -> Maybe String getUMask (YesUMask s) = Just s getUMask NoUMask = Nothing withUMaskFlag :: UMask -> IO a -> IO a withUMaskFlag = maybe id withUMask . getUMask foreign import ccall unsafe "umask.h set_umask" set_umask :: CString -> IO CInt foreign import ccall unsafe "umask.h reset_umask" reset_umask :: CInt -> IO CInt withUMask :: String -> IO a -> IO a withUMask umask job = do rc <- withCString umask set_umask when (rc < 0) (throwErrno "Couldn't set umask") bracket_ (return ()) (reset_umask rc) job -- |A @RepoJob@ wraps up an action to be performed with a repository. Because repositories -- can contain different types of patches, such actions typically need to be polymorphic -- in the kind of patch they work on. @RepoJob@ is used to wrap up the polymorphism, -- and the various functions that act on a @RepoJob@ are responsible for instantiating -- the underlying action with the appropriate patch type. data RepoJob a -- = RepoJob (forall p wR wU . RepoPatch p => Repository p wR wU wR -> IO a) -- TODO: Unbind Tree from RepoJob, possibly renaming existing RepoJob = -- |The most common @RepoJob@; the underlying action can accept any patch type that -- a darcs repository may use. RepoJob (forall p wR wU . (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree) => Repository p wR wU wR -> IO a) -- |A job that only works on darcs 1 patches | V1Job (forall wR wU . Repository (Patch Prim) wR wU wR -> IO a) -- |A job that only works on darcs 2 patches | V2Job (forall wR wU . Repository (RealPatch Prim) wR wU wR -> IO a) -- |A job that works on any repository where the patch type @p@ has 'PrimOf' @p@ = 'Prim'. -- -- This was added to support darcsden, which inspects the internals of V1 prim patches. -- -- In future this should be replaced with a more abstract inspection API as part of 'PrimPatch'. | PrimV1Job (forall p wR wU . (RepoPatch p, ApplyState p ~ Tree, PrimOf p ~ Prim) => Repository p wR wU wR -> IO a) -- A job that works on normal darcs repositories, but will want access to the rebase patch if it exists. | RebaseAwareJob Compression Verbosity UpdateWorking (forall p wR wU . (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree, PrimOf (Named p) ~ PrimOf p) => Repository p wR wU wR -> IO a) | RebaseJob Compression Verbosity UpdateWorking (forall p wR wU . (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree, PrimOf (Named p) ~ PrimOf p) => Repository (Rebasing p) wR wU wR -> IO a) | StartRebaseJob Compression Verbosity UpdateWorking (forall p wR wU . (RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree, PrimOf (Named p) ~ PrimOf p) => Repository (Rebasing p) wR wU wR -> IO a) onRepoJob :: RepoJob a -> (forall p wR wU . (RepoPatch p, ApplyState p ~ Tree) => (Repository p wR wU wR -> IO a) -> Repository p wR wU wR -> IO a) -> RepoJob a onRepoJob (RepoJob job) f = RepoJob (f job) -- onRepoJob (TreeJob job) f = TreeJob (f job) onRepoJob (V1Job job) f = V1Job (f job) onRepoJob (V2Job job) f = V2Job (f job) onRepoJob (PrimV1Job job) f = PrimV1Job (f job) onRepoJob (RebaseAwareJob compr verb uw job) f = RebaseAwareJob compr verb uw (f job) onRepoJob (RebaseJob compr verb uw job) f = RebaseJob compr verb uw (f job) onRepoJob (StartRebaseJob compr verb uw job) f = StartRebaseJob compr verb uw (f job) -- | apply a given RepoJob to a repository in the current working directory withRepository :: UseCache -> RepoJob a -> IO a withRepository useCache = withRepositoryDirectory useCache "." -- | apply a given RepoJob to a repository in a given url withRepositoryDirectory :: UseCache -> String -> RepoJob a -> IO a withRepositoryDirectory useCache url repojob = do Repo dir rf t c <- identifyRepository useCache url let startRebase = case repojob of StartRebaseJob {} -> True _ -> False case (formatHas Darcs2 rf, startRebase || formatHas RebaseInProgress rf) of (True, False) -> do debugMessage $ "Identified darcs-2 repo: " ++ dir let therepo = Repo dir rf t c :: Repository (RealPatch Prim) wR wU wR case repojob of RepoJob job -> job therepo PrimV1Job job -> job therepo -- TreeJob job -> job therepo V2Job job -> job therepo V1Job _ -> fail $ "This repository contains darcs v1 patches," ++ " but the command requires darcs v2 patches." RebaseAwareJob _compr _verb _uw job -> job therepo RebaseJob {} -> fail "No rebase in progress. Try 'darcs rebase suspend' first." StartRebaseJob {} -> impossible (False, False) -> do debugMessage $ "Identified darcs-1 repo: " ++ dir let therepo = Repo dir rf t c :: Repository (Patch Prim) wR wU wR case repojob of RepoJob job -> job therepo PrimV1Job job -> job therepo V1Job job -> job therepo V2Job _ -> fail $ "This repository contains darcs v2 patches," ++ " but the command requires darcs v1 patches." RebaseAwareJob _compr _verb _uw job -> job therepo RebaseJob {} -> fail "No rebase in progress. Try 'darcs rebase suspend' first." StartRebaseJob {} -> impossible (True, True ) -> do debugMessage $ "Identified darcs-2 rebase repo: " ++ dir let therepo = Repo dir rf t c :: Repository (Rebasing (RealPatch Prim)) wR wU wR case repojob of RepoJob job -> repoJobOnRebaseRepo job therepo PrimV1Job job -> repoJobOnRebaseRepo job therepo -- TreeJob job -> job therepo V2Job _ -> fail "This command is not supported while a rebase is in progress." V1Job _ -> fail $ "This repository contains darcs v1 patches," ++ " but the command requires darcs v2 patches." RebaseAwareJob compr verb uw job -> rebaseJob job therepo compr verb uw RebaseJob compr verb uw job -> rebaseJob job therepo compr verb uw StartRebaseJob compr verb uw job -> startRebaseJob job therepo compr verb uw (False, True ) -> do debugMessage $ "Identified darcs-1 rebase repo: " ++ dir let therepo = Repo dir rf t c :: Repository (Rebasing (Patch Prim)) wR wU wR case repojob of RepoJob job -> repoJobOnRebaseRepo job therepo PrimV1Job job -> repoJobOnRebaseRepo job therepo V1Job _ -> fail "This command is not supported while a rebase is in progress." V2Job _ -> fail $ "This repository contains darcs v2 patches," ++ " but the command requires darcs v1 patches." RebaseAwareJob compr verb uw job -> rebaseJob job therepo compr verb uw RebaseJob compr verb uw job -> rebaseJob job therepo compr verb uw StartRebaseJob compr verb uw job -> startRebaseJob job therepo compr verb uw -- | apply a given RepoJob to a repository in the current working directory, -- taking a lock withRepoLock :: DryRun -> UseCache -> UpdateWorking -> UMask -> RepoJob a -> IO a withRepoLock dry useCache uw um repojob = withRepository useCache $ onRepoJob repojob $ \job repository@(Repo _ rf _ _) -> do maybe (return ()) fail $ writeProblem rf let name = "./"++darcsdir++"/lock" withUMaskFlag um $ if dry == YesDryRun then job repository else withLock name (revertRepositoryChanges repository uw >> job repository) -- | apply a given RepoJob to a repository in the current working directory, -- taking a lock. If lock not takeable, do nothing. withRepoLockCanFail :: UseCache -> UpdateWorking -> UMask -> RepoJob () -> IO () withRepoLockCanFail useCache uw um repojob = withRepository useCache $ onRepoJob repojob $ \job repository@(Repo _ rf _ _) -> do maybe (return ()) fail $ writeProblem rf let name = "./"++darcsdir++"/lock" withUMaskFlag um $ do eitherDone <- withLockCanFail name (revertRepositoryChanges repository uw >> job repository) case eitherDone of Left _ -> debugMessage "Lock could not be obtained, not doing the job." Right _ -> return ()