2016-02-23 20:36:08 +00:00
|
|
|
{- git trees
|
|
|
|
-
|
|
|
|
- Copyright 2016 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2016-02-24 00:25:31 +00:00
|
|
|
{-# LANGUAGE BangPatterns #-}
|
|
|
|
|
2016-02-23 20:36:08 +00:00
|
|
|
module Git.Tree (
|
|
|
|
Tree(..),
|
|
|
|
TreeContent(..),
|
|
|
|
getTree,
|
|
|
|
recordTree,
|
2016-02-24 01:35:16 +00:00
|
|
|
TreeItem(..),
|
|
|
|
adjustTree,
|
2016-02-23 20:36:08 +00:00
|
|
|
) where
|
|
|
|
|
|
|
|
import Common
|
|
|
|
import Git
|
|
|
|
import Git.FilePath
|
|
|
|
import Git.Types
|
|
|
|
import Git.Command
|
2016-02-23 22:30:11 +00:00
|
|
|
import Git.Sha
|
2016-02-23 20:36:08 +00:00
|
|
|
import qualified Git.LsTree as LsTree
|
|
|
|
import qualified Utility.CoProcess as CoProcess
|
|
|
|
|
|
|
|
import Numeric
|
|
|
|
import System.Posix.Types
|
2016-02-24 01:35:16 +00:00
|
|
|
import Control.Monad.IO.Class
|
2016-03-11 20:30:06 +00:00
|
|
|
import qualified Data.Set as S
|
2016-02-23 20:36:08 +00:00
|
|
|
|
|
|
|
newtype Tree = Tree [TreeContent]
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
data TreeContent
|
|
|
|
= TreeBlob TopFilePath FileMode Sha
|
|
|
|
-- A subtree that is already recorded in git, with a known sha.
|
|
|
|
| RecordedSubTree TopFilePath Sha [TreeContent]
|
|
|
|
-- A subtree that has not yet been recorded in git.
|
|
|
|
| NewSubTree TopFilePath [TreeContent]
|
2016-03-11 20:30:06 +00:00
|
|
|
deriving (Show, Eq)
|
2016-02-23 20:36:08 +00:00
|
|
|
|
|
|
|
{- Gets the Tree for a Ref. -}
|
2016-02-24 00:25:31 +00:00
|
|
|
getTree :: Ref -> Repo -> IO Tree
|
2016-02-23 20:36:08 +00:00
|
|
|
getTree r repo = do
|
2016-02-24 01:35:16 +00:00
|
|
|
(l, cleanup) <- lsTreeWithObjects r repo
|
2016-02-24 00:25:31 +00:00
|
|
|
let !t = either (\e -> error ("ls-tree parse error:" ++ e)) id
|
|
|
|
(extractTree l)
|
|
|
|
void cleanup
|
|
|
|
return t
|
2016-02-23 20:36:08 +00:00
|
|
|
|
2016-02-24 01:35:16 +00:00
|
|
|
lsTreeWithObjects :: Ref -> Repo -> IO ([LsTree.TreeItem], IO Bool)
|
|
|
|
lsTreeWithObjects = LsTree.lsTree' [Param "-t"]
|
2016-02-23 20:36:08 +00:00
|
|
|
|
2016-02-24 02:21:25 +00:00
|
|
|
newtype MkTreeHandle = MkTreeHandle CoProcess.CoProcessHandle
|
|
|
|
|
|
|
|
withMkTreeHandle :: (MonadIO m, MonadMask m) => Repo -> (MkTreeHandle -> m a) -> m a
|
|
|
|
withMkTreeHandle repo a = bracketIO setup cleanup (a . MkTreeHandle)
|
|
|
|
where
|
|
|
|
setup = CoProcess.rawMode =<< gitCoProcessStart False ps repo
|
|
|
|
ps = [Param "mktree", Param "--batch", Param "-z"]
|
|
|
|
cleanup = CoProcess.stop
|
|
|
|
|
2016-02-23 20:36:08 +00:00
|
|
|
{- Records a Tree in the Repo, returning its Sha.
|
|
|
|
-
|
|
|
|
- Efficiently handles subtrees, by only recording ones that have not
|
|
|
|
- already been recorded before. And even when many subtrees need to be
|
|
|
|
- recorded, it's done with a single call to git mktree, using its batch
|
|
|
|
- interface.
|
|
|
|
-}
|
2016-02-24 01:35:16 +00:00
|
|
|
recordTree :: Tree -> Repo -> IO Sha
|
2016-02-24 02:21:25 +00:00
|
|
|
recordTree t repo = withMkTreeHandle repo $ \h -> recordTree' h t
|
2016-02-23 20:36:08 +00:00
|
|
|
|
2016-02-24 02:21:25 +00:00
|
|
|
recordTree' :: MkTreeHandle -> Tree -> IO Sha
|
2016-02-23 21:21:42 +00:00
|
|
|
recordTree' h (Tree l) = mkTree h =<< mapM (recordSubTree h) l
|
2016-02-23 20:36:08 +00:00
|
|
|
|
2016-02-24 01:35:16 +00:00
|
|
|
{- Note that the returned RecordedSubTree does not have its [TreeContent]
|
|
|
|
- list populated. This is a memory optimisation, since the list is not
|
|
|
|
- used. -}
|
2016-02-24 02:21:25 +00:00
|
|
|
recordSubTree :: MkTreeHandle -> TreeContent -> IO TreeContent
|
2016-02-23 20:36:08 +00:00
|
|
|
recordSubTree h (NewSubTree d l) = do
|
2016-02-24 01:35:16 +00:00
|
|
|
sha <- mkTree h =<< mapM (recordSubTree h) l
|
|
|
|
return (RecordedSubTree d sha [])
|
2016-02-23 20:36:08 +00:00
|
|
|
recordSubTree _ alreadyrecorded = return alreadyrecorded
|
|
|
|
|
2016-02-24 02:21:25 +00:00
|
|
|
mkTree :: MkTreeHandle -> [TreeContent] -> IO Sha
|
|
|
|
mkTree (MkTreeHandle cp) l = CoProcess.query cp send receive
|
2016-02-23 20:36:08 +00:00
|
|
|
where
|
|
|
|
send h = do
|
|
|
|
forM_ l $ \i -> hPutStr h $ case i of
|
|
|
|
TreeBlob f fm s -> mkTreeOutput fm BlobObject s f
|
|
|
|
RecordedSubTree f s _ -> mkTreeOutput 0o040000 TreeObject s f
|
|
|
|
NewSubTree _ _ -> error "recordSubTree internal error; unexpected NewSubTree"
|
|
|
|
hPutStr h "\NUL" -- signal end of tree to --batch
|
2016-02-23 22:30:11 +00:00
|
|
|
receive h = getSha "mktree" (hGetLine h)
|
2016-02-23 20:36:08 +00:00
|
|
|
|
|
|
|
mkTreeOutput :: FileMode -> ObjectType -> Sha -> TopFilePath -> String
|
2016-02-23 21:21:42 +00:00
|
|
|
mkTreeOutput fm ot s f = concat
|
|
|
|
[ showOct fm ""
|
|
|
|
, " "
|
|
|
|
, show ot
|
|
|
|
, " "
|
|
|
|
, fromRef s
|
|
|
|
, "\t"
|
|
|
|
, takeFileName (getTopFilePath f)
|
|
|
|
, "\NUL"
|
|
|
|
]
|
2016-02-24 01:35:16 +00:00
|
|
|
|
|
|
|
data TreeItem = TreeItem TopFilePath FileMode Sha
|
2016-03-11 20:00:14 +00:00
|
|
|
deriving (Show, Eq)
|
2016-02-24 01:35:16 +00:00
|
|
|
|
2016-03-11 18:08:06 +00:00
|
|
|
treeItemToTreeContent :: TreeItem -> TreeContent
|
|
|
|
treeItemToTreeContent (TreeItem f m s) = TreeBlob f m s
|
2016-03-11 20:37:11 +00:00
|
|
|
|
|
|
|
-- FIXME: When addtreeitems has an item in a new
|
|
|
|
-- subdirectory, no subtree contains it. We need to add a
|
|
|
|
-- new subtree in this case, but not in the case where the
|
|
|
|
-- subdirectory already exists in the tree.
|
2016-03-11 18:08:06 +00:00
|
|
|
|
2016-02-24 01:35:16 +00:00
|
|
|
{- Applies an adjustment to items in a tree.
|
|
|
|
-
|
2016-03-11 20:45:40 +00:00
|
|
|
- While less flexible than using getTree and recordTree,
|
|
|
|
- this avoids buffering the whole tree in memory.
|
2016-02-24 01:35:16 +00:00
|
|
|
-}
|
2016-03-11 20:30:06 +00:00
|
|
|
adjustTree
|
|
|
|
:: (MonadIO m, MonadMask m)
|
|
|
|
=> (TreeItem -> m (Maybe TreeItem))
|
|
|
|
-- ^ Adjust an item in the tree. Nothing deletes the item.
|
|
|
|
-- Cannot move the item to a different tree.
|
|
|
|
-> [TreeItem]
|
|
|
|
-- ^ New items to add to the tree.
|
|
|
|
-> [TopFilePath]
|
|
|
|
-- ^ Files to remove from the tree.
|
|
|
|
-> Ref
|
|
|
|
-> Repo
|
|
|
|
-> m Sha
|
|
|
|
adjustTree adjusttreeitem addtreeitems removefiles r repo =
|
|
|
|
withMkTreeHandle repo $ \h -> do
|
|
|
|
(l, cleanup) <- liftIO $ lsTreeWithObjects r repo
|
|
|
|
(l', _, _) <- go h False [] inTopTree l
|
2016-03-11 20:45:40 +00:00
|
|
|
l'' <- adjustlist topitem l'
|
|
|
|
sha <- liftIO $ mkTree h l''
|
2016-03-11 20:30:06 +00:00
|
|
|
void $ liftIO cleanup
|
|
|
|
return sha
|
2016-02-24 01:35:16 +00:00
|
|
|
where
|
|
|
|
go _ wasmodified c _ [] = return (c, wasmodified, [])
|
2016-02-24 01:56:03 +00:00
|
|
|
go h wasmodified c intree (i:is)
|
2016-03-11 18:46:54 +00:00
|
|
|
| intree i = case readObjectType (LsTree.typeobj i) of
|
|
|
|
Just BlobObject -> do
|
|
|
|
let ti = TreeItem (LsTree.file i) (LsTree.mode i) (LsTree.sha i)
|
|
|
|
v <- adjusttreeitem ti
|
|
|
|
case v of
|
|
|
|
Nothing -> go h True c intree is
|
|
|
|
Just ti'@(TreeItem f m s) ->
|
|
|
|
let !modified = wasmodified || ti' /= ti
|
|
|
|
blob = TreeBlob f m s
|
|
|
|
in go h modified (blob:c) intree is
|
|
|
|
Just TreeObject -> do
|
|
|
|
(sl, modified, is') <- go h False [] (beneathSubTree i) is
|
2016-03-11 20:45:40 +00:00
|
|
|
sl' <- adjustlist (inTree i) sl
|
|
|
|
subtree <- if modified || sl' /= sl
|
|
|
|
then liftIO $ recordSubTree h $ NewSubTree (LsTree.file i) sl'
|
2016-03-11 18:46:54 +00:00
|
|
|
else return $ RecordedSubTree (LsTree.file i) (LsTree.sha i) []
|
|
|
|
let !modified' = modified || wasmodified
|
|
|
|
go h modified' (subtree : c) intree is'
|
|
|
|
_ -> error ("unexpected object type \"" ++ LsTree.typeobj i ++ "\"")
|
2016-02-24 01:35:16 +00:00
|
|
|
| otherwise = return (c, wasmodified, i:is)
|
2016-03-11 20:45:40 +00:00
|
|
|
adjustlist ishere l = do
|
|
|
|
let added = filter ishere addtreeitems
|
|
|
|
let l' = map treeItemToTreeContent added ++ l
|
|
|
|
let l'' = filter (not . removed) l'
|
|
|
|
return l''
|
2016-03-11 20:30:06 +00:00
|
|
|
topitem (TreeItem f _ _) = inTopTree' f
|
|
|
|
removeset = S.fromList removefiles
|
|
|
|
removed (TreeBlob f _ _) = S.member f removeset
|
|
|
|
removed _ = False
|
2016-02-24 01:35:16 +00:00
|
|
|
|
|
|
|
{- Assumes the list is ordered, with tree objects coming right before their
|
|
|
|
- contents. -}
|
|
|
|
extractTree :: [LsTree.TreeItem] -> Either String Tree
|
2016-03-11 18:08:06 +00:00
|
|
|
extractTree l = case go [] inTopTree l of
|
2016-02-24 01:35:16 +00:00
|
|
|
Right (t, []) -> Right (Tree t)
|
|
|
|
Right _ -> parseerr "unexpected tree form"
|
|
|
|
Left e -> parseerr e
|
|
|
|
where
|
|
|
|
go t _ [] = Right (t, [])
|
2016-02-24 01:56:03 +00:00
|
|
|
go t intree (i:is)
|
2016-03-11 18:46:54 +00:00
|
|
|
| intree i = case readObjectType (LsTree.typeobj i) of
|
|
|
|
Just BlobObject ->
|
|
|
|
let b = TreeBlob (LsTree.file i) (LsTree.mode i) (LsTree.sha i)
|
|
|
|
in go (b:t) intree is
|
|
|
|
Just TreeObject -> case go [] (beneathSubTree i) is of
|
|
|
|
Right (subtree, is') ->
|
|
|
|
let st = RecordedSubTree (LsTree.file i) (LsTree.sha i) subtree
|
|
|
|
in go (st:t) intree is'
|
|
|
|
Left e -> Left e
|
|
|
|
_ -> parseerr ("unexpected object type \"" ++ LsTree.typeobj i ++ "\"")
|
2016-02-24 01:35:16 +00:00
|
|
|
| otherwise = Right (t, i:is)
|
|
|
|
parseerr = Left
|
2016-02-24 01:56:03 +00:00
|
|
|
|
|
|
|
type InTree = LsTree.TreeItem -> Bool
|
|
|
|
|
2016-03-11 18:08:06 +00:00
|
|
|
inTopTree :: InTree
|
2016-03-11 20:00:14 +00:00
|
|
|
inTopTree = inTopTree' . LsTree.file
|
|
|
|
|
|
|
|
inTopTree' :: TopFilePath -> Bool
|
|
|
|
inTopTree' f = takeDirectory (getTopFilePath f) == "."
|
2016-02-24 01:56:03 +00:00
|
|
|
|
2016-03-11 18:08:06 +00:00
|
|
|
beneathSubTree :: LsTree.TreeItem -> InTree
|
|
|
|
beneathSubTree t =
|
2016-02-24 02:03:47 +00:00
|
|
|
let prefix = getTopFilePath (LsTree.file t) ++ "/"
|
2016-02-24 01:56:03 +00:00
|
|
|
in (\i -> prefix `isPrefixOf` getTopFilePath (LsTree.file i))
|
2016-03-11 18:08:06 +00:00
|
|
|
|
2016-03-11 18:46:54 +00:00
|
|
|
inTree :: LsTree.TreeItem -> TreeItem -> Bool
|
2016-03-11 20:30:06 +00:00
|
|
|
inTree = inTree' . LsTree.file
|
|
|
|
|
|
|
|
inTree' :: TopFilePath -> TreeItem -> Bool
|
|
|
|
inTree' f (TreeItem f' _ _) = takeDirectory (getTopFilePath f') == takeDirectory (getTopFilePath f)
|