packages feed

hit (empty) → 0.1.0

raw patch · 19 files changed

+2142/−0 lines, 19 filesdep +HUnitdep +QuickCheckdep +attoparsecsetup-changed

Dependencies added: HUnit, QuickCheck, attoparsec, base, bytedump, bytestring, containers, cryptohash, directory, filepath, hashable, hashtables, mtl, parsec, patience, random, test-framework, test-framework-quickcheck2, unix, vector, zlib, zlib-bindings

Files

+ Data/Git/Delta.hs view
@@ -0,0 +1,84 @@+-- |+-- Module      : Data.Git.Delta+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Delta+	( Delta(..)+	, DeltaCmd(..)+	, deltaParse+	, deltaRead+	, deltaApply+	) where++import Data.Attoparsec+import qualified Data.Attoparsec as A+import qualified Data.Attoparsec.Lazy as AL+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.Bits+import Data.Word++import Control.Applicative ((<$>))++-- | a delta is a source size, a destination size and a list of delta cmd+data Delta = Delta Word64 Word64 [DeltaCmd]+	deriving (Show,Eq)++-- | possible commands in a delta+data DeltaCmd =+	  DeltaCopy ByteString   -- command to insert this bytestring+	| DeltaSrc Word64 Word64 -- command to copy from source (offset, size)+	deriving (Show,Eq)++-- | parse a delta.+-- format is 2 variable sizes, followed by delta cmds. for each cmd:+-- * if first byte MSB is set, we copy from source.+-- * otherwise, we copy from delta.+-- * extensions are not handled.+deltaParse = do+	srcSize <- getDeltaHdrSize+	resSize <- getDeltaHdrSize+	dcmds   <- many (anyWord8 >>= parseWithCmd)+	return $ Delta srcSize resSize dcmds+	where+		getDeltaHdrSize = do+			z <- A.takeWhile (\w -> w `testBit` 7)+			l <- anyWord8+			return $ unbytes 0 $ (map (\w -> w `clearBit` 7) (B.unpack z) ++ [l])+		-- use a foldl ..+		unbytes _  []     = 0+		unbytes sh (x:xs) = (fromIntegral x) `shiftL` sh + unbytes (sh+7) xs+		-- parse one command, either an extension, a copy from src, or a copy from delta.+		parseWithCmd cmd+			| cmd == 0        = error "delta extension not supported"+			| cmd `testBit` 7 = do+				o1 <- word8cond (cmd `testBit` 0) 0+				o2 <- word8cond (cmd `testBit` 1) 8+				o3 <- word8cond (cmd `testBit` 2) 16 +				o4 <- word8cond (cmd `testBit` 3) 24+				s1 <- word8cond (cmd `testBit` 4) 0+				s2 <- word8cond (cmd `testBit` 5) 8+				s3 <- word8cond (cmd `testBit` 6) 16 +				let offset = o1 .|. o2 .|. o3 .|. o4+				let size   = s1 .|. s2 .|. s3+				return $ DeltaSrc offset (if size == 0 then 0x10000 else size)+			| otherwise       = DeltaCopy <$> A.take (fromIntegral cmd)+		word8cond cond sh = do+			if cond then (flip shiftL sh . fromIntegral) <$> anyWord8 else return 0++-- | read one delta from a lazy bytestring.+deltaRead = AL.maybeResult . AL.parse deltaParse++-- | apply a delta on a lazy bytestring, returning a new bytestring.+deltaApply :: L.ByteString -> Delta -> L.ByteString+deltaApply src (Delta srcSize _ deltaCmds)+	| L.length src /= fromIntegral srcSize = error "source size do not match"+	| otherwise                            = -- FIXME use a bytestring builder here.+		L.fromChunks $ concatMap resolve deltaCmds where+			resolve (DeltaSrc o s) = L.toChunks $ takeAt (fromIntegral s) (fromIntegral o) src+			resolve (DeltaCopy b)  = [b]+			takeAt sz at = L.take sz . L.drop at
+ Data/Git/FileReader.hs view
@@ -0,0 +1,193 @@+-- |+-- Module      : Data.Git.FileReader+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.FileReader+	( FileReader+	, fileReaderNew+	, fileReaderClose+	, withFileReader+	, withFileReaderDecompress+	, fileReaderGetPos+	, fileReaderGet+	, fileReaderGetLBS+	, fileReaderGetBS+	, fileReaderGetVLF+	, fileReaderSeek+	, fileReaderParse++	, fileReaderInflateToSize+	) where+++import Control.Applicative ((<$>))+import Control.Exception (bracket, throwIO)+import Control.Monad++import Data.Attoparsec (parseWith, Parser, Result(..))+import qualified Data.Attoparsec as A+import Data.Bits+import Data.ByteString (ByteString)+import Data.ByteString.Unsafe+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L+import Data.ByteString.Lazy.Internal (defaultChunkSize)+import Data.IORef++import Data.Word++import Codec.Zlib+import Codec.Zlib.Lowlevel+import Foreign.ForeignPtr++import System.IO++data FileReader = FileReader+	{ fbHandle     :: Handle+	, fbUseInflate :: Bool+	, fbInflate    :: Inflate+	, fbRemaining  :: IORef ByteString+	, fbPos        :: IORef Word64+	}++fileReaderNew :: Bool -> Handle -> IO FileReader+fileReaderNew decompress handle = do+	ref <- newIORef B.empty+	pos <- newIORef 0+	inflate <- initInflate defaultWindowBits+	return $ FileReader handle decompress inflate ref pos++fileReaderClose :: FileReader -> IO ()+fileReaderClose = hClose . fbHandle++withFileReader :: FilePath -> (FileReader -> IO a) -> IO a+withFileReader path f =+	bracket (openFile path ReadMode) (hClose) $ \handle ->+		bracket (fileReaderNew False handle) (\_ -> return ()) f++withFileReaderDecompress :: FilePath -> (FileReader -> IO a) -> IO a+withFileReaderDecompress path f =+	bracket (openFile path ReadMode) (hClose) $ \handle ->+		bracket (fileReaderNew True handle) (\_ -> return ()) f++fileReaderGetNext :: FileReader -> IO ByteString+fileReaderGetNext fb = do+	bs <- if fbUseInflate fb then inflateTillPop else B.hGet (fbHandle fb) 8192+	modifyIORef (fbPos fb) (\pos -> pos + (fromIntegral $ B.length bs))+	return bs+	where inflateTillPop = do+		b <- B.hGet (fbHandle fb) 4096+		if B.null b+			then finishInflate (fbInflate fb)+			else maybe inflateTillPop return =<< withInflateInput (fbInflate fb) b id++fileReaderGetPos :: FileReader -> IO Word64+fileReaderGetPos fr = do+	storeLeft <- B.length <$> readIORef (fbRemaining fr)+	pos       <- readIORef (fbPos fr)+	return (pos - fromIntegral storeLeft)++fileReaderFill :: FileReader -> IO ()+fileReaderFill fb = fileReaderGetNext fb >>= writeIORef (fbRemaining fb)++fileReaderGet :: Int -> FileReader -> IO [ByteString]+fileReaderGet size fb@(FileReader { fbRemaining = ref }) = loop size+	where loop left = do+		b <- readIORef ref+		if B.length b >= left+			then do+				let (b1, b2) = B.splitAt left b+				writeIORef ref b2+				return [b1]+			else do+				let nleft = left - B.length b+				fileReaderFill fb+				liftM (b :) (loop nleft)++fileReaderGetLBS :: Int -> FileReader -> IO L.ByteString+fileReaderGetLBS size fb = L.fromChunks <$> fileReaderGet size fb++fileReaderGetBS :: Int -> FileReader -> IO ByteString+fileReaderGetBS size fb = B.concat <$> fileReaderGet size fb++-- | seek in a handle, and reset the remaining buffer to empty.+fileReaderSeek :: FileReader -> Word64 -> IO ()+fileReaderSeek (FileReader { fbHandle = handle, fbRemaining = ref, fbPos = pos }) absPos = do+	writeIORef ref B.empty >> writeIORef pos absPos >> hSeek handle AbsoluteSeek (fromIntegral absPos)++-- | parse from a filebuffer+fileReaderParse :: FileReader -> Parser a -> IO a+fileReaderParse fr@(FileReader { fbRemaining = ref }) parseF = do+	initBS <- readIORef ref+	result <- parseWith (fileReaderGetNext fr) parseF initBS+	case result of+		Done remaining a -> writeIORef ref remaining >> return a+		Partial _        -> error "parsing failed: partial with a handle, reached EOF ?"+		Fail _ ctxs err  -> error ("parsing failed: " ++ show ctxs ++ " : " ++ show err)++-- | get a Variable Length Field. get byte as long as MSB is set, and one byte after+fileReaderGetVLF :: FileReader -> IO [Word8]+fileReaderGetVLF fr = fileReaderParse fr $ do+	bs <- A.takeWhile (\w -> w `testBit` 7)+	l  <- A.anyWord8+	return $ (map (\w -> w `clearBit` 7) $ B.unpack bs) ++ [l]++fileReaderInflateToSize :: FileReader -> Word64 -> IO L.ByteString+fileReaderInflateToSize fb@(FileReader { fbRemaining = ref }) outputSize = do+	--pos <- fileReaderGetPos fb+	--putStrLn ("inflate to size " ++ show outputSize ++ " " ++ show pos)+	inflate <- inflateNew+	l       <- loop inflate outputSize+	--posend <- fileReaderGetPos fb+	--putStrLn ("inflated input " ++ show posend)+	return $ L.fromChunks l+	where loop inflate left = do+		rbs <- readIORef ref+		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)+		writeIORef ref remaining+		let nleft = left - fromIntegral (B.length dbs)+		if nleft > 0+			then liftM (dbs:) (loop inflate nleft)+			else return [dbs]++-- lowlevel helpers to inflate only to a specific size.++inflateNew = do+	zstr <- zstreamNew+	inflateInit2 zstr defaultWindowBits+	newForeignPtr c_free_z_stream_inflate zstr++inflateToSize inflate sz isLastBlock ibs nextBs = withForeignPtr inflate $ \zstr -> do+	let boundSz = min defaultChunkSize sz+	-- create an output buffer+	fbuff <- mallocForeignPtrBytes boundSz+	withForeignPtr fbuff $ \buff -> do+		c_set_avail_out zstr buff (fromIntegral boundSz)+		rbs <- loop zstr ibs+		bs  <- B.packCStringLen (buff, boundSz)+		return (bs, rbs)+	where+		loop zstr nbs = do+			(ai, streamEnd) <- inflateOneInput zstr nbs+			ao <- c_get_avail_out zstr+			if (isLastBlock && streamEnd) || (not isLastBlock && ao == 0)+				then return $ bsTakeLast ai nbs+				else do+					--when (ai /= 0) $ error ("input not consumed completly: ai" ++ show ai)+					(if ai == 0+						then nextBs+						else return (bsTakeLast ai nbs)) >>= loop zstr++		inflateOneInput zstr bs = unsafeUseAsCStringLen bs $ \(istr, ilen) -> do+			c_set_avail_in zstr istr $ fromIntegral ilen+			r <- c_call_inflate_noflush zstr+			when (r < 0 && r /= (-5)) $ do+				throwIO $ ZlibException $ fromIntegral r+			ai <- c_get_avail_in zstr+			return (ai, r == 1)+		bsTakeLast len bs = B.drop (B.length bs - fromIntegral len) bs
+ Data/Git/FileWriter.hs view
@@ -0,0 +1,52 @@+-- |+-- Module      : Data.Git.FileWriter+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.FileWriter where++import Data.Git.Ref+import Data.IORef+import qualified Data.ByteString as B+import Codec.Zlib+import Control.Exception (bracket)++import qualified Crypto.Hash.SHA1 as SHA1++import System.IO++defaultCompression = 6++data FileWriter = FileWriter+	{ writerHandle  :: Handle+	, writerDeflate :: Deflate+	, writerDigest  :: IORef SHA1.Ctx+	}++fileWriterNew handle = do+	deflate <- initDeflate defaultCompression defaultWindowBits+	digest  <- newIORef SHA1.init+	return $ FileWriter+		{ writerHandle  = handle+		, writerDeflate = deflate+		, writerDigest  = digest+		}++withFileWriter path f =+	bracket (openFile path WriteMode) (hClose) $ \handle ->+		bracket (fileWriterNew handle) (fileWriterClose) f++postDeflate _      Nothing    = return ()+postDeflate handle (Just dbs) = B.hPut handle dbs++fileWriterOutput (FileWriter { writerHandle = handle, writerDigest = digest, writerDeflate = deflate }) bs = do+	putStrLn ("outputing" ++ show bs)+	modifyIORef digest (\ctx -> SHA1.update ctx bs)+	postDeflate handle =<< withDeflateInput deflate bs id++fileWriterClose (FileWriter { writerHandle = handle, writerDeflate = deflate }) = do+	postDeflate handle =<< finishDeflate deflate id++fileWriterGetDigest (FileWriter { writerDigest = digest }) = (fromBinary . SHA1.finalize) `fmap` readIORef digest
+ Data/Git/Index.hs view
@@ -0,0 +1,182 @@+-- |+-- Module      : Data.Git.Index+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+{-# LANGUAGE OverloadedStrings, BangPatterns #-}+module Data.Git.Index+	( IndexHeader(..)+	, Index(..)++	-- * handles and enumeration+	, indexOpen+	, indexClose+	, withIndex+	, indexEnumerate++	-- * read from index+	, indexHeaderGetNbWithPrefix+	, indexGetReferenceLocation+	, indexGetReferencesWithPrefix+	, indexReadHeader+	, indexRead+	, indexGetHeader+	) where++import Control.Applicative ((<$>))+import Control.Monad++import System.FilePath+import System.Directory+import System.IO++import Data.List+import Data.Bits+import Data.Word++import Data.Vector (Vector, (!))+import qualified Data.Vector as V++import qualified Data.Attoparsec as A++import Data.Git.Internal+import Data.Git.FileReader+import Data.Git.Path+import Data.Git.Ref++-- | represent an index header with the version and the fanout table+data IndexHeader = IndexHeader !Word32 !(Vector Word32)+	deriving (Show,Eq)++data Index = Index+	{ indexSha1s        :: Vector Ref+	, indexCRCs         :: Vector Word32+	, indexPackoffs     :: Vector Word32+	, indexPackChecksum :: Ref+	, indexChecksum     :: Ref+	}++-- | enumerate every indexes file in the pack directory+indexEnumerate repoPath = map onlyHash . filter isPackFile <$> getDirectoryContents (repoPath </> "objects" </> "pack")+	where+		isPackFile x = ".idx" `isSuffixOf` x && "pack-" `isPrefixOf` x+		onlyHash = fromHexString . takebut 4 . drop 5+		takebut n l = take (length l - n) l++-- | open an index+indexOpen :: FilePath -> Ref -> IO FileReader+indexOpen repoPath indexRef = openFile (indexPath repoPath indexRef) ReadMode >>= fileReaderNew False++-- | close an index+indexClose :: FileReader -> IO ()+indexClose = fileReaderClose++-- | variant of withFile on the index file and with a FileReader+withIndex repoPath indexRef = withFileReader (indexPath repoPath indexRef)++-- | returns the number of references, referenced in this index.+indexHeaderGetSize :: IndexHeader -> Word32+indexHeaderGetSize (IndexHeader _ indexes) = indexes ! 255++-- | byte size of an index header.+indexHeaderByteSize :: Int+indexHeaderByteSize = 2*4 {- header -} + 256*4 {- fanout table -}++-- | get the number of reference in this index with a specific prefix+indexHeaderGetNbWithPrefix :: IndexHeader -> Int -> Word32+indexHeaderGetNbWithPrefix (IndexHeader _ indexes) n+	| n < 0 || n > 255 = 0+	| n == 0           = indexes ! 0+	| otherwise        = (indexes ! n) - (indexes ! (n-1))++-- | fold on refs with a specific prefix+indexHeaderFoldRef :: IndexHeader -> FileReader -> Int -> (a -> Word32 -> Ref -> (a, Bool)) -> a -> IO a+indexHeaderFoldRef idxHdr@(IndexHeader _ indexes) fr refprefix f initAcc+	| nb == 0   = return initAcc+	| otherwise = do+		let spos = (indexes ! refprefix) - nb+		fileReaderSeek fr (fromIntegral (sha1Offset + spos * 20))+		loop nb initAcc+	where+		loop 0 acc = return acc+		loop n acc = do+			b <- fromBinary <$> fileReaderGetBS 20 fr+			let (!nacc, terminate) = f acc (nb-n) b+			if terminate+				then return nacc+				else loop (n-1) nacc+		nb         = indexHeaderGetNbWithPrefix idxHdr refprefix+		(sha1Offset,_,_) = indexOffsets idxHdr++-- | return the reference offset in the packfile if found+indexGetReferenceLocation :: IndexHeader -> FileReader -> Ref -> IO (Maybe Word64)+indexGetReferenceLocation idxHdr@(IndexHeader _ indexes) fr ref = do+	mrpos <- indexHeaderFoldRef idxHdr fr refprefix f Nothing+	case mrpos of+		Nothing   -> return Nothing+		Just rpos -> do+			let spos = (indexes ! refprefix) - nb+			fileReaderSeek fr (fromIntegral (packOffset + 4 * (spos+rpos)))+			Just . fromIntegral . be32 <$> fileReaderGetBS 4 fr+	where+		f acc rpos rref = if ref == rref then (Just rpos,True) else (acc,False)+		refprefix  = refPrefix ref+		nb         = indexHeaderGetNbWithPrefix idxHdr refprefix+		(_,_,packOffset) = indexOffsets idxHdr++-- | get all references that start by prefix.+indexGetReferencesWithPrefix :: IndexHeader -> FileReader -> String -> IO [Ref]+indexGetReferencesWithPrefix idxHdr fr prefix =+	indexHeaderFoldRef idxHdr fr refprefix f []+	where+		f acc _ ref = case cmpPrefix prefix ref of+			GT -> (acc    ,False)+			EQ -> (ref:acc,False)+			LT -> (acc    ,True)+		refprefix   = read ("0x" ++ take 2 prefix)++-- | returns absolute offset in the index file of the sha1s, the crcs and the packfiles offset.+indexOffsets idx = (indexSha1sOffset, indexCRCsOffset, indexPackOffOffset)+	where+		indexPackOffOffset = indexCRCsOffset + crcsTableSz+		indexCRCsOffset    = indexSha1sOffset + sha1TableSz+		indexSha1sOffset   = fromIntegral indexHeaderByteSize+		crcsTableSz        = 4 * sz+		sha1TableSz        = 20 * sz+		sz                 = indexHeaderGetSize idx++-- | parse index header+parseIndexHeader = do+	magic   <- be32 <$> A.take 4+	when (magic /= 0xff744f63) $ error "wrong magic number for index"+	ver     <- be32 <$> A.take 4+	when (ver /= 2) $ error "unsupported index version"+	fanouts <- V.replicateM 256 (be32 <$> A.take 4)+	return $ IndexHeader ver fanouts++-- | read index header from an index filereader+indexReadHeader :: FileReader -> IO IndexHeader+indexReadHeader fr = fileReaderSeek fr 0 >> fileReaderParse fr parseIndexHeader++-- | get index header from an index reference+indexGetHeader :: FilePath -> Ref -> IO IndexHeader+indexGetHeader repoPath indexRef = withIndex repoPath indexRef $ indexReadHeader++-- | read all index+indexRead repoPath indexRef = do+	withIndex repoPath indexRef $ \fr -> do+		idx <- fileReaderParse fr parseIndexHeader+		liftM2 (,) (return idx) (fileReaderParse fr (parseIndex $ indexHeaderGetSize idx))+	where parseIndex sz = do+		sha1s     <- V.replicateM (fromIntegral sz) (fromBinary <$> A.take 20)+		crcs      <- V.replicateM (fromIntegral sz) (be32 <$> A.take 4)+		packoffs  <- V.replicateM (fromIntegral sz) (be32 <$> A.take 4)+		let nbLarge = length $ filter (== True) $ map (\packoff -> packoff `testBit` 31) $ V.toList packoffs+		largeoffs <- replicateM nbLarge (A.take 4)+		packfileChecksum <- fromBinary <$> A.take 20+		idxfileChecksum  <- fromBinary <$> A.take 20+		-- large packfile offsets+		-- trailer+		return (sha1s, crcs, packoffs, largeoffs, packfileChecksum, idxfileChecksum)
+ Data/Git/Internal.hs view
@@ -0,0 +1,20 @@+-- |+-- Module      : Data.Git.Internal+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Internal+	( be32+	) where++import Data.Bits+import Data.Word+import qualified Data.ByteString as B++be32 :: B.ByteString -> Word32+be32 b = fromIntegral (B.index b 0) `shiftL` 24+       + fromIntegral (B.index b 1) `shiftL` 16+       + fromIntegral (B.index b 2) `shiftL` 8+       + fromIntegral (B.index b 3)
+ Data/Git/Loose.hs view
@@ -0,0 +1,150 @@+-- |+-- Module      : Data.Git.Loose+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}+module Data.Git.Loose+	(+	-- * marshall from and to lazy bytestring+	  looseUnmarshall+	, looseUnmarshallRaw+	, looseMarshall+	-- * read and check object existence+	, looseRead+	, looseReadHeader+	, looseReadRaw+	, looseExists+	-- * write objects+	, looseWriteBlobFromFile+	, looseWrite+	-- * enumeration of loose objects+	, looseEnumeratePrefixes+	, looseEnumerateWithPrefixFilter+	, looseEnumerateWithPrefix+	) where++import Codec.Compression.Zlib+import Data.Git.Ref+import Data.Git.Path+import Data.Git.FileWriter+import Data.Git.Object++import System.FilePath+import System.Directory+import System.Posix.Files++import qualified Data.ByteString.Char8 as BC+import qualified Data.ByteString.Lazy as L++import Data.Attoparsec.Lazy+import qualified Data.Attoparsec.Char8 as PC+import Control.Applicative ((<$>), (<|>))+import Control.Monad+import Control.Exception (onException, SomeException)+import qualified Control.Exception as E++import Data.Char (isHexDigit)++isObjectPrefix [a,b] = isHexDigit a && isHexDigit b+isObjectPrefix _     = False++decimal :: Parser Int+decimal = PC.decimal++-- loose object parsing+parseHeader = do+	h <- takeWhile1 ((/=) 0x20)+	_ <- word8 0x20+	sz <- decimal+	return (objectTypeUnmarshall $ BC.unpack h, fromIntegral sz, Nothing)++parseTreeHeader   = string "tree " >> decimal >> word8 0+parseTagHeader    = string "tag " >> decimal >> word8 0+parseCommitHeader = string "commit " >> decimal >> word8 0+parseBlobHeader   = string "blob " >> decimal >> word8 0++parseTree   = parseTreeHeader >> objectParseTree+parseTag    = parseTagHeader >> objectParseTag+parseCommit = parseCommitHeader >> objectParseCommit+parseBlob   = parseBlobHeader >> objectParseBlob+parseObject = parseSuccess (parseTree <|> parseBlob <|> parseCommit <|> parseTag)+	where parseSuccess p = either error id . eitherResult . parse p++-- | unmarshall an object (with header) from a lazy bytestring.+looseUnmarshall :: L.ByteString -> Object+looseUnmarshall = parseObject . decompress++-- | unmarshall an object as (header, data) tuple from a lazy bytestring.+looseUnmarshallRaw :: L.ByteString -> (ObjectHeader, ObjectData)+looseUnmarshallRaw l =+	let dl = decompress l in+	let i = L.findIndex ((==) 0) dl in+	case i of+		Nothing  -> error "object not right format. missing 0"+		Just idx ->+			let (h, r) = L.splitAt (idx+1) dl in+			case maybeResult $ parse parseHeader h of+				Nothing  -> error "cannot open object"+				Just hdr -> (hdr, r)++-- | read a specific ref from a loose object and returns an header and data.+looseReadRaw repoPath ref = looseUnmarshallRaw <$> L.readFile (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++-- | read a specific ref from a loose object and returns an object+looseRead repoPath ref = looseUnmarshall <$> L.readFile (objectPathOfRef repoPath ref)++-- | check if a specific ref exists as loose object+looseExists repoPath ref = doesFileExist (objectPathOfRef repoPath ref)++-- | enumarate all prefixes available in the object store.+looseEnumeratePrefixes repoPath = filter isObjectPrefix <$> getDirectoryContents (repoPath </> "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)+	where+		getDir p = E.catch (getDirectoryContents p) (\(_::SomeException) -> return [])+		isRef l = length l == 38++looseEnumerateWithPrefix repoPath prefix =+	looseEnumerateWithPrefixFilter repoPath prefix (const True)++-- | marshall as lazy bytestring an object except deltas.+looseMarshall (DeltaOfs _ _) = error "cannot write delta offset as single object"+looseMarshall (DeltaRef _ _) = error "cannot write delta ref as single object"+looseMarshall obj            = compress $ L.concat [ L.fromChunks [hdrB], objData ]+	where+		objData = objectWrite obj+		hdrB    = objectWriteHeader (objectToType obj) (fromIntegral $ L.length objData)++-- | 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 <- fromIntegral . fileSize <$> getFileStatus file+	let hdr = objectWriteHeader TypeBlob fsz+	tmpPath <- objectTemporaryPath repoPath+	flip onException (removeFile tmpPath) $ do+		npath <- withFileWriter tmpPath $ \fw -> do+			fileWriterOutput fw hdr+			chunks <- L.toChunks <$> L.readFile file+			mapM_ (fileWriterOutput fw) chunks+			digest <- fileWriterGetDigest fw+			return $ objectPathOfRef repoPath digest+		exists <- doesFileExist npath+		when exists $ error "destination already exists"+		renameFile tmpPath npath++-- | write an object to disk as a loose reference.+-- use looseWriteBlobFromFile for efficiently writing blobs when being commited from a file.+looseWrite repoPath obj = L.writeFile (objectPathOfRef repoPath ref) content+	where+		content = looseMarshall obj+		ref     = hashLBS content
+ Data/Git/Named.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      : Data.Git.Named+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Named+	( headList+	, headExists+	, headRead+	, headWrite+	, remotesList+	, remoteList+	, tagList+	, tagExists+	, tagRead+	, tagWrite+	, specialRead+	, specialExists+	) where++import Control.Applicative ((<$>))++import System.Directory++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++getDirectoryContentNoDots path = filter noDot <$> getDirectoryContents path+	where noDot = (not . isPrefixOf ".")++headList gitRepo = getDirectoryContentNoDots (headsPath gitRepo)+tagList gitRepo = getDirectoryContentNoDots (tagsPath gitRepo)+remotesList gitRepo = getDirectoryContentNoDots (remotesPath gitRepo)+remoteList gitRepo remote = getDirectoryContentNoDots (remotePath gitRepo remote)++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)++headExists gitRepo name    = doesFileExist (headPath gitRepo name)+headRead gitRepo name      = readRef (headPath gitRepo name)+headWrite gitRepo name ref = writeRef (headPath gitRepo name) ref++tagExists gitRepo name    = doesFileExist (tagPath gitRepo name)+tagRead gitRepo name      = readRef (tagPath gitRepo name)+tagWrite gitRepo name ref = writeRef (tagPath gitRepo name) ref++specialRead gitRepo name   = readRefAndFollow gitRepo (specialPath gitRepo name)+specialExists gitRepo name = doesFileExist (specialPath gitRepo name)
+ Data/Git/Object.hs view
@@ -0,0 +1,249 @@+-- |+-- Module      : Data.Git.Object+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+{-# LANGUAGE OverloadedStrings #-}+module Data.Git.Object+	( ObjectLocation(..)+	, ObjectType(..)+	, ObjectHeader+	, ObjectData+	, ObjectPtr(..)+	, Object(..)+	, ObjectInfo(..)+	, objectToType+	, objectTypeMarshall+	, objectTypeUnmarshall+	, objectTypeIsDelta+	-- * parsing function+	, objectParseTree+	, objectParseCommit+	, objectParseTag+	, objectParseBlob+	-- * writing function+	, objectWriteHeader+	, objectWrite+	, objectHash+	) where++import Data.Git.Ref+import Data.Git.Delta++import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC+import qualified Data.ByteString.Lazy as L++import Data.Attoparsec.Lazy+import qualified Data.Attoparsec.Lazy as P+import qualified Data.Attoparsec.Char8 as PC+import Control.Applicative ((<$>))+import Control.Monad++import Data.Word+import Text.Printf++{-+import Blaze.ByteString.Builder+import Blaze.ByteString.Builder.ByteString+-}++-- | location of an object in the database+data ObjectLocation = NotFound | Loose Ref | Packed Ref Word64+	deriving (Show,Eq)++-- | represent one entry in the tree+-- (permission,file or directory name,blob or tree ref)+-- name should maybe a filepath, but not sure about the encoding.+type TreeEnt = (Int,ByteString,Ref)++-- | an author or committer line+-- 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,Int,Int)++-- | type of a git object.+data ObjectType =+	  TypeTree+	| TypeBlob+	| TypeCommit+	| TypeTag+	| TypeDeltaOff+	| TypeDeltaRef+	deriving (Show,Eq)++-- | Delta objects points to some others objects in the database+-- either as offset in the pack or as a direct reference.+data ObjectPtr = PtrRef Ref | PtrOfs Word64 deriving (Show,Eq)++type ObjectHeader = (ObjectType, Word64, Maybe ObjectPtr)++type ObjectData = L.ByteString++-- | Raw objects infos have an header (type, size, ptr),+-- the data and a pointers chains to parents for resolved objects.+data ObjectInfo = ObjectInfo+	{ oiHeader :: ObjectHeader+	, oiData   :: ObjectData+	, oiChains :: [ObjectPtr]+	} deriving (Show,Eq)++-- | 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 =+	  Tree [TreeEnt]+	| Blob L.ByteString+	| Commit Ref [Ref] Name Name ByteString+	| Tag Ref ObjectType ByteString Name ByteString+	| DeltaOfs Word64 Delta+	| DeltaRef Ref Delta+	deriving (Show,Eq)++objectToType :: Object -> ObjectType+objectToType (Commit _ _ _ _ _) = TypeCommit+objectToType (Blob _)           = TypeBlob+objectToType (Tree _)           = TypeTree+objectToType (Tag _ _ _ _ _)    = TypeTag+objectToType (DeltaOfs _ _)     = TypeDeltaOff+objectToType (DeltaRef _ _)     = TypeDeltaRef++objectTypeMarshall :: ObjectType -> String+objectTypeMarshall TypeTree   = "tree"+objectTypeMarshall TypeBlob   = "blob"+objectTypeMarshall TypeCommit = "commit"+objectTypeMarshall TypeTag    = "tag"+objectTypeMarshall _          = error "deltas cannot be marshalled"++objectTypeUnmarshall :: String -> ObjectType+objectTypeUnmarshall "tree"   = TypeTree+objectTypeUnmarshall "blob"   = TypeBlob+objectTypeUnmarshall "commit" = TypeCommit+objectTypeUnmarshall "tag"    = TypeTag+objectTypeUnmarshall _        = error "unknown object type"++objectTypeIsDelta :: ObjectType -> Bool+objectTypeIsDelta TypeDeltaOff = True+objectTypeIsDelta TypeDeltaRef = True+objectTypeIsDelta _            = False++-- | the enum instance is useful when marshalling to pack file.+instance Enum ObjectType where+	fromEnum TypeCommit   = 0x1+	fromEnum TypeTree     = 0x2+	fromEnum TypeBlob     = 0x3+	fromEnum TypeTag      = 0x4+	fromEnum TypeDeltaOff = 0x6+	fromEnum TypeDeltaRef = 0x7++	toEnum 0x1 = TypeCommit+	toEnum 0x2 = TypeTree+	toEnum 0x3 = TypeBlob+	toEnum 0x4 = TypeTag+	toEnum 0x6 = TypeDeltaOff+	toEnum 0x7 = TypeDeltaRef+	toEnum n   = error ("not a valid object: " ++ show n)++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)++skipChar :: Char -> Parser ()+skipChar c = PC.char c >> return ()++referenceHex = fromHex <$> P.take 40+referenceBin = fromBinary <$> P.take 20++-- | parse a tree content+objectParseTree = (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+objectParseBlob = (Blob <$> takeLazyByteString)++-- | parse a commit content+objectParseCommit = do+	tree <- string "tree " >> referenceHex+	skipChar '\n'+	parents   <- many parseParentRef+	author    <- string "author " >> parseName+	committer <- string "committer " >> parseName+	skipChar '\n'+	message <- takeByteString+	return $ Commit tree parents author committer message+	where+		parseParentRef = do+			tree <- string "parent " >> referenceHex+			skipChar '\n'+			return tree+		+-- | parse a tag content+objectParseTag = do+	object <- string "object " >> referenceHex+	skipChar '\n'+	type_ <- objectTypeUnmarshall . BC.unpack <$> (string "type " >> takeTill ((==) 0x0a))+	skipChar '\n'+	tag   <- string "tag " >> takeTill ((==) 0x0a)+	skipChar '\n'+	tagger <- string "tagger " >> parseName+	skipChar '\n'+	signature <- takeByteString+	return $ Tag object type_ tag tagger signature++parseName = do+	name <- B.init <$> PC.takeWhile ((/=) '<')+	skipChar '<'+	email <- PC.takeWhile ((/=) '>')+	_ <- string "> "+	time <- PC.decimal+	_ <- string " "+	timezone <- PC.signed PC.decimal+	skipChar '\n'+	return (name, email, time, timezone)++-- 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 (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+		]++objectWrite (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 ]++objectWrite (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 ]++objectWrite (Blob bData) = bData++objectWrite _       = error "delta object are not supported here"++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 (show time), " ", BC.pack (showtz tz)]+	where showtz i = (if i > 0 then "+" else "") ++ show i
+ Data/Git/Pack.hs view
@@ -0,0 +1,152 @@+-- |+-- Module      : Data.Git.Pack+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Pack+	( PackedObjectInfo(..)+	, PackedObjectRaw+	-- * Enumerators of packs+	, packEnumerate+	-- * Helpers to process packs+	, packOpen+	, packClose+	-- * Command for the content of a pack+	, packReadHeader+	, packReadMapAtOffset+	, packReadAtOffset+	, packReadRawAtOffset+	, packEnumerateObjects+	-- * turn a packed object into a +	, packedObjectToObject+	, packObjectFromRaw+	) where++import Control.Applicative ((<$>))+import Control.Arrow (second)+import Control.Monad++import System.IO+import System.FilePath+import System.Directory++import Data.Bits+import Data.List+import qualified Data.ByteString.Lazy as L++import Data.Attoparsec (anyWord8)+import qualified Data.Attoparsec as A+import qualified Data.Attoparsec.Lazy as AL++import Data.Git.Internal+import Data.Git.Path+import Data.Git.Object+import Data.Git.Delta+import Data.Git.Ref+import Data.Git.FileReader++import Data.Word++type PackedObjectRaw = (PackedObjectInfo, L.ByteString)++data PackedObjectInfo = PackedObjectInfo+	{ poiType       :: ObjectType+	, poiOffset     :: Word64+	, poiSize       :: Word64+	, poiActualSize :: Word64+	, poiExtra      :: Maybe ObjectPtr+	} deriving (Show,Eq)++-- | Enumerate the pack refs available in this repository.+packEnumerate repoPath = map onlyHash . filter isPackFile <$> getDirectoryContents (repoPath </> "objects" </> "pack")+	where+		isPackFile x = ".pack" `isSuffixOf` x+		onlyHash = fromHexString . takebut 5 . drop 5+		takebut n l = take (length l - n) l++-- | open a pack+packOpen :: FilePath -> Ref -> IO FileReader+packOpen repoPath packRef = openFile (packPath repoPath packRef) ReadMode >>= fileReaderNew False++-- | close a pack+packClose :: FileReader -> IO ()+packClose = fileReaderClose++-- | return the number of entries in this pack+packReadHeader repoPath packRef =+	withFileReader (packPath repoPath packRef) $ \filereader ->+		fileReaderParse filereader parseHeader+	where parseHeader = do+		packMagic <- be32 <$> A.take 4+		when (packMagic /= 0x5041434b) $ error "not a git packfile"+		ver <- be32 <$> A.take 4+		when (ver /= 2) $ error ("pack file version not supported: " ++ show ver)+		be32 <$> A.take 4++-- | read an object at a specific position using a map function on the objectData+packReadMapAtOffset fr offset mapData = fileReaderSeek fr offset >> getNextObject fr mapData++-- | read an object at a specific position+packReadAtOffset :: FileReader -> Word64 -> IO (Maybe Object)+packReadAtOffset fr offset = packReadMapAtOffset fr offset id++-- | read a raw representation at a specific position+packReadRawAtOffset :: FileReader -> Word64 -> IO (PackedObjectRaw)+packReadRawAtOffset fr offset = fileReaderSeek fr offset >> getNextObjectRaw fr++-- | enumerate all objects in this pack and callback to f for reach raw objects+packEnumerateObjects repoPath packRef entries f =+	withFileReader (packPath repoPath packRef) $ \filebuffer -> do+		fileReaderSeek filebuffer 12+		parseNext filebuffer entries+	where+		parseNext :: FileReader -> Int -> IO ()+		parseNext _  0    = return ()+		parseNext fr ents = getNextObjectRaw fr >>= f >> parseNext fr (ents-1)++getNextObject :: FileReader -> (L.ByteString -> L.ByteString) -> IO (Maybe Object)+getNextObject fr mapData =+	packedObjectToObject . second mapData <$> getNextObjectRaw fr++packedObjectToObject (PackedObjectInfo { poiType = ty, poiExtra = extra }, objData) =+	packObjectFromRaw (ty, extra, objData)++packObjectFromRaw (TypeCommit, Nothing, objData) = AL.maybeResult $ AL.parse objectParseCommit objData+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) = DeltaOfs o <$> deltaRead objData+packObjectFromRaw (TypeDeltaRef, Just (PtrRef r), objData) = DeltaRef r <$> deltaRead objData+packObjectFromRaw _                              = error "can't happen unless someone change getNextObjectRaw"++getNextObjectRaw :: FileReader -> IO PackedObjectRaw+getNextObjectRaw fr = do+	sobj      <- fileReaderGetPos fr+	(ty, size) <- fileReaderParse fr parseObjectHeader+	extra      <- case ty of+		TypeDeltaRef -> Just . PtrRef . fromBinary <$> fileReaderGetBS 20 fr+		TypeDeltaOff -> Just . PtrOfs . deltaOffFromList <$> fileReaderGetVLF fr+		_            -> return Nothing+	objData    <- fileReaderInflateToSize fr size+	eobj       <- fileReaderGetPos fr++	return (PackedObjectInfo ty sobj (eobj - sobj) size extra, objData)+	where+		parseObjectHeader = do+			(m, ty, sz) <- splitFirst <$> anyWord8+			size <- if m then (sz +) <$> getNextSize 4 else return sz+			return (ty, size)+			where+				getNextSize n = do+					(c, sz) <- splitOther n <$> anyWord8+					if c then (sz +) <$> getNextSize (n+7) else return sz++				splitFirst :: Word8 -> (Bool, ObjectType, Word64)+				splitFirst w = (w `testBit` 7, toEnum $ fromIntegral ((w `shiftR` 4) .&. 0x7), fromIntegral (w .&. 0xf))+				splitOther n w = (w `testBit` 7, fromIntegral (w .&. 0x7f) `shiftL` n)++		deltaOffFromList (x:xs) = foldl' acc (fromIntegral (x `clearBit` 7)) xs+			where acc a w = ((a+1) `shiftL` 7) + fromIntegral (w `clearBit` 7)+		deltaOffFromList [] = error "cannot happen"
+ Data/Git/Path.hs view
@@ -0,0 +1,40 @@+-- |+-- Module      : Data.Git.Path+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Path where++import System.FilePath+import System.Random+import Control.Applicative ((<$>))+import Data.Git.Ref++headsPath gitRepo = gitRepo </> "refs" </> "heads"+tagsPath gitRepo  = gitRepo </> "refs" </> "tags"+remotesPath gitRepo = gitRepo </> "refs" </> "remotes"++headPath gitRepo name = headsPath gitRepo </> name+tagPath gitRepo name = tagsPath gitRepo </> name+remotePath gitRepo name = remotesPath gitRepo </> name+specialPath gitRepo name = gitRepo </> name++remoteEntPath gitRepo name ent = remotePath gitRepo name </> ent++packDirPath repoPath = repoPath </> "objects" </> "pack"++indexPath repoPath indexRef =+	packDirPath repoPath </> ("pack-" ++ toHexString indexRef ++ ".idx")++packPath repoPath packRef =+	packDirPath repoPath </> ("pack-" ++ toHexString packRef ++ ".pack")++objectPath repoPath d f = repoPath </> "objects" </> d </> 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"))
+ Data/Git/Ref.hs view
@@ -0,0 +1,130 @@+-- |+-- Module      : Data.Git.Ref+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Ref+	( Ref+	, isHex+	, isHexString+	, fromHex+	, fromHexString+	, fromBinary+	, toBinary+	, toHex+	, toHexString+	, refPrefix+	, cmpPrefix+	, toFilePathParts+	, hash+	, hashLBS+	) where++import Control.Monad (forM_)+import qualified Crypto.Hash.SHA1 as SHA1+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Internal as B (unsafeCreate)+import qualified Data.ByteString.Unsafe as B (unsafeIndex)+import qualified Data.ByteString.Char8 as BC+import Data.Bits+import Data.Char (isHexDigit)++import Foreign.Storable++-- | represent a git reference (SHA1)+newtype Ref = Ref ByteString+	deriving (Eq,Ord)++instance Show Ref where+	show = BC.unpack . toHex++isHex = and . map isHexDigit . BC.unpack+isHexString = and . map isHexDigit++-- | take a hexadecimal bytestring that represent a reference+-- and turn into a ref+fromHex :: ByteString -> Ref+fromHex s+	| B.length s == 40 = Ref $ B.unsafeCreate 20 populateRef+	| otherwise        = error ("not a valid hex ref: " ++ show s)+	where +		populateRef ptr = forM_ [0..19] $ \i -> do+			let v = (unhex (B.unsafeIndex s (i*2+0)) `shiftL` 4) .|. unhex (B.unsafeIndex s (i*2+1))+			pokeElemOff ptr (i+0) v++		unhex 0x30 = 0  -- '0'+		unhex 0x31 = 1+		unhex 0x32 = 2+		unhex 0x33 = 3+		unhex 0x34 = 4+		unhex 0x35 = 5+		unhex 0x36 = 6+		unhex 0x37 = 7+		unhex 0x38 = 8+		unhex 0x39 = 9  -- '9'+		unhex 0x41 = 10 -- 'A'+		unhex 0x42 = 11+		unhex 0x43 = 12+		unhex 0x44 = 13+		unhex 0x45 = 14+		unhex 0x46 = 15 -- 'F'+		unhex 0x61 = 10 -- 'a'+		unhex 0x62 = 11+		unhex 0x63 = 12+		unhex 0x64 = 13+		unhex 0x65 = 14+		unhex 0x66 = 15 -- 'f'+		unhex _    = error "error fromHex: not a valid hex character"++-- | take a hexadecimal string that represent a reference+-- and turn into a ref+fromHexString :: String -> Ref+fromHexString = fromHex . BC.pack++-- | transform a ref into an hexadecimal bytestring+toHex :: Ref -> ByteString+toHex (Ref bs) = B.unsafeCreate 40 populateHex+	where+		populateHex ptr = forM_ [0..19] $ \i -> do+			let (a,b) = B.unsafeIndex bs i `divMod` 16+			pokeElemOff ptr (i*2+0) (hex a)+			pokeElemOff ptr (i*2+1) (hex b)+		hex i+			| i >= 0 && i <= 9   = 0x30 + i+			| i >= 10 && i <= 15 = 0x61 + i - 10+			| otherwise          = 0++-- | transform a ref into an hexadecimal string+toHexString :: Ref -> String+toHexString = BC.unpack . toHex++-- | transform a bytestring that represent a binary bytestring+-- and returns a ref.+fromBinary :: ByteString -> Ref+fromBinary b+	| B.length b == 20 = Ref b+	| otherwise        = error "not a valid binary ref"++-- | turn a reference into a binary bytestring+toBinary :: Ref -> ByteString+toBinary (Ref b) = b++-- | returns the prefix (leading byte) of this reference+refPrefix :: Ref -> Int+refPrefix (Ref b) = fromIntegral $ B.unsafeIndex b 0++-- | compare prefix+cmpPrefix :: String -> Ref -> Ordering+cmpPrefix pre ref = pre `compare` (take (length pre) $ toHexString ref)++-- | returns the splitted format "prefix/suffix" for addressing the loose object database+toFilePathParts :: Ref -> (String, String)+toFilePathParts ref = splitAt 2 $ show ref++-- | hash a bytestring into a reference+hash = Ref . SHA1.hash++hashLBS = Ref . SHA1.hashlazy
+ Data/Git/Repository.hs view
@@ -0,0 +1,369 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- |+-- Module      : Data.Git.Repository+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Repository+	( Git+	, HTree+	, HTreeEnt(..)+	, gitRepoPath+	, openRepo+	, closeRepo+	, withRepo+	, findRepo+	, findReference+	, findReferencesWithPrefix+	, findObjectRaw+	, findObjectRawAt+	, findObject+	, findObjectAt+	, buildHTree+	, resolvePath+	, resolveTreeish+	, resolveRevision+	, initRepo+	, isRepo+	) where++import System.Directory+import System.FilePath+import System.Environment++import Control.Applicative ((<$>))+import Control.Exception+import qualified Control.Exception as E+import Control.Monad++import Data.Word+import Data.IORef+import Data.List ((\\), find, isPrefixOf)++import Data.ByteString (ByteString)++import Data.Git.Delta+import Data.Git.FileReader+import Data.Git.Index+import Data.Git.Pack+import Data.Git.Named+import Data.Git.Object+import Data.Git.Revision+import Data.Git.Loose+import Data.Git.Ref++data IndexReader = IndexReader IndexHeader FileReader++-- | represent an git repo, with possibly already opened filereaders+-- for indexes and packs+data Git = Git+	{ gitRepoPath  :: FilePath+	, indexReaders :: IORef [(Ref, IndexReader)]+	, packReaders  :: IORef [(Ref, FileReader)]+	}++-- | 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)]++-- | open a new git repository context+openRepo :: FilePath -> IO Git+openRepo path = liftM2 (Git path) (newIORef []) (newIORef [])++-- | close a git repository context, closing all remaining fileReaders.+closeRepo :: Git -> IO ()+closeRepo (Git { indexReaders = ireaders, packReaders = preaders }) = do+	mapM_ (closeIndexReader . snd) =<< readIORef ireaders+	mapM_ (fileReaderClose . snd) =<< readIORef preaders+	where closeIndexReader (IndexReader _ fr) = fileReaderClose fr++-- | find the git repository from the current directory.+findRepo :: IO FilePath+findRepo = do+	menvDir <- E.catch (Just <$> getEnv "GIT_DIR") (\(_:: SomeException) -> return Nothing)+	case menvDir of+		Nothing     -> 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"])+			e <- isRepo filepath+			if e then return filepath else checkDir (n+1)++-- | execute a function f with a git context.+withRepo path f = bracket (openRepo path) closeRepo f++--iterateIndexes :: Git -> (a -> (IdxRef, IndexReader) -> IO (a,Bool)) -> a -> IO a+iterateIndexes git f initAcc = do+	allIndexes    <- indexEnumerate (gitRepoPath git)+	readers       <- readIORef (indexReaders git)+	(a,terminate) <- loop initAcc readers+	if terminate+		then return a+		else readRemainingIndexes a (allIndexes \\ map fst readers)+	where+		loop acc []     = return (acc, False)+		loop acc (r:rs) = do+			(nacc, terminate) <- f acc r+			if terminate+				then return (nacc,True)+				else loop nacc rs++		readRemainingIndexes acc []            = return acc+		readRemainingIndexes acc (idxref:idxs) = do+			fr <- indexOpen (gitRepoPath git) idxref+			idx <- indexReadHeader fr+			let idxreader = IndexReader idx fr+			let r = (idxref, idxreader)+			modifyIORef (indexReaders git) (\l -> r : l)+			(nacc, terminate) <- f acc r+			if terminate+				then return nacc+				else readRemainingIndexes nacc idxs++findReference :: Git -> Ref -> IO ObjectLocation+findReference git ref = maybe NotFound id <$> (findLoose `mplusIO` findInIndexes)+	where+		findLoose :: IO (Maybe ObjectLocation)+		findLoose = do+			isLoose <- looseExists (gitRepoPath git) ref+			if isLoose then return (Just $ Loose ref) else return Nothing++		findInIndexes :: IO (Maybe ObjectLocation)+		findInIndexes = iterateIndexes git isinIndex Nothing --f -> (a -> IndexReader -> IO (a,Bool)) -> a -> IO a++		isinIndex acc (idxref, (IndexReader idxhdr indexreader)) = do+			mloc <- indexGetReferenceLocation idxhdr indexreader ref+			case mloc of+				Nothing  -> return (acc, False)+				Just loc -> return (Just $ Packed idxref loc, True)++		mplusIO :: IO (Maybe a) -> IO (Maybe a) -> IO (Maybe a)+		mplusIO f g = f >>= \vopt -> case vopt of+			Nothing -> g+			Just v  -> return $ Just v++-- | 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"+	| otherwise             = do+		looseRefs  <- looseEnumerateWithPrefixFilter (gitRepoPath git) (take 2 pre) matchRef+		packedRefs <- concat <$> iterateIndexes git idxPrefixMatch []+		return (looseRefs ++ packedRefs)+	where+		-- not very efficient way to do that... will do for now.+		matchRef ref = pre `isPrefixOf` toHexString ref+		invalidLength = length pre < 2 || length pre > 39 ++		idxPrefixMatch acc (_, (IndexReader idxhdr indexreader)) = do+			refs <- indexGetReferencesWithPrefix idxhdr indexreader pre+			return (refs:acc,False)++readRawFromPack :: Git -> Ref -> Word64 -> IO (FileReader, PackedObjectRaw)+readRawFromPack git pref offset = do+	readers <- readIORef (packReaders git)+	reader <- case lookup pref readers of+		Just r  -> return r+		Nothing -> do+			p <- packOpen (gitRepoPath git) pref+			modifyIORef (packReaders git) ((pref, p):)+			return p+	po <- packReadRawAtOffset reader offset+	return (reader, po)++readFromPack :: Git -> Ref -> Word64 -> Bool -> IO (Maybe ObjectInfo)+readFromPack git pref o resolveDelta = do+	(reader, x) <- readRawFromPack git pref o+	if resolveDelta then resolve reader o x else return $ Just $ generifyHeader x+	where+		generifyHeader :: PackedObjectRaw -> ObjectInfo+		generifyHeader (po, objData) = ObjectInfo { oiHeader = hdr, oiData = objData, oiChains = [] }+			where hdr = (poiType po, poiActualSize po, poiExtra po)++		resolve :: FileReader -> Word64 -> PackedObjectRaw -> IO (Maybe ObjectInfo)+		resolve reader offset (po, objData) = do+			case (poiType po, poiExtra po) of+				(TypeDeltaOff, Just ptr@(PtrOfs doff)) -> do+					let delta = deltaRead objData+					let noffset = offset - doff+					base <- resolve reader noffset =<< packReadRawAtOffset reader noffset+					return $ addToChain ptr $ applyDelta delta base+				(TypeDeltaRef, Just ptr@(PtrRef bref)) -> do+					let delta = deltaRead objData+					base <- findObjectRaw git bref True+					return $ addToChain ptr $ applyDelta delta base+				_                                    -> return $ Just $ generifyHeader (po, objData)++		addToChain ptr (Just oi) = Just (oi { oiChains = ptr : oiChains oi })+		addToChain _   Nothing   = Nothing++		applyDelta :: Maybe Delta -> Maybe ObjectInfo -> Maybe ObjectInfo+		applyDelta (Just delta@(Delta _ rSize _)) (Just objInfo) = Just $ objInfo+			{ oiHeader = (\(a,_,c) -> (a,rSize,c)) $ oiHeader objInfo+			, oiData   = deltaApply (oiData objInfo) delta+			}+		applyDelta _ _                                      = Nothing+++-- | get an object from repository+findObjectRawAt :: Git -> ObjectLocation -> Bool -> IO (Maybe ObjectInfo)+findObjectRawAt _   NotFound    _ = return Nothing+findObjectRawAt git (Loose ref) _ = Just . (\(h,d)-> ObjectInfo h d[]) <$> looseReadRaw (gitRepoPath git) ref+findObjectRawAt git (Packed pref o) resolveDelta = readFromPack git pref o resolveDelta++-- | get an object from repository+findObjectRaw :: Git -> Ref -> Bool -> IO (Maybe ObjectInfo)+findObjectRaw git ref resolveDelta = do+	loc <- findReference git ref+	findObjectRawAt git loc resolveDelta++-- | get an object type from repository+findObjectType :: Git -> Ref -> IO (Maybe ObjectType)+findObjectType git ref = findReference git ref >>= findObjectTypeAt+	where+		findObjectTypeAt NotFound        = return Nothing+		findObjectTypeAt (Loose _)       = Just . (\(t,_,_) -> t) <$> looseReadHeader (gitRepoPath git) ref+		findObjectTypeAt (Packed pref o) =+			fmap ((\(ty,_,_) -> ty) . oiHeader) <$> readFromPack git pref o True++-- | get an object from repository using a location to reference it.+findObjectAt :: Git -> ObjectLocation -> Bool -> IO (Maybe Object)+findObjectAt git loc resolveDelta = maybe Nothing toObject <$> findObjectRawAt git loc resolveDelta+	where+		toObject (ObjectInfo { oiHeader = (ty, _, extra), oiData = objData }) = packObjectFromRaw (ty, extra, objData)++-- | get an object from repository using a ref.+findObject :: Git -> Ref -> Bool -> IO (Maybe Object)+findObject git ref resolveDelta = maybe Nothing toObject <$> findObjectRaw git ref resolveDelta+	where+		toObject (ObjectInfo { oiHeader = (ty, _, extra), oiData = objData }) = packObjectFromRaw (ty, extra, objData)++-- | 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++		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"++		fs = [ (specialExists, specialRead), (tagExists, tagRead), (headExists, headRead) ]++		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"++		getParentRefs ref = do+			obj <- findObject git ref True+			case obj of+				Just (Commit _ parents _ _ _) -> return parents+				Just _  -> error "wrong object type, expecting commit"+				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 Object)+resolveTreeish git ref = do+	obj <- findObject git ref True+	case obj of+		Just (Commit tree _ _ _ _) -> resolveTreeish git tree+		Just (Tree _)              -> return obj+		Just (Tag tref _ _ _ _)    -> resolveTreeish git tref +		Just _                     -> return Nothing+		Nothing                    -> return Nothing++-- | build a hierarchy tree from a tree object+buildHTree :: Git -> Object -> IO HTree+buildHTree git (Tree ents) = mapM resolveTree ents+	where resolveTree (perm, ent, ref) = do+		obj <- findObjectType git ref+		case obj of+			Just TypeBlob -> return (perm, ent, TreeFile ref)+			Just TypeTree -> do+				ctree <- findObject git ref True+				case ctree of+					Nothing -> error "unknown reference in tree object: no such child"+					Just t  -> do+						dir   <- buildHTree git t+						return (perm, ent, TreeDir ref dir)+			Just _        -> error "wrong type embedded in tree object"+			Nothing       -> error "unknown reference in tree object"+buildHTree _   _           = error "cannot build a tree structure from anything else than a tree"++-- | resolve the ref (tree or blob) related to a path at a specific commit ref+resolvePath :: Git -> Ref -> [ByteString] -> IO (Maybe Ref)+resolvePath git commitRef paths = do+	commit <- findObject git commitRef True+	case commit of+		Just (Commit tree _ _ _ _) -> resolve tree paths+		Just _                     -> error ("expecting commit object at " ++ show commitRef)+		Nothing                    -> error ("not a valid ref: " ++ show commitRef)+	where+		resolve :: Ref -> [ByteString] -> IO (Maybe Ref)+		resolve treeRef []     = return $ Just treeRef+		resolve treeRef (x:xs) = do+			tree <- findObject git treeRef True+			case tree of+				Just (Tree ents) -> do+					let cEnt = treeEntRef <$> findEnt x ents+					if xs == []+						then return cEnt+						else maybe (return Nothing) (\z -> resolve z xs) cEnt+				Just _           -> error ("expecting tree object at " ++ show treeRef)+				Nothing          -> error ("not a valid ref: " ++ show treeRef)++		findEnt x = find (\(_, b, _) -> b == x)+		treeEntRef (_,_,r) = r+	+-- | 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"</>"remotes","refs"</>"tags"]+	return $ and ([dir] ++ subDirs)++-- | initialize a new repository at a specific location.+initRepo :: FilePath -> IO ()+initRepo path = do+	exists <- doesDirectoryExist path+	when exists $ error "destination directory already exists"+	createDirectory path+	mapM_ (createDirectory . (path </>))+		["branches","hooks","info"+		,"logs","objects","refs"+		,"refs"</>"heads","refs"</>"remotes","refs"</>"tags"]
+ Data/Git/Revision.hs view
@@ -0,0 +1,51 @@+-- |+-- Module      : Data.Git.Delta+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Data.Git.Revision+	( Revision(..)+	, RevModifier(..)+	, revFromString+	) where++import Text.Parsec++data RevModifier =+	  RevModParent Int       -- ^ parent accessor ^<n> and ^+	| RevModParentFirstN Int -- ^ parent accessor ~<n>+	| RevModAtType String    -- ^ @{type} accessor+	| RevModAtDate String    -- ^ @{date} accessor+	| RevModAtN Int          -- ^ @{n} accessor+	deriving (Eq)++data Revision = Revision String [RevModifier]+	deriving (Eq)++revFromString s = either (error.show) id $ parse parser "" s where+	parser = do+		p    <- many (noneOf "^~@")+		mods <- many (choice [parseParent, parseFirstParent, parseAt])+		return $ Revision p mods+	parseParent = try $ do+		_ <- char '^'+		n <- optionMaybe (many1 digit)+		case n of+			Nothing -> return $ RevModParent 1+			Just d  -> return $ RevModParent (read d)+	parseFirstParent = try $+		char '~' >> many1 digit >>= return . RevModParentFirstN . read+	parseAt = try $ do+		_  <- char '@' >> char '{'+		at <- choice [ parseAtType, parseAtDate, parseAtN ]+		_  <- char '}'+		return at+	parseAtType = try $ do+		ty <- choice $ map string ["tree","commit","blob","tag"]+		return $ RevModAtType ty+	parseAtN = try $ do+		many1 digit >>= return . RevModAtN . read+	parseAtDate = try $ do+		many (noneOf "}") >>= return . RevModAtDate
+ Hit.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE BangPatterns #-}+-- |+-- Module      : Hit+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : unix+--+module Main where++import System.Environment+import Control.Applicative ((<$>))+import Control.Monad+import Data.IORef+import Data.Maybe+import Data.Git.Pack+import Data.Git.Object+import Data.Git.Ref+import Data.Git.Repository+import Data.Git.Revision+import Data.Word+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Char8 as BC+import Text.Printf+import qualified Data.Map as M+import qualified Data.HashTable.IO as H+import qualified Data.Hashable as Hashable++type HashTable k v = H.CuckooHashTable k v++instance Hashable.Hashable Ref where+	hash = Hashable.hash . toBinary++verifyPack pref git = do+	offsets     <- H.new+	tree        <- H.new+	refs        <- newIORef M.empty+	entries     <- fromIntegral <$> packReadHeader (gitRepoPath git) pref+	leftParsed  <- newIORef entries+	-- enumerate all objects either directly in tree for fully formed objects+	-- or a list of delta to resolves +	packEnumerateObjects (gitRepoPath git) pref entries (setObj leftParsed refs offsets tree)+	readIORefAndReplace refs M.empty >>= dumpTree offsets tree+	where+		readIORefAndReplace ioref emptyVal = do+			v <- readIORef ioref+			writeIORef ioref emptyVal+			return v++		setObj_ refs offsets tree (!info, objData)+			| objectTypeIsDelta (poiType info) = do+				(!ty, !ref, !ptr, !lenChain) <- do+					let loc = Packed pref (poiOffset info)+					objInfo <- maybe (error "cannot find delta chain") id <$> findObjectRawAt git loc True+					let (ty, sz, _) = oiHeader objInfo+					let !ref = objectHash ty sz (oiData objInfo)+					let ptr = head $ oiChains objInfo -- it's safe since deltas always have a non empty valid chain+					return (ty, ref, ptr, (length $ oiChains objInfo))+				H.insert tree ref (info { poiType = ty }, Just (ptr, lenChain))+			| otherwise = do+				let !ref = objectHash (poiType info) (poiActualSize info) objData+				modifyIORef refs (M.insert ref ())+				H.insert offsets (poiOffset info) ref+				H.insert tree ref (info,Nothing)++		setObj leftParsed refs offsets tree x = do+			parsed <- readIORef leftParsed+			when ((parsed `mod` 256) == 0) $ putStrLn (show parsed ++ " left to parse")+			modifyIORef leftParsed (\i -> i-1)+			setObj_ refs offsets tree x++		dumpTree :: HashTable Word64 Ref -> HashTable Ref (PackedObjectInfo, Maybe (ObjectPtr, Int)) -> M.Map Ref () -> IO ()+		dumpTree offsets tree refs = do+			forM_ (M.toAscList refs) $ \(ref, ()) -> do+				ent <- fromJust <$> H.lookup tree ref+				printEnt offsets ref ent++		-- print one line about the entry+		-- format is <sha1> <type> <real size> <size> <offset> [<number of chain element> <parent element>]+		printEnt _ ref (info,Nothing) = do+			printf "%s %-6s %d %d %d\n" (show ref)+			       (objectTypeMarshall $ poiType info)+			       (poiActualSize info)+			       (poiSize info)+			       (poiOffset info)++		printEnt offsets ref (info,Just (parentOffset, lenChain)) = do+			parentRef <- case parentOffset of+				PtrRef r -> return r+				PtrOfs off -> do+					let poff = poiOffset info - off+					maybe (error "cannot find delta's parent in pack ?") id <$> H.lookup offsets poff+			printf "%s %-6s %d %d %d %d %s\n" (show ref)+			       (objectTypeMarshall $ poiType info)+			       (poiActualSize info)+			       (poiSize info)+			       (poiOffset info)+			       (lenChain)+			       (show parentRef)+++catFile ty ref git = do+	let expectedType = case ty of+		"commit" -> Just TypeCommit+		"blob"   -> Just TypeBlob+		"tag"    -> Just TypeTag+		"tree"   -> Just TypeTree+		"-t"     -> Nothing+		_        -> error "unknown type request"+	mobj <- findObjectRaw git ref True+	case mobj of+		Nothing  -> error "not a valid object"+		Just obj ->+			let (objty, _, _) = oiHeader obj in+			case expectedType of+				Nothing  -> putStrLn $ objectTypeMarshall objty+				Just ety -> do+					when (ety /= objty) $ error "not expected type"+					L.putStrLn (oiData obj)++lsTree revision _ git = do+	ref <- maybe (error "revision cannot be found") id <$> resolveRevision git revision+	tree <- resolveTreeish git ref+	case tree of+		Just t -> do+			htree <- buildHTree git t+			mapM_ (showTreeEnt) htree +		_      -> error "cannot build a tree from this reference"+	where+		showTreeEnt (p,n,TreeDir r _) = printf "%06o tree %s    %s\n" p (show r) (BC.unpack n)+		showTreeEnt (p,n,TreeFile r)  = printf "%06o blob %s    %s\n" p (show r) (BC.unpack n)++revList revision git = do+	ref <- maybe (error "revision cannot be found") id <$> resolveRevision git revision+	loopTillEmpty ref+	where loopTillEmpty ref = do+		obj <- findObject git ref True+		case obj of+			Just (Commit _ parents _ _ _) -> do+				putStrLn $ show ref+				-- this behave like rev-list --first-parent.+				-- otherwise the parents need to be organized and printed+				-- in a reverse chronological fashion.+				case parents of+					[]    -> return ()+					(p:_) -> loopTillEmpty p+			Just _  -> error "wrong object type, expecting commit"+			Nothing -> error "reference in commit chain doesn't exists"++main = do+	p    <- findRepo+	args <- getArgs+	case args of+		["verify-pack",ref]  -> withRepo p $ verifyPack (fromHexString ref)+		["cat-file",ty,ref]  -> withRepo p $ catFile ty (fromHexString ref)+		["ls-tree",rev]      -> withRepo p $ lsTree (revFromString rev) ""+		["ls-tree",rev,path] -> withRepo p $ lsTree (revFromString rev) path+		["rev-list",rev]     -> withRepo p $ revList (revFromString rev)+		cmd : [] -> error ("unknown command: " ++ cmd)+		[]       -> error "no args"+		_        -> error "unknown command line arguments"
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2010-2011 Vincent Hanquez <vincent@snarc.org>++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,50 @@+        Hit+        ===++Hit is a reimplementation of some git operations in pure haskell.+++what it does do:++* read loose objects, and packed objects.+* write new loose objects+* git like operations available: commit, cat-file, verify-pack, rev-list, ls-tree.++what is doesn't do:++* reimplement the whole of git.+* checkout's index reading/writing, fetching, merging, diffing.++The main functions for users are available from the Data.Git.Repository module.++The essential functions are:++* withRepo: create a new git context and execute a function with the context. functional equivalent of withFile but for git repository.+* resolveRevision: turns a git revision (e.g. HEAD, 0a24^^^~3) into a SHA1 reference.+* resolvePath: from a commit ref and a path, it will gives the tree or blob reference of the object at the specific path (see example).+* findObject: from a SHA1 reference, gives a high level object (Commit, Blob, Tree, Tag, Delta) from the git repository. if called with resolveDelta set, it will resolves deltas to be simple objects with the deltas applied.+* findObjectRaw: similar to findObject but gives a raw representation (lazy bytestring) of the object.++API Example+-----------++resolving path of the README file and returning the reference to the blob :++    {-# LANGUAGE OverloadedStrings #-}+    import Data.Git.Repository++    showPathRef commitRef = withRepo ".git" $ \git -> do+	ref <- maybe (error "inexistent object at this path") id `fmap` resolvePath git commitRef ["README"]+        putStrLn ("README has the reference: " ++ show ref)+++catting an object from a ref:++    import Data.Git.Repository++    catFile ref = withRepo ".git" $ \git -> do+	obj <- maybe (error "not a valid object") id `fmap` findObjectRaw git ref True+        L.putStrLn (oiData obj)+++more examples on how to use api can be found in Hit.hs.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Tests.hs view
@@ -0,0 +1,71 @@+import Test.QuickCheck+import Test.Framework(defaultMain, testGroup)+import Test.Framework.Providers.QuickCheck2(testProperty)++import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString as B++import Control.Applicative ((<$>))+import Control.Monad++import Data.Git.Object+import Data.Git.Loose+import Data.Git.Ref++-- for arbitrary instance to generate only data that are writable+-- to disk. i.e. no deltas.+data ObjNoDelta = ObjNoDelta Object++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)++arbitraryBSascii size = B.pack . map fromIntegral <$> replicateM size (choose (0x20,0x7f) :: Gen Int)+arbitraryBSnoangle size = B.pack . map fromIntegral <$> replicateM size (choose (0x40,0x7f) :: Gen Int)++instance Arbitrary Ref where+	arbitrary = fromBinary <$> arbitraryBS 20++arbitraryMsg = arbitraryBSno0 128+arbitraryLazy = L.fromChunks . (:[]) <$> arbitraryBS 4096++arbitraryRefList :: Gen [Ref]+arbitraryRefList = replicateM 2 arbitrary++arbitraryEnt = liftM3 (,,) arbitrary (arbitraryBSno0 48) arbitrary+arbitraryEnts = choose (1,100) >>= \i -> replicateM i arbitraryEnt++arbitraryName = liftM4 (,,,) (arbitraryBSnoangle 16)+                             (arbitraryBSnoangle 16)+                             (arbitrary `suchThat` (\i -> i > 0))+                              arbitrary++arbitraryObjTypeNoDelta = oneof [return TypeTree,return TypeBlob,return TypeCommit,return TypeTag]++instance Arbitrary ObjNoDelta where+	arbitrary = ObjNoDelta <$> oneof+		[ liftM5 Commit arbitrary arbitraryRefList arbitraryName arbitraryName arbitraryMsg+		, liftM Tree arbitraryEnts+		, liftM Blob arbitraryLazy+		, liftM5 Tag arbitrary arbitraryObjTypeNoDelta (arbitraryBSascii 20) arbitraryName arbitraryMsg+		]++prop_object_marshalling_id (ObjNoDelta obj) = obj == (looseUnmarshall $ looseMarshall obj)++refTests =+	[ 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+	]++main = defaultMain+	[ testGroup "ref marshalling" refTests+	, testGroup "object marshalling" objTests+	]
+ hit.cabal view
@@ -0,0 +1,97 @@+Name:                hit+Version:             0.1.0+Synopsis:            Git operations+Description:         Provides low level git operations+License:             BSD3+License-file:        LICENSE+Copyright:           Vincent Hanquez <vincent@snarc.org>+Author:              Vincent Hanquez <vincent@snarc.org>+Maintainer:          Vincent Hanquez <vincent@snarc.org>+Category:            Development+Stability:           experimental+Build-Type:          Simple+Homepage:            http://github.com/vincenthz/hit+Cabal-Version:       >=1.6+data-files:          README.md++Flag test+  Description:       Build unit test+  Default:           False++Flag executable+  Description:       Build the executable+  Default:           False++Flag debug+  Description:       Add some debugging options+  Default:           False++Library+  Build-Depends:     base >= 4 && < 5+                   , mtl+                   , bytestring+                   , attoparsec+                   , parsec     >= 3+                   , filepath+                   , directory+                   , cryptohash+                   , vector+                   , random+                   , zlib+                   , zlib-bindings >= 0.0.1+                   , patience+                   , bytedump+                   , unix+  Exposed-modules:   Data.Git.Index+                     Data.Git.Pack+                     Data.Git.Object+                     Data.Git.Loose+                     Data.Git.Named+                     Data.Git.Delta+                     Data.Git.Ref+                     Data.Git.Revision+                     Data.Git.Repository+  Other-modules:     Data.Git.Internal+                     Data.Git.FileReader+                     Data.Git.FileWriter+                     Data.Git.Path+  ghc-options:       -Wall -fno-warn-missing-signatures++Executable           Hit+  Main-Is:           Hit.hs+  ghc-options:       -Wall -fno-warn-missing-signatures+  if flag(debug)+    ghc-options:     -rtsops -auto-all -caf-all+  if flag(executable)+    Build-depends:   base >= 4 && < 5+                   , mtl+                   , containers+                   , hashable+                   , hashtables+                   , bytestring+                   , attoparsec+                   , filepath+                   , directory+                   , zlib+                   , bytedump+    Buildable: True+  else+    Buildable: False++Executable           Tests+  Main-Is:           Tests.hs+  if flag(test)+    Buildable:       True+    Build-depends:   base >= 3 && < 7+                   , HUnit+                   , QuickCheck >= 2+                   , bytestring+                   , test-framework >= 0.3+                   , test-framework-quickcheck2 >= 0.2+  else+    Buildable:       False+++source-repository head+  type: git+  location: git://github.com/vincenthz/hit