2012-11-05 21:43:17 +00:00
|
|
|
{- git over XMPP
|
|
|
|
-
|
|
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
module Assistant.XMPP.Git where
|
|
|
|
|
|
|
|
import Assistant.Common
|
2012-11-08 18:02:37 +00:00
|
|
|
import Assistant.NetMessager
|
|
|
|
import Assistant.Types.NetMessager
|
2012-11-05 21:43:17 +00:00
|
|
|
import Assistant.XMPP
|
|
|
|
import Assistant.XMPP.Buddies
|
|
|
|
import Assistant.DaemonStatus
|
|
|
|
import Assistant.Alert
|
|
|
|
import Assistant.MakeRemote
|
|
|
|
import Assistant.Sync
|
2012-11-10 18:38:50 +00:00
|
|
|
import qualified Command.Sync
|
|
|
|
import qualified Annex.Branch
|
2012-11-05 21:43:17 +00:00
|
|
|
import Annex.UUID
|
|
|
|
import Config
|
2012-11-06 20:36:44 +00:00
|
|
|
import Git
|
2012-11-08 20:44:23 +00:00
|
|
|
import qualified Git.Branch
|
2012-11-06 04:52:35 +00:00
|
|
|
import Locations.UserConfig
|
2012-11-05 21:43:17 +00:00
|
|
|
import qualified Types.Remote as Remote
|
2012-11-09 16:51:54 +00:00
|
|
|
import Utility.FileMode
|
2012-11-11 17:38:28 +00:00
|
|
|
import Utility.ThreadScheduler
|
2012-11-05 21:43:17 +00:00
|
|
|
|
|
|
|
import Network.Protocol.XMPP
|
|
|
|
import qualified Data.Text as T
|
2012-11-06 04:52:35 +00:00
|
|
|
import System.Posix.Env
|
|
|
|
import System.Posix.Types
|
2012-11-06 20:36:44 +00:00
|
|
|
import System.Process (std_in, std_out, std_err)
|
2012-11-06 14:14:00 +00:00
|
|
|
import Control.Concurrent
|
|
|
|
import qualified Data.ByteString as B
|
2012-11-09 19:03:16 +00:00
|
|
|
import qualified Data.Map as M
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-05 21:43:17 +00:00
|
|
|
finishXMPPPairing :: JID -> UUID -> Assistant ()
|
|
|
|
finishXMPPPairing jid u = void $ alertWhile alert $
|
|
|
|
makeXMPPGitRemote buddy (baseJID jid) u
|
|
|
|
where
|
|
|
|
buddy = T.unpack $ buddyName jid
|
|
|
|
alert = pairRequestAcknowledgedAlert buddy Nothing
|
|
|
|
|
2012-11-10 03:43:08 +00:00
|
|
|
gitXMPPLocation :: JID -> String
|
|
|
|
gitXMPPLocation jid = "xmpp::" ++ T.unpack (formatJID $ baseJID jid)
|
|
|
|
|
2012-11-05 21:43:17 +00:00
|
|
|
makeXMPPGitRemote :: String -> JID -> UUID -> Assistant Bool
|
|
|
|
makeXMPPGitRemote buddyname jid u = do
|
2012-11-10 03:43:08 +00:00
|
|
|
remote <- liftAnnex $ addRemote $
|
|
|
|
makeGitRemote buddyname $ gitXMPPLocation jid
|
2012-11-09 18:34:06 +00:00
|
|
|
liftAnnex $ storeUUID (remoteConfig (Remote.repo remote) "uuid") u
|
2012-11-05 21:43:17 +00:00
|
|
|
syncNewRemote remote
|
|
|
|
return True
|
|
|
|
|
2012-11-10 18:38:50 +00:00
|
|
|
{- Pushes over XMPP, communicating with a specific client.
|
|
|
|
- Runs an arbitrary IO action to push, which should run git-push with
|
|
|
|
- an xmpp:: url.
|
2012-11-06 04:52:35 +00:00
|
|
|
-
|
2012-11-09 16:51:54 +00:00
|
|
|
- To handle xmpp:: urls, git push will run git-remote-xmpp, which is
|
|
|
|
- injected into its PATH, and in turn runs git-annex xmppgit. The
|
|
|
|
- dataflow them becomes:
|
2012-11-06 04:52:35 +00:00
|
|
|
-
|
|
|
|
- git push <--> git-annex xmppgit <--> xmppPush <-------> xmpp
|
|
|
|
- |
|
|
|
|
- git receive-pack <--> xmppReceivePack <---------------> xmpp
|
|
|
|
-
|
|
|
|
- The pipe between git-annex xmppgit and us is set up and communicated
|
2012-11-10 04:08:15 +00:00
|
|
|
- using two environment variables, relayIn and relayOut, that are set
|
|
|
|
- to the file descriptors to use. Another, relayControl, is used to
|
|
|
|
- propigate the exit status of git receive-pack.
|
2012-11-06 04:52:35 +00:00
|
|
|
-
|
|
|
|
- We listen at the other end of the pipe and relay to and from XMPP.
|
|
|
|
-}
|
2012-11-10 18:38:50 +00:00
|
|
|
xmppPush :: ClientID -> (Git.Repo -> IO Bool) -> Assistant Bool
|
|
|
|
xmppPush cid gitpush = runPush (SendPushRunning cid) handleDeferred $ do
|
2012-11-10 16:18:00 +00:00
|
|
|
sendNetMessage $ Pushing cid StartingPush
|
2012-11-06 14:46:58 +00:00
|
|
|
|
|
|
|
(Fd inf, writepush) <- liftIO createPipe
|
|
|
|
(readpush, Fd outf) <- liftIO createPipe
|
|
|
|
(Fd controlf, writecontrol) <- liftIO createPipe
|
|
|
|
|
2012-11-09 16:51:54 +00:00
|
|
|
tmp <- liftAnnex $ fromRepo gitAnnexTmpDir
|
|
|
|
let tmpdir = tmp </> "xmppgit"
|
|
|
|
installwrapper tmpdir
|
|
|
|
|
2012-11-06 14:46:58 +00:00
|
|
|
env <- liftIO getEnvironment
|
2012-11-09 16:51:54 +00:00
|
|
|
path <- liftIO getSearchPath
|
2012-11-09 19:03:16 +00:00
|
|
|
let myenv = M.fromList
|
2012-11-09 16:51:54 +00:00
|
|
|
[ ("PATH", join [searchPathSeparator] $ tmpdir:path)
|
2012-11-06 14:46:58 +00:00
|
|
|
, (relayIn, show inf)
|
|
|
|
, (relayOut, show outf)
|
|
|
|
, (relayControl, show controlf)
|
|
|
|
]
|
2012-11-09 19:03:16 +00:00
|
|
|
`M.union` M.fromList env
|
2012-11-06 14:46:58 +00:00
|
|
|
|
|
|
|
inh <- liftIO $ fdToHandle readpush
|
|
|
|
outh <- liftIO $ fdToHandle writepush
|
|
|
|
controlh <- liftIO $ fdToHandle writecontrol
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-06 14:46:58 +00:00
|
|
|
t1 <- forkIO <~> toxmpp inh
|
2012-11-08 20:44:23 +00:00
|
|
|
t2 <- forkIO <~> fromxmpp outh controlh
|
2012-11-06 14:46:58 +00:00
|
|
|
|
2012-11-09 19:03:16 +00:00
|
|
|
{- This can take a long time to run, so avoid running it in the
|
|
|
|
- Annex monad. Also, override environment. -}
|
|
|
|
g <- liftAnnex gitRepo
|
2012-11-10 18:38:50 +00:00
|
|
|
r <- liftIO $ gitpush $ g { gitEnv = Just $ M.toList myenv }
|
2012-11-09 19:03:16 +00:00
|
|
|
|
2012-11-10 03:27:07 +00:00
|
|
|
liftIO $ do
|
|
|
|
mapM_ killThread [t1, t2]
|
|
|
|
mapM_ hClose [inh, outh, controlh]
|
|
|
|
|
2012-11-10 03:43:08 +00:00
|
|
|
return r
|
2012-11-06 14:46:58 +00:00
|
|
|
where
|
|
|
|
toxmpp inh = forever $ do
|
2012-11-09 21:40:59 +00:00
|
|
|
b <- liftIO $ B.hGetSome inh chunkSize
|
2012-11-08 20:44:23 +00:00
|
|
|
if B.null b
|
|
|
|
then liftIO $ killThread =<< myThreadId
|
2012-11-10 16:18:00 +00:00
|
|
|
else sendNetMessage $ Pushing cid $ SendPackOutput b
|
2012-11-08 20:44:23 +00:00
|
|
|
fromxmpp outh controlh = forever $ do
|
2012-11-11 17:38:28 +00:00
|
|
|
m <- runTimeout xmppTimeout <~> waitNetPushMessage
|
2012-11-08 20:44:23 +00:00
|
|
|
case m of
|
2012-11-11 17:38:28 +00:00
|
|
|
(Right (Pushing _ (ReceivePackOutput b))) ->
|
2012-11-10 16:18:00 +00:00
|
|
|
liftIO $ writeChunk outh b
|
2012-11-11 17:38:28 +00:00
|
|
|
(Right (Pushing _ (ReceivePackDone exitcode))) ->
|
2012-11-10 16:18:00 +00:00
|
|
|
liftIO $ do
|
|
|
|
hPrint controlh exitcode
|
|
|
|
hFlush controlh
|
2012-11-11 17:38:28 +00:00
|
|
|
(Right _) -> noop
|
|
|
|
(Left _) -> do
|
|
|
|
debug ["timeout waiting for git receive-pack output via XMPP"]
|
|
|
|
-- Send a synthetic exit code to git-annex
|
|
|
|
-- xmppgit, which will exit and cause git push
|
|
|
|
-- to die.
|
|
|
|
liftIO $ do
|
|
|
|
hPrint controlh (ExitFailure 1)
|
|
|
|
hFlush controlh
|
2012-11-09 16:51:54 +00:00
|
|
|
installwrapper tmpdir = liftIO $ do
|
|
|
|
createDirectoryIfMissing True tmpdir
|
|
|
|
let wrapper = tmpdir </> "git-remote-xmpp"
|
|
|
|
program <- readProgramFile
|
|
|
|
writeFile wrapper $ unlines
|
|
|
|
[ "#!/bin/sh"
|
|
|
|
, "exec " ++ program ++ " xmppgit"
|
|
|
|
]
|
|
|
|
modifyFileMode wrapper $ addModes executeModes
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-10 04:08:15 +00:00
|
|
|
type EnvVar = String
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-10 04:08:15 +00:00
|
|
|
envVar :: String -> EnvVar
|
|
|
|
envVar s = "GIT_ANNEX_XMPPGIT_" ++ s
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-10 04:08:15 +00:00
|
|
|
relayIn :: EnvVar
|
|
|
|
relayIn = envVar "IN"
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-10 04:08:15 +00:00
|
|
|
relayOut :: EnvVar
|
|
|
|
relayOut = envVar "OUT"
|
|
|
|
|
|
|
|
relayControl :: EnvVar
|
|
|
|
relayControl = envVar "CONTROL"
|
|
|
|
|
|
|
|
relayHandle :: EnvVar -> IO Handle
|
2012-11-06 14:14:00 +00:00
|
|
|
relayHandle var = do
|
2012-11-06 04:52:35 +00:00
|
|
|
v <- getEnv var
|
|
|
|
case readish =<< v of
|
|
|
|
Nothing -> error $ var ++ " not set"
|
2012-11-06 14:14:00 +00:00
|
|
|
Just n -> fdToHandle $ Fd n
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-09 21:40:59 +00:00
|
|
|
{- Called by git-annex xmppgit.
|
|
|
|
-
|
|
|
|
- git-push is talking to us on stdin
|
|
|
|
- we're talking to git-push on stdout
|
|
|
|
- git-receive-pack is talking to us on relayIn (via XMPP)
|
|
|
|
- we're talking to git-receive-pack on relayOut (via XMPP)
|
2012-11-10 04:02:55 +00:00
|
|
|
- git-receive-pack's exit code will be passed to us on relayControl
|
2012-11-09 21:40:59 +00:00
|
|
|
-}
|
2012-11-06 04:52:35 +00:00
|
|
|
xmppGitRelay :: IO ()
|
|
|
|
xmppGitRelay = do
|
2012-11-10 03:12:54 +00:00
|
|
|
flip relay stdout =<< relayHandle relayIn
|
|
|
|
relay stdin =<< relayHandle relayOut
|
2012-11-10 04:02:55 +00:00
|
|
|
code <- hGetLine =<< relayHandle relayControl
|
|
|
|
exitWith $ fromMaybe (ExitFailure 1) $ readish code
|
2012-11-10 03:12:54 +00:00
|
|
|
where
|
|
|
|
{- Is it possible to set up pipes and not need to copy the data
|
|
|
|
- ourselves? See splice(2) -}
|
|
|
|
relay fromh toh = void $ forkIO $ forever $ do
|
|
|
|
b <- B.hGetSome fromh chunkSize
|
|
|
|
when (B.null b) $ do
|
|
|
|
hClose fromh
|
|
|
|
hClose toh
|
|
|
|
killThread =<< myThreadId
|
2012-11-10 04:24:26 +00:00
|
|
|
writeChunk toh b
|
2012-11-05 21:43:17 +00:00
|
|
|
|
2012-11-08 18:02:37 +00:00
|
|
|
{- Relays git receive-pack stdin and stdout via XMPP, as well as propigating
|
|
|
|
- its exit status to XMPP. -}
|
|
|
|
xmppReceivePack :: ClientID -> Assistant Bool
|
2012-11-08 20:44:23 +00:00
|
|
|
xmppReceivePack cid = runPush (ReceivePushRunning cid) handleDeferred $ do
|
2012-11-06 20:36:44 +00:00
|
|
|
repodir <- liftAnnex $ fromRepo repoPath
|
|
|
|
let p = (proc "git" ["receive-pack", repodir])
|
|
|
|
{ std_in = CreatePipe
|
|
|
|
, std_out = CreatePipe
|
|
|
|
, std_err = Inherit
|
|
|
|
}
|
2012-11-10 04:13:55 +00:00
|
|
|
(Just inh, Just outh, _, pid) <- liftIO $ createProcess p
|
|
|
|
readertid <- forkIO <~> relayfromxmpp inh
|
|
|
|
relaytoxmpp outh
|
|
|
|
code <- liftIO $ waitForProcess pid
|
2012-11-10 16:18:00 +00:00
|
|
|
void $ sendNetMessage $ Pushing cid $ ReceivePackDone code
|
2012-11-06 20:36:44 +00:00
|
|
|
liftIO $ do
|
2012-11-09 21:40:59 +00:00
|
|
|
killThread readertid
|
2012-11-10 03:27:07 +00:00
|
|
|
hClose inh
|
|
|
|
hClose outh
|
2012-11-06 20:36:44 +00:00
|
|
|
return $ code == ExitSuccess
|
2012-11-06 20:08:36 +00:00
|
|
|
where
|
2012-11-10 04:13:55 +00:00
|
|
|
relaytoxmpp outh = do
|
2012-11-09 21:40:59 +00:00
|
|
|
b <- liftIO $ B.hGetSome outh chunkSize
|
2012-11-10 03:27:07 +00:00
|
|
|
-- empty is EOF, so exit
|
|
|
|
unless (B.null b) $ do
|
2012-11-10 16:18:00 +00:00
|
|
|
sendNetMessage $ Pushing cid $ ReceivePackOutput b
|
2012-11-10 04:13:55 +00:00
|
|
|
relaytoxmpp outh
|
|
|
|
relayfromxmpp inh = forever $ do
|
2012-11-11 17:38:28 +00:00
|
|
|
m <- runTimeout xmppTimeout <~> waitNetPushMessage
|
2012-11-08 20:44:23 +00:00
|
|
|
case m of
|
2012-11-11 17:38:28 +00:00
|
|
|
(Right (Pushing _ (SendPackOutput b))) ->
|
2012-11-10 16:18:00 +00:00
|
|
|
liftIO $ writeChunk inh b
|
2012-11-11 17:38:28 +00:00
|
|
|
(Right _) -> noop
|
|
|
|
(Left _) -> do
|
|
|
|
debug ["timeout waiting for git send-pack output via XMPP"]
|
|
|
|
-- closing the handle will make
|
|
|
|
-- git receive-pack exit
|
|
|
|
liftIO $ do
|
|
|
|
hClose inh
|
|
|
|
killThread =<< myThreadId
|
2012-11-08 20:44:23 +00:00
|
|
|
|
|
|
|
xmppRemotes :: ClientID -> Assistant [Remote]
|
|
|
|
xmppRemotes cid = case baseJID <$> parseJID cid of
|
|
|
|
Nothing -> return []
|
|
|
|
Just jid -> do
|
2012-11-10 03:43:08 +00:00
|
|
|
let loc = gitXMPPLocation jid
|
|
|
|
filter (matching loc . Remote.repo) . syncRemotes
|
|
|
|
<$> getDaemonStatus
|
2012-11-08 20:44:23 +00:00
|
|
|
where
|
2012-11-10 03:43:08 +00:00
|
|
|
matching loc r = repoIsUrl r && repoLocation r == loc
|
2012-11-08 20:44:23 +00:00
|
|
|
|
2012-11-10 03:17:47 +00:00
|
|
|
whenXMPPRemote :: ClientID -> Assistant () -> Assistant ()
|
|
|
|
whenXMPPRemote cid = unlessM (null <$> xmppRemotes cid)
|
2012-11-08 20:44:23 +00:00
|
|
|
|
2012-11-09 20:04:55 +00:00
|
|
|
handlePushMessage :: NetMessage -> Assistant ()
|
2012-11-10 18:38:50 +00:00
|
|
|
handlePushMessage (Pushing cid CanPush) =
|
|
|
|
whenXMPPRemote cid $
|
|
|
|
sendNetMessage $ Pushing cid PushRequest
|
|
|
|
|
|
|
|
handlePushMessage (Pushing cid PushRequest) =
|
|
|
|
go =<< liftAnnex (inRepo Git.Branch.current)
|
|
|
|
where
|
|
|
|
go Nothing = noop
|
|
|
|
go (Just branch) = do
|
|
|
|
rs <- xmppRemotes cid
|
|
|
|
liftAnnex $ Annex.Branch.commit "update"
|
|
|
|
(g, u) <- liftAnnex $ (,)
|
|
|
|
<$> gitRepo
|
|
|
|
<*> getUUID
|
|
|
|
liftIO $ Command.Sync.updateBranch (Command.Sync.syncBranch branch) g
|
|
|
|
debug ["pushing to", show rs]
|
|
|
|
forM_ rs $ \r -> xmppPush cid $ pushFallback u branch r
|
|
|
|
|
|
|
|
handlePushMessage (Pushing cid StartingPush) =
|
|
|
|
whenXMPPRemote cid $
|
|
|
|
void $ xmppReceivePack cid
|
2012-11-09 20:04:55 +00:00
|
|
|
handlePushMessage _ = noop
|
2012-11-09 21:40:59 +00:00
|
|
|
|
2012-11-10 03:17:47 +00:00
|
|
|
handleDeferred :: NetMessage -> Assistant ()
|
|
|
|
handleDeferred = handlePushMessage
|
|
|
|
|
2012-11-10 04:24:26 +00:00
|
|
|
writeChunk :: Handle -> B.ByteString -> IO ()
|
|
|
|
writeChunk h b = do
|
|
|
|
B.hPut h b
|
|
|
|
hFlush h
|
2012-11-11 17:38:28 +00:00
|
|
|
|
|
|
|
{- Largest chunk of data to send in a single XMPP message. -}
|
|
|
|
chunkSize :: Int
|
|
|
|
chunkSize = 4096
|
|
|
|
|
|
|
|
{- How long to wait for an expected message before assuming the other side
|
|
|
|
- has gone away and canceling a push.
|
|
|
|
-
|
|
|
|
- This needs to be long enough to allow a message of up to 2+ times
|
|
|
|
- chunkSize to propigate up to a XMPP server, perhaps across to another
|
|
|
|
- server, and back down to us. On the other hand, other XMPP pushes can be
|
|
|
|
- delayed for running until the timeout is reached, so it should not be
|
|
|
|
- excessive.
|
|
|
|
-}
|
|
|
|
xmppTimeout :: Seconds
|
|
|
|
xmppTimeout = Seconds 120
|