packages feed

hit-0.1.0: Hit.hs

{-# 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"