git-annex/Git/Construct.hs

251 lines
7 KiB
Haskell
Raw Normal View History

{- Construction of Git Repo objects
-
- Copyright 2010-2020 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
2013-05-10 21:29:59 +00:00
{-# LANGUAGE CPP #-}
module Git.Construct (
fromCwd,
fromAbsPath,
fromPath,
fromUrl,
fromUnknown,
localToUrl,
2011-12-14 19:30:14 +00:00
remoteNamed,
remoteNamedFromKey,
fromRemotes,
fromRemoteLocation,
repoAbsPath,
checkForRepo,
2015-02-12 19:33:05 +00:00
newFrom,
) where
#ifndef mingw32_HOST_OS
import System.Posix.User
2013-05-10 21:29:59 +00:00
#endif
import qualified Data.Map as M
import Network.URI
import Common
import Git.Types
import Git
import Git.Remote
import Git.FilePath
2011-12-14 19:30:14 +00:00
import qualified Git.Url as Url
import Utility.UserInfo
{- Finds the git repository used for the cwd, which may be in a parent
- directory. -}
fromCwd :: IO (Maybe Repo)
fromCwd = getCurrentDirectory >>= seekUp
2012-12-13 04:24:19 +00:00
where
seekUp dir = do
r <- checkForRepo dir
2012-12-13 04:24:19 +00:00
case r of
Nothing -> case upFrom dir of
Nothing -> return Nothing
Just d -> seekUp d
2015-02-12 19:33:05 +00:00
Just loc -> pure $ Just $ newFrom loc
2012-01-13 18:40:36 +00:00
{- Local Repo constructor, accepts a relative or absolute path. -}
fromPath :: FilePath -> IO Repo
fromPath dir = fromAbsPath =<< absPath dir
{- Local Repo constructor, requires an absolute path to the repo be
- specified. -}
fromAbsPath :: FilePath -> IO Repo
fromAbsPath dir
| absoluteGitPath (encodeBS dir) = hunt
2012-03-16 05:59:07 +00:00
| otherwise =
error $ "internal error, " ++ dir ++ " is not absolute"
2012-12-13 04:24:19 +00:00
where
ret = pure . newFrom . LocalUnknown . toRawFilePath
2012-12-13 04:24:19 +00:00
canondir = dropTrailingPathSeparator dir
{- When dir == "foo/.git", git looks for "foo/.git/.git",
- and failing that, uses "foo" as the repository. -}
hunt
2013-05-14 19:44:49 +00:00
| (pathSeparator:".git") `isSuffixOf` canondir =
2012-12-13 04:24:19 +00:00
ifM (doesDirectoryExist $ dir </> ".git")
( ret dir
, ret (takeDirectory canondir)
2012-12-13 04:24:19 +00:00
)
| otherwise = ifM (doesDirectoryExist dir)
( gitDirFile dir >>= maybe (ret dir) (pure . newFrom)
-- git falls back to dir.git when dir doesn't
-- exist, as long as dir didn't end with a
-- path separator
, if dir == canondir
then ret (dir ++ ".git")
else ret dir
)
{- Remote Repo constructor. Throws exception on invalid url.
-
- Git is somewhat forgiving about urls to repositories, allowing
- eg spaces that are not normally allowed unescaped in urls.
-}
fromUrl :: String -> IO Repo
fromUrl url
| not (isURI url) = fromUrlStrict $ escapeURIString isUnescapedInURI url
| otherwise = fromUrlStrict url
fromUrlStrict :: String -> IO Repo
fromUrlStrict url
| "file://" `isPrefixOf` url = fromAbsPath $ unEscapeString $ uriPath u
2015-02-12 19:33:05 +00:00
| otherwise = pure $ newFrom $ Url u
2012-12-13 04:24:19 +00:00
where
u = fromMaybe bad $ parseURI url
bad = error $ "bad url " ++ url
{- Creates a repo that has an unknown location. -}
2015-02-12 19:33:05 +00:00
fromUnknown :: Repo
2012-01-14 01:06:00 +00:00
fromUnknown = newFrom Unknown
{- Converts a local Repo into a remote repo, using the reference repo
- which is assumed to be on the same host. -}
localToUrl :: Repo -> Repo -> Repo
localToUrl reference r
| not $ repoIsUrl reference = error "internal error; reference repo not url"
| repoIsUrl r = r
| otherwise = case Url.authority reference of
Nothing -> r
Just auth ->
let absurl = concat
[ Url.scheme reference
, "//"
, auth
, fromRawFilePath (repoPath r)
]
in r { location = Url $ fromJust $ parseURI absurl }
{- Calculates a list of a repo's configured remotes, by parsing its config. -}
fromRemotes :: Repo -> IO [Repo]
fromRemotes repo = mapM construct remotepairs
2012-12-13 04:24:19 +00:00
where
filterconfig f = filter f $ M.toList $ config repo
filterkeys f = filterconfig (\(k,_) -> f k)
remotepairs = filterkeys isRemoteKey
construct (k,v) = remoteNamedFromKey k (fromRemoteLocation (fromConfigValue v) repo)
2011-12-14 19:30:14 +00:00
{- Sets the name of a remote when constructing the Repo to represent it. -}
remoteNamed :: String -> IO Repo -> IO Repo
remoteNamed n constructor = do
r <- constructor
return $ r { remoteName = Just n }
{- Sets the name of a remote based on the git config key, such as
2012-12-13 04:45:27 +00:00
- "remote.foo.url". -}
remoteNamedFromKey :: ConfigKey -> IO Repo -> IO Repo
remoteNamedFromKey = remoteNamed . remoteKeyToRemoteName
{- Constructs a new Repo for one of a Repo's remotes using a given
- location (ie, an url). -}
fromRemoteLocation :: String -> Repo -> IO Repo
fromRemoteLocation s repo = gen $ parseRemoteLocation s repo
2012-12-13 04:24:19 +00:00
where
gen (RemotePath p) = fromRemotePath p repo
gen (RemoteUrl u) = fromUrl u
{- Constructs a Repo from the path specified in the git remotes of
- another Repo. -}
fromRemotePath :: FilePath -> Repo -> IO Repo
fromRemotePath dir repo = do
dir' <- expandTilde dir
fromPath $ fromRawFilePath (repoPath repo) </> dir'
{- Git remotes can have a directory that is specified relative
- to the user's home directory, or that contains tilde expansions.
- This converts such a directory to an absolute path.
- Note that it has to run on the system where the remote is.
-}
repoAbsPath :: FilePath -> IO FilePath
repoAbsPath d = do
d' <- expandTilde d
h <- myHomeDir
return $ h </> d'
expandTilde :: FilePath -> IO FilePath
#ifdef mingw32_HOST_OS
expandTilde = return
#else
expandTilde = expandt True
2012-12-13 04:24:19 +00:00
where
expandt _ [] = return ""
expandt _ ('/':cs) = do
v <- expandt True cs
return ('/':v)
expandt True ('~':'/':cs) = do
h <- myHomeDir
return $ h </> cs
expandt True ('~':cs) = do
let (name, rest) = findname "" cs
u <- getUserEntryForName name
return $ homeDirectory u </> rest
expandt _ (c:cs) = do
v <- expandt False cs
return (c:v)
findname n [] = (n, "")
findname n (c:cs)
| c == '/' = (n, cs)
| otherwise = findname (n++[c]) cs
#endif
{- Checks if a git repository exists in a directory. Does not find
- git repositories in parent directories. -}
checkForRepo :: FilePath -> IO (Maybe RepoLocation)
checkForRepo dir =
check isRepo $
check (gitDirFile dir) $
check isBareRepo $
return Nothing
2012-12-13 04:24:19 +00:00
where
check test cont = maybe cont (return . Just) =<< test
checkdir c = ifM c
( return $ Just $ LocalUnknown $ toRawFilePath dir
2012-12-13 04:24:19 +00:00
, return Nothing
)
isRepo = checkdir $
gitSignature (".git" </> "config")
<||>
-- A git-worktree lacks .git/config, but has .git/commondir.
-- (Normally the .git is a file, not a symlink, but it can
-- be converted to a symlink and git will still work;
-- this handles that case.)
gitSignature (".git" </> "gitdir")
2012-12-13 04:24:19 +00:00
isBareRepo = checkdir $ gitSignature "config"
<&&> doesDirectoryExist (dir </> "objects")
gitSignature file = doesFileExist $ dir </> file
-- git-submodule, git-worktree, and --separate-git-dir
-- make .git be a file pointing to the real git directory.
-- Detect that, and return a RepoLocation with gitdir pointing
-- to the real git directory.
gitDirFile :: FilePath -> IO (Maybe RepoLocation)
gitDirFile dir = do
c <- firstLine <$>
catchDefaultIO "" (readFile $ dir </> ".git")
return $ if gitdirprefix `isPrefixOf` c
then Just $ Local
{ gitdir = toRawFilePath $ absPathFrom dir $
drop (length gitdirprefix) c
, worktree = Just (toRawFilePath dir)
}
else Nothing
where
gitdirprefix = "gitdir: "
2015-02-12 19:33:05 +00:00
newFrom :: RepoLocation -> Repo
newFrom l = Repo
2012-01-14 01:06:00 +00:00
{ location = l
, config = M.empty
, fullconfig = M.empty
, remoteName = Nothing
2012-08-25 00:50:39 +00:00
, gitEnv = Nothing
, gitEnvOverridesGitDir = False
, gitGlobalOpts = []
}