git-annex/Command/Map.hs

230 lines
6.6 KiB
Haskell
Raw Normal View History

{- git-annex command
-
- Copyright 2010 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Command.Map where
import Control.Exception.Extensible
import qualified Data.Map as M
2011-10-05 20:02:51 +00:00
import Common.Annex
import Command
import qualified Git
import Annex.UUID
2011-10-15 20:21:08 +00:00
import Logs.UUID
import Logs.Trust
2011-10-16 04:04:26 +00:00
import Annex.Ssh
2011-07-06 00:36:43 +00:00
import qualified Utility.Dot as Dot
-- a link from the first repository to the second (its remote)
data Link = Link Git.Repo Git.Repo
def :: [Command]
def = [dontCheck repoExists $
command "map" paramNothing seek "generate map of repositories"]
seek :: [CommandSeek]
seek = [withNothing start]
start :: CommandStart
start = do
rs <- spider =<< gitRepo
umap <- uuidMap
2011-02-08 22:04:19 +00:00
trusted <- trustGet Trusted
2011-02-08 22:04:19 +00:00
liftIO $ writeFile file (drawMap rs umap trusted)
showLongNote $ "running: dot -Tx11 " ++ file
showOutput
r <- liftIO $ boolSystem "dot" [Param "-Tx11", File file]
2011-05-15 06:02:46 +00:00
next $ next $ return r
where
file = "map.dot"
{- Generates a graph for dot(1). Each repository, and any other uuids, are
- displayed as a node, and each of its remotes is represented as an edge
2011-02-04 04:13:47 +00:00
- pointing at the node for the remote.
-
- The order nodes are added to the graph matters, since dot will draw
- the first ones near to the top and left. So it looks better to put
- the repositories first, followed by uuids that were not matched
- to a repository.
-}
2011-07-15 16:47:14 +00:00
drawMap :: [Git.Repo] -> M.Map UUID String -> [UUID] -> String
2011-02-08 22:04:19 +00:00
drawMap rs umap ts = Dot.graph $ repos ++ trusted ++ others
where
2011-02-04 03:23:16 +00:00
repos = map (node umap rs) rs
2011-02-08 22:04:19 +00:00
ruuids = ts ++ map getUncachedUUID rs
others = map (unreachable . uuidnode) $
filter (`notElem` ruuids) (M.keys umap)
trusted = map (trustworthy . uuidnode) ts
uuidnode u = Dot.graphNode (fromUUID u) $
M.findWithDefault "" u umap
2011-02-04 03:23:16 +00:00
hostname :: Git.Repo -> String
hostname r
| Git.repoIsUrl r = Git.urlHost r
| otherwise = "localhost"
2011-02-04 03:23:16 +00:00
basehostname :: Git.Repo -> String
basehostname r = head $ split "." $ hostname r
2011-02-04 03:23:16 +00:00
{- A name to display for a repo. Uses the name from uuid.log if available,
- or the remote name if not. -}
2011-07-15 16:47:14 +00:00
repoName :: M.Map UUID String -> Git.Repo -> String
repoName umap r
| repouuid == NoUUID = fallback
| otherwise = M.findWithDefault fallback repouuid umap
where
repouuid = getUncachedUUID r
2011-07-15 16:47:14 +00:00
fallback = fromMaybe "unknown" $ Git.repoRemoteName r
2011-02-04 04:06:23 +00:00
{- A unique id for the node for a repo. Uses the annex.uuid if available. -}
2011-02-04 03:23:16 +00:00
nodeId :: Git.Repo -> String
nodeId r =
2011-07-15 16:47:14 +00:00
case getUncachedUUID r of
NoUUID -> Git.repoLocation r
UUID u -> u
2011-02-04 03:23:16 +00:00
{- A node representing a repo. -}
2011-07-15 16:47:14 +00:00
node :: M.Map UUID String -> [Git.Repo] -> Git.Repo -> String
2011-02-04 03:23:16 +00:00
node umap fullinfo r = unlines $ n:edges
where
2011-02-08 22:26:38 +00:00
n = Dot.subGraph (hostname r) (basehostname r) "lightblue" $
2011-02-04 04:06:23 +00:00
decorate $ Dot.graphNode (nodeId r) (repoName umap r)
2011-02-04 03:23:16 +00:00
edges = map (edge umap fullinfo r) (Git.remotes r)
2011-02-04 04:06:23 +00:00
decorate
| Git.configMap r == M.empty = unreachable
| otherwise = reachable
2011-02-04 03:23:16 +00:00
{- An edge between two repos. The second repo is a remote of the first. -}
2011-07-15 16:47:14 +00:00
edge :: M.Map UUID String -> [Git.Repo] -> Git.Repo -> Git.Repo -> String
2011-02-04 03:23:16 +00:00
edge umap fullinfo from to =
Dot.graphEdge (nodeId from) (nodeId fullto) edgename
2011-02-04 03:23:16 +00:00
where
-- get the full info for the remote, to get its UUID
2011-02-08 21:52:32 +00:00
fullto = findfullinfo to
2011-02-04 03:23:16 +00:00
findfullinfo n =
2011-07-15 16:47:14 +00:00
case filter (same n) fullinfo of
2011-02-04 03:23:16 +00:00
[] -> n
(n':_) -> n'
{- Only name an edge if the name is different than the name
- that will be used for the destination node, and is
- different from its hostname. (This reduces visual clutter.) -}
2011-05-15 06:49:43 +00:00
edgename = maybe Nothing calcname $ Git.repoRemoteName to
calcname n
2011-07-15 16:47:14 +00:00
| n `elem` [repoName umap fullto, hostname fullto] = Nothing
2011-05-15 06:49:43 +00:00
| otherwise = Just n
2011-02-04 03:23:16 +00:00
2011-02-04 04:06:23 +00:00
unreachable :: String -> String
2011-02-04 04:13:47 +00:00
unreachable = Dot.fillColor "red"
2011-02-04 04:06:23 +00:00
reachable :: String -> String
2011-02-04 04:13:47 +00:00
reachable = Dot.fillColor "white"
2011-02-08 22:04:19 +00:00
trustworthy :: String -> String
trustworthy = Dot.fillColor "green"
2011-02-04 04:06:23 +00:00
{- Recursively searches out remotes starting with the specified repo. -}
spider :: Git.Repo -> Annex [Git.Repo]
spider r = spider' [r] []
spider' :: [Git.Repo] -> [Git.Repo] -> Annex [Git.Repo]
spider' [] known = return known
spider' (r:rs) known
| any (same r) known = spider' rs known
| otherwise = do
r' <- scan r
2011-02-08 21:52:32 +00:00
-- The remotes will be relative to r', and need to be
-- made absolute for later use.
let remotes = map (absRepo r') (Git.remotes r')
2011-02-08 21:52:32 +00:00
let r'' = Git.remotesAdd r' remotes
spider' (rs ++ remotes) (r'':known)
absRepo :: Git.Repo -> Git.Repo -> Git.Repo
absRepo reference r
| Git.repoIsUrl reference = Git.localToUrl reference r
| otherwise = r
{- Checks if two repos are the same. -}
same :: Git.Repo -> Git.Repo -> Bool
same a b
| both Git.repoIsSsh = matching Git.urlAuthority && matching Git.workTree
| both Git.repoIsUrl && neither Git.repoIsSsh = matching show
| neither Git.repoIsSsh = matching Git.workTree
| otherwise = False
where
matching t = t a == t b
both t = t a && t b
neither t = not (t a) && not (t b)
{- reads the config of a remote, with progress display -}
scan :: Git.Repo -> Annex Git.Repo
scan r = do
2011-02-04 03:23:16 +00:00
showStart "map" $ Git.repoDescribe r
v <- tryScan r
case v of
Just r' -> do
showEndOk
return r'
Nothing -> do
showOutput
showEndFail
return r
{- tries to read the config of a remote, returning it only if it can
- be accessed -}
tryScan :: Git.Repo -> Annex (Maybe Git.Repo)
tryScan r
| Git.repoIsSsh r = sshscan
| Git.repoIsUrl r = return Nothing
| otherwise = safely $ Git.configRead r
where
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 Nothing
Right r' -> return $ Just r'
pipedconfig cmd params = safely $
2011-02-28 20:25:31 +00:00
pOpen ReadFromPipe cmd (toCommand params) $
Git.hConfigRead r
configlist =
2011-04-09 18:26:32 +00:00
onRemote r (pipedconfig, Nothing) "configlist" []
manualconfiglist = do
sshparams <- sshToRepo r [Param sshcmd]
liftIO $ pipedconfig "ssh" sshparams
where
sshcmd = cddir ++ " && " ++
"git config --list"
dir = Git.workTree r
cddir
| take 2 dir == "/~" =
let (userhome, reldir) = span (/= '/') (drop 1 dir)
in "cd " ++ userhome ++ " && cd " ++ shellEscape (drop 1 reldir)
| otherwise = "cd " ++ shellEscape dir
-- First, try sshing and running git config manually,
-- only fall back to git-annex-shell configlist if that
-- fails.
--
-- This is done for two reasons, first I'd like this
-- subcommand to be usable on non-git-annex repos.
-- Secondly, configlist doesn't include information about
-- the remote's remotes.
sshscan = do
sshnote
v <- manualconfiglist
case v of
Nothing -> do
sshnote
configlist
ok -> return ok
sshnote = do
showAction "sshing"
showOutput