2011-12-13 19:05:07 +00:00
|
|
|
{- Construction of Git Repo objects
|
|
|
|
-
|
Clean up handling of git directory and git worktree.
Baked into the code was an assumption that a repository's git directory
could be determined by adding ".git" to its work tree (or nothing for bare
repos). That fails when core.worktree, or GIT_DIR and GIT_WORK_TREE are
used to separate the two.
This was attacked at the type level, by storing the gitdir and worktree
separately, so Nothing for the worktree means a bare repo.
A complication arose because we don't learn where a repository is bare
until its configuration is read. So another Location type handles
repositories that have not had their config read yet. I am not entirely
happy with this being a Location type, rather than representing them
entirely separate from the Git type. The new code is not worse than the
old, but better types could enforce more safety.
Added support for core.worktree. Overriding it with -c isn't supported
because it's not really clear what to do if a git repo's config is read, is
not bare, and is then overridden to bare. What is the right git directory
in this case? I will worry about this if/when someone has a use case for
overriding core.worktree with -c. (See Git.Config.updateLocation)
Also removed and renamed some functions like gitDir and workTree that
misused git's terminology.
One minor regression is known: git annex add in a bare repository does not
print a nice error message, but runs git ls-files in a way that fails
earlier with a less nice error message. This is because before --work-tree
was always passed to git commands, even in a bare repo, while now it's not.
2012-05-18 20:38:26 +00:00
|
|
|
- Copyright 2010-2012 Joey Hess <joey@kitenet.net>
|
2011-12-13 19:05:07 +00:00
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
module Git.Construct (
|
|
|
|
fromCwd,
|
|
|
|
fromAbsPath,
|
2012-01-26 00:42:01 +00:00
|
|
|
fromPath,
|
2011-12-13 19:05:07 +00:00
|
|
|
fromUrl,
|
|
|
|
fromUnknown,
|
|
|
|
localToUrl,
|
2011-12-14 19:30:14 +00:00
|
|
|
remoteNamed,
|
|
|
|
remoteNamedFromKey,
|
2011-12-13 19:05:07 +00:00
|
|
|
fromRemotes,
|
|
|
|
fromRemoteLocation,
|
|
|
|
repoAbsPath,
|
Additional GIT_DIR support bugfixes. May actually work now.
Two fixes. First, and most importantly, relax the isLinkToAnnex check
to only look for /annex/objects/, not [^|/].git/annex/objects. If
GIT_DIR is used with a detached work tree, the git directory is
not necessarily named .git.
There are important caveats with doing that at all, since git-annex will
make symlinks that point at GIT_DIR, which means that the relative path
between GIT_DIR and GIT_WORK_TREE needs to remain stable across all clones
of the repository.
----
The other fix is just fixing crazy and wrong code that, when GIT_DIR is
set, expects to still find a git repository in the path below the work
tree, and uses some of its configuration, and some of GIT_DIR. What was I
thinking, and why can't I seem to get this code right?
2013-02-23 16:32:09 +00:00
|
|
|
newFrom,
|
2013-03-12 12:09:31 +00:00
|
|
|
checkForRepo,
|
2011-12-13 19:05:07 +00:00
|
|
|
) where
|
|
|
|
|
|
|
|
import System.Posix.User
|
|
|
|
import qualified Data.Map as M hiding (map, split)
|
|
|
|
import Network.URI
|
|
|
|
|
|
|
|
import Common
|
|
|
|
import Git.Types
|
|
|
|
import Git
|
2011-12-14 19:30:14 +00:00
|
|
|
import qualified Git.Url as Url
|
2012-10-25 22:17:32 +00:00
|
|
|
import Utility.UserInfo
|
2011-12-13 19:05:07 +00:00
|
|
|
|
2012-09-16 02:40:04 +00:00
|
|
|
{- Finds the git repository used for the cwd, which may be in a parent
|
2012-01-13 16:52:09 +00:00
|
|
|
- directory. -}
|
Additional GIT_DIR support bugfixes. May actually work now.
Two fixes. First, and most importantly, relax the isLinkToAnnex check
to only look for /annex/objects/, not [^|/].git/annex/objects. If
GIT_DIR is used with a detached work tree, the git directory is
not necessarily named .git.
There are important caveats with doing that at all, since git-annex will
make symlinks that point at GIT_DIR, which means that the relative path
between GIT_DIR and GIT_WORK_TREE needs to remain stable across all clones
of the repository.
----
The other fix is just fixing crazy and wrong code that, when GIT_DIR is
set, expects to still find a git repository in the path below the work
tree, and uses some of its configuration, and some of GIT_DIR. What was I
thinking, and why can't I seem to get this code right?
2013-02-23 16:32:09 +00:00
|
|
|
fromCwd :: IO (Maybe Repo)
|
|
|
|
fromCwd = getCurrentDirectory >>= seekUp
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
Additional GIT_DIR support bugfixes. May actually work now.
Two fixes. First, and most importantly, relax the isLinkToAnnex check
to only look for /annex/objects/, not [^|/].git/annex/objects. If
GIT_DIR is used with a detached work tree, the git directory is
not necessarily named .git.
There are important caveats with doing that at all, since git-annex will
make symlinks that point at GIT_DIR, which means that the relative path
between GIT_DIR and GIT_WORK_TREE needs to remain stable across all clones
of the repository.
----
The other fix is just fixing crazy and wrong code that, when GIT_DIR is
set, expects to still find a git repository in the path below the work
tree, and uses some of its configuration, and some of GIT_DIR. What was I
thinking, and why can't I seem to get this code right?
2013-02-23 16:32:09 +00:00
|
|
|
seekUp dir = do
|
|
|
|
r <- checkForRepo dir
|
2012-12-13 04:24:19 +00:00
|
|
|
case r of
|
|
|
|
Nothing -> case parentDir dir of
|
Additional GIT_DIR support bugfixes. May actually work now.
Two fixes. First, and most importantly, relax the isLinkToAnnex check
to only look for /annex/objects/, not [^|/].git/annex/objects. If
GIT_DIR is used with a detached work tree, the git directory is
not necessarily named .git.
There are important caveats with doing that at all, since git-annex will
make symlinks that point at GIT_DIR, which means that the relative path
between GIT_DIR and GIT_WORK_TREE needs to remain stable across all clones
of the repository.
----
The other fix is just fixing crazy and wrong code that, when GIT_DIR is
set, expects to still find a git repository in the path below the work
tree, and uses some of its configuration, and some of GIT_DIR. What was I
thinking, and why can't I seem to get this code right?
2013-02-23 16:32:09 +00:00
|
|
|
"" -> return Nothing
|
|
|
|
d -> seekUp d
|
|
|
|
Just loc -> Just <$> newFrom loc
|
2011-12-13 19:05:07 +00:00
|
|
|
|
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
|
|
|
|
|
2011-12-13 19:05:07 +00:00
|
|
|
{- Local Repo constructor, requires an absolute path to the repo be
|
|
|
|
- specified. -}
|
|
|
|
fromAbsPath :: FilePath -> IO Repo
|
|
|
|
fromAbsPath dir
|
2012-03-16 05:59:07 +00:00
|
|
|
| "/" `isPrefixOf` dir =
|
|
|
|
ifM (doesDirectoryExist dir') ( ret dir' , hunt )
|
|
|
|
| otherwise =
|
|
|
|
error $ "internal error, " ++ dir ++ " is not absolute"
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
ret = newFrom . LocalUnknown
|
|
|
|
{- Git always looks for "dir.git" in preference to
|
|
|
|
- to "dir", even if dir ends in a "/". -}
|
|
|
|
canondir = dropTrailingPathSeparator dir
|
|
|
|
dir' = canondir ++ ".git"
|
|
|
|
{- When dir == "foo/.git", git looks for "foo/.git/.git",
|
|
|
|
- and failing that, uses "foo" as the repository. -}
|
|
|
|
hunt
|
|
|
|
| "/.git" `isSuffixOf` canondir =
|
|
|
|
ifM (doesDirectoryExist $ dir </> ".git")
|
|
|
|
( ret dir
|
|
|
|
, ret $ takeDirectory canondir
|
|
|
|
)
|
|
|
|
| otherwise = ret dir
|
2011-12-13 19:05:07 +00:00
|
|
|
|
2012-01-05 18:32:20 +00:00
|
|
|
{- 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.
|
|
|
|
-}
|
2011-12-13 19:05:07 +00:00
|
|
|
fromUrl :: String -> IO Repo
|
|
|
|
fromUrl url
|
2012-01-05 18:32:20 +00:00
|
|
|
| not (isURI url) = fromUrlStrict $ escapeURIString isUnescapedInURI url
|
|
|
|
| otherwise = fromUrlStrict url
|
|
|
|
|
|
|
|
fromUrlStrict :: String -> IO Repo
|
|
|
|
fromUrlStrict url
|
2011-12-13 19:05:07 +00:00
|
|
|
| startswith "file://" url = fromAbsPath $ uriPath u
|
2012-01-14 01:06:00 +00:00
|
|
|
| otherwise = newFrom $ Url u
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
u = fromMaybe bad $ parseURI url
|
|
|
|
bad = error $ "bad url " ++ url
|
2011-12-13 19:05:07 +00:00
|
|
|
|
|
|
|
{- Creates a repo that has an unknown location. -}
|
2011-12-14 19:30:14 +00:00
|
|
|
fromUnknown :: IO Repo
|
2012-01-14 01:06:00 +00:00
|
|
|
fromUnknown = newFrom Unknown
|
2011-12-13 19:05:07 +00:00
|
|
|
|
|
|
|
{- 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 = r { location = Url $ fromJust $ parseURI absurl }
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
absurl = concat
|
|
|
|
[ Url.scheme reference
|
|
|
|
, "//"
|
|
|
|
, Url.authority reference
|
|
|
|
, repoPath r
|
|
|
|
]
|
2011-12-13 19:05:07 +00:00
|
|
|
|
|
|
|
{- 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 isremote
|
|
|
|
isremote k = startswith "remote." k && endswith ".url" k
|
|
|
|
construct (k,v) = remoteNamedFromKey k $ fromRemoteLocation 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". -}
|
2011-12-14 19:30:14 +00:00
|
|
|
remoteNamedFromKey :: String -> IO Repo -> IO Repo
|
|
|
|
remoteNamedFromKey k = remoteNamed basename
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
2013-04-23 00:24:53 +00:00
|
|
|
basename = intercalate "." $
|
|
|
|
reverse $ drop 1 $ reverse $ drop 1 $ split "." k
|
2011-12-13 19:05:07 +00:00
|
|
|
|
|
|
|
{- 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 $ calcloc s
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
gen v
|
|
|
|
| scpstyle v = fromUrl $ scptourl v
|
|
|
|
| urlstyle v = fromUrl v
|
|
|
|
| otherwise = fromRemotePath v repo
|
|
|
|
-- insteadof config can rewrite remote location
|
|
|
|
calcloc l
|
|
|
|
| null insteadofs = l
|
|
|
|
| otherwise = replacement ++ drop (length bestvalue) l
|
|
|
|
where
|
|
|
|
replacement = drop (length prefix) $
|
|
|
|
take (length bestkey - length suffix) bestkey
|
|
|
|
(bestkey, bestvalue) = maximumBy longestvalue insteadofs
|
|
|
|
longestvalue (_, a) (_, b) = compare b a
|
|
|
|
insteadofs = filterconfig $ \(k, v) ->
|
|
|
|
startswith prefix k &&
|
|
|
|
endswith suffix k &&
|
|
|
|
startswith v l
|
|
|
|
filterconfig f = filter f $
|
|
|
|
concatMap splitconfigs $ M.toList $ fullconfig repo
|
|
|
|
splitconfigs (k, vs) = map (\v -> (k, v)) vs
|
|
|
|
(prefix, suffix) = ("url." , ".insteadof")
|
|
|
|
urlstyle v = isURI v || ":" `isInfixOf` v && "//" `isInfixOf` v
|
|
|
|
-- git remotes can be written scp style -- [user@]host:dir
|
|
|
|
-- but foo::bar is a git-remote-helper location instead
|
|
|
|
scpstyle v = ":" `isInfixOf` v
|
|
|
|
&& not ("//" `isInfixOf` v)
|
|
|
|
&& not ("::" `isInfixOf` v)
|
|
|
|
scptourl v = "ssh://" ++ host ++ slash dir
|
|
|
|
where
|
|
|
|
(host, dir) = separate (== ':') v
|
|
|
|
slash d | d == "" = "/~/" ++ d
|
|
|
|
| "/" `isPrefixOf` d = d
|
|
|
|
| "~" `isPrefixOf` d = '/':d
|
|
|
|
| otherwise = "/~/" ++ d
|
2011-12-13 19:05:07 +00:00
|
|
|
|
|
|
|
{- 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
|
Clean up handling of git directory and git worktree.
Baked into the code was an assumption that a repository's git directory
could be determined by adding ".git" to its work tree (or nothing for bare
repos). That fails when core.worktree, or GIT_DIR and GIT_WORK_TREE are
used to separate the two.
This was attacked at the type level, by storing the gitdir and worktree
separately, so Nothing for the worktree means a bare repo.
A complication arose because we don't learn where a repository is bare
until its configuration is read. So another Location type handles
repositories that have not had their config read yet. I am not entirely
happy with this being a Location type, rather than representing them
entirely separate from the Git type. The new code is not worse than the
old, but better types could enforce more safety.
Added support for core.worktree. Overriding it with -c isn't supported
because it's not really clear what to do if a git repo's config is read, is
not bare, and is then overridden to bare. What is the right git directory
in this case? I will worry about this if/when someone has a use case for
overriding core.worktree with -c. (See Git.Config.updateLocation)
Also removed and renamed some functions like gitDir and workTree that
misused git's terminology.
One minor regression is known: git annex add in a bare repository does not
print a nice error message, but runs git ls-files in a way that fails
earlier with a less nice error message. This is because before --work-tree
was always passed to git commands, even in a bare repo, while now it's not.
2012-05-18 20:38:26 +00:00
|
|
|
fromAbsPath $ repoPath repo </> dir'
|
2011-12-13 19:05:07 +00:00
|
|
|
|
|
|
|
{- 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
|
|
|
|
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
|
2011-12-13 19:05:07 +00:00
|
|
|
|
2013-03-12 12:09:31 +00:00
|
|
|
{- Checks if a git repository exists in a directory. Does not find
|
|
|
|
- git repositories in parent directories. -}
|
2012-09-16 02:40:04 +00:00
|
|
|
checkForRepo :: FilePath -> IO (Maybe RepoLocation)
|
|
|
|
checkForRepo dir =
|
|
|
|
check isRepo $
|
|
|
|
check gitDirFile $
|
|
|
|
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 dir
|
|
|
|
, return Nothing
|
|
|
|
)
|
|
|
|
isRepo = checkdir $ gitSignature $ ".git" </> "config"
|
|
|
|
isBareRepo = checkdir $ gitSignature "config"
|
|
|
|
<&&> doesDirectoryExist (dir </> "objects")
|
|
|
|
gitDirFile = do
|
|
|
|
c <- firstLine <$>
|
|
|
|
catchDefaultIO "" (readFile $ dir </> ".git")
|
|
|
|
return $ if gitdirprefix `isPrefixOf` c
|
|
|
|
then Just $ Local
|
|
|
|
{ gitdir = absPathFrom dir $
|
|
|
|
drop (length gitdirprefix) c
|
|
|
|
, worktree = Just dir
|
|
|
|
}
|
|
|
|
else Nothing
|
|
|
|
where
|
|
|
|
gitdirprefix = "gitdir: "
|
|
|
|
gitSignature file = doesFileExist $ dir </> file
|
2011-12-13 19:05:07 +00:00
|
|
|
|
2012-01-14 01:06:00 +00:00
|
|
|
newFrom :: RepoLocation -> IO Repo
|
|
|
|
newFrom l = return Repo
|
|
|
|
{ location = l
|
|
|
|
, config = M.empty
|
|
|
|
, fullconfig = M.empty
|
|
|
|
, remotes = []
|
|
|
|
, remoteName = Nothing
|
2012-08-25 00:50:39 +00:00
|
|
|
, gitEnv = Nothing
|
2011-12-13 19:05:07 +00:00
|
|
|
}
|
2012-05-18 22:20:53 +00:00
|
|
|
|
|
|
|
|