git-annex/Remote/Git.hs

232 lines
6.9 KiB
Haskell
Raw Normal View History

{- Standard git remotes.
-
- Copyright 2011 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
2011-04-09 18:26:32 +00:00
module Remote.Git (remote) where
import Control.Exception.Extensible
import Control.Monad.State (liftIO)
2011-03-29 17:49:54 +00:00
import qualified Data.Map as M
import System.Cmd.Utils
import System.Posix.Files
import System.IO
import Types
import Types.Remote
import qualified Git
import qualified Annex
import Locations
import UUID
import Utility
import qualified Content
import Messages
2011-07-06 00:24:10 +00:00
import Utility.CopyFile
import Utility.RsyncFile
import Remote.Helper.Ssh
import qualified Remote.Helper.Url as Url
import Config
2011-03-29 03:51:07 +00:00
remote :: RemoteType Annex
2011-03-29 18:55:59 +00:00
remote = RemoteType {
typename = "git",
2011-03-29 21:57:20 +00:00
enumerate = list,
generate = gen,
2011-03-29 18:55:59 +00:00
setup = error "not supported"
}
2011-03-29 03:51:07 +00:00
2011-03-29 21:57:20 +00:00
list :: Annex [Git.Repo]
list = do
g <- Annex.gitRepo
2011-03-29 22:28:37 +00:00
return $ Git.remotes g
2011-04-15 19:09:36 +00:00
gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex (Remote Annex)
2011-03-30 19:15:46 +00:00
gen r u _ = do
{- It's assumed to be cheap to read the config of non-URL remotes,
- so this is done each time git-annex is run. Conversely,
- the config of an URL remote is only read when there is no
- cached UUID value. -}
2011-03-30 18:00:54 +00:00
let cheap = not $ Git.repoIsUrl r
r' <- case (cheap, u) of
(True, _) -> tryGitConfigRead r
(False, "") -> tryGitConfigRead r
_ -> return r
2011-04-01 16:19:26 +00:00
u' <- getUUID r'
2011-04-09 00:55:22 +00:00
let defcst = if cheap then cheapRemoteCost else expensiveRemoteCost
2011-03-30 19:15:46 +00:00
cst <- remoteCost r' defcst
2011-07-15 16:47:14 +00:00
return Remote {
2011-04-01 16:19:26 +00:00
uuid = u',
2011-03-30 18:00:54 +00:00
cost = cst,
name = Git.repoDescribe r',
storeKey = copyToRemote r',
retrieveKeyFile = copyFromRemote r',
removeKey = dropKey r',
hasKey = inAnnex r',
2011-04-09 00:55:22 +00:00
hasKeyCheap = cheap,
2011-03-29 18:55:59 +00:00
config = Nothing
}
{- Tries to read the config for a specified remote, updates state, and
- returns the updated repo. -}
tryGitConfigRead :: Git.Repo -> Annex Git.Repo
tryGitConfigRead r
2011-03-29 17:49:54 +00:00
| not $ M.null $ Git.configMap r = return r -- already read
| Git.repoIsSsh r = store $ onRemote r (pipedconfig, r) "configlist" []
| Git.repoIsHttp r = store $ safely $ geturlconfig
| Git.repoIsUrl r = return r
| otherwise = store $ safely $ Git.configRead r
where
-- Reading config can fail due to IO error or
-- for other reasons; catch all possible exceptions.
safely a = do
2011-07-15 16:47:14 +00:00
result <- liftIO (try a :: IO (Either SomeException Git.Repo))
case result of
Left _ -> return r
Right r' -> return r'
pipedconfig cmd params = safely $
pOpen ReadFromPipe cmd (toCommand params) $
Git.hConfigRead r
geturlconfig = do
s <- Url.get (Git.repoLocation r ++ "/config")
withTempFile "git-annex.tmp" $ \tmpfile -> \h -> do
hPutStr h s
hClose h
pOpen ReadFromPipe "git" ["config", "--list", "--file", tmpfile] $
Git.hConfigRead r
store a = do
r' <- a
g <- Annex.gitRepo
let l = Git.remotes g
let g' = Git.remotesAdd g $ exchange l r'
Annex.changeState $ \s -> s { Annex.repo = g' }
return r'
exchange [] _ = []
exchange (old:ls) new =
if Git.repoRemoteName old == Git.repoRemoteName new
then new : exchange ls new
else old : exchange ls new
{- Checks if a given remote has the content for a key inAnnex.
- If the remote cannot be accessed, returns a Left error.
-}
inAnnex :: Git.Repo -> Key -> Annex (Either IOException Bool)
inAnnex r key
| Git.repoIsHttp r = safely checkhttp
| Git.repoIsUrl r = checkremote
| otherwise = safely checklocal
where
checklocal = do
-- run a local check inexpensively,
-- by making an Annex monad using the remote
a <- Annex.new r
Annex.eval a (Content.inAnnex key)
checkremote = do
showAction $ "checking " ++ Git.repoDescribe r
inannex <- onRemote r (boolSystem, False) "inannex"
[Param (show key)]
return $ Right inannex
checkhttp = Url.exists $ keyUrl r key
safely a = liftIO (try a ::IO (Either IOException Bool))
keyUrl :: Git.Repo -> Key -> String
keyUrl r key = Git.repoLocation r ++ "/" ++ annexLocation key
dropKey :: Git.Repo -> Key -> Annex Bool
2011-08-17 01:20:14 +00:00
dropKey r key
| Git.repoIsHttp r = error "dropping from http repo not supported"
| otherwise = onRemote r (boolSystem, False) "dropkey"
[ Params "--quiet --force"
, Param $ show key
]
{- Tries to copy a key's content from a remote's annex to a file. -}
copyFromRemote :: Git.Repo -> Key -> FilePath -> Annex Bool
copyFromRemote r key file
| not $ Git.repoIsUrl r = rsyncOrCopyFile r (gitAnnexLocation r key) file
| Git.repoIsSsh r = rsyncHelper =<< rsyncParamsRemote r True key file
| Git.repoIsHttp r = Url.download (keyUrl r key) file
| otherwise = error "copying from non-ssh, non-http repo not supported"
{- Tries to copy a key's content to a remote's annex. -}
copyToRemote :: Git.Repo -> Key -> Annex Bool
copyToRemote r key
| not $ Git.repoIsUrl r = do
g <- Annex.gitRepo
let keysrc = gitAnnexLocation g key
-- run copy from perspective of remote
liftIO $ do
a <- Annex.new r
Annex.eval a $ do
ok <- Content.getViaTmp key $
rsyncOrCopyFile r keysrc
Content.saveState
return ok
| Git.repoIsSsh r = do
g <- Annex.gitRepo
let keysrc = gitAnnexLocation g key
rsyncHelper =<< rsyncParamsRemote r False key keysrc
| otherwise = error "copying to non-ssh repo not supported"
2011-07-15 16:47:14 +00:00
rsyncHelper :: [CommandParam] -> Annex Bool
rsyncHelper p = do
showOutput -- make way for progress bar
res <- liftIO $ rsync p
if res
then return res
else do
showLongNote "rsync failed -- run git annex again to resume file transfer"
return res
{- Copys a file with rsync unless both locations are on the same
- filesystem. Then cp could be faster. -}
rsyncOrCopyFile :: Git.Repo -> FilePath -> FilePath -> Annex Bool
rsyncOrCopyFile r src dest = do
2011-06-14 01:46:28 +00:00
ss <- liftIO $ getFileStatus $ parentDir src
ds <- liftIO $ getFileStatus $ parentDir dest
if deviceID ss == deviceID ds
then liftIO $ copyFile src dest
else do
params <- rsyncParams r
rsyncHelper $ params ++ [Param src, Param dest]
{- Generates rsync parameters that ssh to the remote and asks it
- to either receive or send the key's content. -}
rsyncParamsRemote :: Git.Repo -> Bool -> Key -> FilePath -> Annex [CommandParam]
rsyncParamsRemote r sending key file = do
Just (shellcmd, shellparams) <- git_annex_shell r
(if sending then "sendkey" else "recvkey")
[ Param $ show key
-- Command is terminated with "--", because
-- rsync will tack on its own options afterwards,
-- and they need to be ignored.
, Param "--"
]
-- Convert the ssh command into rsync command line.
let eparam = rsyncShell (Param shellcmd:shellparams)
o <- rsyncParams r
if sending
then return $ o ++ eparam ++ [dummy, File file]
else return $ o ++ eparam ++ [File file, dummy]
where
-- the rsync shell parameter controls where rsync
-- goes, so the source/dest parameter can be a dummy value,
-- that just enables remote rsync mode.
dummy = Param ":"
rsyncParams :: Git.Repo -> Annex [CommandParam]
rsyncParams r = do
o <- getConfig r "rsync-options" ""
return $ options ++ map Param (words o)
where
-- --inplace to resume partial files
options = [Params "-p --progress --inplace"]