summaryrefslogtreecommitdiff
path: root/Remote/Git.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Remote/Git.hs')
-rw-r--r--Remote/Git.hs244
1 files changed, 113 insertions, 131 deletions
diff --git a/Remote/Git.hs b/Remote/Git.hs
index e269b9ad8..d4e5987dc 100644
--- a/Remote/Git.hs
+++ b/Remote/Git.hs
@@ -13,12 +13,7 @@ module Remote.Git (
repoAvail,
) where
-import qualified Data.Map as M
-import Control.Exception.Extensible
-
import Common.Annex
-import Utility.Rsync
-import Remote.Helper.Ssh
import Annex.Ssh
import Types.Remote
import Types.GitConfig
@@ -26,6 +21,7 @@ import qualified Git
import qualified Git.Config
import qualified Git.Construct
import qualified Git.Command
+import qualified Git.GCrypt
import qualified Annex
import Logs.Presence
import Logs.Transfer
@@ -34,7 +30,7 @@ import Annex.Exception
import qualified Annex.Content
import qualified Annex.BranchState
import qualified Annex.Branch
-import qualified Utility.Url as Url
+import qualified Annex.Url as Url
import Utility.Tmp
import Config
import Config.Cost
@@ -46,10 +42,19 @@ import Utility.Metered
#ifndef mingw32_HOST_OS
import Utility.CopyFile
#endif
+import Utility.Env
+import Utility.Batch
+import Remote.Helper.Git
+import Remote.Helper.Messages
+import qualified Remote.Helper.Ssh as Ssh
+import qualified Remote.GCrypt
+import Config.Files
import Control.Concurrent
import Control.Concurrent.MSampleVar
import System.Process (std_in, std_err)
+import qualified Data.Map as M
+import Control.Exception.Extensible
remote :: RemoteType
remote = RemoteType {
@@ -90,14 +95,13 @@ configRead r = do
(False, _, NoUUID) -> tryGitConfigRead r
_ -> return r
-repoCheap :: Git.Repo -> Bool
-repoCheap = not . Git.repoIsUrl
-
-gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex Remote
-gen r u _ gc = go <$> remoteCost gc defcst
+gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex (Maybe Remote)
+gen r u c gc
+ | Git.GCrypt.isEncrypted r = Remote.GCrypt.gen r u c gc
+ | otherwise = go <$> remoteCost gc defcst
where
defcst = if repoCheap r then cheapRemoteCost else expensiveRemoteCost
- go cst = new
+ go cst = Just new
where
new = Remote
{ uuid = u
@@ -110,15 +114,19 @@ gen r u _ gc = go <$> remoteCost gc defcst
, hasKey = inAnnex r
, hasKeyCheap = repoCheap r
, whereisKey = Nothing
- , config = M.empty
- , localpath = if Git.repoIsLocal r || Git.repoIsLocalUnknown r
- then Just $ Git.repoPath r
- else Nothing
+ , remoteFsck = if Git.repoIsUrl r
+ then Nothing
+ else Just $ fsckOnRemote r
+ , repairRepo = if Git.repoIsUrl r
+ then Nothing
+ else Just $ repairRemote r
+ , config = c
+ , localpath = localpathCalc r
, repo = r
, gitconfig = gc
{ remoteGitConfig = Just $ extractGitConfig r }
, readonly = Git.repoIsHttp r
- , globallyAvailable = not $ Git.repoIsLocal r || Git.repoIsLocalUnknown r
+ , globallyAvailable = globallyAvailableCalc r
, remotetype = remote
}
@@ -126,24 +134,25 @@ gen r u _ gc = go <$> remoteCost gc defcst
repoAvail :: Git.Repo -> Annex Bool
repoAvail r
| Git.repoIsHttp r = return True
+ | Git.GCrypt.isEncrypted r = do
+ g <- gitRepo
+ liftIO $ do
+ er <- Git.GCrypt.encryptedRemote g r
+ if Git.repoIsLocal er || Git.repoIsLocalUnknown er
+ then catchBoolIO $
+ void (Git.Config.read er) >> return True
+ else return True
| Git.repoIsUrl r = return True
| Git.repoIsLocalUnknown r = return False
| otherwise = liftIO $ catchBoolIO $ onLocal r $ return True
-{- Avoids performing an action on a local repository that's not usable.
- - Does not check that the repository is still available on disk. -}
-guardUsable :: Git.Repo -> a -> Annex a -> Annex a
-guardUsable r onerr a
- | Git.repoIsLocalUnknown r = return onerr
- | otherwise = a
-
{- Tries to read the config for a specified remote, updates state, and
- returns the updated repo. -}
tryGitConfigRead :: Git.Repo -> Annex Git.Repo
tryGitConfigRead r
| haveconfig r = return r -- already read
| Git.repoIsSsh r = store $ do
- v <- onRemote r (pipedconfig, Left undefined) "configlist" [] []
+ v <- Ssh.onRemote r (pipedconfig, Left undefined) "configlist" [] []
case v of
Right r'
| haveconfig r' -> return r'
@@ -152,6 +161,7 @@ tryGitConfigRead r
| Git.repoIsHttp r = do
headers <- getHttpHeaders
store $ geturlconfig headers
+ | Git.GCrypt.isEncrypted r = handlegcrypt =<< getConfigMaybe (remoteConfig r "uuid")
| Git.repoIsUrl r = return r
| otherwise = store $ safely $ onLocal r $ do
ensureInitialized
@@ -164,23 +174,22 @@ tryGitConfigRead r
safely a = either (const $ return r) return
=<< liftIO (try a :: IO (Either SomeException Git.Repo))
- pipedconfig cmd params = try run :: IO (Either SomeException Git.Repo)
- where
- run = withHandle StdoutHandle createProcessSuccess p $ \h -> do
- fileEncoding h
- val <- hGetContentsStrict h
- r' <- Git.Config.store val r
- when (getUncachedUUID r' == NoUUID && not (null val)) $ do
- warningIO $ "Failed to get annex.uuid configuration of repository " ++ Git.repoDescribe r
- warningIO $ "Instead, got: " ++ show val
- warningIO $ "This is unexpected; please check the network transport!"
- return r'
- p = proc cmd $ toCommand params
+ pipedconfig cmd params = do
+ v <- Git.Config.fromPipe r cmd params
+ case v of
+ Right (r', val) -> do
+ when (getUncachedUUID r' == NoUUID && not (null val)) $ do
+ warningIO $ "Failed to get annex.uuid configuration of repository " ++ Git.repoDescribe r
+ warningIO $ "Instead, got: " ++ show val
+ warningIO $ "This is unexpected; please check the network transport!"
+ return $ Right r'
+ Left l -> return $ Left l
geturlconfig headers = do
+ ua <- Url.getUserAgent
v <- liftIO $ withTmpFile "git-annex.tmp" $ \tmpfile h -> do
hClose h
- ifM (Url.downloadQuiet (Git.repoLocation r ++ "/config") headers [] tmpfile)
+ ifM (Url.downloadQuiet (Git.repoLocation r ++ "/config") headers [] tmpfile ua)
( pipedconfig "git" [Param "config", Param "--null", Param "--list", Param "--file", File tmpfile]
, return $ Left undefined
)
@@ -210,7 +219,7 @@ tryGitConfigRead r
Nothing -> return r
Just n -> do
whenM (inRepo $ Git.Command.runBool [Param "fetch", Param "--quiet", Param n]) $
- set_ignore $ "does not have git-annex installed"
+ set_ignore "does not have git-annex installed"
return r
set_ignore msg = case Git.remoteName r of
@@ -219,6 +228,15 @@ tryGitConfigRead r
let k = "remote." ++ n ++ ".annex-ignore"
warning $ "Remote " ++ n ++ " " ++ msg ++ "; setting " ++ k
inRepo $ Git.Command.run [Param "config", Param k, Param "true"]
+
+ handlegcrypt Nothing = return r
+ handlegcrypt (Just _cacheduuid) = do
+ -- Generate UUID from the gcrypt-id
+ g <- gitRepo
+ case Git.GCrypt.remoteRepoId g (Git.remoteName r) of
+ Nothing -> return r
+ Just v -> store $ liftIO $ setUUID r $
+ genUUIDInNameSpace gCryptNameSpace v
{- Checks if a given remote has the content for a key inAnnex.
- If the remote cannot be accessed, or if it cannot determine
@@ -231,39 +249,19 @@ inAnnex r key
| otherwise = checklocal
where
checkhttp headers = do
- showchecking
- liftIO $ ifM (anyM (\u -> Url.check u headers (keySize key)) (keyUrls r key))
+ showChecking r
+ ifM (anyM (\u -> Url.withUserAgent $ Url.checkBoth u headers (keySize key)) (keyUrls r key))
( return $ Right True
, return $ Left "not found"
)
- checkremote = do
- showchecking
- onRemote r (check, unknown) "inannex" [Param (key2file key)] []
- where
- check c p = dispatch <$> safeSystem c p
- dispatch ExitSuccess = Right True
- dispatch (ExitFailure 1) = Right False
- dispatch _ = unknown
- checklocal = guardUsable r unknown $ dispatch <$> check
+ checkremote = Ssh.inAnnex r key
+ checklocal = guardUsable r (cantCheck r) $ dispatch <$> check
where
check = liftIO $ catchMsgIO $ onLocal r $
Annex.Content.inAnnexSafe key
dispatch (Left e) = Left e
dispatch (Right (Just b)) = Right b
- dispatch (Right Nothing) = unknown
- unknown = Left $ "unable to check " ++ Git.repoDescribe r
- showchecking = showAction $ "checking " ++ Git.repoDescribe r
-
-{- Runs an action on a local repository inexpensively, by making an annex
- - monad using that repository. -}
-onLocal :: Git.Repo -> Annex a -> IO a
-onLocal r a = do
- s <- Annex.new r
- Annex.eval s $ do
- -- No need to update the branch; its data is not used
- -- for anything onLocal is used to do.
- Annex.BranchState.disableUpdate
- a
+ dispatch (Right Nothing) = cantCheck r
keyUrls :: Git.Repo -> Key -> [String]
keyUrls r key = map tourl locs
@@ -286,12 +284,8 @@ dropKey r key
logStatus key InfoMissing
Annex.Content.saveState True
return True
- | Git.repoIsHttp (repo r) = error "dropping from http repo not supported"
- | otherwise = commitOnCleanup r $ onRemote (repo r) (boolSystem, False) "dropkey"
- [ Params "--quiet --force"
- , Param $ key2file key
- ]
- []
+ | Git.repoIsHttp (repo r) = error "dropping from http remote not supported"
+ | otherwise = commitOnCleanup r $ Ssh.dropKey (repo r) key
{- Tries to copy a key's content from a remote's annex to a file. -}
copyFromRemote :: Remote -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex Bool
@@ -299,7 +293,7 @@ copyFromRemote r key file dest _p = copyFromRemote' r key file dest
copyFromRemote' :: Remote -> Key -> AssociatedFile -> FilePath -> Annex Bool
copyFromRemote' r key file dest
| not $ Git.repoIsUrl (repo r) = guardUsable (repo r) False $ do
- let params = rsyncParams r
+ let params = Ssh.rsyncParams r
u <- getUUID
-- run copy from perspective of remote
liftIO $ onLocal (repo r) $ do
@@ -311,11 +305,12 @@ copyFromRemote' r key file dest
upload u key file noRetry
(rsyncOrCopyFile params object dest)
<&&> checksuccess
- | Git.repoIsSsh (repo r) = feedprogressback $ \feeder ->
- rsyncHelper (Just feeder)
- =<< rsyncParamsRemote r Download key dest file
+ | Git.repoIsSsh (repo r) = feedprogressback $ \feeder -> do
+ direct <- isDirect
+ Ssh.rsyncHelper (Just feeder)
+ =<< Ssh.rsyncParamsRemote direct r Download key dest file
| Git.repoIsHttp (repo r) = Annex.Content.downloadUrl (keyUrls (repo r) key) dest
- | otherwise = error "copying from non-ssh, non-http repo not supported"
+ | otherwise = error "copying from non-ssh, non-http remote not supported"
where
{- Feed local rsync's progress info back to the remote,
- by forking a feeder thread that runs
@@ -340,9 +335,9 @@ copyFromRemote' r key file dest
u <- getUUID
let fields = (Fields.remoteUUID, fromUUID u)
: maybe [] (\f -> [(Fields.associatedFile, f)]) file
- Just (cmd, params) <- git_annex_shell (repo r) "transferinfo"
+ Just (cmd, params) <- Ssh.git_annex_shell (repo r) "transferinfo"
[Param $ key2file key] fields
- v <- liftIO $ (newEmptySV :: IO (MSampleVar Integer))
+ v <- liftIO (newEmptySV :: IO (MSampleVar Integer))
tid <- liftIO $ forkIO $ void $ tryIO $ do
bytes <- readSV v
p <- createProcess $
@@ -353,7 +348,7 @@ copyFromRemote' r key file dest
hClose $ stderrHandle p
let h = stdinHandle p
let send b = do
- hPutStrLn h $ show b
+ hPrint h b
hFlush h
send bytes
forever $
@@ -385,8 +380,10 @@ copyToRemote r key file p
guardUsable (repo r) False $ commitOnCleanup r $
copylocal =<< Annex.Content.prepSendAnnex key
| Git.repoIsSsh (repo r) = commitOnCleanup r $
- Annex.Content.sendAnnex key noop $ \object ->
- rsyncHelper (Just p) =<< rsyncParamsRemote r Upload key object file
+ Annex.Content.sendAnnex key noop $ \object -> do
+ direct <- isDirect
+ Ssh.rsyncHelper (Just p)
+ =<< Ssh.rsyncParamsRemote direct r Upload key object file
| otherwise = error "copying to non-ssh repo not supported"
where
copylocal Nothing = return False
@@ -395,7 +392,7 @@ copyToRemote r key file p
-- the remote's Annex, but it needs access to the current
-- Annex monad's state.
checksuccessio <- Annex.withCurrentState checksuccess
- let params = rsyncParams r
+ let params = Ssh.rsyncParams r
u <- getUUID
-- run copy from perspective of remote
liftIO $ onLocal (repo r) $ ifM (Annex.Content.inAnnex key)
@@ -408,15 +405,37 @@ copyToRemote r key file p
(\d -> rsyncOrCopyFile params object d p)
)
-rsyncHelper :: Maybe MeterUpdate -> [CommandParam] -> Annex Bool
-rsyncHelper callback params = do
- showOutput -- make way for progress bar
- ifM (liftIO $ (maybe rsync rsyncProgress callback) params)
- ( return True
- , do
- showLongNote "rsync failed -- run git annex again to resume file transfer"
- return False
- )
+fsckOnRemote :: Git.Repo -> [CommandParam] -> Annex (IO Bool)
+fsckOnRemote r params
+ | Git.repoIsUrl r = do
+ s <- Ssh.git_annex_shell r "fsck" params []
+ return $ case s of
+ Nothing -> return False
+ Just (c, ps) -> batchCommand c ps
+ | otherwise = return $ do
+ program <- readProgramFile
+ env <- getEnvironment
+ r' <- Git.Config.read r
+ let env' =
+ [ ("GIT_WORK_TREE", Git.repoPath r')
+ , ("GIT_DIR", Git.localGitDir r')
+ ] ++ env
+ batchCommandEnv program (Param "fsck" : params) (Just env')
+
+{- The passed repair action is run in the Annex monad of the remote. -}
+repairRemote :: Git.Repo -> Annex Bool -> Annex (IO Bool)
+repairRemote r a = return $ Remote.Git.onLocal r a
+
+{- Runs an action on a local repository inexpensively, by making an annex
+ - monad using that repository. -}
+onLocal :: Git.Repo -> Annex a -> IO a
+onLocal r a = do
+ s <- Annex.new r
+ Annex.eval s $ do
+ -- No need to update the branch; its data is not used
+ -- for anything onLocal is used to do.
+ Annex.BranchState.disableUpdate
+ a
{- Copys a file with rsync unless both locations are on the same
- filesystem. Then cp could be faster. -}
@@ -428,7 +447,7 @@ rsyncOrCopyFile rsyncparams src dest p =
#else
ifM (sameDeviceIds src dest) (docopy, dorsync)
where
- sameDeviceIds a b = (==) <$> (getDeviceId a) <*> (getDeviceId b)
+ sameDeviceIds a b = (==) <$> getDeviceId a <*> getDeviceId b
getDeviceId f = deviceID <$> liftIO (getFileStatus $ parentDir f)
docopy = liftIO $ bracket
(forkIO $ watchfilesize zeroBytesProcessed)
@@ -446,46 +465,9 @@ rsyncOrCopyFile rsyncparams src dest p =
watchfilesize sz
_ -> watchfilesize oldsz
#endif
- dorsync = rsyncHelper (Just p) $
+ dorsync = Ssh.rsyncHelper (Just p) $
rsyncparams ++ [File src, File dest]
-{- Generates rsync parameters that ssh to the remote and asks it
- - to either receive or send the key's content. -}
-rsyncParamsRemote :: Remote -> Direction -> Key -> FilePath -> AssociatedFile -> Annex [CommandParam]
-rsyncParamsRemote r direction key file afile = do
- u <- getUUID
- direct <- isDirect
- let fields = (Fields.remoteUUID, fromUUID u)
- : (Fields.direct, if direct then "1" else "")
- : maybe [] (\f -> [(Fields.associatedFile, f)]) afile
- Just (shellcmd, shellparams) <- git_annex_shell (repo r)
- (if direction == Download then "sendkey" else "recvkey")
- [ Param $ key2file key ]
- fields
- -- Convert the ssh command into rsync command line.
- let eparam = rsyncShell (Param shellcmd:shellparams)
- let o = rsyncParams r
- if direction == Download
- then return $ o ++ rsyncopts eparam dummy (File file)
- else return $ o ++ rsyncopts eparam (File file) dummy
- where
- rsyncopts ps source dest
- | end ps == [dashdash] = ps ++ [source, dest]
- | otherwise = ps ++ [dashdash, source, dest]
- dashdash = Param "--"
- {- The rsync shell parameter controls where rsync
- - goes, so the source/dest parameter can be a dummy value,
- - that just enables remote rsync mode.
- - For maximum compatability with some patched rsyncs,
- - the dummy value needs to still contain a hostname,
- - even though this hostname will never be used. -}
- dummy = Param "dummy:"
-
--- --inplace to resume partial files
-rsyncParams :: Remote -> [CommandParam]
-rsyncParams r = [Params "--progress --inplace"] ++
- map Param (remoteAnnexRsyncOptions $ gitconfig r)
-
commitOnCleanup :: Remote -> Annex a -> Annex a
commitOnCleanup r a = go `after` a
where
@@ -496,12 +478,12 @@ commitOnCleanup r a = go `after` a
Annex.Branch.commit "update"
| otherwise = void $ do
Just (shellcmd, shellparams) <-
- git_annex_shell (repo r) "commit" [] []
+ Ssh.git_annex_shell (repo r) "commit" [] []
-- Throw away stderr, since the remote may not
-- have a new enough git-annex shell to
-- support committing.
- liftIO $ catchMaybeIO $ do
+ liftIO $ catchMaybeIO $
withQuietOutput createProcessSuccess $
proc shellcmd $
toCommand shellparams