diff options
Diffstat (limited to 'app/History.hs')
-rw-r--r-- | app/History.hs | 217 |
1 files changed, 15 insertions, 202 deletions
diff --git a/app/History.hs b/app/History.hs index c0427fa..d423907 100644 --- a/app/History.hs +++ b/app/History.hs @@ -1,143 +1,38 @@ module History ( Issues (..), - getIssues, IssueEvents (..), + getIssues, getIssueEvents, getIssuesOfFile, ) where -import CMark qualified as D -import Cache (cachedMaybe) import Comment qualified as G -import Control.Arrow (first) -import Control.Exception (Handler (..), catch, catches, try) -import Data.Binary (Binary) -import Data.ByteString.Lazy qualified as LB -import Data.Digest.Pure.SHA qualified as S -import Data.Function (on) -import Data.Map qualified as M +import Control.Exception (Handler (..), catches) +import Data.List.NonEmpty qualified as NE import Data.Maybe (catMaybes) -import Data.Text qualified as T -import Data.Text.Encoding qualified as T -import Data.Text.Lazy qualified as LT -import Data.Text.Lazy.Encoding qualified as LT -import Die (die) +import Data.Proxy (Proxy (Proxy)) import Exception qualified as E -import GHC.Generics (Generic) import Git qualified -import Git.CommitHash (CommitHash (..)) -import Git.CommitHash qualified as C +import History.IssueEvents (IssueEvents (..)) +import History.Issues (Issues (..)) +import History.Plan (formulate, realise) +import History.Scramble (fromComment) import Issue qualified as I -import Issue.Parser qualified as I -import Issue.Tag qualified as I -import Issue.Text qualified as I -import IssueEvent (IssueEvent (..)) import Parallel (parMapM) -import Process (proc, sh) -import Render qualified as P -import Tuple () - --- TODO Reduce cached data size --- --- Right now we are caching complete `Issue` instances, which --- contain the full issue title and description. For a fast --- lookup it may already be enough to only store the issue's --- --- \* filename --- \* start position --- \* end position --- --- With this information we can use git to quickly look up the --- complete issue text and parse it. --- --- @topic caching --- @backlog - -data Issues = Issues - { commitHash :: CommitHash, - issues :: M.Map T.Text I.Issue - } - deriving (Show, Generic, Binary) getIssues :: IO Issues -getIssues = getIssuesOf WorkingTree - -getIssuesOf :: CommitHash -> IO Issues -getIssuesOf commitHash = cachedMaybe (fmap ("issues-" <>) (C.toText commitHash)) do - maybeParentCommitHash <- getParentCommitHashOf commitHash - case maybeParentCommitHash of - Just parentCommitHash -> do - oldIssues <- (.issues) <$> getIssuesOf parentCommitHash - scramble <- getScrambleOf commitHash - let issues = propagateIssues oldIssues scramble - pure Issues {..} - Nothing -> do - scramble <- getScrambleOf commitHash - let issues = scramble.issues - pure Issues {..} - -data IssueEvents = IssueEvents - { commitHash :: CommitHash, - issueEvents :: [IssueEvent] - } - deriving (Show, Generic, Binary) +getIssues = + realise . (formulate Proxy) . NE.fromList + =<< Git.getCommitHashes Nothing (Just Git.WorkingTree) getIssueEvents :: IO IssueEvents -getIssueEvents = getIssueEventsOf WorkingTree - -getIssueEventsOf :: CommitHash -> IO IssueEvents -getIssueEventsOf commitHash = cachedMaybe (fmap ("events-" <>) (C.toText commitHash)) do - maybeParentCommitHash <- getParentCommitHashOf commitHash - case maybeParentCommitHash of - Just parentCommitHash -> do - oldIssues <- (.issues) <$> getIssuesOf parentCommitHash - issues <- (.issues) <$> getIssuesOf commitHash - oldIssueEvents <- (.issueEvents) <$> getIssueEventsOf parentCommitHash - let issueEvents = propagateIssueEvents oldIssueEvents oldIssues commitHash issues - pure IssueEvents {..} - Nothing -> do - scramble <- getScrambleOf commitHash - let issueEvents = propagateIssueEvents [] M.empty commitHash scramble.issues - pure IssueEvents {..} - -getParentCommitHashOf :: CommitHash -> IO (Maybe CommitHash) -getParentCommitHashOf commitHash = - either - (\_ -> Nothing) - (Just . Commit . T.strip . T.decodeUtf8 . LB.toStrict) - <$> try @E.ProcessException - ( case commitHash of - WorkingTree -> sh "git show -s --format=%H HEAD" - Commit hash -> sh (proc "git show -s --format=%%H %^" hash) - ) - --- | `Scramble` records the complete issues ONLY in files that have --- been changed in the commit. -data Scramble = Scramble - { commitHash :: CommitHash, - filesChanged :: [FilePath], - issues :: M.Map T.Text I.Issue - } - deriving (Show, Binary, Generic) - -getScrambleOf :: CommitHash -> IO Scramble -getScrambleOf commitHash = do - (issues, filesChanged) <- first (M.fromList . map (\i -> (i.id, i))) <$> getIssuesAndFilesChanged commitHash - pure $ Scramble {..} - -getIssuesAndFilesChanged :: CommitHash -> IO ([I.Issue], [FilePath]) -getIssuesAndFilesChanged commitHash = do - files <- Git.getChangedFilesOf commitHash - issues <- - concat - <$> catch - (parMapM (getIssuesOfFile commitHash) files) - (\(e :: E.InvalidTreeGrepperResult) -> die (show e)) - pure (issues, files) +getIssueEvents = + realise . (formulate Proxy) . NE.fromList + =<< Git.getCommitHashes Nothing (Just Git.WorkingTree) -- | Get all issues in the given directory and file. -getIssuesOfFile :: CommitHash -> FilePath -> IO [I.Issue] +getIssuesOfFile :: Git.CommitHash -> FilePath -> IO [I.Issue] getIssuesOfFile commitHash filename = ( fmap catMaybes . parMapM (fromComment commitHash) =<< G.getComments commitHash filename @@ -145,85 +40,3 @@ getIssuesOfFile commitHash filename = `catches` [ Handler \(_ :: E.UnknownFile) -> pure [], Handler \(_ :: E.UnsupportedLanguage) -> pure [] ] - --- | Note that `provenance` is trivial and needs to be fixed up later. -fromComment :: CommitHash -> G.Comment -> IO (Maybe I.Issue) -fromComment commitHash comment = do - commit <- Git.getCommitOf commitHash - let provenance = I.Provenance commit commit - - pure $ - ( \parseResult -> - let (markers, title) = - I.stripIssueMarkers (T.pack (show (P.render parseResult.heading))) - in I.Issue - { commitHash = commitHash, - language = comment.language, - rawTextHash = S.sha1 (LT.encodeUtf8 (LT.fromStrict rawText)), - title = title, - file = comment.filePath, - provenance = provenance, - startByte = comment.startByte, - endByte = comment.endByte, - startPoint = comment.startPoint, - endPoint = comment.endPoint, - tags = I.extractTags parseResult.tags, - markers = markers, - commentStyle = commentStyle, - closed = False - } - ) - <$> I.parse I.issueMarkers (D.commonmarkToNode [] rawText) - where - (commentStyle, rawText) = G.uncomment comment.language comment.text - -propagateIssues :: M.Map T.Text I.Issue -> Scramble -> M.Map T.Text I.Issue -propagateIssues oldIssues scramble = - M.mergeWithKey - ( \_ old new -> - Just $ - new - { I.provenance = - I.Provenance - { first = old.provenance.first, - last = - if ((/=) `on` (.rawTextHash)) old new - then new.provenance.last - else old.provenance.last - }, - I.closed = False - } - ) - ( M.map - ( \old -> - if M.member old.id scramble.issues - || not (old.file `elem` scramble.filesChanged) - then old - else old {I.closed = True} - ) - ) - id - oldIssues - scramble.issues - -propagateIssueEvents :: [IssueEvent] -> M.Map T.Text I.Issue -> CommitHash -> M.Map T.Text I.Issue -> [IssueEvent] -propagateIssueEvents oldIssueEvents oldIssues commitHash issues = - oldIssueEvents ++ newIssueEvents oldIssues commitHash issues - -newIssueEvents :: M.Map T.Text I.Issue -> CommitHash -> M.Map T.Text I.Issue -> [IssueEvent] -newIssueEvents oldIssues' commitHash issues' = - concat - [ [ IssueCreated commitHash issue - | issue <- M.elems (issues `M.difference` oldIssues) - ], - [ IssueChanged commitHash oldIssue newIssue - | (newIssue, oldIssue) <- M.elems (M.intersectionWith (,) issues oldIssues), - ((/=) `on` (.rawTextHash)) newIssue oldIssue - ], - [ IssueDeleted commitHash issue {I.closed = True} - | issue <- M.elems (oldIssues `M.difference` issues) - ] - ] - where - issues = M.filter (not . (.closed)) issues' - oldIssues = M.filter (not . (.closed)) oldIssues' |