git-0.1: Data/Git/WorkTree.hs
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- |
-- Module : Data.Git.WorkTree
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unix
--
-- a load-on-demand, write-on-demand working tree.
--
module Data.Git.WorkTree
( WorkTree
, EntType(..)
-- * Create new work trees
, workTreeNew
, workTreeFrom
-- * Modifications methods
, workTreeDelete
, workTreeSet
, workTreeFlush
) where
import Data.Git.Ref
import Data.Git.Types
import Data.Git.Storage.Object
import Data.Git.Storage
import Data.Git.Repository
--import qualified Data.ByteString as B
import qualified Data.Map as M
import Control.Monad
import Control.Concurrent.MVar
type Dir = M.Map EntName (ModePerm, TreeSt)
type TreeVar = MVar Dir
data TreeSt = TreeRef Ref | TreeLoaded TreeVar
type WorkTree = MVar TreeSt
data EntType = EntDirectory | EntFile | EntExecutable
deriving (Show,Eq)
-- | Create a new worktree
workTreeNew :: IO WorkTree
workTreeNew = newMVar M.empty >>= newMVar . TreeLoaded
-- | Create a worktree from a tree reference.
workTreeFrom :: Ref -> IO WorkTree
workTreeFrom ref = newMVar (TreeRef ref)
-- | delete a path from a working tree
--
-- if the path doesn't exist, no error is raised
workTreeDelete :: Git -> WorkTree -> EntPath -> IO ()
workTreeDelete git wt path = diveFromRoot git wt path dive
where dive _ [] = error "internal error: delete: empty dive"
dive varCurrent [file] = modifyMVar_ varCurrent (return . M.delete file)
dive varCurrent (x:xs) = do
evarChild <- loadOrGetTree git x varCurrent $ \m -> return (m, Right ())
case evarChild of
Left varChild -> dive varChild xs
Right () -> return ()
-- | Set a file in this working tree to a specific ref.
--
-- The ref should point to a valid blob or tree object, and
-- it's safer to write the referenced tree or blob object first.
workTreeSet :: Git -> WorkTree -> EntPath -> (EntType, Ref) -> IO ()
workTreeSet git wt path (entType, entRef) = diveFromRoot git wt path dive
where dive :: TreeVar -> EntPath -> IO ()
dive _ [] = error "internal error: set: empty dive"
dive varCurrent [file] = modifyMVar_ varCurrent (return . M.insert file (entTypeToPerm entType, TreeRef entRef))
dive varCurrent (x:xs) = do
evarChild <- loadOrGetTree git x varCurrent $ \m -> do
-- create an empty tree
v <- newMVar M.empty
return (M.insert x (entTypeToPerm EntDirectory, TreeLoaded v) m, Left v)
case evarChild of
Left varChild -> dive varChild xs
Right () -> return ()
{-
workTreeFlushAt :: Git -> WorkTree -> EntPath -> IO ()
workTreeFlushAt git wt path = do
undefined
-}
-- | Flush the worktree by creating all the necessary trees in the git store
-- and return the root ref of the work tree.
workTreeFlush :: Git -> WorkTree -> IO Ref
workTreeFlush git wt = do
-- write all the trees that need to be written
-- switch to modifyMVar
wtVal <- takeMVar wt
case wtVal of
TreeRef ref -> putMVar wt wtVal >> return ref
TreeLoaded var -> do
ref <- writeTreeRecursively (TreeLoaded var)
putMVar wt $ TreeRef ref
return ref
where writeTreeRecursively (TreeRef ref) = return ref
writeTreeRecursively (TreeLoaded var) = do
c <- readMVar var
ents <- forM (M.toList c) $ \(bs, (mperm, entSt)) -> do
ref <- writeTreeRecursively entSt
return (mperm, bs, ref)
setTree ents
setTree ents = setObject git (toObject $ Tree ents)
----- helpers -----
loadTreeVar :: Git -> Ref -> IO TreeVar
loadTreeVar git treeRef = do
(Tree ents) <- getTree git treeRef
let t = foldr (\(m,b,r) acc -> M.insert b (m,TreeRef r) acc) M.empty ents
newMVar t
entTypeToPerm :: EntType -> ModePerm
entTypeToPerm EntDirectory = ModePerm 0o040000
entTypeToPerm EntExecutable = ModePerm 0o100755
entTypeToPerm EntFile = ModePerm 0o100644
loadOrGetTree :: Git -> EntName -> TreeVar -> (Dir -> IO (Dir, Either TreeVar a)) -> IO (Either TreeVar a)
loadOrGetTree git x varCurrent onMissing =
modifyMVar varCurrent $ \m -> do
case M.lookup x m of
Nothing -> onMissing m
Just (_, treeSt) -> -- check perm to see if it is a directory
case treeSt of
TreeRef ref -> do
-- replace the ref by a loaded tree
var <- loadTreeVar git ref
return (M.adjust (\(perm,_) -> (perm, TreeLoaded var)) x m, Left var)
TreeLoaded var -> return (m, Left var)
diveFromRoot :: Git -> WorkTree -> EntPath
-> (TreeVar -> EntPath -> IO ())
-> IO ()
diveFromRoot git wt path dive
| path == [] = return ()
| otherwise = do
-- switch to modifyMVar
wtVal <- takeMVar wt
current <- case wtVal of
TreeLoaded var -> return var
TreeRef ref -> loadTreeVar git ref
putMVar wt $ TreeLoaded current
dive current path