2012-10-31 19:17:00 +00:00
|
|
|
{- git remote stuff
|
|
|
|
-
|
2015-01-21 16:50:09 +00:00
|
|
|
- Copyright 2012 Joey Hess <id@joeyh.name>
|
2012-10-31 19:17:00 +00:00
|
|
|
-
|
2019-03-13 19:48:14 +00:00
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
2012-10-31 19:17:00 +00:00
|
|
|
-}
|
|
|
|
|
2013-09-26 21:26:13 +00:00
|
|
|
{-# LANGUAGE CPP #-}
|
2019-11-27 20:54:11 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2013-09-26 21:26:13 +00:00
|
|
|
|
2012-10-31 19:17:00 +00:00
|
|
|
module Git.Remote where
|
|
|
|
|
|
|
|
import Common
|
2013-09-18 19:30:53 +00:00
|
|
|
import Git
|
2013-11-07 22:02:00 +00:00
|
|
|
import Git.Types
|
2013-09-18 19:30:53 +00:00
|
|
|
|
2012-10-31 19:17:00 +00:00
|
|
|
import Data.Char
|
2013-09-26 21:26:13 +00:00
|
|
|
import qualified Data.Map as M
|
2019-11-27 20:54:11 +00:00
|
|
|
import qualified Data.ByteString as S
|
|
|
|
import qualified Data.ByteString.Char8 as S8
|
2013-09-26 21:26:13 +00:00
|
|
|
import Network.URI
|
2013-10-03 00:26:00 +00:00
|
|
|
#ifdef mingw32_HOST_OS
|
|
|
|
import Git.FilePath
|
|
|
|
#endif
|
2012-10-31 19:17:00 +00:00
|
|
|
|
2018-01-09 19:36:56 +00:00
|
|
|
{- Is a git config key one that specifies the location of a remote? -}
|
2019-12-02 14:57:09 +00:00
|
|
|
isRemoteKey :: ConfigKey -> Bool
|
|
|
|
isRemoteKey (ConfigKey k) = "remote." `S.isPrefixOf` k && ".url" `S.isSuffixOf` k
|
2018-01-09 19:36:56 +00:00
|
|
|
|
|
|
|
{- Get a remote's name from the config key that specifies its location. -}
|
2019-12-02 14:57:09 +00:00
|
|
|
remoteKeyToRemoteName :: ConfigKey -> RemoteName
|
|
|
|
remoteKeyToRemoteName (ConfigKey k) = decodeBS' $
|
|
|
|
S.intercalate "." $ dropFromEnd 1 $ drop 1 $ S8.split '.' k
|
2018-01-09 19:36:56 +00:00
|
|
|
|
2012-10-31 19:17:00 +00:00
|
|
|
{- Construct a legal git remote name out of an arbitrary input string.
|
|
|
|
-
|
|
|
|
- There seems to be no formal definition of this in the git source,
|
|
|
|
- just some ad-hoc checks, and some other things that fail with certian
|
|
|
|
- types of names (like ones starting with '-').
|
|
|
|
-}
|
2013-09-18 19:30:53 +00:00
|
|
|
makeLegalName :: String -> RemoteName
|
2012-10-31 19:17:00 +00:00
|
|
|
makeLegalName s = case filter legal $ replace "/" "_" s of
|
|
|
|
-- it can't be empty
|
|
|
|
[] -> "unnamed"
|
|
|
|
-- it can't start with / or - or .
|
|
|
|
'.':s' -> makeLegalName s'
|
|
|
|
'/':s' -> makeLegalName s'
|
|
|
|
'-':s' -> makeLegalName s'
|
|
|
|
s' -> s'
|
|
|
|
where
|
|
|
|
{- Only alphanumerics, and a few common bits of punctuation common
|
|
|
|
- in hostnames. -}
|
|
|
|
legal '_' = True
|
|
|
|
legal '.' = True
|
|
|
|
legal c = isAlphaNum c
|
2013-09-18 19:30:53 +00:00
|
|
|
|
2013-09-26 21:26:13 +00:00
|
|
|
data RemoteLocation = RemoteUrl String | RemotePath FilePath
|
2019-11-18 20:09:09 +00:00
|
|
|
deriving (Eq)
|
2013-09-26 21:26:13 +00:00
|
|
|
|
|
|
|
remoteLocationIsUrl :: RemoteLocation -> Bool
|
|
|
|
remoteLocationIsUrl (RemoteUrl _) = True
|
|
|
|
remoteLocationIsUrl _ = False
|
|
|
|
|
2013-11-07 22:02:00 +00:00
|
|
|
remoteLocationIsSshUrl :: RemoteLocation -> Bool
|
|
|
|
remoteLocationIsSshUrl (RemoteUrl u) = "ssh://" `isPrefixOf` u
|
|
|
|
remoteLocationIsSshUrl _ = False
|
|
|
|
|
2013-09-26 21:26:13 +00:00
|
|
|
{- Determines if a given remote location is an url, or a local
|
|
|
|
- path. Takes the repository's insteadOf configuration into account. -}
|
|
|
|
parseRemoteLocation :: String -> Repo -> RemoteLocation
|
|
|
|
parseRemoteLocation s repo = ret $ calcloc s
|
|
|
|
where
|
2014-10-09 18:53:13 +00:00
|
|
|
ret v
|
2013-09-26 21:26:13 +00:00
|
|
|
#ifdef mingw32_HOST_OS
|
|
|
|
| dosstyle v = RemotePath (dospath v)
|
|
|
|
#endif
|
|
|
|
| scpstyle v = RemoteUrl (scptourl v)
|
|
|
|
| urlstyle v = RemoteUrl v
|
|
|
|
| otherwise = RemotePath v
|
|
|
|
-- insteadof config can rewrite remote location
|
|
|
|
calcloc l
|
|
|
|
| null insteadofs = l
|
2019-11-27 20:54:11 +00:00
|
|
|
| otherwise = replacement ++ drop (S.length bestvalue) l
|
2013-09-26 21:26:13 +00:00
|
|
|
where
|
2019-11-27 20:54:11 +00:00
|
|
|
replacement = decodeBS' $ S.drop (S.length prefix) $
|
|
|
|
S.take (S.length bestkey - S.length suffix) bestkey
|
2020-04-13 19:56:10 +00:00
|
|
|
(bestkey, bestvalue) =
|
|
|
|
case maximumBy longestvalue insteadofs of
|
|
|
|
(ConfigKey k, ConfigValue v) -> (k, v)
|
|
|
|
(ConfigKey k, NoConfigValue) -> (k, mempty)
|
2013-09-26 21:26:13 +00:00
|
|
|
longestvalue (_, a) (_, b) = compare b a
|
2020-04-13 19:56:10 +00:00
|
|
|
insteadofs = filterconfig $ \case
|
|
|
|
(ConfigKey k, ConfigValue v) ->
|
|
|
|
prefix `S.isPrefixOf` k &&
|
|
|
|
suffix `S.isSuffixOf` k &&
|
|
|
|
v `S.isPrefixOf` encodeBS l
|
|
|
|
(_, NoConfigValue) -> False
|
2013-09-26 21:26:13 +00:00
|
|
|
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
|
2014-09-10 18:17:02 +00:00
|
|
|
(host, dir)
|
|
|
|
-- handle ipv6 address inside []
|
|
|
|
| "[" `isPrefixOf` v = case break (== ']') v of
|
|
|
|
(h, ']':':':d) -> (h ++ "]", d)
|
|
|
|
(h, ']':d) -> (h ++ "]", d)
|
|
|
|
(h, d) -> (h, d)
|
|
|
|
| otherwise = separate (== ':') v
|
2013-09-26 21:26:13 +00:00
|
|
|
slash d | d == "" = "/~/" ++ d
|
|
|
|
| "/" `isPrefixOf` d = d
|
|
|
|
| "~" `isPrefixOf` d = '/':d
|
|
|
|
| otherwise = "/~/" ++ d
|
|
|
|
#ifdef mingw32_HOST_OS
|
|
|
|
-- git on Windows will write a path to .git/config with "drive:",
|
|
|
|
-- which is not to be confused with a "host:"
|
|
|
|
dosstyle = hasDrive
|
2020-01-01 17:46:03 +00:00
|
|
|
dospath = fromRawFilePath . fromInternalGitPath . toRawFilePath
|
2013-09-26 21:26:13 +00:00
|
|
|
#endif
|