105 lines
2.8 KiB
Haskell
105 lines
2.8 KiB
Haskell
{-# LANGUAGE BangPatterns #-}
|
|
|
|
{- git commit history interface
|
|
-
|
|
- Copyright 2019 Joey Hess <id@joeyh.name>
|
|
-
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
-}
|
|
|
|
module Git.History where
|
|
|
|
import Common
|
|
import Git
|
|
import Git.Command
|
|
import Git.Sha
|
|
|
|
import qualified Data.Set as S
|
|
|
|
data History t = History t (S.Set (History t))
|
|
deriving (Show, Eq, Ord)
|
|
|
|
mapHistory :: (Ord a, Ord b) => (a -> b) -> History a -> History b
|
|
mapHistory f (History t s) = History (f t) (S.map (mapHistory f) s)
|
|
|
|
historyDepth :: History t -> Integer
|
|
historyDepth (History _ s)
|
|
| S.null s = 1
|
|
| otherwise = 1 + maximum (map historyDepth (S.toList s))
|
|
|
|
truncateHistoryToDepth :: Ord t => Integer -> History t -> History t
|
|
truncateHistoryToDepth n (History t ps) = History t (go 1 ps)
|
|
where
|
|
go depth s
|
|
| depth >= n = S.empty
|
|
| otherwise =
|
|
let depth' = succ depth
|
|
in flip S.map s $ \(History t' s') ->
|
|
History t' (go depth' s')
|
|
|
|
|
|
data HistoryCommit = HistoryCommit
|
|
{ historyCommit :: Sha
|
|
, historyCommitTree :: Sha
|
|
, historyCommitParents :: [Sha]
|
|
} deriving (Show, Eq, Ord)
|
|
|
|
{- Gets a History starting with the provided commit, and down to the
|
|
- requested depth. -}
|
|
getHistoryToDepth :: Integer -> Ref -> Repo -> IO (Maybe (History HistoryCommit))
|
|
getHistoryToDepth n commit r = do
|
|
(_, Just inh, _, pid) <- createProcess (gitCreateProcess params r)
|
|
{ std_out = CreatePipe }
|
|
!h <- fmap (truncateHistoryToDepth n)
|
|
. build Nothing
|
|
. map parsehistorycommit
|
|
. lines
|
|
<$> hGetContents inh
|
|
hClose inh
|
|
void $ waitForProcess pid
|
|
return h
|
|
where
|
|
build h [] = fmap (mapHistory fst) h
|
|
build _ (Nothing:_) = Nothing
|
|
build Nothing (Just v:rest) =
|
|
build (Just (History v S.empty)) rest
|
|
build (Just h) (Just v:rest) =
|
|
let h' = traverseadd v h
|
|
in build (Just h') $
|
|
-- detect when all parents down to desired depth
|
|
-- have been found, and avoid processing any more,
|
|
-- this makes it much faster when there is a lot of
|
|
-- history.
|
|
if parentsfound h' then [] else rest
|
|
|
|
traverseadd v@(hc, _ps) (History v'@(hc', ps') s)
|
|
| historyCommit hc `elem` ps' =
|
|
let ps'' = filter (/= historyCommit hc) ps'
|
|
in History (hc', ps'') (S.insert (History v S.empty) s)
|
|
| otherwise = History v' (S.map (traverseadd v) s)
|
|
|
|
parentsfound = parentsfound' 1
|
|
parentsfound' depth (History (_hc, ps) s)
|
|
| not (null ps) = False
|
|
| null ps && depth == n = True
|
|
| depth >= n = True
|
|
| otherwise = all (parentsfound' (succ depth)) (S.toList s)
|
|
|
|
params =
|
|
[ Param "log"
|
|
, Param (fromRef commit)
|
|
, Param "--full-history"
|
|
, Param "--no-abbrev"
|
|
, Param "--format=%T %H %P"
|
|
]
|
|
|
|
parsehistorycommit l = case map extractSha (splitc ' ' l) of
|
|
(Just t:Just c:ps) -> Just $
|
|
( HistoryCommit
|
|
{ historyCommit = c
|
|
, historyCommitTree = t
|
|
, historyCommitParents = catMaybes ps
|
|
}
|
|
, catMaybes ps
|
|
)
|
|
_ -> Nothing
|