darcs-2.18.5: src/Darcs/Repository/Transaction.hs
{-# LANGUAGE OverloadedStrings #-}
module Darcs.Repository.Transaction
( revertRepositoryChanges
, finalizeRepositoryChanges
, upgradeOldStyleRebase
) where
import Darcs.Prelude
import Control.Monad ( unless, void, when )
import System.Directory ( doesFileExist, removeFile )
import System.IO ( IOMode(..), hClose, hPutStrLn, openBinaryFile, stderr )
import System.IO.Error ( catchIOError )
import Darcs.Patch ( ApplyState, PatchInfoAnd, RepoPatch )
import qualified Darcs.Patch.Rebase.Legacy.Wrapped as W
import Darcs.Patch.Rebase.Suspended ( Suspended(..), showSuspended )
import Darcs.Patch.Set ( Origin, PatchSet(..), Tagged(..) )
import Darcs.Patch.Show ( ShowPatchFor(..) )
import Darcs.Patch.Witnesses.Ordered ( FL(..), RL(..), (:>)(..) )
import Darcs.Patch.Witnesses.Sealed ( Dup(..), Sealed(..) )
import Darcs.Repository.Flags ( DryRun(..) )
import Darcs.Repository.Format
( RepoProperty(HashedInventory, RebaseInProgress, RebaseInProgress_2_16)
, addToFormat
, formatHas
, removeFromFormat
)
import Darcs.Repository.Hashed
( finalizeTentativeChanges
, readPatches
, readTentativePatches
, revertTentativeChanges
, writeTentativeInventory
)
import Darcs.Repository.InternalTypes
( AccessType(..)
, Repository
, modifyRepoFormat
, repoCache
, repoFormat
, repoLocation
, unsafeCoerceR
, unsafeEndTransaction
, unsafeStartTransaction
, withRepoDir
)
import Darcs.Repository.Inventory ( readOneInventory )
import qualified Darcs.Repository.Old as Old ( oldRepoFailMsg )
import Darcs.Repository.PatchIndex
( createOrUpdatePatchIndexDisk
, doesPatchIndexExist
)
import Darcs.Repository.Paths
( indexInvalidPath
, indexPath
, tentativeHashedInventoryPath
)
import Darcs.Repository.Pending ( finalizePending, revertPending )
import Darcs.Repository.Rebase
( extractOldStyleRebase
, finalizeTentativeRebase
, readTentativeRebase
, revertTentativeRebase
, updateRebaseFormat
, writeTentativeRebase
)
import Darcs.Repository.State ( updateIndex )
import Darcs.Repository.Unrevert
( finalizeTentativeUnrevert
, revertTentativeUnrevert
)
import Darcs.Util.Printer ( text, ($$) )
import Darcs.Util.Printer.Color ( ePutDocLn )
import Darcs.Util.Progress ( debugMessage )
import Darcs.Util.SignalHandler ( withSignalsBlocked )
import Darcs.Util.Tree ( Tree )
-- TODO: rename this and document the transaction protocol (revert/finalize)
-- clearly.
-- |Slightly confusingly named: as well as throwing away any tentative
-- changes, revertRepositoryChanges also re-initialises the tentative state.
-- It's therefore used before makign any changes to the repo.
revertRepositoryChanges :: RepoPatch p
=> Repository 'RO p wU wR
-> IO (Repository 'RW p wU wR)
revertRepositoryChanges r
| formatHas HashedInventory (repoFormat r) =
withRepoDir r $ do
checkIndexIsWritable
`catchIOError` \e -> fail (unlines ["Cannot write index", show e])
revertTentativeUnrevert
revertPending r
revertTentativeChanges r
let r' = unsafeCoerceR r
revertTentativeRebase r'
return $ unsafeStartTransaction r'
| otherwise = fail Old.oldRepoFailMsg
-- | Atomically copy the tentative state to the recorded state,
-- thereby committing the tentative changes that were made so far.
-- This includes inventories, pending, rebase, and the index.
finalizeRepositoryChanges :: (RepoPatch p, ApplyState p ~ Tree)
=> Repository 'RW p wU wR
-> DryRun
-> IO (Repository 'RO p wU wR)
finalizeRepositoryChanges r dryrun
| formatHas HashedInventory (repoFormat r) =
withRepoDir r $ do
let r' = unsafeEndTransaction $ unsafeCoerceR r
when (dryrun == NoDryRun) $ do
debugMessage "Finalizing changes..."
withSignalsBlocked $ do
updateRebaseFormat r
finalizeTentativeRebase
finalizeTentativeChanges r
finalizePending r
finalizeTentativeUnrevert
debugMessage "Done finalizing changes..."
ps <- readPatches r'
pi_exists <- doesPatchIndexExist (repoLocation r')
when pi_exists $
createOrUpdatePatchIndexDisk r' ps
`catchIOError` \e ->
hPutStrLn stderr $ "Cannot create or update patch index: "++ show e
updateIndex r'
return r'
| otherwise = fail Old.oldRepoFailMsg
-- | Upgrade a possible old-style rebase in progress to the new style.
upgradeOldStyleRebase :: forall p wU wR.
(RepoPatch p, ApplyState p ~ Tree)
=> Repository 'RW p wU wR -> IO ()
upgradeOldStyleRebase repo = do
PatchSet (ts :: RL (Tagged p) Origin wX) _ <- readTentativePatches repo
Sealed wps <-
readOneInventory @(W.WrappedNamed p) (repoCache repo) tentativeHashedInventoryPath
case extractOldStyleRebase wps of
Nothing ->
ePutDocLn $ text "No old-style rebase state found, no upgrade needed."
Just ((ps :: RL (PatchInfoAnd p) wX wZ) :> Dup r) -> do
-- low-level call, must not try to update an existing rebase patch,
-- nor update anything else beside the inventory
writeTentativeInventory repo (PatchSet ts ps)
Items old_r <- readTentativeRebase repo
case old_r of
NilFL -> do
writeTentativeRebase (unsafeCoerceR repo) r
repo' <-
modifyRepoFormat
(addToFormat RebaseInProgress_2_16 . removeFromFormat RebaseInProgress)
repo
void $ finalizeRepositoryChanges repo' NoDryRun
_ -> do
ePutDocLn
$ "A new-style rebase is already in progress, not overwriting it."
$$ "This should not have happened! This is the old-style rebase I found"
$$ "and removed from the repository:"
$$ showSuspended ForDisplay r
checkIndexIsWritable :: IO ()
checkIndexIsWritable = do
checkWritable indexInvalidPath
checkWritable indexPath
where
checkWritable path = do
exists <- doesFileExist path
touchFile path
unless exists $ removeFile path
touchFile path = openBinaryFile path AppendMode >>= hClose