packages feed

hit 0.3.0 → 0.4.0

raw patch · 17 files changed

+602/−280 lines, 17 filesdep +blaze-builderdep +system-fileiodep +system-filepathdep ~bytedumpPVP ok

version bump matches the API change (PVP)

Dependencies added: blaze-builder, system-fileio, system-filepath

Dependency ranges changed: bytedump

API changes (from Hackage documentation)

- Data.Git.Named: headExists :: FilePath -> FilePath -> IO Bool
- Data.Git.Named: headRead :: FilePath -> FilePath -> IO Ref
- Data.Git.Named: headWrite :: FilePath -> FilePath -> Ref -> IO ()
- Data.Git.Named: headsList :: FilePath -> IO [[Char]]
- Data.Git.Named: remoteList :: FilePath -> FilePath -> IO [[Char]]
- Data.Git.Named: remotesList :: FilePath -> IO [[Char]]
- Data.Git.Named: specialExists :: FilePath -> FilePath -> IO Bool
- Data.Git.Named: specialRead :: [Char] -> FilePath -> IO Ref
- Data.Git.Named: tagExists :: FilePath -> FilePath -> IO Bool
- Data.Git.Named: tagRead :: FilePath -> FilePath -> IO Ref
- Data.Git.Named: tagWrite :: FilePath -> FilePath -> Ref -> IO ()
- Data.Git.Named: tagsList :: FilePath -> IO [[Char]]
- Data.Git.Storage.Object: Object :: a -> Object
- Data.Git.Storage.Object: objectWrap :: Objectable a => a -> Object
- Data.Git.Types: type Name = (ByteString, ByteString, UTCTime, TimeZone)
+ Data.Git: CommitExtra :: ByteString -> ByteString -> CommitExtra
+ Data.Git: GitTime :: Integer -> Int -> GitTime
+ Data.Git: Person :: ByteString -> ByteString -> GitTime -> Person
+ Data.Git: commitEncoding :: Commit -> Maybe ByteString
+ Data.Git: commitExtraKey :: CommitExtra -> ByteString
+ Data.Git: commitExtraValue :: CommitExtra -> ByteString
+ Data.Git: commitExtras :: Commit -> [CommitExtra]
+ Data.Git: data CommitExtra
+ Data.Git: data GitTime
+ Data.Git: data Person
+ Data.Git: personEmail :: Person -> ByteString
+ Data.Git: personName :: Person -> ByteString
+ Data.Git: personTime :: Person -> GitTime
+ Data.Git.Named: RefBranch :: String -> RefSpecTy
+ Data.Git.Named: RefContentUnknown :: ByteString -> RefContentTy
+ Data.Git.Named: RefDirect :: Ref -> RefContentTy
+ Data.Git.Named: RefFetchHead :: RefSpecTy
+ Data.Git.Named: RefHead :: RefSpecTy
+ Data.Git.Named: RefLink :: RefSpecTy -> RefContentTy
+ Data.Git.Named: RefOrigHead :: RefSpecTy
+ Data.Git.Named: RefOther :: String -> RefSpecTy
+ Data.Git.Named: RefPatches :: String -> RefSpecTy
+ Data.Git.Named: RefRemote :: String -> RefSpecTy
+ Data.Git.Named: RefStash :: RefSpecTy
+ Data.Git.Named: RefTag :: String -> RefSpecTy
+ Data.Git.Named: data RefContentTy
+ Data.Git.Named: data RefSpecTy
+ Data.Git.Named: existsRefFile :: FilePath -> RefSpecTy -> IO Bool
+ Data.Git.Named: instance Eq RefContentTy
+ Data.Git.Named: instance Eq RefSpecTy
+ Data.Git.Named: instance Ord RefSpecTy
+ Data.Git.Named: instance Show RefContentTy
+ Data.Git.Named: instance Show RefSpecTy
+ Data.Git.Named: readPackedRefs :: FilePath -> IO [(RefSpecTy, Ref)]
+ Data.Git.Named: readRefFile :: FilePath -> RefSpecTy -> IO RefContentTy
+ Data.Git.Named: writeRefFile :: FilePath -> RefSpecTy -> RefContentTy -> IO ()
+ Data.Git.Storage: packedNamed :: Git -> PackedRef
+ Data.Git.Storage.Loose: Zipped :: ByteString -> Zipped
+ Data.Git.Storage.Loose: getZippedData :: Zipped -> ByteString
+ Data.Git.Storage.Loose: looseUnmarshallZipped :: Zipped -> Object
+ Data.Git.Storage.Loose: looseUnmarshallZippedRaw :: Zipped -> (ObjectHeader, ObjectData)
+ Data.Git.Storage.Loose: newtype Zipped
+ Data.Git.Storage.Object: ObjBlob :: Blob -> Object
+ Data.Git.Storage.Object: ObjCommit :: Commit -> Object
+ Data.Git.Storage.Object: ObjDeltaOfs :: DeltaOfs -> Object
+ Data.Git.Storage.Object: ObjDeltaRef :: DeltaRef -> Object
+ Data.Git.Storage.Object: ObjTag :: Tag -> Object
+ Data.Git.Storage.Object: ObjTree :: Tree -> Object
+ Data.Git.Storage.Object: class Objectable a
+ Data.Git.Storage.Object: getRaw :: Objectable a => a -> ByteString
+ Data.Git.Storage.Object: getType :: Objectable a => a -> ObjectType
+ Data.Git.Storage.Object: instance Eq Object
+ Data.Git.Storage.Object: instance Show Object
+ Data.Git.Storage.Object: isDelta :: Objectable a => a -> Bool
+ Data.Git.Storage.Object: toObject :: Objectable a => a -> Object
+ Data.Git.Types: CommitExtra :: ByteString -> ByteString -> CommitExtra
+ Data.Git.Types: GitTime :: Integer -> Int -> GitTime
+ Data.Git.Types: Person :: ByteString -> ByteString -> GitTime -> Person
+ Data.Git.Types: commitEncoding :: Commit -> Maybe ByteString
+ Data.Git.Types: commitExtraKey :: CommitExtra -> ByteString
+ Data.Git.Types: commitExtraValue :: CommitExtra -> ByteString
+ Data.Git.Types: commitExtras :: Commit -> [CommitExtra]
+ Data.Git.Types: data CommitExtra
+ Data.Git.Types: data GitTime
+ Data.Git.Types: data Person
+ Data.Git.Types: instance Eq CommitExtra
+ Data.Git.Types: instance Eq GitTime
+ Data.Git.Types: instance Eq Person
+ Data.Git.Types: instance Show CommitExtra
+ Data.Git.Types: instance Show GitTime
+ Data.Git.Types: instance Show Person
+ Data.Git.Types: personEmail :: Person -> ByteString
+ Data.Git.Types: personName :: Person -> ByteString
+ Data.Git.Types: personTime :: Person -> GitTime
+ Data.Git.Types: toPOSIXTime :: GitTime -> POSIXTime
+ Data.Git.Types: toUTCTime :: GitTime -> UTCTime
- Data.Git: Commit :: Ref -> [Ref] -> Name -> Name -> ByteString -> Commit
+ Data.Git: Commit :: Ref -> [Ref] -> Person -> Person -> Maybe ByteString -> [CommitExtra] -> ByteString -> Commit
- Data.Git: Tag :: Ref -> ObjectType -> ByteString -> Name -> ByteString -> Tag
+ Data.Git: Tag :: Ref -> ObjectType -> ByteString -> Person -> ByteString -> Tag
- Data.Git: commitAuthor :: Commit -> Name
+ Data.Git: commitAuthor :: Commit -> Person
- Data.Git: commitCommitter :: Commit -> Name
+ Data.Git: commitCommitter :: Commit -> Person
- Data.Git: tagName :: Tag -> Name
+ Data.Git: tagName :: Tag -> Person
- Data.Git.Storage.PackIndex: PackIndexHeader :: !Word32 -> !Vector Word32 -> PackIndexHeader
+ Data.Git.Storage.PackIndex: PackIndexHeader :: !Word32 -> !(Vector Word32) -> PackIndexHeader
- Data.Git.Types: Commit :: Ref -> [Ref] -> Name -> Name -> ByteString -> Commit
+ Data.Git.Types: Commit :: Ref -> [Ref] -> Person -> Person -> Maybe ByteString -> [CommitExtra] -> ByteString -> Commit
- Data.Git.Types: Tag :: Ref -> ObjectType -> ByteString -> Name -> ByteString -> Tag
+ Data.Git.Types: Tag :: Ref -> ObjectType -> ByteString -> Person -> ByteString -> Tag
- Data.Git.Types: commitAuthor :: Commit -> Name
+ Data.Git.Types: commitAuthor :: Commit -> Person
- Data.Git.Types: commitCommitter :: Commit -> Name
+ Data.Git.Types: commitCommitter :: Commit -> Person
- Data.Git.Types: tagName :: Tag -> Name
+ Data.Git.Types: tagName :: Tag -> Person

Files

