darcs-2.18.1: src/Darcs/Repository/Job.hs
-- 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 MultiWayIf #-}
module Darcs.Repository.Job
( RepoJob(..)
, IsPrimV1(..)
, withRepoLock
, withOldRepoLock
, withRepoLockCanFail
, withRepository
, withRepositoryLocation
, withUMaskFlag
) where
import Darcs.Prelude
import Darcs.Patch.Apply ( ApplyState )
import Darcs.Patch.V1 ( RepoPatchV1 )
import Darcs.Patch.V2 ( RepoPatchV2 )
import Darcs.Patch.V3 ( RepoPatchV3 )
import qualified Darcs.Patch.V1.Prim as V1 ( Prim(..) )
import qualified Darcs.Patch.V2.Prim as V2 ( Prim(..) )
import Darcs.Patch ( PrimOf )
import Darcs.Patch.Prim.V1 ( Prim )
import Darcs.Patch.RepoPatch ( RepoPatch )
import Darcs.Repository.Flags ( UMask(..), UseCache(..) )
import Darcs.Repository.Format
( RepoProperty( Darcs2
, Darcs3
, HashedInventory
)
, formatHas
, writeProblem
)
import Darcs.Repository.Identify ( identifyRepository )
import Darcs.Repository.Transaction( revertRepositoryChanges )
import Darcs.Repository.InternalTypes
( Repository
, AccessType(..)
, repoFormat
, unsafeCoercePatchType
, unsafeStartTransaction
)
import Darcs.Repository.Paths ( lockPath )
import Darcs.Repository.Rebase
( displayRebaseStatus
, checkOldStyleRebaseStatus
)
import Darcs.Util.Lock ( withLock, withLockCanFail )
import Darcs.Util.Progress ( debugMessage )
import Control.Monad ( when )
import Control.Exception ( bracket_, finally )
import Data.Constraint ( Dict(..) )
import Foreign.C.String ( CString, withCString )
import Foreign.C.Error ( throwErrno )
import Foreign.C.Types ( CInt(..) )
import Darcs.Util.Tree ( Tree )
withUMaskFlag :: UMask -> IO a -> IO a
withUMaskFlag NoUMask = id
withUMaskFlag (YesUMask umask) = withUMask umask
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
type Job rt p wR wU a = Repository rt p wU wR -> IO a
type TreePatch p = (RepoPatch p, ApplyState p ~ Tree)
type V1Patch p = p ~ RepoPatchV1 V1.Prim
type V2Patch p = p ~ RepoPatchV2 V2.Prim
type PrimV1Patch p = (TreePatch p, IsPrimV1 (PrimOf p))
type TreePatchJob rt a = forall p wR wU . TreePatch p => Job rt p wR wU a
type V1PatchJob rt a = forall p wR wU . V1Patch p => Job rt p wR wU a
type V2PatchJob rt a = forall p wR wU . V2Patch p => Job rt p wR wU a
type PrimV1PatchJob rt a = forall p wR wU . PrimV1Patch p => Job rt p wR wU a
-- |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 rt a
-- TODO: Unbind Tree from RepoJob, possibly renaming existing RepoJob
-- |The most common 'RepoJob'; the underlying action can accept any patch
-- whose 'ApplyState' is 'Tree'.
= RepoJob (TreePatchJob rt a)
-- |A job that only works on darcs 1 patches
| V1Job (V1PatchJob rt a)
-- |A job that only works on darcs 2 patches
| V2Job (V2PatchJob rt 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 it should be
-- replaced with a more abstract inspection API as part of 'PrimPatch'.
| PrimV1Job (PrimV1PatchJob rt a)
-- |A job that works even if there is an old-style rebase in progress.
| OldRebaseJob (TreePatchJob rt a)
onRepoJob
:: RepoJob rt1 a -- original repojob passed to withXxx
-> ( forall p wR wU
. TreePatch p
=> (Repository rt1 p wU wR -> IO a)
-> (Repository rt2 p wU wR -> IO a)
)
-> RepoJob rt2 a -- result job takes a Repo rt2
onRepoJob (RepoJob job) f = RepoJob (f job)
onRepoJob (V1Job job) f = V1Job (f job)
onRepoJob (V2Job job) f = V2Job (f job)
onRepoJob (PrimV1Job job) f = PrimV1Job (f job)
onRepoJob (OldRebaseJob job) f = OldRebaseJob (f job)
-- | This is just an internal type to Darcs.Repository.Job for
-- calling runJob in a strongly-typed way
data RepoPatchType p where
RepoV1 :: RepoPatchType (RepoPatchV1 V1.Prim)
RepoV2 :: RepoPatchType (RepoPatchV2 V2.Prim)
RepoV3 :: RepoPatchType (RepoPatchV3 V2.Prim)
-- | Check multiple patch types against the
-- constraints required by most repository jobs
checkTree :: RepoPatchType p -> Dict (ApplyState p ~ Tree)
checkTree RepoV1 = Dict
checkTree RepoV2 = Dict
checkTree RepoV3 = Dict
class IsPrimV1 p where
toPrimV1 :: p wX wY -> Prim wX wY
instance IsPrimV1 V1.Prim where
toPrimV1 = V1.unPrim
instance IsPrimV1 V2.Prim where
toPrimV1 = V2.unPrim
-- | Check multiple patch types against the
-- constraints required by 'PrimV1Job'
checkPrimV1 :: RepoPatchType p -> Dict (IsPrimV1 (PrimOf p))
checkPrimV1 RepoV1 = Dict
checkPrimV1 RepoV2 = Dict
checkPrimV1 RepoV3 = Dict
runJob
:: forall rt p pDummy wR wU a
. RepoPatch p
=> RepoPatchType p
-> Repository rt pDummy wU wR
-> RepoJob rt a
-> IO a
runJob patchType repo repojob = do
-- The actual type the repository should have is only known when
-- when this function is called, so we need to "cast" it to its proper type
let
therepo = unsafeCoercePatchType repo :: Repository rt p wU wR
incompatible want got = fail $
"This repository contains darcs "++got++" patches,\
\ but the command requires darcs "++want++" patches."
Dict <- return $ checkTree patchType
let thejob =
case repojob of
RepoJob job -> do
checkOldStyleRebaseStatus therepo
job therepo
PrimV1Job job -> do
Dict <- return $ checkPrimV1 patchType
checkOldStyleRebaseStatus therepo
job therepo
V2Job job ->
case patchType of
RepoV2 -> do
checkOldStyleRebaseStatus therepo
job therepo
RepoV1 -> incompatible "v2" "v1"
RepoV3 -> incompatible "v2" "v3"
V1Job job ->
case patchType of
RepoV1 -> do
checkOldStyleRebaseStatus therepo
job therepo
RepoV2 -> incompatible "v1" "v2"
RepoV3 -> incompatible "v1" "v3"
OldRebaseJob job -> job therepo
thejob `finally` displayRebaseStatus therepo
-- | apply a given RepoJob to a repository in a given url
withRepositoryLocation :: UseCache -> String -> RepoJob 'RO a -> IO a
withRepositoryLocation useCache url repojob = do
repo <- identifyRepository useCache url
let rf = repoFormat repo
if | formatHas Darcs3 rf -> runJob RepoV3 repo repojob
| formatHas Darcs2 rf -> runJob RepoV2 repo repojob
| otherwise -> runJob RepoV1 repo repojob
-- | apply a given RepoJob to a repository in the current working directory
withRepository :: UseCache -> RepoJob 'RO a -> IO a
withRepository useCache = withRepositoryLocation useCache "."
-- | Apply a given RepoJob to a repository in the current working directory.
-- However, before doing the job, take the repo lock and initializes a repo
-- transaction.
withRepoLock :: UseCache -> UMask -> RepoJob 'RW a -> IO a
withRepoLock useCache um repojob =
withLock lockPath $
withRepository useCache $ onRepoJob repojob $ \job repository -> do
maybe (return ()) fail $ writeProblem (repoFormat repository)
withUMaskFlag um $ revertRepositoryChanges repository >>= job
-- | run a lock-taking job in an old-fashion repository.
-- only used by `darcs optimize upgrade`.
withOldRepoLock :: RepoJob 'RW a -> IO a
withOldRepoLock repojob =
withRepository NoUseCache $ onRepoJob repojob $ \job repository ->
withLock lockPath $ job $ unsafeStartTransaction repository
-- | Apply a given RepoJob to a repository in the current working directory,
-- taking a lock. If lock not takeable, do nothing. If old-fashioned
-- repository, do nothing. The job must not touch pending or pending.tentative,
-- because there is no call to revertRepositoryChanges. This entry point is
-- currently only used for attemptCreatePatchIndex.
withRepoLockCanFail :: UseCache -> RepoJob 'RO () -> IO ()
withRepoLockCanFail useCache repojob = do
eitherDone <-
withLockCanFail lockPath $
withRepository useCache $ onRepoJob repojob $ \job repository -> do
let rf = repoFormat repository
if formatHas HashedInventory rf then do
maybe (return ()) fail $ writeProblem rf
job repository
else
debugMessage
"Not doing the job because this is an old-fashioned repository."
case eitherDone of
Left _ -> debugMessage "Lock could not be obtained, not doing the job."
Right _ -> return ()