git-annex/Git.hs

323 lines
9.4 KiB
Haskell
Raw Normal View History

2010-10-12 03:22:38 +00:00
{- git repository handling
-
- This is written to be completely independant of git-annex and should be
- suitable for other uses.
-
- Copyright 2010, 2011 Joey Hess <joey@kitenet.net>
2010-10-27 20:53:54 +00:00
-
- Licensed under the GNU GPL version 3 or higher.
2010-10-14 06:36:41 +00:00
-}
2010-10-10 01:06:46 +00:00
module Git (
2010-10-14 06:36:41 +00:00
Repo,
Ref(..),
Branch,
Sha,
Tag,
2010-10-22 18:05:30 +00:00
repoIsUrl,
2010-10-22 17:40:19 +00:00
repoIsSsh,
2011-08-16 23:23:56 +00:00
repoIsHttp,
repoIsLocalBare,
2010-10-14 06:36:41 +00:00
repoDescribe,
repoLocation,
2010-10-14 06:36:41 +00:00
workTree,
2010-10-31 19:38:47 +00:00
gitDir,
urlPath,
urlHost,
urlPort,
urlHostUser,
urlAuthority,
urlScheme,
2010-10-14 06:36:41 +00:00
configMap,
2010-10-28 16:15:21 +00:00
configTrue,
gitCommandLine,
2010-10-14 06:36:41 +00:00
run,
2011-06-22 20:02:07 +00:00
runBool,
2010-10-16 18:20:43 +00:00
pipeRead,
pipeWrite,
pipeWriteRead,
pipeNullSplit,
pipeNullSplitB,
2010-10-14 06:36:41 +00:00
attributes,
remotes,
remotesAdd,
2010-10-16 18:20:43 +00:00
repoRemoteName,
repoRemoteNameSet,
repoRemoteNameFromKey,
reap,
useIndex,
getSha,
shaSize,
assertLocal,
2010-10-11 21:52:46 +00:00
) where
2010-10-10 01:06:46 +00:00
2011-12-13 19:22:43 +00:00
import qualified Data.Map as M
2010-10-12 04:53:42 +00:00
import Network.URI
2010-11-06 21:07:11 +00:00
import Data.Char
import System.Posix.Env (setEnv, unsetEnv, getEnv)
import qualified Data.ByteString.Lazy.Char8 as L
2010-10-16 20:20:49 +00:00
import Common
import Git.Types
{- User-visible description of a git repo. -}
2010-10-31 19:38:47 +00:00
repoDescribe :: Repo -> String
repoDescribe Repo { remoteName = Just name } = name
repoDescribe Repo { location = Url url } = show url
repoDescribe Repo { location = Dir dir } = dir
repoDescribe Repo { location = Unknown } = "UNKNOWN"
2010-10-13 18:40:56 +00:00
{- Location of the repo, either as a path or url. -}
repoLocation :: Repo -> String
repoLocation Repo { location = Url url } = show url
repoLocation Repo { location = Dir dir } = dir
repoLocation Repo { location = Unknown } = undefined
2010-10-14 02:59:43 +00:00
{- Constructs and returns an updated version of a repo with
- different remotes list. -}
2010-10-14 06:36:41 +00:00
remotesAdd :: Repo -> [Repo] -> Repo
remotesAdd repo rs = repo { remotes = rs }
2010-10-14 02:59:43 +00:00
{- Returns the name of the remote that corresponds to the repo, if
- it is a remote. -}
repoRemoteName :: Repo -> Maybe String
repoRemoteName Repo { remoteName = Just name } = Just name
repoRemoteName _ = Nothing
{- Sets the name of a remote. -}
repoRemoteNameSet :: String -> Repo -> Repo
repoRemoteNameSet n r = r { remoteName = Just n }
{- Sets the name of a remote based on the git config key, such as
"remote.foo.url". -}
repoRemoteNameFromKey :: String -> Repo -> Repo
repoRemoteNameFromKey k = repoRemoteNameSet basename
where
basename = join "." $ reverse $ drop 1 $
reverse $ drop 1 $ split "." k
2010-10-22 18:05:30 +00:00
{- Some code needs to vary between URL and normal repos,
2010-10-22 16:38:20 +00:00
- or bare and non-bare, these functions help with that. -}
2010-10-31 19:38:47 +00:00
repoIsUrl :: Repo -> Bool
repoIsUrl Repo { location = Url _ } = True
repoIsUrl _ = False
2010-10-31 19:38:47 +00:00
repoIsSsh :: Repo -> Bool
repoIsSsh Repo { location = Url url }
| uriScheme url == "ssh:" = True
-- git treats these the same as ssh
| uriScheme url == "git+ssh:" = True
| uriScheme url == "ssh+git:" = True
| otherwise = False
repoIsSsh _ = False
2011-08-16 23:23:56 +00:00
repoIsHttp :: Repo -> Bool
repoIsHttp Repo { location = Url url }
| uriScheme url == "http:" = True
| uriScheme url == "https:" = True
| otherwise = False
repoIsHttp _ = False
configAvail ::Repo -> Bool
configAvail Repo { config = c } = c /= M.empty
repoIsLocalBare :: Repo -> Bool
repoIsLocalBare r@(Repo { location = Dir _ }) = configAvail r && configBare r
repoIsLocalBare _ = False
2010-10-31 19:38:47 +00:00
assertLocal :: Repo -> a -> a
2010-10-22 18:05:30 +00:00
assertLocal repo action =
if not $ repoIsUrl repo
2010-10-12 04:53:42 +00:00
then action
2010-11-06 21:07:11 +00:00
else error $ "acting on URL git repo " ++ repoDescribe repo ++
2010-10-12 06:51:44 +00:00
" not supported"
2010-10-31 19:38:47 +00:00
assertUrl :: Repo -> a -> a
2010-10-22 18:05:30 +00:00
assertUrl repo action =
if repoIsUrl repo
2010-10-22 17:40:19 +00:00
then action
2010-11-06 21:07:11 +00:00
else error $ "acting on local git repo " ++ repoDescribe repo ++
2010-10-22 17:40:19 +00:00
" not supported"
2010-12-31 19:46:33 +00:00
configBare :: Repo -> Bool
configBare repo = maybe unknown configTrue $ M.lookup "core.bare" $ config repo
2011-05-15 16:25:58 +00:00
where
unknown = error $ "it is not known if git repo " ++
2010-11-06 21:07:11 +00:00
repoDescribe repo ++
" is a bare repository; config not read"
2010-10-11 21:52:46 +00:00
{- Path to a repository's gitattributes file. -}
2010-10-14 06:36:41 +00:00
attributes :: Repo -> String
attributes repo
| configBare repo = workTree repo ++ "/info/.gitattributes"
2010-11-06 21:07:11 +00:00
| otherwise = workTree repo ++ "/.gitattributes"
2010-10-10 06:29:58 +00:00
2011-08-19 16:59:07 +00:00
{- Path to a repository's .git directory. -}
2010-10-31 19:38:47 +00:00
gitDir :: Repo -> String
gitDir repo
2011-08-19 16:59:07 +00:00
| configBare repo = workTree repo
| otherwise = workTree repo </> ".git"
2010-10-10 06:29:58 +00:00
{- Path to a repository's --work-tree, that is, its top.
-
- Note that for URL repositories, this is the path on the remote host. -}
2010-10-14 06:36:41 +00:00
workTree :: Repo -> FilePath
workTree r@(Repo { location = Url _ }) = urlPath r
workTree (Repo { location = Dir d }) = d
workTree Repo { location = Unknown } = undefined
2010-10-12 04:53:42 +00:00
{- Path of an URL repo. -}
urlPath :: Repo -> String
urlPath Repo { location = Url u } = uriPath u
urlPath repo = assertUrl repo $ error "internal"
{- Scheme of an URL repo. -}
urlScheme :: Repo -> String
urlScheme Repo { location = Url u } = uriScheme u
urlScheme repo = assertUrl repo $ error "internal"
{- Work around a bug in the real uriRegName
- <http://trac.haskell.org/network/ticket/40> -}
uriRegName' :: URIAuth -> String
uriRegName' a = fixup $ uriRegName a
where
fixup x@('[':rest)
| rest !! len == ']' = take len rest
| otherwise = x
where
len = length rest - 1
fixup x = x
{- Hostname of an URL repo. -}
urlHost :: Repo -> String
urlHost = urlAuthPart uriRegName'
{- Port of an URL repo, if it has a nonstandard one. -}
urlPort :: Repo -> Maybe Integer
urlPort r =
case urlAuthPart uriPort r of
":" -> Nothing
2011-12-09 22:57:09 +00:00
(':':p) -> readMaybe p
_ -> Nothing
{- Hostname of an URL repo, including any username (ie, "user@host") -}
urlHostUser :: Repo -> String
urlHostUser r = urlAuthPart uriUserInfo r ++ urlAuthPart uriRegName' r
{- The full authority portion an URL repo. (ie, "user@host:port") -}
urlAuthority :: Repo -> String
2011-10-15 20:21:08 +00:00
urlAuthority = urlAuthPart assemble
2011-10-15 05:37:55 +00:00
where
2011-10-15 20:21:08 +00:00
assemble a = uriUserInfo a ++ uriRegName' a ++ uriPort a
{- Applies a function to extract part of the uriAuthority of an URL repo. -}
urlAuthPart :: (URIAuth -> a) -> Repo -> a
urlAuthPart a Repo { location = Url u } = a auth
where
auth = fromMaybe (error $ "bad url " ++ show u) (uriAuthority u)
urlAuthPart _ repo = assertUrl repo $ error "internal"
2010-10-12 03:22:38 +00:00
{- Constructs a git command line operating on the specified repo. -}
gitCommandLine :: [CommandParam] -> Repo -> [CommandParam]
gitCommandLine params repo@(Repo { location = Dir _ } ) =
2010-10-12 03:22:38 +00:00
-- force use of specified repo via --git-dir and --work-tree
2011-08-19 16:59:07 +00:00
[ Param ("--git-dir=" ++ gitDir repo)
, Param ("--work-tree=" ++ workTree repo)
] ++ params
gitCommandLine _ repo = assertLocal repo $ error "internal"
2010-10-12 03:22:38 +00:00
2011-06-22 20:02:07 +00:00
{- Runs git in the specified repo. -}
runBool :: String -> [CommandParam] -> Repo -> IO Bool
runBool subcommand params repo = assertLocal repo $
boolSystem "git" $ gitCommandLine (Param subcommand : params) repo
2011-06-22 20:02:07 +00:00
{- Runs git in the specified repo, throwing an error if it fails. -}
run :: String -> [CommandParam] -> Repo -> IO ()
run subcommand params repo = assertLocal repo $
runBool subcommand params repo
>>! error $ "git " ++ show params ++ " failed"
2010-10-10 19:04:18 +00:00
{- Runs a git subcommand and returns its output, lazily.
-
- Note that this leaves the git process running, and so zombies will
- result unless reap is called.
-}
pipeRead :: [CommandParam] -> Repo -> IO L.ByteString
pipeRead params repo = assertLocal repo $ do
(_, h) <- hPipeFrom "git" $ toCommand $ gitCommandLine params repo
hSetBinaryMode h True
L.hGetContents h
{- Runs a git subcommand, feeding it input.
- You should call either getProcessStatus or forceSuccess on the PipeHandle. -}
pipeWrite :: [CommandParam] -> L.ByteString -> Repo -> IO PipeHandle
pipeWrite params s repo = assertLocal repo $ do
(p, h) <- hPipeTo "git" (toCommand $ gitCommandLine params repo)
L.hPut h s
hClose h
return p
{- Runs a git subcommand, feeding it input, and returning its output.
- You should call either getProcessStatus or forceSuccess on the PipeHandle. -}
pipeWriteRead :: [CommandParam] -> L.ByteString -> Repo -> IO (PipeHandle, L.ByteString)
pipeWriteRead params s repo = assertLocal repo $ do
(p, from, to) <- hPipeBoth "git" (toCommand $ gitCommandLine params repo)
hSetBinaryMode from True
L.hPut to s
hClose to
c <- L.hGetContents from
return (p, c)
{- Reads null terminated output of a git command (as enabled by the -z
- parameter), and splits it. -}
pipeNullSplit :: [CommandParam] -> Repo -> IO [String]
pipeNullSplit params repo = map L.unpack <$> pipeNullSplitB params repo
{- For when Strings are not needed. -}
pipeNullSplitB ::[CommandParam] -> Repo -> IO [L.ByteString]
pipeNullSplitB params repo = filter (not . L.null) . L.split '\0' <$>
pipeRead params repo
{- Reaps any zombie git processes. -}
reap :: IO ()
reap = do
-- throws an exception when there are no child processes
r <- catchDefaultIO (getAnyProcessStatus False True) Nothing
2011-05-15 06:49:43 +00:00
maybe (return ()) (const reap) r
{- Forces git to use the specified index file.
- Returns an action that will reset back to the default
- index file. -}
useIndex :: FilePath -> IO (IO ())
useIndex index = do
res <- getEnv var
setEnv var index True
return $ reset res
where
var = "GIT_INDEX_FILE"
reset (Just v) = setEnv var v True
reset _ = unsetEnv var
{- Runs an action that causes a git subcommand to emit a sha, and strips
any trailing newline, returning the sha. -}
getSha :: String -> IO String -> IO Sha
getSha subcommand a = do
t <- a
let t' = if last t == '\n'
2011-09-21 03:24:48 +00:00
then init t
else t
when (length t' /= shaSize) $
error $ "failed to read sha from git " ++ subcommand ++ " (" ++ t' ++ ")"
return $ Ref t'
{- Size of a git sha. -}
shaSize :: Int
shaSize = 40
2011-06-21 20:08:09 +00:00
{- Checks if a string from git config is a true value. -}
configTrue :: String -> Bool
configTrue s = map toLower s == "true"
2010-10-14 02:59:43 +00:00
{- Access to raw config Map -}
configMap :: Repo -> M.Map String String
configMap = config