{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} {-| Description: References to git hashes---HEAD, branches, tags, etc. A ref is a pointer to a hash (or "symref", which is a pointer to another ref). Refs are just files under @.git@ (usually @.git/refs@, though @HEAD@ is a notable exception) that contain the (40-byte) hash to which they refer. We factor refs into 'Ref's proper and 'RefFile's---the former denoting a ref's name, and the latter the contents of ref file. -} module Data.Git.Ref ( Ref(..) , RefFile(..) , mkRef , readRefFile , readPackedRefsFile ) where import Control.Applicative import Control.Monad.State import Data.Attoparsec.ByteString as A import Data.Attoparsec.ByteString.Char8 (isSpace_w8) import Data.ByteString (ByteString) import Data.String import System.Posix.FilePath (RawFilePath, ()) import Data.Git.Internal.FileUtil import Data.Git.Hash import Data.Git.Internal.Parsers import Data.Git.Paths import Data.Git.RefName -- | A reference to a git hash data Ref = HEAD | Branch RefName -- ^ branches under @refs/heads@ | TagRef RefName (Maybe Sha1) -- ^ tags under @refs/tags@, possibly peeled | RemRef RemoteName RefName -- ^ remote refs under @refs/remotes@ | ExpRef RefName -- ^ any path under @.git@ deriving (Eq, Ord, Show) instance IsString Ref where fromString s = maybe (error $ "can't parse ref: " ++ s) id $ mkRef (fromString s) -- | The path of a 'Ref' relative to the git directory. instance InRepo Ref where inRepo HEAD = "HEAD" inRepo (Branch b) = "refs/heads" getRefName b inRepo (TagRef b _) = "refs/tags" getRefName b inRepo (RemRef r b) = "refs/remotes" getRemoteName r getRefName b inRepo (ExpRef p) = getRefName p parseRef :: Parser Ref parseRef = "HEAD" *> pure HEAD <|> Branch <$> ("refs/heads/" *> parseRefName) <|> TagRef <$> ("refs/tags/" *> parseRefName) <*> pure Nothing <|> RemRef <$> ("refs/remotes/" *> parseRemoteName <* "/") <*> parseRefName <|> ExpRef <$> parseRefName -- | The contents of a file containing a 'Ref'. Either a 'Sha1' or a "Symbolic Reference" (e.g., -- @ref: refs/heads/master@) to another 'Ref'. data RefFile = ShaRef Sha1 | SymRef Ref deriving (Eq, Ord, Show) parseRefFile :: Parser RefFile parseRefFile = SymRef <$> (string "ref: " *> parseRef <* eol) <|> ShaRef <$> (parseSha1Hex <* eol) parseRefName :: Parser RefName parseRefName = do rn <- takeTill isSpace_w8 maybe empty return $ refName rn parseRemoteName :: Parser RemoteName parseRemoteName = do rn <- A.takeWhile (/= 0o57) maybe empty return $ remoteName rn -- | Try to parse a 'Ref'. mkRef :: ByteString -> Maybe Ref mkRef = either (const Nothing) Just . parseOnly parseRef maybeReadFile :: RawFilePath -> IO (Maybe ByteString) maybeReadFile fp = mwhenFileExists fp (liftIO . readRawFileS $ fp) parseFile :: Parser a -> RawFilePath -> IO (Maybe a) parseFile p fp = do file <- maybeReadFile fp return $ case file of Nothing -> Nothing Just f -> either (const Nothing) Just (parseOnly p f) -- | Try to parse a 'RefFile' out of an actual file. readRefFile :: RawFilePath -> IO (Maybe RefFile) readRefFile = parseFile parseRefFile parsePackedRef :: Parser (Ref, Sha1) parsePackedRef = do sha <- parseSha1Hex void space ref <- parseRef eol case ref of TagRef t _ -> (,sha) . TagRef t <$> optional ("^" *> parseSha1Hex <* eol) _ -> return (ref, sha) parsePackedRefs :: Parser [(Ref, Sha1)] parsePackedRefs = endOfInput *> pure [] <|> (:) <$> parsePackedRef <*> parsePackedRefs <|> skipLine *> parsePackedRefs -- | Try to parse any given @packed-refs@ file. readPackedRefsFile :: RawFilePath -> IO (Maybe [(Ref, Sha1)]) readPackedRefsFile = parseFile parsePackedRefs