new map subcommand, basically working
Still todo: - add repos from uuid.log that were not directly found - group repos into their respective hosts - display inaccessible repos and broken remote connections in red - anonymize the url display somewhat, so the maps can be shared - use uuid info to tell when two apparently different repos are actually the same repo accessed in different ways
This commit is contained in:
parent
14bc885de9
commit
0c7d17ae06
6 changed files with 185 additions and 6 deletions
153
Command/Map.hs
Normal file
153
Command/Map.hs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
{- 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.Monad.State (liftIO)
|
||||||
|
import Control.Exception.Extensible
|
||||||
|
import System.Cmd.Utils
|
||||||
|
|
||||||
|
import Command
|
||||||
|
import qualified Annex
|
||||||
|
import qualified GitRepo as Git
|
||||||
|
import qualified Remotes
|
||||||
|
import Messages
|
||||||
|
import Types
|
||||||
|
import Utility
|
||||||
|
|
||||||
|
-- a link from the first repository to the second (its remote)
|
||||||
|
data Link = Link Git.Repo Git.Repo
|
||||||
|
|
||||||
|
command :: [Command]
|
||||||
|
command = [Command "map" paramNothing seek "generate map of repositories"]
|
||||||
|
|
||||||
|
seek :: [CommandSeek]
|
||||||
|
seek = [withNothing start]
|
||||||
|
|
||||||
|
start :: CommandStartNothing
|
||||||
|
start = do
|
||||||
|
g <- Annex.gitRepo
|
||||||
|
rs <- spider g
|
||||||
|
|
||||||
|
liftIO $ writeFile file (dotGraph rs)
|
||||||
|
showLongNote $ "running: dot -Tx11 " ++ file ++ "\n"
|
||||||
|
r <- liftIO $ boolSystem "dot" ["-Tx11", file]
|
||||||
|
return $ Just $ return $ Just $ return r
|
||||||
|
where
|
||||||
|
file = "map.dot"
|
||||||
|
|
||||||
|
{- Generates a graph for dot(1). Each repository is displayed
|
||||||
|
- as a node, and each of its remotes is represented as an edge
|
||||||
|
- pointing at the node for the remote. -}
|
||||||
|
dotGraph :: [Git.Repo] -> String
|
||||||
|
dotGraph rs = unlines $ [header] ++ map dotGraphRepo rs ++ [footer]
|
||||||
|
where
|
||||||
|
header = "digraph map {"
|
||||||
|
footer= "}"
|
||||||
|
|
||||||
|
dotGraphRepo :: Git.Repo -> String
|
||||||
|
dotGraphRepo r = unlines $ map dotline (node:edges)
|
||||||
|
where
|
||||||
|
node = nodename r ++
|
||||||
|
" [ label=" ++ dotquote (Git.repoDescribe r) ++ " ]"
|
||||||
|
edges = map edge (Git.remotes r)
|
||||||
|
edge e = nodename r ++ " -> " ++ nodename (makeabs r e)
|
||||||
|
nodename n = dotquote (Git.repoLocation n)
|
||||||
|
dotquote s = "\"" ++ s ++ "\""
|
||||||
|
dotline s = "\t" ++ s ++ ";"
|
||||||
|
|
||||||
|
{- 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
|
||||||
|
let remotes = map (makeabs r') (Git.remotes r')
|
||||||
|
spider' (rs ++ remotes) (r':known)
|
||||||
|
|
||||||
|
{- Makes a remote have an absolute url, rather than a host-local path. -}
|
||||||
|
makeabs :: Git.Repo -> Git.Repo -> Git.Repo
|
||||||
|
makeabs repo remote
|
||||||
|
| Git.repoIsUrl remote = remote
|
||||||
|
| not $ Git.repoIsUrl repo = remote
|
||||||
|
| otherwise = Git.repoFromUrl combinedurl
|
||||||
|
where
|
||||||
|
combinedurl =
|
||||||
|
Git.urlScheme repo ++ "//" ++
|
||||||
|
Git.urlHost repo ++
|
||||||
|
Git.workTree remote
|
||||||
|
|
||||||
|
{- Checks if two repos are the same. -}
|
||||||
|
same :: Git.Repo -> Git.Repo -> Bool
|
||||||
|
same a b
|
||||||
|
| both Git.repoIsSsh = matching Git.urlHost && matching Git.workTree
|
||||||
|
| both Git.repoIsUrl && neither Git.repoIsSsh = matching show
|
||||||
|
| 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
|
||||||
|
showStart "map" (Git.repoDescribe r)
|
||||||
|
v <- tryScan r
|
||||||
|
case v of
|
||||||
|
Just r' -> do
|
||||||
|
showEndOk
|
||||||
|
return r'
|
||||||
|
Nothing -> do
|
||||||
|
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
|
||||||
|
result <- liftIO (try (a)::IO (Either SomeException Git.Repo))
|
||||||
|
case result of
|
||||||
|
Left _ -> return Nothing
|
||||||
|
Right r' -> return $ Just r'
|
||||||
|
pipedconfig cmd params = safely $
|
||||||
|
pOpen ReadFromPipe cmd params $
|
||||||
|
Git.hConfigRead r
|
||||||
|
|
||||||
|
configlist =
|
||||||
|
Remotes.onRemote r (pipedconfig, Nothing) "configlist" []
|
||||||
|
manualconfiglist = do
|
||||||
|
sshoptions <- Remotes.repoConfig r "ssh-options" ""
|
||||||
|
let sshcmd =
|
||||||
|
"cd " ++ shellEscape(Git.workTree r) ++ " && " ++
|
||||||
|
"git config --list"
|
||||||
|
liftIO $ pipedconfig "ssh" $
|
||||||
|
words sshoptions ++ [Git.urlHost r, sshcmd]
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
showNote "sshing..."
|
||||||
|
showProgress
|
||||||
|
v <- manualconfiglist
|
||||||
|
case v of
|
||||||
|
Nothing -> configlist
|
||||||
|
ok -> return ok
|
|
@ -38,6 +38,7 @@ import qualified Command.Uninit
|
||||||
import qualified Command.Trust
|
import qualified Command.Trust
|
||||||
import qualified Command.Untrust
|
import qualified Command.Untrust
|
||||||
import qualified Command.Semitrust
|
import qualified Command.Semitrust
|
||||||
|
import qualified Command.Map
|
||||||
|
|
||||||
cmds :: [Command]
|
cmds :: [Command]
|
||||||
cmds = concat
|
cmds = concat
|
||||||
|
@ -64,6 +65,7 @@ cmds = concat
|
||||||
, Command.DropUnused.command
|
, Command.DropUnused.command
|
||||||
, Command.Find.command
|
, Command.Find.command
|
||||||
, Command.Migrate.command
|
, Command.Migrate.command
|
||||||
|
, Command.Map.command
|
||||||
]
|
]
|
||||||
|
|
||||||
options :: [Option]
|
options :: [Option]
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
module Remotes (
|
module Remotes (
|
||||||
list,
|
list,
|
||||||
|
tryGitConfigRead,
|
||||||
readConfigs,
|
readConfigs,
|
||||||
keyPossibilities,
|
keyPossibilities,
|
||||||
inAnnex,
|
inAnnex,
|
||||||
|
@ -14,7 +15,8 @@ module Remotes (
|
||||||
byName,
|
byName,
|
||||||
copyFromRemote,
|
copyFromRemote,
|
||||||
copyToRemote,
|
copyToRemote,
|
||||||
onRemote
|
onRemote,
|
||||||
|
repoConfig
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Exception.Extensible
|
import Control.Exception.Extensible
|
||||||
|
@ -77,7 +79,6 @@ tryGitConfigRead r
|
||||||
then new : exchange ls new
|
then new : exchange ls new
|
||||||
else old : exchange ls new
|
else old : exchange ls new
|
||||||
|
|
||||||
|
|
||||||
{- Reads the configs of all remotes.
|
{- Reads the configs of all remotes.
|
||||||
-
|
-
|
||||||
- This has to be called before things that rely on eg, the UUID of
|
- This has to be called before things that rely on eg, the UUID of
|
||||||
|
@ -92,9 +93,9 @@ tryGitConfigRead r
|
||||||
- -}
|
- -}
|
||||||
readConfigs :: Annex ()
|
readConfigs :: Annex ()
|
||||||
readConfigs = do
|
readConfigs = do
|
||||||
g <- Annex.gitRepo
|
|
||||||
remotesread <- Annex.getState Annex.remotesread
|
remotesread <- Annex.getState Annex.remotesread
|
||||||
unless remotesread $ do
|
unless remotesread $ do
|
||||||
|
g <- Annex.gitRepo
|
||||||
allremotes <- filterM repoNotIgnored $ Git.remotes g
|
allremotes <- filterM repoNotIgnored $ Git.remotes g
|
||||||
let cheap = filter (not . Git.repoIsUrl) allremotes
|
let cheap = filter (not . Git.repoIsUrl) allremotes
|
||||||
let expensive = filter Git.repoIsUrl allremotes
|
let expensive = filter Git.repoIsUrl allremotes
|
||||||
|
|
10
UUID.hs
10
UUID.hs
|
@ -11,13 +11,15 @@
|
||||||
module UUID (
|
module UUID (
|
||||||
UUID,
|
UUID,
|
||||||
getUUID,
|
getUUID,
|
||||||
|
getUncachedUUID,
|
||||||
prepUUID,
|
prepUUID,
|
||||||
genUUID,
|
genUUID,
|
||||||
reposByUUID,
|
reposByUUID,
|
||||||
reposWithoutUUID,
|
reposWithoutUUID,
|
||||||
prettyPrintUUIDs,
|
prettyPrintUUIDs,
|
||||||
describeUUID,
|
describeUUID,
|
||||||
uuidLog
|
uuidLog,
|
||||||
|
uuidMap
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad.State
|
import Control.Monad.State
|
||||||
|
@ -60,7 +62,7 @@ getUUID r = do
|
||||||
g <- Annex.gitRepo
|
g <- Annex.gitRepo
|
||||||
|
|
||||||
let c = cached g
|
let c = cached g
|
||||||
let u = uncached
|
let u = getUncachedUUID r
|
||||||
|
|
||||||
if c /= u && u /= ""
|
if c /= u && u /= ""
|
||||||
then do
|
then do
|
||||||
|
@ -68,11 +70,13 @@ getUUID r = do
|
||||||
return u
|
return u
|
||||||
else return c
|
else return c
|
||||||
where
|
where
|
||||||
uncached = Git.configGet r "annex.uuid" ""
|
|
||||||
cached g = Git.configGet g cachekey ""
|
cached g = Git.configGet g cachekey ""
|
||||||
updatecache g u = when (g /= r) $ Annex.setConfig cachekey u
|
updatecache g u = when (g /= r) $ Annex.setConfig cachekey u
|
||||||
cachekey = "remote." ++ Git.repoRemoteName r ++ ".annex-uuid"
|
cachekey = "remote." ++ Git.repoRemoteName r ++ ".annex-uuid"
|
||||||
|
|
||||||
|
getUncachedUUID :: Git.Repo -> UUID
|
||||||
|
getUncachedUUID r = Git.configGet r "annex.uuid" ""
|
||||||
|
|
||||||
{- Make sure that the repo has an annex.uuid setting. -}
|
{- Make sure that the repo has an annex.uuid setting. -}
|
||||||
prepUUID :: Annex ()
|
prepUUID :: Annex ()
|
||||||
prepUUID = do
|
prepUUID = do
|
||||||
|
|
2
debian/changelog
vendored
2
debian/changelog
vendored
|
@ -6,6 +6,8 @@ git-annex (0.20) UNRELEASED; urgency=low
|
||||||
* unannex: Commit staged changes at end, to avoid some confusing behavior
|
* unannex: Commit staged changes at end, to avoid some confusing behavior
|
||||||
with the pre-commit hook, which would see some types of commits after
|
with the pre-commit hook, which would see some types of commits after
|
||||||
an unannex as checking in of an unlocked file.
|
an unannex as checking in of an unlocked file.
|
||||||
|
* map: New subcommand that uses graphviz to display a nice map of
|
||||||
|
the git repository network.
|
||||||
|
|
||||||
-- Joey Hess <joeyh@debian.org> Mon, 31 Jan 2011 20:06:02 -0400
|
-- Joey Hess <joeyh@debian.org> Mon, 31 Jan 2011 20:06:02 -0400
|
||||||
|
|
||||||
|
|
|
@ -154,6 +154,23 @@ Many git-annex commands will stage changes for later `git commit` by you.
|
||||||
Note that the content is not removed from the backend it was previously in.
|
Note that the content is not removed from the backend it was previously in.
|
||||||
Use `git annex unused` to find and remove such content.
|
Use `git annex unused` to find and remove such content.
|
||||||
|
|
||||||
|
* map
|
||||||
|
|
||||||
|
Helps you keep track of your repositories, and the connections between them,
|
||||||
|
by going out and looking at all the ones it can get to, and generating a
|
||||||
|
Graphviz file displaying it all. If the `dot` command is available, it is
|
||||||
|
used to display the file to your screen (using x11 backend).
|
||||||
|
|
||||||
|
Note that this only connects to hosts that the host it's run on can
|
||||||
|
directly connect to. It does not try to tunnel through intermediate hosts.
|
||||||
|
So it might not show all connections between the repositories in the network.
|
||||||
|
|
||||||
|
Also, if connecting to a host requires a password, you might have to enter
|
||||||
|
it several times as the map is being built.
|
||||||
|
|
||||||
|
Note that this subcommand can be used to graph any git repository; it
|
||||||
|
is not limited to git-annex repositories.
|
||||||
|
|
||||||
* unannex [path ...]
|
* unannex [path ...]
|
||||||
|
|
||||||
Use this to undo an accidental `git annex add` command. You can use
|
Use this to undo an accidental `git annex add` command. You can use
|
||||||
|
|
Loading…
Add table
Reference in a new issue