{-# OPTIONS -Wall #-} module Git.Path ( -- * Generate paths in git dir gitPath , gitRoot , gitDeref -- * General path handling , pathExistOr ) where import Control.Monad ((<=<)) import Control.Monad.Trans (liftIO) import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Char8 as C -- show-prefix, show-root use these import System.FilePath hiding (normalise) import System.Directory import System.Posix.Files ------------------------------------------------------------ -- gitRoot -- gitPath :: FilePath -> IO FilePath gitPath f = do root <- gitRoot return $ root ".git" f gitRoot :: IO FilePath gitRoot = do mp <- liftIO $ gitRoot' "." case mp of Just path -> return (path ++ [pathSeparator]) Nothing -> error "fatal: Not a git repository (or any of the parent directories)" gitRoot' :: FilePath -> IO (Maybe FilePath) gitRoot' path = do b <- fileExist path case b of True -> do d <- dirIsRoot path case d of True -> return (Just (normalise path)) False -> do let newPath = ".." path canPath <- canonicalizePath path canNewPath <- canonicalizePath newPath if (canPath == canNewPath) then return Nothing else gitRoot' newPath False -> return Nothing where dirIsRoot p = liftIO $ fileExist (p ".git") ------------------------------------------------------------ -- deref -- gitDeref :: String -> IO C.ByteString gitDeref = deref <=< L.readFile <=< gitPath where deref bs | refHeader `L.isPrefixOf` bs = gitDeref refPath | otherwise = return (chomp bs) where refHeader = C.pack "ref: " refPath = C.unpack (chomp $ L.drop 5 bs) chomp = C.takeWhile (/= '\n') ------------------------------------------------------------ -- pathExistOr -- | Return the given path if it exists, else the result of applying the -- modifier function pathExistOr :: (FilePath -> IO FilePath) -> FilePath -> IO FilePath pathExistOr f path = do exists <- doesFileExist path if exists then return path else f path ------------------------------------------------------------ -- normalise -- -- NOTE: this is a modified version of normalise from filepath, -- fixed to handle the case of a trailing dot. This version was -- submitted via the libraries process as ticket #3975: -- http://hackage.haskell.org/trac/ghc/ticket/3975 -- which was applied on 08 Jan 2011. -- | Normalise a file -- -- * \/\/ outside of the drive can be made blank -- -- * \/ -> 'pathSeparator' -- -- * .\/ -> \"\" -- -- > Posix: normalise "/file/\\test////" == "/file/\\test/" -- > Posix: normalise "/file/./test" == "/file/test" -- > Posix: normalise "/test/file/../bob/fred/" == "/test/file/../bob/fred/" -- > Posix: normalise "../bob/fred/" == "../bob/fred/" -- > Posix: normalise "./bob/fred/" == "bob/fred/" -- > Windows: normalise "c:\\file/bob\\" == "C:\\file\\bob\\" -- > Windows: normalise "c:\\" == "C:\\" -- > Windows: normalise "\\\\server\\test" == "\\\\server\\test" -- > Windows: normalise "c:/file" == "C:\\file" -- > normalise "." == "." -- > Posix: normalise "./" == "./" -- > Posix: normalise "./." == "./" -- > Posix: normalise "bob/fred/." == "bob/fred/" normalise :: FilePath -> FilePath normalise path = joinDrive (normaliseDrive drv) (f pth) ++ [pathSeparator | isDirPath pth] where (drv,pth) = splitDrive path isDirPath xs = lastSep xs || not (null xs) && last xs == '.' && lastSep (init xs) lastSep xs = not (null xs) && isPathSeparator (last xs) f = joinPath . dropDots [] . splitDirectories . propSep propSep (a:b:xs) | isPathSeparator a && isPathSeparator b = propSep (a:xs) propSep (a:xs) | isPathSeparator a = pathSeparator : propSep xs propSep (x:xs) = x : propSep xs propSep [] = [] dropDots _ xs | all (==".") xs = ["."] dropDots acc xs = dropDots' acc xs dropDots' acc (".":xs) = dropDots' acc xs dropDots' acc (x:xs) = dropDots' (x:acc) xs dropDots' acc [] = reverse acc --joinDrive = ++ normaliseDrive :: FilePath -> FilePath normaliseDrive = id