{-# LANGUAGE OverloadedStrings #-}
module Darcs.UI.Commands.ShowDependencies ( showDeps ) where
import Darcs.Prelude
import qualified Data.Map.Strict as M
import Data.Maybe( fromJust, fromMaybe )
import qualified Data.Set as S
import Darcs.Repository ( RepoJob(..), readRepo, withRepositoryLocation )
import Darcs.UI.Flags ( DarcsFlag, getRepourl, useCache )
import Darcs.UI.Options ( oid, odesc, ocheck, defaultFlags, (?) )
import qualified Darcs.UI.Options.All as O
import Darcs.UI.Commands ( DarcsCommand(..), nodefaults, findRepository, withStdOpts )
import Darcs.UI.Commands.Util ( matchRange )
import Darcs.UI.Completion ( noArgs )
import Darcs.Util.Hash ( sha1short, showAsHex )
import Darcs.Util.Path ( AbsolutePath )
import Darcs.Util.Printer
( Doc
, (<+>)
, ($+$)
, formatText
, formatWords
, hsep
, prefixLines
, putDocLn
, quoted
, renderString
, text
, vcat
)
import Darcs.Util.Progress ( beginTedious, endTedious, progress, tediousSize )
import Darcs.Patch.Commute ( Commute, commuteFL )
import Darcs.Patch.Ident ( PatchId, Ident(..) )
import Darcs.Patch.Info ( PatchInfo, piName, makePatchname )
import Darcs.Patch.Witnesses.Ordered
( (:>)(..)
, FL(..)
, RL(..)
, reverseFL
, lengthFL
)
import Darcs.Patch.Witnesses.Sealed ( Sealed2(..) )
showDepsDescription :: String
showDepsDescription = "Generate the graph of dependencies."
showDepsHelp :: Doc
showDepsHelp =
formatWords
[ "This command creates a graph of the dependencies between patches."
, "The output format is the Dot Language, see"
, "https://www.graphviz.org/doc/info/lang.html. The resulting graph"
, "is transitively reduced, in other words,"
, "it contains only the direct dependencies, not the indirect ones."
]
$+$ formatWords
[ "By default all patches in your repository are considered. You can"
, "limit this to a range of patches using patch matching options, see"
, "`darcs help patterns` and the options avaiable for this command."
, "For instance, to visualize the dependencies between all patches"
, "since the last tag, do:"
]
$+$ " darcs show dependencies --from-tag=. | dot -Tpdf -o FILE.pdf"
$+$ formatWords
[ "This command can take a very(!) long time to compute its result,"
, "depending on the number of patches in the selected range. For N"
, "patches it needs to do on the order of N^3 commutations in the"
, "worst case."
]
showDeps :: DarcsCommand
showDeps = DarcsCommand
{ commandProgramName = "darcs"
, commandName = "dependencies"
, commandHelp = showDepsHelp
, commandDescription = showDepsDescription
, commandExtraArgs = 0
, commandExtraArgHelp = []
, commandCommand = depsCmd
, commandPrereq = findRepository
, commandCompleteArgs = noArgs
, commandArgdefaults = nodefaults
, commandAdvancedOptions = []
, commandBasicOptions = odesc showDepsBasicOpts
, commandDefaults = defaultFlags showDepsOpts
, commandCheckOptions = ocheck showDepsOpts
}
where
showDepsBasicOpts = O.matchRange
showDepsOpts = showDepsBasicOpts `withStdOpts` oid
progressKey :: String
progressKey = "Determining dependencies"
depsCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO ()
depsCmd _ opts _ = do
let repodir = fromMaybe "." (getRepourl opts)
withRepositoryLocation (useCache ? opts) repodir $ RepoJob $ \repo -> do
Sealed2 range <- matchRange (O.matchRange ? opts) <$> readRepo repo
beginTedious progressKey
tediousSize progressKey (lengthFL range)
putDocLn $ renderDepsGraphAsDot $ depsGraph $ reverseFL range
endTedious progressKey
type DepsGraph p = M.Map (PatchId p) (Deps p)
type Deps p = (S.Set (PatchId p), S.Set (PatchId p))
depsGraph :: forall p wX wY. (Commute p, Ident p) => RL p wX wY -> DepsGraph p
depsGraph NilRL = M.empty
depsGraph (ps :<: p) =
M.insert (ident p) (foldDeps ps (p :>: NilFL) NilFL (S.empty, S.empty)) m
where
m = depsGraph ps
allDeps j = uncurry S.union . fromJust . M.lookup j
addDeps j = S.insert j . S.union (allDeps j m)
foldDeps :: RL p wA wB -> FL p wB wC -> FL p wC wD -> Deps p -> Deps p
foldDeps NilRL _ _ acc = progress progressKey acc
foldDeps (qs :<: q) p_and_deps non_deps acc@(direct, indirect)
| j `S.member` indirect = foldDeps qs (q :>: p_and_deps) non_deps acc
| Just (p_and_deps' :> q') <- commuteFL (q :> p_and_deps) =
foldDeps qs p_and_deps' (q' :>: non_deps) acc
| otherwise =
foldDeps qs (q :>: p_and_deps) non_deps (S.insert j direct, addDeps j indirect)
where
j = ident q
renderDepsGraphAsDot :: M.Map PatchInfo (S.Set PatchInfo, S.Set PatchInfo) -> Doc
renderDepsGraphAsDot g = vcat ["digraph {", indent body, "}"]
where
indent = prefixLines (" ")
body = vcat
[ "graph [rankdir=LR];"
, "node [imagescale=true];"
, vcat (map showNode (map fst pairs))
, vcat (map showEdges pairs)
]
pairs = M.toList $ M.map fst g
showEdges (i, ds)
| S.null ds = mempty
| otherwise =
hsep [showID i, "->", "{" <> hsep (map showID (S.toList ds)) <> "}"]
showNode i = showID i <+> "[label=" <> showLabel i <> "]"
showID = quoted . showAsHex . sha1short . makePatchname
showLabel i = text $ show $ renderString $ formatText 20 [piName i]