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
|
|
|
|
import Annex.UUID
|
|
|
|
import Config
|
2012-11-06 20:36:44 +00:00
|
|
|
import Git
|
2012-11-06 14:46:58 +00:00
|
|
|
import Git.Command
|
2012-11-08 20:44:23 +00:00
|
|
|
import qualified Git.Branch
|
|
|
|
import qualified Annex.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-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-06 04:52:35 +00:00
|
|
|
|
2012-11-08 20:44:23 +00:00
|
|
|
configKey :: UnqualifiedConfigKey
|
|
|
|
configKey = "xmppaddress"
|
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
|
|
|
|
|
|
|
|
makeXMPPGitRemote :: String -> JID -> UUID -> Assistant Bool
|
|
|
|
makeXMPPGitRemote buddyname jid u = do
|
2012-11-09 16:51:54 +00:00
|
|
|
remote <- liftAnnex $ addRemote $ makeGitRemote buddyname xmppaddress
|
2012-11-05 21:43:17 +00:00
|
|
|
liftAnnex $ do
|
|
|
|
let r = Remote.repo remote
|
|
|
|
storeUUID (remoteConfig r "uuid") u
|
2012-11-08 20:44:23 +00:00
|
|
|
setConfig (remoteConfig r configKey) xmppaddress
|
2012-11-05 21:43:17 +00:00
|
|
|
syncNewRemote remote
|
|
|
|
return True
|
|
|
|
where
|
2012-11-09 16:51:54 +00:00
|
|
|
xmppaddress = "xmpp::" ++ T.unpack (formatJID $ baseJID jid)
|
2012-11-05 21:43:17 +00:00
|
|
|
|
2012-11-08 20:44:23 +00:00
|
|
|
{- Pushes the named refs to the remote, over XMPP, communicating with a
|
|
|
|
- specific client that either requested the push, or responded to our
|
2012-11-09 16:51:54 +00:00
|
|
|
- message.
|
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
|
|
|
|
- using two file descriptors, GIT_ANNEX_XMPPGIT_IN and
|
|
|
|
- GIT_ANNEX_XMPPGIT_OUT. It simply connects those up to its stdin
|
|
|
|
- and stdout, respectively, which are in turn connected to "git-push".
|
|
|
|
- There is also a GIT_ANNEX_XMPPGIT_CONTROL descriptor, to which an
|
|
|
|
- exit status is sent for xmppgit to propigate.
|
|
|
|
-
|
|
|
|
- We listen at the other end of the pipe and relay to and from XMPP.
|
|
|
|
-}
|
2012-11-08 20:44:23 +00:00
|
|
|
xmppPush :: ClientID -> Remote -> [Ref] -> Assistant Bool
|
|
|
|
xmppPush cid remote refs = runPush (SendPushRunning cid) handleDeferred $ do
|
|
|
|
sendNetMessage $ StartingPush cid
|
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-06 14:46:58 +00:00
|
|
|
let myenv =
|
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)
|
|
|
|
]
|
|
|
|
g <- liftAnnex gitRepo
|
|
|
|
let name = Remote.name remote
|
|
|
|
let params = Param "push" : Param name : map (Param . show) refs
|
|
|
|
|
|
|
|
inh <- liftIO $ fdToHandle readpush
|
|
|
|
outh <- liftIO $ fdToHandle writepush
|
|
|
|
controlh <- liftIO $ fdToHandle writecontrol
|
|
|
|
liftIO $ hSetBuffering outh NoBuffering
|
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
|
|
|
|
|
|
|
ok <- liftIO $ boolSystemEnv "git"
|
2012-11-09 16:51:54 +00:00
|
|
|
(gitCommandLine params g)
|
2012-11-06 14:46:58 +00:00
|
|
|
(Just $ env ++ myenv)
|
2012-11-08 20:44:23 +00:00
|
|
|
liftIO $ mapM_ killThread [t1, t2]
|
2012-11-06 14:46:58 +00:00
|
|
|
return ok
|
|
|
|
where
|
|
|
|
toxmpp inh = forever $ do
|
|
|
|
b <- liftIO $ B.hGetSome inh 1024
|
2012-11-08 20:44:23 +00:00
|
|
|
if B.null b
|
|
|
|
then liftIO $ killThread =<< myThreadId
|
|
|
|
else sendNetMessage $ SendPackOutput cid b
|
|
|
|
fromxmpp outh controlh = forever $ do
|
|
|
|
m <- waitNetPushMessage
|
|
|
|
case m of
|
|
|
|
(ReceivePackOutput _ b) -> liftIO $ B.hPut outh b
|
|
|
|
(ReceivePackDone _ exitcode) -> do
|
|
|
|
liftIO $ hPutStrLn controlh (show exitcode)
|
|
|
|
_ -> noop
|
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
|
|
|
|
|
|
|
relayIn :: String
|
|
|
|
relayIn = "GIT_ANNEX_XMPPGIT_IN"
|
|
|
|
|
|
|
|
relayOut :: String
|
|
|
|
relayOut = "GIT_ANNEX_XMPPGIT_OUT"
|
|
|
|
|
|
|
|
relayControl :: String
|
|
|
|
relayControl = "GIT_ANNEX_XMPPGIT_CONTROL"
|
|
|
|
|
2012-11-06 14:14:00 +00:00
|
|
|
relayHandle :: String -> IO Handle
|
|
|
|
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
|
|
|
|
|
|
|
{- Called by git-annex xmppgit. -}
|
|
|
|
xmppGitRelay :: IO ()
|
|
|
|
xmppGitRelay = do
|
2012-11-06 14:14:00 +00:00
|
|
|
inh <- relayHandle relayIn
|
|
|
|
outh <- relayHandle relayOut
|
|
|
|
|
|
|
|
hSetBuffering outh NoBuffering
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-06 14:14:00 +00:00
|
|
|
{- Is it possible to set up pipes and not need to copy the data
|
2012-11-06 19:57:18 +00:00
|
|
|
- ourselves? See splice(2) -}
|
2012-11-06 14:14:00 +00:00
|
|
|
void $ forkIO $ forever $ do
|
|
|
|
b <- B.hGetSome inh 1024
|
|
|
|
when (B.null b) $
|
|
|
|
killThread =<< myThreadId
|
|
|
|
B.hPut stdout b
|
|
|
|
void $ forkIO $ forever $ B.hGetSome stdin 1024 >>= B.hPut outh
|
2012-11-06 04:52:35 +00:00
|
|
|
|
2012-11-06 14:14:00 +00:00
|
|
|
controlh <- relayHandle relayControl
|
2012-11-06 04:52:35 +00:00
|
|
|
s <- hGetLine controlh
|
|
|
|
exitWith $ case readish s of
|
|
|
|
Just n
|
|
|
|
| n == 0 -> ExitSuccess
|
|
|
|
| otherwise -> ExitFailure n
|
|
|
|
Nothing -> ExitFailure 1
|
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:08:36 +00:00
|
|
|
feeder <- asIO1 toxmpp
|
|
|
|
reader <- asIO1 fromxmpp
|
2012-11-08 18:02:37 +00:00
|
|
|
sendexitcode <- asIO1 $ sendNetMessage . ReceivePackDone cid
|
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
|
|
|
|
}
|
|
|
|
liftIO $ do
|
|
|
|
(Just inh, Just outh, _, pid) <- createProcess p
|
|
|
|
feedertid <- forkIO $ feeder outh
|
|
|
|
void $ reader inh
|
|
|
|
code <- waitForProcess pid
|
2012-11-08 18:02:37 +00:00
|
|
|
void $ sendexitcode code
|
2012-11-06 20:36:44 +00:00
|
|
|
killThread feedertid
|
|
|
|
return $ code == ExitSuccess
|
2012-11-06 20:08:36 +00:00
|
|
|
where
|
2012-11-06 20:36:44 +00:00
|
|
|
toxmpp outh = do
|
|
|
|
b <- liftIO $ B.hGetSome outh 1024
|
|
|
|
if B.null b
|
|
|
|
then return () -- EOF
|
|
|
|
else do
|
2012-11-08 18:02:37 +00:00
|
|
|
sendNetMessage $ ReceivePackOutput cid b
|
2012-11-06 20:36:44 +00:00
|
|
|
toxmpp outh
|
2012-11-08 20:44:23 +00:00
|
|
|
fromxmpp inh = forever $ do
|
|
|
|
m <- waitNetPushMessage
|
|
|
|
case m of
|
|
|
|
(SendPackOutput _ b) -> liftIO $ B.hPut inh b
|
|
|
|
_ -> noop
|
|
|
|
|
|
|
|
xmppRemotes :: ClientID -> Assistant [Remote]
|
|
|
|
xmppRemotes cid = case baseJID <$> parseJID cid of
|
|
|
|
Nothing -> return []
|
|
|
|
Just jid -> do
|
|
|
|
rs <- syncRemotes <$> getDaemonStatus
|
|
|
|
let want = T.unpack $ formatJID jid
|
|
|
|
liftAnnex $ filterM (matching want) rs
|
|
|
|
where
|
|
|
|
matching want r = do
|
|
|
|
v <- getRemoteConfig (Remote.repo r) configKey ""
|
|
|
|
return $ v == want
|
|
|
|
|
|
|
|
handleDeferred :: NetMessage -> Assistant ()
|
|
|
|
handleDeferred = void . handlePush
|
|
|
|
|
|
|
|
handlePush :: NetMessage -> Assistant Bool
|
|
|
|
handlePush (PushRequest cid) = do
|
|
|
|
rs <- xmppRemotes cid
|
|
|
|
current <- liftAnnex $ inRepo Git.Branch.current
|
|
|
|
let refs = catMaybes [current, Just Annex.Branch.fullname]
|
|
|
|
any id <$> (forM rs $ \r -> xmppPush cid r refs)
|
|
|
|
handlePush (StartingPush cid) = do
|
|
|
|
rs <- xmppRemotes cid
|
|
|
|
if null rs
|
|
|
|
then return False
|
|
|
|
else xmppReceivePack cid
|
|
|
|
handlePush _ = return False
|