Data/Git.hs view
@@ -10,9 +10,12 @@     -- * Basic types       Ref     , Commit(..)+    , Person(..)+    , CommitExtra(..)     , Tree(..)     , Blob(..)     , Tag(..)+    , GitTime(..)      -- * Revision     , Revision
Data/Git/Internal.hs view
@@ -6,13 +6,22 @@ -- Portability : unix -- module Data.Git.Internal-        ( be32-        , be16-        ) where+    ( be32+    , be16+    , Zipped(..)+    , readZippedFile+    , dezip+    ) where +import Control.Applicative import Data.Bits import Data.Word import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Codec.Compression.Zlib+import Filesystem.Path+import Filesystem.Path.Rules+import Prelude hiding (FilePath)  be32 :: B.ByteString -> Word32 be32 b = fromIntegral (B.index b 0) `shiftL` 24@@ -23,3 +32,12 @@ be16 :: B.ByteString -> Word16 be16 b = fromIntegral (B.index b 0) `shiftL` 8        + fromIntegral (B.index b 1)++newtype Zipped = Zipped { getZippedData :: L.ByteString }+    deriving (Show,Eq)++readZippedFile :: FilePath -> IO Zipped+readZippedFile fp = Zipped <$> L.readFile (encodeString posix fp)++dezip :: Zipped -> L.ByteString+dezip = decompress . getZippedData
Data/Git/Named.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-} -- | -- Module      : Data.Git.Named -- License     : BSD-style@@ -7,33 +8,100 @@ -- Portability : unix -- module Data.Git.Named-        ( headsList-        , headExists-        , headRead-        , headWrite-        , remotesList-        , remoteList-        , tagsList-        , tagExists-        , tagRead-        , tagWrite-        , specialRead-        , specialExists-        ) where+    ( RefSpecTy(..)+    , RefContentTy(..)+    , readPackedRefs+    , existsRefFile+    , writeRefFile+    , readRefFile+    ) where  import Control.Applicative ((<$>)) -import System.Directory+import qualified Filesystem as F+import qualified Filesystem.Path.Rules as FP (posix, decode, encode, encodeString, decodeString)+import Filesystem.Path.CurrentOS +import Data.String import Data.Git.Path import Data.Git.Ref import Data.List (isPrefixOf) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as BC+import Prelude hiding (FilePath) -getDirectoryContentNoDots path = filter noDot <$> getDirectoryContents path-        where noDot = (not . isPrefixOf ".")+-- | Represent a named specifier.+data RefSpecTy = RefHead+               | RefOrigHead+               | RefFetchHead+               | RefBranch String+               | RefTag String+               | RefRemote String+               | RefPatches String+               | RefStash+               | RefOther String+               deriving (Show,Eq,Ord) +-- | content of a ref file.+data RefContentTy = RefDirect Ref+                  | RefLink   RefSpecTy+                  | RefContentUnknown B.ByteString+                  deriving (Show,Eq)++-- FIXME BC.unpack/pack should be probably be utf8.toString,+-- however i don't know if encoding is consistant.+-- it should probably be overridable.+pathDecode :: B.ByteString -> FilePath+pathDecode = FP.decode FP.posix++pathEncode :: FilePath -> B.ByteString+pathEncode = FP.encode FP.posix++toRefTy :: String -> RefSpecTy+toRefTy s+    | "refs/tags/" `isPrefixOf` s    = RefTag $ drop 10 s+    | "refs/heads/" `isPrefixOf` s   = RefBranch $ drop 11 s+    | "refs/remotes/" `isPrefixOf` s = RefRemote $ drop 13 s+    | "refs/patches/" `isPrefixOf` s = RefPatches $ drop 13 s+    | "refs/stash" == s              = RefStash+    | "HEAD" == s                    = RefHead+    | "ORIG_HEAD" == s               = RefOrigHead+    | "FETCH_HEAD" == s              = RefFetchHead+    | otherwise                      = RefOther $ s++fromRefTy :: RefSpecTy -> String+fromRefTy (RefBranch h)  = "refs/heads/" ++ h+fromRefTy (RefTag h)     = "refs/tags/" ++ h+fromRefTy (RefRemote h)  = "refs/remotes/" ++ h+fromRefTy (RefPatches h) = "refs/patches/" ++ h+fromRefTy RefStash       = "refs/stash"+fromRefTy RefHead        = "HEAD"+fromRefTy RefOrigHead    = "ORIG_HEAD"+fromRefTy RefFetchHead   = "FETCH_HEAD"+fromRefTy (RefOther h)   = h++toPath :: FilePath -> RefSpecTy -> FilePath+toPath gitRepo (RefBranch h)  = gitRepo </> "refs" </> "heads" </> fromString h+toPath gitRepo (RefTag h)     = gitRepo </> "refs" </> "tags" </> fromString h+toPath gitRepo (RefRemote h)  = gitRepo </> "refs" </> "remotes" </> fromString h+toPath gitRepo (RefPatches h) = gitRepo </> "refs" </> "patches" </> fromString h+toPath gitRepo RefStash       = gitRepo </> "refs" </> "stash"+toPath gitRepo RefHead        = gitRepo </> "HEAD"+toPath gitRepo RefOrigHead    = gitRepo </> "ORIG_HEAD"+toPath gitRepo RefFetchHead   = gitRepo </> "FETCH_HEAD"+toPath gitRepo (RefOther h)   = gitRepo </> fromString h++readPackedRefs gitRepo = do+    exists <- F.isFile (packedRefsPath gitRepo)+    if exists then readLines else return []+    where readLines = foldl accu [] . BC.lines <$> F.readFile (packedRefsPath gitRepo)+          accu a l+            | "#" `BC.isPrefixOf` l = a+            | otherwise = let (ref, r) = B.splitAt 40 l+                              name     = FP.encodeString FP.posix $ pathDecode $ B.tail r+                           in (toRefTy name, fromHex ref) : a++{- headsList gitRepo = getDirectoryContentNoDots (headsPath gitRepo) tagsList gitRepo = getDirectoryContentNoDots (tagsPath gitRepo) remotesList gitRepo = getDirectoryContentNoDots (remotesPath gitRepo)@@ -41,15 +109,27 @@  writeRef path ref = B.writeFile path (B.concat [toHex ref, B.singleton 0xa]) readRef path = fromHex . B.take 40 <$> B.readFile path+-} -readRefAndFollow gitRepo path = do-        content <- B.readFile path-        if "ref: " `B.isPrefixOf` content-                then do -- BC.unpack should be utf8.toString, and the whole thing is really fragile. need to do the proper thing.-                        let file = BC.unpack $ BC.init $ B.drop 5 content-                        readRefAndFollow gitRepo (gitRepo ++ "/" ++ file)-                else return (fromHex $ B.take 40 content)+existsRefFile :: FilePath -> RefSpecTy -> IO Bool+existsRefFile gitRepo specty = F.isFile $ toPath gitRepo specty +writeRefFile :: FilePath -> RefSpecTy -> RefContentTy -> IO ()+writeRefFile gitRepo specty refcont = F.writeFile filepath $ fromRefContent refcont+    where filepath = toPath gitRepo specty+          fromRefContent (RefLink link)        = B.concat ["ref: ", pathEncode $ FP.decodeString FP.posix $ fromRefTy link, B.singleton 0xa]+          fromRefContent (RefDirect ref)       = B.concat [toHex ref, B.singleton 0xa]+          fromRefContent (RefContentUnknown c) = c++readRefFile :: FilePath -> RefSpecTy -> IO RefContentTy+readRefFile gitRepo specty = toRefContent <$> F.readFile filepath+    where filepath = toPath gitRepo specty+          toRefContent content+            | "ref: " `B.isPrefixOf` content = RefLink $ toRefTy $ FP.encodeString FP.posix $ pathDecode $ head $ BC.lines $ B.drop 5 content+            | B.length content < 42          = RefDirect $ fromHex $ B.take 40 content+            | otherwise                      = RefContentUnknown content++{- headExists gitRepo name    = doesFileExist (headPath gitRepo name) headRead gitRepo name      = readRef (headPath gitRepo name) headWrite gitRepo name ref = writeRef (headPath gitRepo name) ref@@ -60,3 +140,4 @@  specialRead gitRepo name   = readRefAndFollow gitRepo (specialPath gitRepo name) specialExists gitRepo name = doesFileExist (specialPath gitRepo name)+-}
Data/Git/Path.hs view
@@ -5,36 +5,39 @@ -- Stability   : experimental -- Portability : unix --+{-# LANGUAGE OverloadedStrings #-} module Data.Git.Path where -import System.FilePath+import Filesystem.Path.CurrentOS import System.Random import Control.Applicative ((<$>)) import Data.Git.Ref+import Data.String  headsPath gitRepo = gitRepo </> "refs" </> "heads" tagsPath gitRepo  = gitRepo </> "refs" </> "tags" remotesPath gitRepo = gitRepo </> "refs" </> "remotes"+packedRefsPath gitRepo = gitRepo </> "packed-refs" -headPath gitRepo name = headsPath gitRepo </> name-tagPath gitRepo name = tagsPath gitRepo </> name-remotePath gitRepo name = remotesPath gitRepo </> name-specialPath gitRepo name = gitRepo </> name+headPath gitRepo name = headsPath gitRepo </> fromString name+tagPath gitRepo name = tagsPath gitRepo </> fromString name+remotePath gitRepo name = remotesPath gitRepo </> fromString name+specialPath gitRepo name = gitRepo </> fromString name -remoteEntPath gitRepo name ent = remotePath gitRepo name </> ent+remoteEntPath gitRepo name ent = remotePath gitRepo name </> fromString ent  packDirPath repoPath = repoPath </> "objects" </> "pack"  indexPath repoPath indexRef =-        packDirPath repoPath </> ("pack-" ++ toHexString indexRef ++ ".idx")+        packDirPath repoPath </> fromString ("pack-" ++ toHexString indexRef ++ ".idx")  packPath repoPath packRef =-        packDirPath repoPath </> ("pack-" ++ toHexString packRef ++ ".pack")+        packDirPath repoPath </> fromString ("pack-" ++ toHexString packRef ++ ".pack") -objectPath repoPath d f = repoPath </> "objects" </> d </> f+objectPath repoPath d f = repoPath </> "objects" </> fromString d </> fromString f objectPathOfRef repoPath ref = objectPath repoPath d f         where (d,f) = toFilePathParts ref  objectTemporaryPath repoPath = do         r <- fst . random <$> getStdGen :: IO Int-        return (repoPath </> "objects" </> ("tmp-" ++ show r ++ ".tmp"))+        return (repoPath </> "objects" </> fromString ("tmp-" ++ show r ++ ".tmp"))
Data/Git/Repository.hs view
@@ -36,8 +36,11 @@ import Data.Git.Storage import Data.Git.Revision import Data.Git.Storage.Loose+import Data.Git.Storage.CacheFile import Data.Git.Ref +import qualified Data.Map as M+ -- | hierarchy tree, either a reference to a blob (file) or a tree (directory). data HTreeEnt = TreeDir Ref HTree | TreeFile Ref type HTree = [(Int,ByteString,HTreeEnt)]@@ -50,66 +53,87 @@ getCommit :: Git -> Ref -> IO (Maybe Commit) getCommit git ref = getObject git ref True >>= mapJustM unwrap         where-                unwrap (objectToCommit -> Just c@(Commit _ _ _ _ _)) = return $ Just c-                unwrap _                                             = return Nothing+                unwrap (objectToCommit -> Just c@(Commit {})) = return $ Just c+                unwrap _                                      = return Nothing  -- | get a specified tree getTree :: Git -> Ref -> IO (Maybe Tree) getTree git ref = getObject git ref True >>= mapJustM unwrap         where-                unwrap (objectToTree -> Just c@(Tree _ )) = return $ Just c+                unwrap (objectToTree -> Just c@(Tree {})) = return $ Just c                 unwrap _                                  = return Nothing  -- | try to resolve a string to a specific commit ref -- for example: HEAD, HEAD^, master~3, shortRef resolveRevision :: Git -> Revision -> IO (Maybe Ref)-resolveRevision git (Revision prefix modifiers) = resolvePrefix >>= modf modifiers-        where-                resolvePrefix :: IO Ref-                resolvePrefix = resolveNamedPrefix fs >>= maybe resolvePrePrefix (return . Just) >>= maybe (return $ fromHexString prefix) return+resolveRevision git (Revision prefix modifiers) =+    getCacheVal (packedNamed git) >>= \c -> resolvePrefix c >>= modf modifiers+    where+          resolvePrefix lookupCache = tryResolvers+                [resolveNamedPrefix lookupCache namedResolvers+                ,resolvePrePrefix+                ] -                resolvePrePrefix :: IO (Maybe Ref)-                resolvePrePrefix = do-                        refs <- findReferencesWithPrefix git prefix-                        case refs of-                                []  -> return Nothing-                                [r] -> return (Just r)-                                _   -> error "multiple references with this prefix"+          resolveNamedPrefix _           []     = return Nothing+          resolveNamedPrefix lookupCache (x:xs) = followToRef (resolveNamedPrefix lookupCache xs) x+            where followToRef onFailure refty = do+                      exists <- existsRefFile (gitRepoPath git) refty+                      if exists+                          then do refcont <- readRefFile (gitRepoPath git) refty+                                  case refcont of+                                       RefDirect ref     -> return $ Just ref+                                       RefLink refspecty -> followToRef onFailure refspecty+                                       _                 -> error "cannot handle reference content"+                          else case M.lookup refty lookupCache of+                                    Nothing -> onFailure+                                    y       -> return y -                fs = [ (specialExists, specialRead), (tagExists, tagRead), (headExists, headRead) ]+          namedResolvers = case prefix of+                               "HEAD"       -> [ RefHead ]+                               "ORIG_HEAD"  -> [ RefOrigHead ]+                               "FETCH_HEAD" -> [ RefFetchHead ]+                               _            -> [ RefTag prefix, RefBranch prefix, RefRemote prefix ] -                resolveNamedPrefix []     = return Nothing-                resolveNamedPrefix (x:xs) = do-                        exists <- (fst x) (gitRepoPath git) prefix-                        if exists-                                then Just <$> (snd x) (gitRepoPath git) prefix-                                else resolveNamedPrefix xs-        -                modf [] ref                  = return (Just ref)-                modf (RevModParent i:xs) ref = do-                        parentRefs <- getParentRefs ref-                        case i of-                                0 -> error "revision modifier ^0 is not implemented"-                                _ -> case drop (i - 1) parentRefs of-                                        []    -> error "no such parent"-                                        (p:_) -> modf xs p -                modf (RevModParentFirstN 1:xs) ref = modf (RevModParent 1:xs) ref-                modf (RevModParentFirstN n:xs) ref = do-                        parentRefs <- getParentRefs ref-                        modf (RevModParentFirstN (n-1):xs) (head parentRefs)-                modf (_:_) _ = error "unimplemented revision modifier"+          tryResolvers :: [IO (Maybe Ref)] -> IO Ref+          tryResolvers []            = return $ fromHexString prefix+          tryResolvers (resolver:xs) = resolver >>= isResolved+             where isResolved (Just r) = return r+                   isResolved Nothing  = tryResolvers xs -                getParentRefs ref = do-                        obj <- getCommit git ref-                        case obj of-                                Just (Commit _ parents _ _ _) -> return parents-                                Nothing -> error "reference in commit chain doesn't exists"+          resolvePrePrefix :: IO (Maybe Ref)+          resolvePrePrefix = do+              refs <- findReferencesWithPrefix git prefix+              case refs of+                  []  -> return Nothing+                  [r] -> return (Just r)+                  _   -> error "multiple references with this prefix" +          modf [] ref                  = return (Just ref)+          modf (RevModParent i:xs) ref = do+              parentRefs <- getParentRefs ref+              case i of+                  0 -> error "revision modifier ^0 is not implemented"+                  _ -> case drop (i - 1) parentRefs of+                            []    -> error "no such parent"+                            (p:_) -> modf xs p++          modf (RevModParentFirstN 1:xs) ref = modf (RevModParent 1:xs) ref+          modf (RevModParentFirstN n:xs) ref = do+              parentRefs <- getParentRefs ref+              modf (RevModParentFirstN (n-1):xs) (head parentRefs)+          modf (_:_) _ = error "unimplemented revision modifier"++          getParentRefs ref = do+              obj <- getCommit git ref+              case obj of+                  Just (Commit { commitParents = parents }) -> return parents+                  Nothing -> error "reference in commit chain doesn't exists"+ -- | returns a tree from a ref that might be either a commit, a tree or a tag. resolveTreeish :: Git -> Ref -> IO (Maybe Tree) resolveTreeish git ref = getObject git ref True >>= mapJustM recToTree where-        recToTree (objectToCommit -> Just (Commit tree _ _ _ _)) = resolveTreeish git tree+        recToTree (objectToCommit -> Just (Commit { commitTreeish = tree })) = resolveTreeish git tree         recToTree (objectToTag    -> Just (Tag tref _ _ _ _))    = resolveTreeish git tref         recToTree (objectToTree   -> Just t@(Tree _))            = return $ Just t         recToTree _                                              = return Nothing@@ -146,12 +170,12 @@            process [] = error "nothing to rewrite"           process ((_,commit):next) =-                      mapCommit commit >>= looseWrite (gitRepoPath git) . objectWrap >>= flip rewriteOne next+                      mapCommit commit >>= looseWrite (gitRepoPath git) . toObject >>= flip rewriteOne next            rewriteOne prevRef [] = return prevRef           rewriteOne prevRef ((_,commit):next) = do                       newCommit <- mapCommit $ commit { commitParents = [prevRef] }-                      ref       <- looseWrite (gitRepoPath git) (objectWrap newCommit)+                      ref       <- looseWrite (gitRepoPath git) (toObject newCommit)                       rewriteOne ref next  -- | build a hierarchy tree from a tree object@@ -179,7 +203,7 @@ resolvePath git commitRef paths = do         commit <- getCommit git commitRef         case commit of-                Just (Commit tree _ _ _ _) -> resolve tree paths+                Just (Commit { commitTreeish = tree }) -> resolve tree paths                 Nothing                    -> error ("not a valid commit ref: " ++ show commitRef)         where                 resolve :: Ref -> [ByteString] -> IO (Maybe Ref)
Data/Git/Storage.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings #-} -- | -- Module      : Data.Git.Storage -- License     : BSD-style@@ -9,6 +10,7 @@  module Data.Git.Storage     ( Git+    , packedNamed     , gitRepoPath     , openRepo     , closeRepo@@ -30,8 +32,9 @@     , setObject     ) where -import System.Directory-import System.FilePath+import Filesystem+import Filesystem.Path hiding (concat)+import Filesystem.Path.Rules import System.Environment  import Control.Applicative ((<$>))@@ -39,31 +42,44 @@ import qualified Control.Exception as E import Control.Monad +import Data.String import Data.List ((\\), isPrefixOf) import Data.IORef import Data.Word +import Data.Git.Named+import Data.Git.Path (packedRefsPath) import Data.Git.Delta import Data.Git.Storage.FileReader import Data.Git.Storage.PackIndex import Data.Git.Storage.Object import Data.Git.Storage.Pack import Data.Git.Storage.Loose+import Data.Git.Storage.CacheFile import Data.Git.Ref +import qualified Data.Map as M++import Prelude hiding (FilePath)+ data PackIndexReader = PackIndexReader PackIndexHeader FileReader +-- | this is a cache representation of the packed-ref file+type PackedRef = CacheFile (M.Map RefSpecTy Ref)+ -- | represent an git repo, with possibly already opened filereaders -- for indexes and packs data Git = Git         { gitRepoPath  :: FilePath         , indexReaders :: IORef [(Ref, PackIndexReader)]         , packReaders  :: IORef [(Ref, FileReader)]+        , packedNamed  :: PackedRef         }  -- | open a new git repository context openRepo :: FilePath -> IO Git-openRepo path = liftM2 (Git path) (newIORef []) (newIORef [])+openRepo path = liftM3 (Git path) (newIORef []) (newIORef []) packedRef+    where packedRef = newCacheVal (packedRefsPath path) (M.fromList <$> readPackedRefs path) M.empty  -- | close a git repository context, closing all remaining fileReaders. closeRepo :: Git -> IO ()@@ -78,19 +94,20 @@ -- otherwise iterate from current directory, up to 128 parents for a .git directory findRepo :: IO FilePath findRepo = do-        menvDir <- E.catch (Just <$> getEnv "GIT_DIR") (\(_:: SomeException) -> return Nothing)+        menvDir <- E.catch (Just . decodeString posix_ghc704 <$> getEnv "GIT_DIR") (\(_:: SomeException) -> return Nothing)         case menvDir of-                Nothing     -> checkDir 0+                Nothing     -> getWorkingDirectory >>= checkDir 0                 Just envDir -> do                         e <- isRepo envDir                         when (not e) $ error "environment GIT_DIR is not a git repository"                          return envDir         where-                checkDir 128 = error "not a git repository"-                checkDir n   = do-                        let filepath = concat (replicate n ("../") ++ [".git"])+                checkDir :: Int -> FilePath -> IO FilePath+                checkDir 128 _  = error "not a git repository"+                checkDir n   wd = do+                        let filepath = wd </> ".git"                         e <- isRepo filepath-                        if e then return filepath else checkDir (n+1)+                        if e then return filepath else checkDir (n+1) (if absolute wd then parent wd else wd </> "..")  -- | execute a function f with a git context. withRepo path f = bracket (openRepo path) closeRepo f@@ -104,23 +121,23 @@ -- | basic checks to see if a specific path looks like a git repo. isRepo :: FilePath -> IO Bool isRepo path = do-        dir     <- doesDirectoryExist path-        subDirs <- mapM (doesDirectoryExist . (path </>))-                ["branches","hooks","info"-                ,"logs","objects","refs"-                ,"refs"</>"heads","refs"</>"tags"]+        dir     <- isDirectory path+        subDirs <- mapM (isDirectory . (path </>))+                [ "branches", "hooks", "info"+                , "logs", "objects", "refs"+                , "refs"</> "heads", "refs"</> "tags"]         return $ and ([dir] ++ subDirs)  -- | initialize a new repository at a specific location. initRepo :: FilePath -> IO () initRepo path = do-        exists <- doesDirectoryExist path+        exists <- isDirectory path         when exists $ error "destination directory already exists"-        createDirectory path-        mapM_ (createDirectory . (path </>))-                ["branches","hooks","info"-                ,"logs","objects","refs"-                ,"refs"</>"heads","refs"</>"tags"]+        createDirectory True path+        mapM_ (createDirectory False . (path </>))+                [ "branches", "hooks", "info"+                , "logs", "objects", "refs"+                , "refs"</> "heads", "refs"</> "tags"]  iterateIndexes git f initAcc = do         allIndexes    <- packIndexEnumerate (gitRepoPath git)@@ -175,8 +192,8 @@ -- | get all the references that start by a specific prefix findReferencesWithPrefix :: Git -> String -> IO [Ref] findReferencesWithPrefix git pre-        | invalidLength         = error "not a valid prefix"-        | not (isHexString pre) = error "reference prefix contains non hexchar"+        | invalidLength         = error ("not a valid prefix: " ++ show pre)+        | not (isHexString pre) = error ("reference prefix contains non hexchar: " ++ show pre)         | otherwise             = do                 looseRefs  <- looseEnumerateWithPrefixFilter (gitRepoPath git) (take 2 pre) matchRef                 packedRefs <- concat <$> iterateIndexes git idxPrefixMatch []@@ -256,18 +273,18 @@  -- | get an object from repository using a location to reference it. getObjectAt :: Git -> ObjectLocation -> Bool -> IO (Maybe Object)-getObjectAt git loc resolveDelta = maybe Nothing toObject <$> getObjectRawAt git loc resolveDelta+getObjectAt git loc resolveDelta = maybe Nothing toObj <$> getObjectRawAt git loc resolveDelta         where-                toObject (ObjectInfo { oiHeader = (ty, _, extra), oiData = objData }) = packObjectFromRaw (ty, extra, objData)+                toObj (ObjectInfo { oiHeader = (ty, _, extra), oiData = objData }) = packObjectFromRaw (ty, extra, objData)  -- | get an object from repository using a ref. getObject :: Git               -- ^ repository           -> Ref               -- ^ the object's reference to           -> Bool              -- ^ whether to resolve deltas if found           -> IO (Maybe Object) -- ^ returned object if found-getObject git ref resolveDelta = maybe Nothing toObject <$> getObjectRaw git ref resolveDelta+getObject git ref resolveDelta = maybe Nothing toObj <$> getObjectRaw git ref resolveDelta         where-                toObject (ObjectInfo { oiHeader = (ty, _, extra), oiData = objData }) = packObjectFromRaw (ty, extra, objData)+                toObj (ObjectInfo { oiHeader = (ty, _, extra), oiData = objData }) = packObjectFromRaw (ty, extra, objData)  -- | set an object in the store and returns the new ref -- this is always going to create a loose object.
+ Data/Git/Storage/CacheFile.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module      : Data.Git.Storage.CacheFile+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Storage.CacheFile (CacheFile, newCacheVal, getCacheVal) where++import Control.Applicative ((<$>))+import Control.Concurrent.MVar+import qualified Control.Exception as E+import Data.Time.Clock+import Filesystem+import Filesystem.Path+import Prelude hiding (FilePath)++data CacheFile a = CacheFile+    { cacheFilepath :: FilePath+    , cacheRefresh  :: IO a+    , cacheIniVal   :: a+    , cacheLock     :: MVar (MTime, a)+    }++utcZero = UTCTime (toEnum 0) 0++newCacheVal :: FilePath -> IO a -> a -> IO (CacheFile a)+newCacheVal path refresh initialVal =+    CacheFile path refresh initialVal <$> newMVar (MTime utcZero, initialVal)++getCacheVal :: CacheFile a -> IO a+getCacheVal cachefile = modifyMVar (cacheLock cachefile) getOrRefresh+    where getOrRefresh s@(mtime, cachedVal) = do+             cMTime <- getMTime $ cacheFilepath cachefile+             case cMTime of+                  Nothing -> return ((MTime utcZero, cacheIniVal cachefile), cacheIniVal cachefile)+                  Just newMtime | newMtime > mtime -> cacheRefresh cachefile >>= \v -> return ((newMtime, v), v)+                                | otherwise        -> return (s, cachedVal)++newtype MTime = MTime UTCTime deriving (Eq,Ord)++getMTime filepath = (Just . MTime <$> getModified filepath)+            `E.catch` \(_ :: E.SomeException) -> return Nothing
Data/Git/Storage/FileReader.hs view
@@ -5,6 +5,7 @@ -- Stability   : experimental -- Portability : unix --+{-# LANGUAGE DeriveDataTypeable #-} module Data.Git.Storage.FileReader         ( FileReader         , fileReaderNew@@ -37,13 +38,18 @@ import Data.ByteString.Lazy.Internal (defaultChunkSize) import Data.IORef +import Data.Data import Data.Word  import Codec.Zlib import Codec.Zlib.Lowlevel import Foreign.ForeignPtr+import qualified Control.Exception as E -import System.IO+import System.IO (hClose, hSeek, SeekMode(..))+import Filesystem+import Filesystem.Path+import Prelude hiding (FilePath)  data FileReader = FileReader         { fbHandle     :: Handle@@ -53,6 +59,11 @@         , fbPos        :: IORef Word64         } +data InflateException = InflateException Word64 Word64 String+    deriving (Show,Eq,Typeable)++instance E.Exception InflateException+ fileReaderNew :: Bool -> Handle -> IO FileReader fileReaderNew decompress handle = do         ref <- newIORef B.empty@@ -149,11 +160,14 @@                 let maxToInflate = min left (16 * 1024)                 let lastBlock = if left == maxToInflate then True else False                 (dbs,remaining) <- inflateToSize inflate (fromIntegral maxToInflate) lastBlock rbs (fileReaderGetNext fb)+                                   `E.catch` augmentAndRaise left                 writeIORef ref remaining                 let nleft = left - fromIntegral (B.length dbs)                 if nleft > 0                         then liftM (dbs:) (loop inflate nleft)                         else return [dbs]+              augmentAndRaise :: Word64 -> E.SomeException -> IO a+              augmentAndRaise left exn = throwIO $ InflateException outputSize left (show exn)  -- lowlevel helpers to inflate only to a specific size. 
Data/Git/Storage/FileWriter.hs view
@@ -15,7 +15,8 @@  import qualified Crypto.Hash.SHA1 as SHA1 -import System.IO+import System.IO (hClose)+import Filesystem  defaultCompression = 6 
Data/Git/Storage/Loose.hs view
@@ -5,12 +5,15 @@ -- Stability   : experimental -- Portability : unix ---{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables, ViewPatterns #-} module Data.Git.Storage.Loose         (+          Zipped(..)         -- * marshall from and to lazy bytestring-          looseUnmarshall+        , looseUnmarshall         , looseUnmarshallRaw+        , looseUnmarshallZipped+        , looseUnmarshallZippedRaw         , looseMarshall         -- * read and check object existence         , looseRead@@ -29,15 +32,17 @@ import Codec.Compression.Zlib import Data.Git.Ref import Data.Git.Path+import Data.Git.Internal import Data.Git.Storage.FileWriter import Data.Git.Storage.Object -import System.FilePath-import System.Directory-import System.IO (openFile, hFileSize, IOMode(..))+import Filesystem+import Filesystem.Path+import Filesystem.Path.Rules  import qualified Data.ByteString.Char8 as BC import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString as B  import Data.Attoparsec.Lazy import qualified Data.Attoparsec.Char8 as PC@@ -46,8 +51,11 @@ import Control.Exception (onException, SomeException) import qualified Control.Exception as E +import Data.String import Data.Char (isHexDigit) +import Prelude hiding (FilePath)+ isObjectPrefix [a,b] = isHexDigit a && isHexDigit b isObjectPrefix _     = False @@ -75,43 +83,49 @@ parseObject = parseSuccess (parseTree <|> parseBlob <|> parseCommit <|> parseTag)         where parseSuccess p = either error id . eitherResult . parse p --- | unmarshall an object (with header) from a lazy bytestring.+-- | unmarshall an object (with header) from a bytestring. looseUnmarshall :: L.ByteString -> Object-looseUnmarshall = parseObject . decompress+looseUnmarshall = parseObject --- | unmarshall an object as (header, data) tuple from a lazy bytestring.+-- | unmarshall an object (with header) from a zipped stream.+looseUnmarshallZipped :: Zipped -> Object+looseUnmarshallZipped = parseObject . dezip++-- | unmarshall an object as (header, data) tuple from a bytestring looseUnmarshallRaw :: L.ByteString -> (ObjectHeader, ObjectData)-looseUnmarshallRaw l =-        let dl = decompress l in-        let i = L.findIndex ((==) 0) dl in-        case i of+looseUnmarshallRaw stream =+        case L.findIndex ((==) 0) stream of                 Nothing  -> error "object not right format. missing 0"                 Just idx ->-                        let (h, r) = L.splitAt (idx+1) dl in+                        let (h, r) = L.splitAt (idx+1) stream in                         case maybeResult $ parse parseHeader h of                                 Nothing  -> error "cannot open object"                                 Just hdr -> (hdr, r) +-- | unmarshall an object as (header, data) tuple from a zipped stream+looseUnmarshallZippedRaw :: Zipped -> (ObjectHeader, ObjectData)+looseUnmarshallZippedRaw = looseUnmarshallRaw . dezip+ -- | read a specific ref from a loose object and returns an header and data.-looseReadRaw repoPath ref = looseUnmarshallRaw <$> L.readFile (objectPathOfRef repoPath ref)+looseReadRaw repoPath ref = looseUnmarshallZippedRaw <$> readZippedFile (objectPathOfRef repoPath ref)  -- | read only the header of a loose object.-looseReadHeader repoPath ref = toHeader <$> L.readFile (objectPathOfRef repoPath ref)-        where toHeader = either error id . eitherResult . parse parseHeader . decompress+looseReadHeader repoPath ref = toHeader <$> readZippedFile (objectPathOfRef repoPath ref)+        where toHeader = either error id . eitherResult . parse parseHeader . dezip  -- | read a specific ref from a loose object and returns an object-looseRead repoPath ref = looseUnmarshall <$> L.readFile (objectPathOfRef repoPath ref)+looseRead repoPath ref = looseUnmarshallZipped <$> readZippedFile (objectPathOfRef repoPath ref)  -- | check if a specific ref exists as loose object-looseExists repoPath ref = doesFileExist (objectPathOfRef repoPath ref)+looseExists repoPath ref = isFile (objectPathOfRef repoPath ref)  -- | enumarate all prefixes available in the object store.-looseEnumeratePrefixes repoPath = filter isObjectPrefix <$> getDirectoryContents (repoPath </> "objects")+looseEnumeratePrefixes repoPath = filter isObjectPrefix <$> getDirectoryContents (repoPath </> fromString "objects")  -- | enumerate all references available with a specific prefix. looseEnumerateWithPrefixFilter :: FilePath -> String -> (Ref -> Bool) -> IO [Ref] looseEnumerateWithPrefixFilter repoPath prefix filterF =-        filter filterF . map (fromHexString . (prefix ++)) . filter isRef <$> getDir (repoPath </> "objects" </> prefix)+        filter filterF . map (fromHexString . (prefix ++)) . filter isRef <$> getDir (repoPath </> fromString "objects" </> fromString prefix)         where                 getDir p = E.catch (getDirectoryContents p) (\(_::SomeException) -> return [])                 isRef l = length l == 38@@ -131,28 +145,35 @@ -- | create a new blob on a temporary location and on success move it to -- the object store with its digest name. looseWriteBlobFromFile repoPath file = do-        fsz <- openFile file ReadMode >>= hFileSize+        fsz <- getSize file         let hdr = objectWriteHeader TypeBlob (fromIntegral fsz)         tmpPath <- objectTemporaryPath repoPath         flip onException (removeFile tmpPath) $ do                 (ref, npath) <- withFileWriter tmpPath $ \fw -> do                         fileWriterOutput fw hdr-                        chunks <- L.toChunks <$> L.readFile file-                        mapM_ (fileWriterOutput fw) chunks+                        withFile file ReadMode $ \h -> loop h fw                         digest <- fileWriterGetDigest fw                         return (digest, objectPathOfRef repoPath digest)-                exists <- doesFileExist npath+                exists <- isFile npath                 when exists $ error "destination already exists"-                renameFile tmpPath npath+                rename tmpPath npath                 return ref+    where loop h fw = do+                r <- B.hGet h (32*1024)+                if B.null r+                    then return ()+                    else fileWriterOutput fw r >> loop h fw  -- | write an object to disk as a loose reference. -- use looseWriteBlobFromFile for efficiently writing blobs when being commited from a file.-looseWrite repoPath obj = createDirectoryIfMissing True (takeDirectory path)-                       >> doesFileExist path-                       >>= \exists -> unless exists (L.writeFile path $ compress content)+looseWrite repoPath obj = createDirectory False (directory path)+                       >> isFile path+                       >>= \exists -> unless exists (writeFileLazy path $ compress content)                        >> return ref         where                 path    = objectPathOfRef repoPath ref                 content = looseMarshall obj                 ref     = hashLBS content+                writeFileLazy p bs = withFile p WriteMode (\h -> L.hPut h bs)++getDirectoryContents p = map (encodeString posix . filename) <$> listDirectory p
Data/Git/Storage/Object.hs view
@@ -15,7 +15,7 @@         , ObjectPtr(..)         , Object(..)         , ObjectInfo(..)-        , objectWrap+        , Objectable(..)         , objectToType         , objectTypeMarshall         , objectTypeUnmarshall@@ -51,19 +51,16 @@ import Data.Attoparsec.Lazy import qualified Data.Attoparsec.Lazy as P import qualified Data.Attoparsec.Char8 as PC-import Control.Applicative ((<$>), many)+import Control.Applicative ((<$>), (<*), (*>), many) import Control.Monad +import Data.List (intersperse)+import Data.Monoid import Data.Word import Text.Printf -import Data.Time.LocalTime-import Data.Time.Clock.POSIX--{- import Blaze.ByteString.Builder-import Blaze.ByteString.Builder.ByteString--}+import Blaze.ByteString.Builder.Char8  -- | location of an object in the database data ObjectLocation = NotFound | Loose Ref | Packed Ref Word64@@ -85,30 +82,30 @@         , oiChains :: [ObjectPtr]         } deriving (Show,Eq) -class Objectable a where-        getType :: a -> ObjectType-        getRaw  :: a -> L.ByteString-        isDelta :: a -> Bool--        toCommit :: a -> Maybe Commit-        toCommit = const Nothing-        toTree   :: a -> Maybe Tree-        toTree = const Nothing-        toTag    :: a -> Maybe Tag-        toTag = const Nothing-        toBlob   :: a -> Maybe Blob-        toBlob = const Nothing- -- | describe a git object, that could of 6 differents types: -- tree, blob, commit, tag and deltas (offset or ref).--- the deltas one are only available through packs.-data Object = forall a . Objectable a => Object a+-- the deltas one are only available in packs.+data Object = ObjCommit   Commit+            | ObjTag      Tag+            | ObjBlob     Blob+            | ObjTree     Tree+            | ObjDeltaOfs DeltaOfs+            | ObjDeltaRef DeltaRef+            deriving (Show,Eq) -objectWrap :: Objectable a => a -> Object-objectWrap a = Object a+class Objectable a where+        getType  :: a -> ObjectType+        getRaw   :: a -> L.ByteString+        isDelta  :: a -> Bool+        toObject :: a -> Object  objectToType :: Object -> ObjectType-objectToType (Object a) = getType a+objectToType (ObjTree _)     = TypeTree+objectToType (ObjBlob _)     = TypeBlob+objectToType (ObjCommit _)   = TypeCommit+objectToType (ObjTag _)      = TypeTag+objectToType (ObjDeltaOfs _) = TypeDeltaOff+objectToType (ObjDeltaRef _) = TypeDeltaRef  objectTypeMarshall :: ObjectType -> String objectTypeMarshall TypeTree   = "tree"@@ -130,25 +127,36 @@ objectTypeIsDelta _            = False  objectIsDelta :: Object -> Bool-objectIsDelta (Object a) = isDelta a+objectIsDelta (ObjDeltaOfs _) = True+objectIsDelta (ObjDeltaRef _) = True+objectIsDelta _               = False  objectToTree :: Object -> Maybe Tree-objectToTree (Object a) = toTree a+objectToTree (ObjTree tree) = Just tree+objectToTree _              = Nothing  objectToCommit :: Object -> Maybe Commit-objectToCommit (Object a) = toCommit a+objectToCommit (ObjCommit commit) = Just commit+objectToCommit _                  = Nothing  objectToTag :: Object -> Maybe Tag-objectToTag (Object a) = toTag a+objectToTag (ObjTag tag) = Just tag+objectToTag _            = Nothing  objectToBlob :: Object -> Maybe Blob-objectToBlob (Object a) = toBlob a+objectToBlob (ObjBlob blob) = Just blob+objectToBlob _              = Nothing  octal :: Parser Int octal = B.foldl' step 0 `fmap` takeWhile1 isOct where         isOct w = w >= 0x30 && w <= 0x37         step a w = a * 8 + fromIntegral (w - 0x30) +tillEOL :: Parser ByteString+tillEOL = PC.takeWhile ((/= '\n'))++skipEOL = skipChar '\n'+ skipChar :: Char -> Parser () skipChar c = PC.char c >> return () @@ -156,9 +164,10 @@ referenceBin = fromBinary <$> P.take 20  -- | parse a tree content-treeParse = (Tree <$> parseEnts) where-        parseEnts = atEnd >>= \end -> if end then return [] else liftM2 (:) parseEnt parseEnts-        parseEnt = liftM3 (,,) octal (PC.char ' ' >> takeTill ((==) 0)) (word8 0 >> referenceBin)+treeParse = Tree <$> parseEnts+    where parseEnts = atEnd >>= \end -> if end then return [] else liftM2 (:) parseEnt parseEnts+          parseEnt = liftM3 (,,) octal (PC.char ' ' >> takeTill ((==) 0)) (word8 0 >> referenceBin)+ -- | parse a blob content blobParse = (Blob <$> takeLazyByteString) @@ -167,17 +176,26 @@         tree <- string "tree " >> referenceHex         skipChar '\n'         parents   <- many parseParentRef-        author    <- string "author " >> parseName-        committer <- string "committer " >> parseName+        author    <- string "author " >> parsePerson+        committer <- string "committer " >> parsePerson+        encoding  <- option Nothing $ Just <$> (string "encoding " >> tillEOL)+        extras    <- many parseExtra         skipChar '\n'         message <- takeByteString-        return $ Commit tree parents author committer message+        return $ Commit tree parents author committer encoding extras message         where                 parseParentRef = do                         tree <- string "parent " >> referenceHex                         skipChar '\n'                         return tree-                +                parseExtra = do+                        f <- B.pack . (:[]) <$> notWord8 0xa+                        r <- tillEOL+                        skipEOL+                        v <- concatLines <$> many (string " " *> tillEOL <* skipEOL)+                        return $ CommitExtra (f `B.append` r) v+                concatLines = B.concat . intersperse (B.pack [0xa])+ -- | parse a tag content tagParse = do         object <- string "object " >> referenceHex@@ -186,12 +204,12 @@         skipChar '\n'         tag   <- string "tag " >> takeTill ((==) 0x0a)         skipChar '\n'-        tagger <- string "tagger " >> parseName+        tagger <- string "tagger " >> parsePerson         skipChar '\n'         signature <- takeByteString         return $ Tag object type_ tag tagger signature -parseName = do+parsePerson = do         name <- B.init <$> PC.takeWhile ((/=) '<')         skipChar '<'         email <- PC.takeWhile ((/=) '>')@@ -199,89 +217,112 @@         time <- PC.decimal :: Parser Integer         _ <- string " "         timezone <- PC.signed PC.decimal-        let (hours,minutes) = timezone `divMod` 100         skipChar '\n'-        let tz = minutesToTimeZone (hours*60 + (if hours > 0 then minutes else (-minutes)))-        return (name, email, posixSecondsToUTCTime $ fromIntegral time, tz)+        return $ Person name email (GitTime time timezone) -objectParseTree = objectWrap <$> treeParse-objectParseCommit = objectWrap <$> commitParse-objectParseTag = objectWrap <$> tagParse-objectParseBlob = objectWrap <$> blobParse+objectParseTree   = ObjTree <$> treeParse+objectParseCommit = ObjCommit <$> commitParse+objectParseTag    = ObjTag <$> tagParse+objectParseBlob   = ObjBlob <$> blobParse  -- header of loose objects, but also useful for any object to determine object's hash objectWriteHeader :: ObjectType -> Word64 -> ByteString objectWriteHeader ty sz = BC.pack (objectTypeMarshall ty ++ " " ++ show sz ++ [ '\0' ])  objectWrite :: Object -> L.ByteString-objectWrite (Object a) = getRaw a+objectWrite (ObjCommit commit) = commitWrite commit+objectWrite (ObjTag tag)       = tagWrite tag+objectWrite (ObjBlob blob)     = blobWrite blob+objectWrite (ObjTree tree)     = treeWrite tree+objectWrite _                  = error "delta cannot be marshalled" -treeWrite (Tree ents) = L.fromChunks $ concat $ map writeTreeEnt ents where-        writeTreeEnt (perm,name,ref) =-                [ BC.pack $ printf "%o" perm-                , BC.singleton ' '-                , name-                , B.singleton 0-                , toBinary ref+treeWrite (Tree ents) = toLazyByteString $ mconcat $ concatMap writeTreeEnt ents+    where writeTreeEnt (perm,name,ref) =+                [ fromString (printf "%o" perm)+                , fromString " "+                , fromByteString name+                , fromString "\0"+                , fromByteString $ toBinary ref                 ] -commitWrite (Commit tree parents author committer msg) =-        L.fromChunks [BC.unlines ls, B.singleton 0xa, msg]-        where-                ls = [ "tree " `BC.append` (toHex tree) ] ++-                     map (BC.append "parent " . toHex) parents ++-                     [ writeName "author" author-                     , writeName "committer" committer ]+commitWrite (Commit tree parents author committer encoding extra msg) =+    toLazyByteString $ mconcat els+    where+          toNamedRef s r = mconcat [fromString s, fromByteString (toHex r),eol]+          toParent       = toNamedRef "parent "+          toCommitExtra :: CommitExtra -> [Builder]+          toCommitExtra (CommitExtra k v) = [fromByteString k, eol] +++                                            (concatMap (\l -> [fromByteString " ", fromByteString l, eol]) $ linesLast v) +          linesLast b+            | B.length b > 0 && B.last b == 0xa = BC.lines b ++ [ "" ]+            | otherwise                         = BC.lines b+          els = [toNamedRef "tree " tree ]+             ++ map toParent parents+             ++ [fromByteString $ writeName "author" author, eol+                ,fromByteString $ writeName "committer" committer, eol+                ,maybe (fromByteString B.empty) (fromByteString) encoding -- FIXME need eol+                ]+             ++ concatMap toCommitExtra extra+             ++ [eol+                ,fromByteString msg+                ]+ tagWrite (Tag ref ty tag tagger signature) =-        L.fromChunks [BC.unlines ls, B.singleton 0xa, signature]-        where-                ls = [ "object " `BC.append` (toHex ref)-                     , "type " `BC.append` (BC.pack $ objectTypeMarshall ty)-                     , "tag " `BC.append` tag-                     , writeName "tagger" tagger ]+    toLazyByteString $ mconcat els+    where els = [ fromString "object ", fromByteString (toHex ref), eol+                , fromString "type ", fromString (objectTypeMarshall ty), eol+                , fromString "tag ", fromByteString tag, eol+                , fromByteString $ writeName "tagger" tagger, eol+                , eol+                , fromByteString signature+                ] +eol = fromString "\n"+ blobWrite (Blob bData) = bData  instance Objectable Blob where         getType _ = TypeBlob         getRaw    = blobWrite+        toObject  = ObjBlob         isDelta   = const False-        toBlob t  = Just t  instance Objectable Commit where-        getType _  = TypeCommit-        getRaw     = commitWrite-        isDelta    = const False-        toCommit t = Just t+        getType _ = TypeCommit+        getRaw    = commitWrite+        toObject  = ObjCommit+        isDelta   = const False  instance Objectable Tag where         getType _ = TypeTag         getRaw    = tagWrite+        toObject  = ObjTag         isDelta   = const False-        toTag t   = Just t  instance Objectable Tree where         getType _ = TypeTree         getRaw    = treeWrite+        toObject  = ObjTree         isDelta   = const False-        toTree t  = Just t  instance Objectable DeltaOfs where         getType _ = TypeDeltaOff         getRaw    = error "delta offset cannot be marshalled"+        toObject  = ObjDeltaOfs         isDelta   = const True  instance Objectable DeltaRef where         getType _ = TypeDeltaRef         getRaw    = error "delta ref cannot be marshalled"+        toObject  = ObjDeltaRef         isDelta   = const True  objectHash :: ObjectType -> Word64 -> L.ByteString -> Ref objectHash ty w lbs = hashLBS $ L.fromChunks (objectWriteHeader ty w : L.toChunks lbs)  -- used for objectWrite for commit and tag-writeName label (name, email, time, tz) =-        B.concat [label, " ", name, " <", email, "> ", BC.pack (printf "%d %s" timeSeconds (timeZoneOffsetString tz)) ]-        where timeSeconds :: Integer-              timeSeconds = truncate (realToFrac $ utcTimeToPOSIXSeconds time :: Double)+writeName label (Person name email (GitTime time tz)) =+        B.concat [label, " ", name, " <", email, "> ", BC.pack (printf "%d %s%02d%02d" time tzs tzh tzm) ]+        where tzs = if tz >= 0 then "+" else "-" :: String+              (tzh,tzm) = abs (tz) `divMod` 100
Data/Git/Storage/Pack.hs view
@@ -5,6 +5,7 @@ -- Stability   : experimental -- Portability : unix --+{-# LANGUAGE OverloadedStrings #-} module Data.Git.Storage.Pack         ( PackedObjectInfo(..)         , PackedObjectRaw@@ -28,9 +29,9 @@ import Control.Arrow (second) import Control.Monad -import System.IO-import System.FilePath-import System.Directory+import Filesystem.Path.Rules+import Filesystem.Path+import Filesystem  import Data.Bits import Data.List@@ -49,6 +50,7 @@ import Data.Git.Storage.FileReader  import Data.Word+import Prelude hiding (FilePath)  type PackedObjectRaw = (PackedObjectInfo, L.ByteString) @@ -61,7 +63,7 @@         } deriving (Show,Eq)  -- | Enumerate the pack refs available in this repository.-packEnumerate repoPath = map onlyHash . filter isPackFile <$> getDirectoryContents (repoPath </> "objects" </> "pack")+packEnumerate repoPath = map onlyHash . filter isPackFile . map (encodeString posix . filename) <$> listDirectory (repoPath </> "objects" </> "pack")         where                 isPackFile x = ".pack" `isSuffixOf` x                 onlyHash = fromHexString . takebut 5 . drop 5@@ -118,8 +120,8 @@ packObjectFromRaw (TypeTree, Nothing, objData)   = AL.maybeResult $ AL.parse objectParseTree objData packObjectFromRaw (TypeBlob, Nothing, objData)   = AL.maybeResult $ AL.parse objectParseBlob objData packObjectFromRaw (TypeTag, Nothing, objData)    = AL.maybeResult $ AL.parse objectParseTag objData-packObjectFromRaw (TypeDeltaOff, Just (PtrOfs o), objData) = objectWrap . DeltaOfs o <$> deltaRead objData-packObjectFromRaw (TypeDeltaRef, Just (PtrRef r), objData) = objectWrap . DeltaRef r <$> deltaRead objData+packObjectFromRaw (TypeDeltaOff, Just (PtrOfs o), objData) = toObject . DeltaOfs o <$> deltaRead objData+packObjectFromRaw (TypeDeltaRef, Just (PtrRef r), objData) = toObject . DeltaRef r <$> deltaRead objData packObjectFromRaw _                              = error "can't happen unless someone change getNextObjectRaw"  getNextObjectRaw :: FileReader -> IO PackedObjectRaw
Data/Git/Storage/PackIndex.hs view
@@ -28,13 +28,14 @@ import Control.Applicative ((<$>)) import Control.Monad -import System.FilePath-import System.Directory-import System.IO+import Filesystem+import Filesystem.Path+import Filesystem.Path.Rules  import Data.List import Data.Bits import Data.Word+import Data.String  import Data.Vector (Vector, (!)) import qualified Data.Vector as V@@ -46,6 +47,8 @@ import Data.Git.Path import Data.Git.Ref +import Prelude hiding (FilePath)+ -- | represent an packIndex header with the version and the fanout table data PackIndexHeader = PackIndexHeader !Word32 !(Vector Word32)         deriving (Show,Eq)@@ -59,7 +62,7 @@         }  -- | enumerate every indexes file in the pack directory-packIndexEnumerate repoPath = map onlyHash . filter isPackFile <$> getDirectoryContents (repoPath </> "objects" </> "pack")+packIndexEnumerate repoPath = map onlyHash . filter isPackFile . map (encodeString posix . filename) <$> listDirectory (repoPath </> "objects" </> "pack")         where                 isPackFile x = ".idx" `isSuffixOf` x && "pack-" `isPrefixOf` x                 onlyHash = fromHexString . takebut 4 . drop 5
Data/Git/Types.hs view
@@ -12,14 +12,19 @@     -- * Main git types     , Tree(..)     , Commit(..)+    , CommitExtra(..)     , Blob(..)     , Tag(..)+    , Person(..)+    -- * time type+    , GitTime(..)+    , toUTCTime+    , toPOSIXTime     -- * Pack delta types     , DeltaOfs(..)     , DeltaRef(..)     -- * Basic types part of other bigger types     , TreeEnt-    , Name     ) where  import Data.Word@@ -30,7 +35,7 @@ import Data.Git.Ref import Data.Git.Delta import Data.Time.Clock-import Data.Time.LocalTime+import Data.Time.Clock.POSIX  -- | type of a git object. data ObjectType =@@ -42,6 +47,16 @@         | TypeDeltaRef         deriving (Show,Eq) +-- | Git time is number of seconds since unix epoch+data GitTime = GitTime Integer Int+    deriving (Show,Eq)++toUTCTime :: GitTime -> UTCTime+toUTCTime (GitTime seconds _) = posixSecondsToUTCTime $ fromIntegral seconds++toPOSIXTime :: GitTime -> POSIXTime+toPOSIXTime = utcTimeToPOSIXSeconds . toUTCTime+ -- | the enum instance is useful when marshalling to pack file. instance Enum ObjectType where         fromEnum TypeCommit   = 0x1@@ -68,7 +83,11 @@ -- has the format: name <email> time timezone -- FIXME: should be a string, but I don't know if the data is stored -- consistantly in one encoding (UTF8)-type Name = (ByteString,ByteString,UTCTime,TimeZone)+data Person = Person+    { personName  :: ByteString+    , personEmail :: ByteString+    , personTime  :: GitTime+    } deriving (Show,Eq)  -- | Represent a root tree with zero to many tree entries. data Tree = Tree { treeGetEnts :: [TreeEnt] } deriving (Show,Eq)@@ -85,17 +104,24 @@ data Commit = Commit         { commitTreeish   :: Ref         , commitParents   :: [Ref]-        , commitAuthor    :: Name-        , commitCommitter :: Name+        , commitAuthor    :: Person+        , commitCommitter :: Person+        , commitEncoding  :: Maybe ByteString+        , commitExtras    :: [CommitExtra]         , commitMessage   :: ByteString         } deriving (Show,Eq) +data CommitExtra = CommitExtra+    { commitExtraKey   :: ByteString+    , commitExtraValue :: ByteString+    } deriving (Show,Eq)+ -- | Represent a signed tag. data Tag = Tag         { tagRef        :: Ref         , tagObjectType :: ObjectType         , tagBlob       :: ByteString-        , tagName       :: Name+        , tagName       :: Person         , tagS          :: ByteString         } deriving (Show,Eq) 
Hit/Hit.hs view
@@ -138,7 +138,7 @@ 	where loopTillEmpty ref = do 		obj <- getCommit git ref 		case obj of-			Just (Commit _ parents _ _ _) -> do+			Just (Commit { commitParents = parents }) -> do 				putStrLn $ show ref 				-- this behave like rev-list --first-parent. 				-- otherwise the parents need to be organized and printed@@ -146,7 +146,7 @@ 				case parents of 					[]    -> return () 					(p:_) -> loopTillEmpty p-			Nothing -> error "reference in commit chain doesn't exists"+			Nothing -> error ("commit reference " ++ show ref ++ " in commit chain doesn't exists")  main = do 	args <- getArgs
Tests/Tests.hs view
@@ -16,12 +16,15 @@ import Data.Time.Clock import Data.Time.Calendar +import Data.Maybe+ -- for arbitrary instance to generate only data that are writable -- to disk. i.e. no deltas.---data ObjNoDelta = ObjNoDelta Object+data ObjNoDelta = ObjNoDelta Object+    deriving (Eq) ---instance Show ObjNoDelta where---	show (ObjNoDelta o) = show o+instance Show ObjNoDelta where+    show (ObjNoDelta o) = show o  arbitraryBS size = B.pack . map fromIntegral <$> replicateM size (choose (0,255) :: Gen Int) arbitraryBSno0 size = B.pack . map fromIntegral <$> replicateM size (choose (1,255) :: Gen Int)@@ -30,70 +33,81 @@ arbitraryBSnoangle size = B.pack . map fromIntegral <$> replicateM size (choose (0x40,0x7f) :: Gen Int)  instance Arbitrary Ref where-	arbitrary = fromBinary <$> arbitraryBS 20+    arbitrary = fromBinary <$> arbitraryBS 20 -arbitraryMsg = arbitraryBSno0 128-arbitraryLazy = L.fromChunks . (:[]) <$> arbitraryBS 4096+arbitraryMsg = arbitraryBSno0 1+arbitraryLazy = L.fromChunks . (:[]) <$> arbitraryBS 40  arbitraryRefList :: Gen [Ref] arbitraryRefList = replicateM 2 arbitrary  arbitraryEnt = liftM3 (,,) arbitrary (arbitraryBSno0 48) arbitrary-arbitraryEnts = choose (1,100) >>= \i -> replicateM i arbitraryEnt+arbitraryEnts = choose (1,2) >>= \i -> replicateM i arbitraryEnt  instance Arbitrary TimeZone where-    arbitrary = hoursToTimeZone <$> arbitrary +    arbitrary = hoursToTimeZone . rel <$> arbitrary+        where rel a = (a `mod` 24) - 12  instance Arbitrary UTCTime where     arbitrary = UTCTime <$> (flip addDays b <$> choose (0, 365 * 40))                         <*> (secondsToDiffTime <$> arbitrary)         where b = fromGregorian 1970 1 1-arbitraryName = liftM4 (,,,) (arbitraryBSnoangle 16)-                             (arbitraryBSnoangle 16)-                             arbitrary-                             arbitrary +instance Arbitrary GitTime where+    arbitrary = GitTime <$> (arbitrary `suchThat` \i -> i > 0) <*> arbitraryTz+        where arbitraryTz = do+                    h <- choose (0, 23)+                    m <- (* 30) <$> choose (0,1)+                    return (h * 100 + m - 1200)++arbitraryName = liftM3 Person (arbitraryBSnoangle 16)+                              (arbitraryBSnoangle 16)+                              arbitrary+ arbitraryObjTypeNoDelta = oneof [return TypeTree,return TypeBlob,return TypeCommit,return TypeTag] +arbitrarySmallList = frequency [ (2, return []), (1, resize 3 arbitrary) ]+ instance Arbitrary Commit where-	arbitrary = Commit <$> arbitrary <*> arbitraryRefList <*> arbitraryName <*> arbitraryName <*> arbitraryMsg+    arbitrary = Commit <$> arbitrary <*> arbitraryRefList <*> arbitraryName <*> arbitraryName <*> return Nothing <*> arbitrarySmallList <*> arbitraryMsg +instance Arbitrary CommitExtra where+    arbitrary = CommitExtra <$> arbitraryBSascii 80 <*> arbitraryMsg+ instance Arbitrary Tree where-	arbitrary = Tree <$> arbitraryEnts+    arbitrary = Tree <$> arbitraryEnts  instance Arbitrary Blob where-	arbitrary = Blob <$> arbitraryLazy+    arbitrary = Blob <$> arbitraryLazy  instance Arbitrary Tag where-	arbitrary = Tag <$> arbitrary <*> arbitraryObjTypeNoDelta <*> arbitraryBSascii 20 <*> arbitraryName <*> arbitraryMsg--{--instance Arbitrary Object where-	arbitrary = undefined+    arbitrary = Tag <$> arbitrary <*> arbitraryObjTypeNoDelta <*> arbitraryBSascii 20 <*> arbitraryName <*> arbitraryMsg  instance Arbitrary ObjNoDelta where-	arbitrary = ObjNoDelta <$> oneof-		[ liftM5 Commit arbitrary arbitraryRefList arbitraryName arbitraryName arbitraryMsg-		, liftM Tree arbitraryEnts-		, liftM Blob arbitraryLazy-		, liftM5 Tag-		]--}+    arbitrary = ObjNoDelta <$> oneof+        [ toObject <$> (arbitrary :: Gen Commit)+        , toObject <$> (arbitrary :: Gen Tree)+        , toObject <$> (arbitrary :: Gen Blob)+        , toObject <$> (arbitrary :: Gen Tag)+        ] ---prop_object_marshalling_id (ObjNoDelta obj) = obj == (looseUnmarshall $ looseMarshall obj)+prop_object_marshalling_id (ObjNoDelta obj) = obj `assertEq` (looseUnmarshall $ looseMarshall obj)+    where assertEq a b+            | show a == show b    = True+            | otherwise = error ("not equal:\n"  ++ show a ++ "\ngot: " ++ show b)  refTests =-	[ testProperty "hexadecimal" (marshEqual (fromHex . toHex))-	, testProperty "binary" (marshEqual (fromBinary . toBinary))-	]-	where-		marshEqual t ref = ref == t ref+    [ testProperty "hexadecimal" (marshEqual (fromHex . toHex))+    , testProperty "binary" (marshEqual (fromBinary . toBinary))+    ]+    where+        marshEqual t ref = ref == t ref  objTests =-	[ -- testProperty "unmarshall.marshall==id" prop_object_marshalling_id-	]+    [ testProperty "unmarshall.marshall==id" prop_object_marshalling_id+    ]  main = defaultMain-	[ testGroup "ref marshalling" refTests-	, testGroup "object marshalling" objTests-	]+    [ testGroup "ref marshalling" refTests+    , testGroup "object marshalling" objTests+    ]
hit.cabal view
@@ -1,7 +1,16 @@ Name:                hit-Version:             0.3.0-Synopsis:            Git operations-Description:         Provides low level git operations+Version:             0.4.0+Synopsis:            Git operations in haskell+Description:+    .+    An haskell implementation of git storage operations, allowing users+    to manipulate git repositories (read and write).+    .+    This implementation is fully interoperable with the main C implementation.+    .+    This is stricly only manipulating the git store (what's inside the .git directory),+    and doesn't do anything with the index or your working directory files.+    . License:             BSD3 License-file:        LICENSE Copyright:           Vincent Hanquez <vincent@snarc.org>@@ -27,16 +36,17 @@   Build-Depends:     base >= 4 && < 5                    , mtl                    , bytestring+                   , blaze-builder                    , attoparsec >= 0.10.1                    , parsec     >= 3-                   , filepath-                   , directory+                   , containers+                   , system-filepath+                   , system-fileio                    , cryptohash                    , vector                    , random                    , zlib                    , zlib-bindings >= 0.1 && < 0.2-                   , bytedump                    , time   Exposed-modules:   Data.Git                      Data.Git.Types@@ -53,6 +63,7 @@   Other-modules:     Data.Git.Internal                      Data.Git.Storage.FileReader                      Data.Git.Storage.FileWriter+                     Data.Git.Storage.CacheFile                      Data.Git.Path   ghc-options:       -Wall -fno-warn-missing-signatures @@ -73,7 +84,6 @@                    , parsec     >= 3                    , filepath                    , directory-                   , bytedump                    , hit     Buildable: True   else@@ -103,7 +113,7 @@                    , test-framework >= 0.3                    , test-framework-quickcheck2 >= 0.2                    , time-                   , bytedump+                   , bytedump >= 1.0                    , hit  source-repository head