support git funky remote syntaxes

* Look for dir.git directories the same as git does.
* Support remote urls specified as relative paths.
* Support non-ssh remote paths that contain tilde expansions.
This commit is contained in:
Joey Hess 2011-03-03 21:02:29 -04:00
parent b27b0d5cd4
commit 42259eee92
3 changed files with 77 additions and 44 deletions

View file

@ -11,7 +11,7 @@
module GitRepo ( module GitRepo (
Repo, Repo,
repoFromCwd, repoFromCwd,
repoFromPath, repoFromAbsPath,
repoFromUrl, repoFromUrl,
localToUrl, localToUrl,
repoIsUrl, repoIsUrl,
@ -49,7 +49,7 @@ module GitRepo (
encodeGitFile, encodeGitFile,
typeChangedFiles, typeChangedFiles,
typeChangedStagedFiles, typeChangedStagedFiles,
absDir, repoAbsPath,
reap, reap,
prop_idempotent_deencode prop_idempotent_deencode
@ -57,6 +57,7 @@ module GitRepo (
import Control.Monad (unless) import Control.Monad (unless)
import System.Directory import System.Directory
import System.FilePath
import System.Posix.Directory import System.Posix.Directory
import System.Posix.User import System.Posix.User
import System.Posix.Process import System.Posix.Process
@ -98,15 +99,23 @@ newFrom l =
remoteName = Nothing remoteName = Nothing
} }
{- Local Repo constructor. -} {- Local Repo constructor, requires an absolute path to the repo be
repoFromPath :: FilePath -> Repo - specified. -}
repoFromPath dir = newFrom $ Dir dir repoFromAbsPath :: FilePath -> IO Repo
repoFromAbsPath dir
| "/" `isPrefixOf` dir = do
-- Git always looks for "dir.git" in preference to
-- to "dir", even if dir ends in a "/".
let dir' = (dropTrailingPathSeparator dir) ++ ".git"
e <- doesDirectoryExist dir'
return $ newFrom $ Dir $ if e then dir' else dir
| otherwise = error $ "internal error, " ++ dir ++ " is not absolute"
{- Remote Repo constructor. Throws exception on invalid url. -} {- Remote Repo constructor. Throws exception on invalid url. -}
repoFromUrl :: String -> Repo repoFromUrl :: String -> IO Repo
repoFromUrl url repoFromUrl url
| startswith "file://" url = repoFromPath $ uriPath u | startswith "file://" url = repoFromAbsPath $ uriPath u
| otherwise = newFrom $ Url u | otherwise = return $ newFrom $ Url u
where where
u = case (parseURI url) of u = case (parseURI url) of
Just v -> v Just v -> v
@ -356,31 +365,35 @@ configRead r = assertLocal r $ error "internal"
hConfigRead :: Repo -> Handle -> IO Repo hConfigRead :: Repo -> Handle -> IO Repo
hConfigRead repo h = do hConfigRead repo h = do
val <- hGetContentsStrict h val <- hGetContentsStrict h
return $ configStore repo val configStore repo val
{- Parses a git config and returns a version of the repo using it. -} {- Parses a git config and returns a version of the repo using it. -}
configStore :: Repo -> String -> Repo configStore :: Repo -> String -> IO Repo
configStore repo s = r { remotes = configRemotes r } configStore repo s = do
where r = repo { config = configParse s } rs <- configRemotes r
return $ r { remotes = rs }
where
r = repo { config = configParse s }
{- Checks if a string from git config is a true value. -} {- Checks if a string from git config is a true value. -}
configTrue :: String -> Bool configTrue :: String -> Bool
configTrue s = map toLower s == "true" configTrue s = map toLower s == "true"
{- Calculates a list of a repo's configured remotes, by parsing its config. -} {- Calculates a list of a repo's configured remotes, by parsing its config. -}
configRemotes :: Repo -> [Repo] configRemotes :: Repo -> IO [Repo]
configRemotes repo = map construct remotepairs configRemotes repo = mapM construct remotepairs
where where
remotepairs = Map.toList $ filterremotes $ config repo remotepairs = Map.toList $ filterremotes $ config repo
filterremotes = Map.filterWithKey (\k _ -> isremote k) filterremotes = Map.filterWithKey (\k _ -> isremote k)
isremote k = startswith "remote." k && endswith ".url" k isremote k = startswith "remote." k && endswith ".url" k
remotename k = split "." k !! 1 remotename k = split "." k !! 1
construct (k,v) = (gen v) { remoteName = Just $ remotename k } construct (k,v) = do
r <- gen v
return $ r { remoteName = Just $ remotename k }
gen v | scpstyle v = repoFromUrl $ scptourl v gen v | scpstyle v = repoFromUrl $ scptourl v
| isURI v = repoFromUrl v | isURI v = repoFromUrl v
| otherwise = repoFromPath v | otherwise = repoFromRemotePath v repo
-- git remotes can be written scp style -- [user@]host:dir -- git remotes can be written scp style -- [user@]host:dir
-- where dir is relative to the user's home directory.
scpstyle v = ":" `isInfixOf` v && (not $ "//" `isInfixOf` v) scpstyle v = ":" `isInfixOf` v && (not $ "//" `isInfixOf` v)
scptourl v = "ssh://" ++ host ++ slash dir scptourl v = "ssh://" ++ host ++ slash dir
where where
@ -389,6 +402,7 @@ configRemotes repo = map construct remotepairs
dir = join ":" $ drop 1 bits dir = join ":" $ drop 1 bits
slash d | d == "" = "/~/" ++ dir slash d | d == "" = "/~/" ++ dir
| d !! 0 == '/' = dir | d !! 0 == '/' = dir
| d !! 0 == '~' = '/':dir
| otherwise = "/~/" ++ dir | otherwise = "/~/" ++ dir
{- Parses git config --list output into a config map. -} {- Parses git config --list output into a config map. -}
@ -503,37 +517,51 @@ encodeGitFile s = foldl (++) "\"" (map echar s) ++ "\""
e_utf c = concat $ map showoctal $ e_utf c = concat $ map showoctal $
(encode [c] :: [Word8]) (encode [c] :: [Word8])
{- for quickcheck -} {- for quickcheck -}
prop_idempotent_deencode :: String -> Bool prop_idempotent_deencode :: String -> Bool
prop_idempotent_deencode s = s == decodeGitFile (encodeGitFile s) prop_idempotent_deencode s = s == decodeGitFile (encodeGitFile s)
{- Git ssh remotes can have a directory that is specified relative {- Constructs a Repo from the path specified in the git remotes of
- to a home directory. This converts such a directory to an absolute path. - another Repo. -}
- Note that it has to run on the remote system. repoFromRemotePath :: FilePath -> Repo -> IO Repo
repoFromRemotePath dir repo = do
dir' <- expandTilde dir
repoFromAbsPath $ workTree 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.
-} -}
absDir :: String -> IO String repoAbsPath :: FilePath -> IO FilePath
absDir d repoAbsPath d = do
| "/" `isPrefixOf` d = expandt d d' <- expandTilde d
| otherwise = do h <- myHomeDir
h <- myhomedir hPutStrLn stderr $ "repoAbsPath " ++ d
return $ h ++ d return $ h </> d'
myHomeDir :: IO FilePath
myHomeDir = do
uid <- getEffectiveUserID
u <- getUserEntryForID uid
return $ homeDirectory u
expandTilde :: FilePath -> IO FilePath
expandTilde = expandt True
where where
homedir u = (homeDirectory u) ++ "/" expandt _ [] = return ""
myhomedir = do expandt _ ('/':cs) = do
uid <- getEffectiveUserID v <- expandt True cs
u <- getUserEntryForID uid return ('/':v)
return $ homedir u expandt True ('~':'/':cs) = do
expandt [] = return "" h <- myHomeDir
expandt ('/':'~':'/':cs) = do return $ h </> cs
h <- myhomedir expandt True ('~':cs) = do
return $ h ++ cs
expandt ('/':'~':cs) = do
let (name, rest) = findname "" cs let (name, rest) = findname "" cs
u <- getUserEntryForName name u <- getUserEntryForName name
return $ homedir u ++ rest return $ homeDirectory u </> rest
expandt (c:cs) = do expandt _ (c:cs) = do
v <- expandt cs v <- expandt False cs
return (c:v) return (c:v)
findname n [] = (n, "") findname n [] = (n, "")
findname n (c:cs) findname n (c:cs)
@ -546,10 +574,12 @@ repoFromCwd = do
cwd <- getCurrentDirectory cwd <- getCurrentDirectory
top <- seekUp cwd isRepoTop top <- seekUp cwd isRepoTop
case top of case top of
(Just dir) -> return $ repoFromPath dir -- repoFromAbsPath is not used to avoid looking for
-- "dir.git" directories.
(Just dir) -> return $ newFrom $ Dir dir
Nothing -> error "Not in a git repository." Nothing -> error "Not in a git repository."
seekUp :: String -> (String -> IO Bool) -> IO (Maybe String) seekUp :: FilePath -> (FilePath -> IO Bool) -> IO (Maybe FilePath)
seekUp dir want = do seekUp dir want = do
ok <- want dir ok <- want dir
if ok if ok

3
debian/changelog vendored
View file

@ -16,6 +16,9 @@ git-annex (0.22) UNRELEASED; urgency=low
use caution! use caution!
* describe: New subcommand that can set or change the description of * describe: New subcommand that can set or change the description of
a repository. a repository.
* Look for dir.git directories the same as git does.
* Support remote urls specified as relative paths.
* Support non-ssh remote paths that contain tilde expansions.
-- Joey Hess <joeyh@debian.org> Sun, 13 Feb 2011 00:48:02 -0400 -- Joey Hess <joeyh@debian.org> Sun, 13 Feb 2011 00:48:02 -0400

View file

@ -60,8 +60,8 @@ builtins = map cmdname cmds
builtin :: String -> String -> [String] -> IO () builtin :: String -> String -> [String] -> IO ()
builtin cmd dir params = do builtin cmd dir params = do
dir' <- Git.absDir dir dir' <- Git.repoAbsPath dir
let gitrepo = Git.repoFromPath dir' gitrepo <- Git.repoFromAbsPath dir'
dispatch gitrepo (cmd:(filterparams params)) cmds commonOptions header dispatch gitrepo (cmd:(filterparams params)) cmds commonOptions header
external :: [String] -> IO () external :: [String] -> IO ()