module Git.Pack (
Pack(..),
PackObject(..),
PackObjectType,
packPretty,
packObjectPretty,
packRead,
packReadObject,
packPath
) where
import Control.Applicative
import Data.Bits
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy.Char8 as C
import qualified Data.ByteString.Lazy as L
import qualified Data.Iteratee as I
import Data.Iteratee.Binary
import Data.Iteratee.ZLib
import Data.Maybe (catMaybes)
import Data.Word
import System.FilePath
import System.Posix.Types
import Git.Path
data Pack = Pack
{ packVersion :: Int
, packNumObjects :: Int
, packObjects :: [PackObject]
} deriving (Show)
data PackObjectType = OBJ_COMMIT
| OBJ_TREE
| OBJ_BLOB
| OBJ_TAG
| OBJ_OFS_DELTA Int
| OBJ_REF_DELTA [Word8]
deriving (Show, Eq)
data PackObject = PackObject
{ poType :: PackObjectType
, poSize :: Int
, poData :: ByteString
} deriving (Show)
packPath :: String -> IO FilePath
packPath pack = gitPath ("objects" </> "pack" </> ("pack-" ++ pack ++ ".pack"))
packRead :: FilePath -> IO (Maybe Pack)
packRead = I.fileDriverRandom packReader
packReader :: I.Iteratee ByteString IO (Maybe Pack)
packReader = do
n <- I.heads "PACK"
if (n == 4)
then do
ver <- fromIntegral <$> endianRead4 MSB
num <- fromIntegral <$> endianRead4 MSB
os <- catMaybes <$> sequence (replicate num packObjectRead)
return $ Just (Pack ver num os)
else return Nothing
packReadObject :: FilePath -> FileOffset -> IO (Maybe PackObject)
packReadObject fp off = I.fileDriverRandom (packReadObject' off) fp
packReadObject' :: FileOffset -> I.Iteratee ByteString IO (Maybe PackObject)
packReadObject' off = do
n <- I.heads "PACK"
if (n == 4)
then do
I.seek off
packObjectRead
else return Nothing
packObjectRead :: I.Iteratee ByteString IO (Maybe PackObject)
packObjectRead = do
x <- I.head
let t = parseOBJ $ (x .&. 0x70) `shiftR` 4
sz = castEnum (x .&. 0x0f)
sz' <- if doNext x
then readSize 4 sz
else return sz
t' <- readBase t
d <- I.joinI $ enumInflate Zlib defaultDecompressParams I.stream2stream
return $ PackObject <$> t' <*> pure sz' <*> pure d
where
parseOBJ :: Word8 -> Maybe PackObjectType
parseOBJ 1 = Just OBJ_COMMIT
parseOBJ 2 = Just OBJ_TREE
parseOBJ 3 = Just OBJ_BLOB
parseOBJ 4 = Just OBJ_TAG
parseOBJ 6 = Just (OBJ_OFS_DELTA 0)
parseOBJ 7 = Just (OBJ_REF_DELTA [])
parseOBJ _ = Nothing
doNext :: Word8 -> Bool
doNext x = (x .&. 0x80) /= 0
readSize :: Int -> Int -> I.Iteratee ByteString IO Int
readSize shft acc = do
x <- I.head
let sz = acc + (((castEnum (x .&. 0x7f)) :: Int) `shiftL` shft)
if doNext x
then readSize (shft+7) sz
else return sz
readBase :: Maybe PackObjectType
-> I.Iteratee ByteString IO (Maybe PackObjectType)
readBase (Just (OBJ_OFS_DELTA 0)) =
Just . OBJ_OFS_DELTA <$> readOFSBase 0 0
readBase (Just (OBJ_REF_DELTA [])) =
Just . OBJ_REF_DELTA <$> (sequence $ replicate 20 I.head)
readBase (Just t) = return (Just t)
readBase Nothing = return Nothing
readOFSBase :: Int -> Int -> I.Iteratee ByteString IO Int
readOFSBase shft acc = do
x <- I.head
let bs = acc + (((castEnum (x .&. 0x7f)) :: Int) `shiftL` shft)
if doNext x
then readOFSBase (shft+7) (bs+1)
else return bs
castEnum = toEnum . fromEnum
packObjectPretty :: ByteString -> PackObject -> L.ByteString
packObjectPretty sha PackObject{..}
| poType == OBJ_COMMIT =
C.concat [commitHeader, sha'c, C.pack "\n", poData'c]
| otherwise = poData'c
where
commitHeader = C.pack "commit "
sha'c = C.fromChunks [sha]
poData'c = C.fromChunks [poData]
packPretty :: Pack -> L.ByteString
packPretty (Pack ver n _) =
C.unlines [
C.concat [(C.pack "Version: "), C.pack (show ver)],
C.concat [(C.pack "Num Objects: "), C.pack (show n)]
]