1
0
Fork 0
mirror of https://code.naskya.net/repos/ndqEd synced 2025-03-20 15:14:54 +09:00
vervis/src/Data/Git/Local.hs
2016-03-03 08:15:54 +00:00

185 lines
6.9 KiB
Haskell

{- This file is part of Vervis.
-
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
-
- ♡ Copying is an act of love. Please copy, reuse and share.
-
- The author(s) have dedicated all copyright and related and neighboring
- rights to this software to the public domain worldwide. This software is
- distributed without any warranty.
-
- You should have received a copy of the CC0 Public Domain Dedication along
- with this software. If not, see
- <http://creativecommons.org/publicdomain/zero/1.0/>.
-}
-- | Git repo tools using the @hit@ package.
module Data.Git.Local
( loadCommits
, NodeLabel
, EdgeLabel
, CommitGraph
, rootN
, loadCommitGraphByRef
, loadCommitGraphByNameMaybe
, loadCommitGraphByName
, loadCommitsTopsort
, loadCommitsTopsortList
)
where
import Prelude
import Control.Monad.IO.Class
import Data.Foldable (foldl', foldlM)
import Data.Git.Ref (Ref, toBinary)
import Data.Git.Repository (getCommit, resolveRevision)
import Data.Git.Revision (Revision (..))
import Data.Git.Storage (Git)
import Data.Git.Types (Commit (..))
import Data.Graph.Inductive.Graph (Graph (mkGraph), Node)
import Data.Graph.Inductive.PatriciaTree (Gr)
import Data.Hashable (Hashable (..))
import Data.Ord (Down (..))
import qualified Data.DList as D
import qualified Data.HashMap.Strict as M
import Data.Graph.Inductive.Local
instance Hashable Ref where
hashWithSalt salt = hashWithSalt salt . toBinary
hash = hash . toBinary
-- | Load the entire graph of commits which are ancestors of the given ref
-- (and that ref itself). Fold the commit structure into a value of type @a@
-- inside monad @m@.
--
-- This is a low-level function which operates on a commit tree, i.e. the same
-- ref may be visited more than once (if it has more than one child commit).
-- You can use the provided flexibility to implement graph algorithms over the
-- commits, or build a graph using some graph library and use that library's
-- tools for further processing.
loadCommits
:: MonadIO m
=> Git
-- ^ Open git repository context
-> ((Ref, Commit) -> Ref -> a -> m (a, Maybe Commit))
-- ^ Given a child commit, one of its parent commits and an @a@ value,
-- generate an updated @a@ value. The second returned value determines
-- whether traversal should proceed to the parent of the parent commit. If
-- you return 'Nothing', it won't. If you load the parent commit (e.g. with
-- 'getCommit') and return 'Just' it, traversal will proceed to its
-- parents.
-> a
-- ^ Initial value
-> Ref
-- ^ Hash of the commit whose ancestor graph should be loaded
-> Maybe Commit
-- ^ If you already read the commit for the ref passed as the previous
-- parameter, pass the commit here to avoid repeated loading of it.
-- Otherwise, pass 'Nothing' and it will be read from the repo.
-> m a
loadCommits git func val ref mcmt = readCommitMaybe ref mcmt >>= go val ref
where
readCommit = liftIO . getCommit git
readCommitMaybe r = maybe (readCommit r) return
--readRefCommit r = do
-- c <- readCommit r
-- return (r, c)
step p v r = do
(v', mc) <- func p r v
case mc of
Nothing -> return v'
Just c -> go v' r c
go v r c = foldlM (step (r, c)) v $ commitParents c
--let rs = commitParents c
--ps <- mapM readRefCommit rs
--foldlM (step (r, c)) v rs
-- | Each node in the commit graph represents a commit.
type NodeLabel = (Ref, Commit)
-- | Edges are tagged by numbers defining the order of parents of a commit. For
-- each commit, the out-edges pointing to its parents are numbered according to
-- the order in which the parents were specified in the 'commitParents' field.
--
-- The 'Down' wrapper reverses the comparison (the 'Ord' instance), so that
-- merged-from branches are inserted earlier into the sorted list than
-- merged-to branches.
type EdgeLabel = Down Int
type CommitGraph g = g NodeLabel EdgeLabel
-- | The node number of the root node in loaded commit graphs.
rootN :: Node
rootN = 1
-- | Use 'loadCommits' to build a directed acyclic graph of commits. There is a
-- single root node, which is the ref passed to this function.
loadCommitGraphByRef :: Graph g => Git -> Ref -> IO (CommitGraph g)
loadCommitGraphByRef git ref = do
let visit (_rChild, _cChild) rParent v@(nextNode, commits) =
if rParent `M.member` commits
then return (v, Nothing)
else do
cParent <- getCommit git rParent
let commits' = M.insert rParent (cParent, nextNode) commits
return ((nextNode + 1, commits'), Just cParent)
cmt <- getCommit git ref
(_, commits) <- loadCommits git visit (rootN + 1, M.empty) ref (Just cmt)
let commits' = M.insert ref (cmt, rootN) commits
nodeOf r = maybe (error "ref has no node") snd $ M.lookup r commits'
mkNode l r (c, n) = (n, (r, c)) : l
nodes = M.foldlWithKey' mkNode [] commits'
mkEdge n l (r, e) = (n, nodeOf r, e) : l
edgeNums = map Down [1..]
mkEdges l (c, n) = foldl' (mkEdge n) l $ zip (commitParents c) edgeNums
edges = M.foldl' mkEdges [] commits'
return $ mkGraph nodes edges
-- | Like 'loadCommitGraphByRef', but lets you specify a named ref, such as a
-- branch or tag name. Returns 'Nothing' if ref isn't found.
loadCommitGraphByNameMaybe ::
Graph g => Git -> String -> IO (Maybe (CommitGraph g))
loadCommitGraphByNameMaybe git name = do
mref <- resolveRevision git $ Revision name []
case mref of
Nothing -> return Nothing
Just ref -> Just <$> loadCommitGraphByRef git ref
-- | Like 'loadCommitGraphByNameMaybe', but throws an exception if the ref name
-- can't be resolved.
loadCommitGraphByName :: Graph g => Git -> String -> IO (CommitGraph g)
loadCommitGraphByName git name = do
mg <- loadCommitGraphByNameMaybe git name
case mg of
Nothing -> error "no such ref"
Just g -> return g
-- | Load a commit graph and topsort the commits. The resulting list starts
-- with the last commit in the repo and ends with the initial commit.
loadCommitsTopsort
:: (ResultList l, Functor l)
=> Git
-> String
-> IO (l (Ref, Commit))
loadCommitsTopsort git name = do
let load :: IO (CommitGraph Gr)
load = loadCommitGraphByName git name
graph <- load
let mnodes = topsortUnmixOrder graph (NodeStack [rootN])
nodes = case mnodes of
Nothing -> error "commit graph contains a cycle"
Just ns -> ns
return $ fmap (nodeLabel graph) nodes
instance ResultList D.DList where
emptyList = D.empty
appendItem = flip D.snoc
-- | Runs 'loadCommitsTopsort' with a 'D.DList', then converts to list and
-- returns it. At least at the time of writing, DList mapping and folding goes
-- through a regular list anyway.
loadCommitsTopsortList :: Git -> String -> IO [(Ref, Commit)]
loadCommitsTopsortList git name = D.toList <$> loadCommitsTopsort git name