module System.Git (
gitPathToGitObject
, GitError(..)
, GitDir, GitPath
, findGitDir
, rootSha1
, rootCommitObj
, gitPathToSha1
, gitPathToObj
, sha1ToObjFile
, sha1ToObj
) where
import Control.Applicative
import Control.Exception
import Data.Git
import Data.Typeable (Typeable)
import Prelude hiding (catch)
import System.Directory
import System.FilePath
import System.IO
type GitDir = FilePath
type GitPath = FilePath
data GitError = GitDirNotExist | GitEntryNotExist
deriving (Show, Typeable)
instance Exception GitError
gitPathToGitObject :: GitPath -> IO (Either SomeException GitObject)
gitPathToGitObject path = pathtoobj `catch` errorhandle
where
pathtoobj = findGitDir >>= gitPathToObj path >>= return . Right
errorhandle :: SomeException -> IO (Either SomeException GitObject)
errorhandle = return . Left
gitPathToObj :: GitPath -> GitDir -> IO GitObject
gitPathToObj path gitDir = gitPathToSha1 path gitDir >>= flip sha1ToObj gitDir
gitPathToSha1 :: GitPath -> GitDir -> IO SHA1
gitPathToSha1 path gitDir = do
GoCommit _ commit <- rootCommitObj gitDir
let sha1OfRootTreeObj = commitRef commit
pathToSha1 ps sha1OfRootTreeObj gitDir
where
ps = tail $ splitFilePath path
pathToSha1 :: [String] -> SHA1 -> GitDir -> IO SHA1
pathToSha1 [] sha _ = return sha
pathToSha1 (f:fs) sha gitDir = do
obj <- sha1ToObj sha gitDir
case obj of
GoTree _ ents -> case lokup f ents of
Nothing -> throw GitEntryNotExist
Just sha' -> pathToSha1 fs sha' gitDir
_ -> throw GitEntryNotExist
lokup :: FilePath -> [GitTreeEntry] -> Maybe SHA1
lokup _ [] = Nothing
lokup key (e:es)
| key == fileName e = Just (fileRef e)
| otherwise = lokup key es
rootCommitObj :: GitDir -> IO GitObject
rootCommitObj gitDir = rootSha1 gitDir >>= flip sha1ToObj gitDir
rootSha1 :: GitDir -> IO SHA1
rootSha1 gitDir = SHA1 <$> (getRootRefFile gitDir >>= readFileLine)
getRootRefFile :: GitDir -> IO FilePath
getRootRefFile gitDir = fieldToFile <$> readFileLine headFile
where
fieldToFile field = gitDir </> drop 5 field
headFile = gitDir </> "HEAD"
findGitDir :: IO GitDir
findGitDir = getCurrentDirectory >>= loop
where
loop dir = do
let gitDir = dir </> ".git"
exist <- doesDirectoryExist gitDir
if exist
then return gitDir
else if dir == "/"
then throw GitDirNotExist
else loop (takeDirectory dir)
sha1ToObj :: SHA1 -> GitDir -> IO GitObject
sha1ToObj sha gitDir = parseGitObject $ sha1ToObjFile sha gitDir
sha1ToObjFile :: SHA1 -> GitDir -> FilePath
sha1ToObjFile (SHA1 hash) gitDir =
gitDir </> "objects" </> take 2 hash </> drop 2 hash
readFileLine :: FilePath -> IO String
readFileLine file = withBinaryFile file ReadMode hGetLine
splitFilePath :: FilePath -> [String]
splitFilePath "" = []
splitFilePath path = case break ('/'==) path of
(xs,"") -> [xs]
(xs,_:ys) -> xs : splitFilePath ys