darcs-2.16.3: src/Darcs/UI/Commands/ShowDependencies.hs
{-# 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
-- | A 'M.Map' from 'PatchId's to 'Deps'.
type DepsGraph p = M.Map (PatchId p) (Deps p)
-- | A pair of (direct, indirect) dependencies. For the result we need only the
-- direct dependencies. We store the indirect ones as an optimization to avoid
-- doing commutes for which we already know that they cannot succeed. Note that
-- the two sets are always disjoint.
type Deps p = (S.Set (PatchId p), S.Set (PatchId p))
-- | Determine the 'DepsGraph' of an 'RL' of patches.
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
-- First recurse on the context. The result now has all the 'Deps' for
-- all patches preceding p.
m = depsGraph ps
-- Lookup all (direct and indirect) dependencies of a patch in a given
-- 'DepthGraph'
allDeps j = uncurry S.union . fromJust . M.lookup j
-- Add all (direct and indirect) dependencies of a patch to a given set
-- assuming 'm' already
addDeps j = S.insert j . S.union (allDeps j m)
-- Add direct and indirect dependencies of a patch, assuming that the
-- graph has already been constructed for all patches in the context.
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)
-- If we already know we indirectly depend on q, then there is
-- nothing left to do. Note that (j `S.member` direct) is impossible.
| j `S.member` indirect = foldDeps qs (q :>: p_and_deps) non_deps acc
-- If q commutes past p_and_deps then we don't depend on it
| Just (p_and_deps' :> q') <- commuteFL (q :> p_and_deps) =
foldDeps qs p_and_deps' (q' :>: non_deps) acc
-- We have a new dependency which must be a direct one, so add it to
-- 'direct' and all its dependencies to 'indirect'. The invariant that
-- direct and indirect are disjoint is maintained because neither the
-- direct and indirect deps of a patch contain its own 'PatchId'.
| otherwise =
foldDeps qs (q :>: p_and_deps) non_deps (S.insert j direct, addDeps j indirect)
where
j = ident q
-- | Render a 'DepthGraph' in the Dot Language format. This function
-- considers only the direct dependencies.
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]