aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Alexander Foremny <aforemny@posteo.de>2023-12-07 03:55:45 +0100
committerLibravatar Alexander Foremny <aforemny@posteo.de>2023-12-07 03:55:47 +0100
commit3c6e62b75293b6625509ade3c278fc2d4d147c30 (patch)
treeb33f76c2634a771879f9178cff8e5335e43d2f43
parenta5dde0c6e1c1f54a1660f6c2345277927beef30f (diff)
chore: increase performance by caching everything
Initial cache generation is slower, as we are losing out on parallelism.
-rw-r--r--anissue.cabal4
-rw-r--r--app/Debug.hs5
-rw-r--r--app/History.hs267
-rw-r--r--app/History/Cache.hs23
-rw-r--r--app/History/CommitHash.hs16
-rw-r--r--app/History/CommitInfo.hs137
-rw-r--r--app/History/IssueEvent.hs4
-rw-r--r--app/History/PartialCommitInfo.hs138
-rw-r--r--app/Issue.hs2
-rw-r--r--app/Main.hs26
-rw-r--r--app/Patch.hs8
-rw-r--r--app/Text/Diff/Extra.hs30
12 files changed, 338 insertions, 322 deletions
diff --git a/anissue.cabal b/anissue.cabal
index dbd32ed..118567e 100644
--- a/anissue.cabal
+++ b/anissue.cabal
@@ -74,9 +74,7 @@ executable anissue
History
History.Cache
History.CommitHash
- History.CommitInfo
History.IssueEvent
- History.PartialCommitInfo
Issue
Issue.Filter
Issue.Group
@@ -93,6 +91,7 @@ executable anissue
Process
Render
Settings
+ Text.Diff.Extra
TreeGrepper.Comment
TreeGrepper.FileType
TreeGrepper.Match
@@ -113,6 +112,7 @@ executable anissue
diff-parse,
directory,
filepath,
+ generic-deriving,
megaparsec,
optparse-applicative,
parallel-io,
diff --git a/app/Debug.hs b/app/Debug.hs
index c6549a6..6ad9480 100644
--- a/app/Debug.hs
+++ b/app/Debug.hs
@@ -1,4 +1,7 @@
-module Debug where
+module Debug
+ ( debug,
+ )
+where
import Debug.Trace (trace)
import Text.Printf (printf)
diff --git a/app/History.hs b/app/History.hs
index 6a4ddbe..e1ea0ab 100644
--- a/app/History.hs
+++ b/app/History.hs
@@ -1,15 +1,44 @@
module History
- ( getHistory,
+ ( History (..),
+ getHistory,
)
where
-import History.CommitHash (CommitHash)
-import History.CommitInfo (CommitInfo (..), fromPartialCommitInfos, issueEvents)
+import CMark qualified as D
+import Control.Exception (catch, handle, try)
+import Data.Binary (Binary)
+import Data.ByteString.Lazy qualified as LB
+import Data.Function (on, (&))
+import Data.List (deleteFirstsBy, find)
+import Data.List.NonEmpty qualified as N
+import Data.Maybe (catMaybes, isJust)
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as T
+import Data.Text.IO qualified as T
+import Data.Text.Lazy qualified as LT
+import Data.Text.Lazy.Encoding qualified as LT
+import Die (die)
+import Exception qualified as E
+import GHC.Generics (Generic)
+import Git qualified
+import History.Cache (cachedMaybe)
+import History.CommitHash (CommitHash (..))
+import History.CommitHash qualified as C
import History.IssueEvent (IssueEvent (..))
-import History.PartialCommitInfo (getPartialCommitInfos)
-import Issue (Issue)
+import Issue qualified as I
+import Issue.Parser qualified as I
+import Issue.Provenance qualified as I
+import Issue.Tag qualified as I
+import Issue.Text qualified as I
+import Patch qualified as A
+import Process (proc, sh)
+import Render qualified as P
+import System.Directory (getCurrentDirectory)
+import System.FilePath ((</>))
+import System.IO.Temp (withSystemTempDirectory)
+import System.Process.Typed (setWorkingDir)
+import TreeGrepper.Comment qualified as G
import Tuple ()
-import Prelude hiding (id, lines)
-- TODO Reduce cached data size
--
@@ -27,10 +56,222 @@ import Prelude hiding (id, lines)
-- @topic caching
-- @backlog
-getHistory :: IO [(CommitHash, [IssueEvent], [Issue])]
-getHistory = do
- commitInfos <- fromPartialCommitInfos <$> getPartialCommitInfos
- let commitHashes = map (.hash) commitInfos
- issueses = map (.issues) commitInfos
- issueEventses <- map (._2) <$> issueEvents commitInfos
- pure (zip3 commitHashes issueEventses issueses)
+data History = History
+ { commitHash :: CommitHash,
+ issues :: [I.Issue],
+ issueEvents :: [IssueEvent]
+ }
+ deriving (Show, Generic, Binary)
+
+getHistory :: IO History
+getHistory = getHistoryOf WorkingTree
+
+getHistoryOf :: CommitHash -> IO History
+getHistoryOf commitHash = cachedMaybe (C.toText commitHash) do
+ maybeParentCommitHash <- getParentCommitHashOf commitHash
+ case maybeParentCommitHash of
+ Just parentCommitHash -> do
+ parentHistory <- getHistoryOf parentCommitHash
+ scramble <- getScrambleOf commitHash
+ propagate commitHash parentHistory scramble
+ Nothing -> unsafeAssume commitHash =<< getScrambleOf commitHash
+
+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 :: [I.Issue]
+ }
+ deriving (Show, Binary, Generic)
+
+getScrambleOf :: CommitHash -> IO Scramble
+getScrambleOf commitHash@WorkingTree = do
+ (issues, filesChanged) <- getIssuesAndFilesWorkingTreeChanged []
+ pure $ Scramble {..}
+getScrambleOf commitHash@(Commit hash) = do
+ (issues, filesChanged) <- getIssuesAndFilesCommitChanged hash
+ pure $ Scramble {..}
+
+-- | Given the hash of a commit, get all issues in the files which have
+-- been changed by this commit, as well as all changed files.
+getIssuesAndFilesCommitChanged :: T.Text -> IO ([I.Issue], [FilePath])
+getIssuesAndFilesCommitChanged hash = do
+ withSystemTempDirectory "history" $ \tmp -> do
+ let cwd = tmp </> T.unpack hash
+ Git.withWorkingTree cwd hash do
+ files <- gitShowChanged cwd
+ issues <- concat <$> catch (mapM (getIssues cwd) files) dieOfInvalidTreeGrepperResult
+ pure (issues, files)
+
+-- | Get all issues in the given directory and file.
+getIssues :: FilePath -> FilePath -> IO [I.Issue]
+getIssues cwd filename =
+ handle (\(_ :: E.UnknownFileExtension) -> pure []) $
+ fmap catMaybes . mapM (fromComment cwd)
+ =<< G.getComments cwd filename
+
+-- | Note that `provenance` is trivial and needs to be fixed up later.
+fromComment :: FilePath -> G.Comment -> IO (Maybe I.Issue)
+fromComment cwd comment = do
+ commit <- I.commitFromHEAD cwd
+ let provenance = I.Provenance commit commit
+
+ pure $
+ ( \parseResult ->
+ let (markers, title) =
+ I.stripIssueMarkers (T.pack (show (P.render parseResult.heading)))
+ in I.Issue
+ { title = title,
+ description = N.nonEmpty parseResult.paragraphs,
+ file = comment.file,
+ provenance = provenance,
+ start = comment.start,
+ end = comment.end,
+ tags = I.extractTags parseResult.tags,
+ markers = markers,
+ rawText = rawText,
+ commentStyle = commentStyle,
+ comments = N.nonEmpty parseResult.comments,
+ closed = False
+ }
+ )
+ <$> I.parse I.issueMarkers (D.commonmarkToNode [] rawText)
+ where
+ (commentStyle, rawText) = G.uncomment comment.file_type comment.text
+
+dieOfInvalidTreeGrepperResult :: E.InvalidTreeGrepperResult -> IO a
+dieOfInvalidTreeGrepperResult (E.InvalidTreeGrepperResult e) =
+ die e
+
+-- | Gets issues in all files which have been changed in your current
+-- [working
+-- - tree](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefworkingtreeaworkingtree)
+getIssuesAndFilesWorkingTreeChanged :: [FilePath] -> IO ([I.Issue], [FilePath])
+getIssuesAndFilesWorkingTreeChanged paths = do
+ cwd <- getCurrentDirectory
+ files <- gitLsFilesModifiedIn cwd paths
+ issues <- concat <$> catch (mapM (getIssues cwd) files) dieOfInvalidTreeGrepperResult
+ pure (issues, files)
+
+gitShowChanged :: FilePath -> IO [FilePath]
+gitShowChanged cwd =
+ map T.unpack . T.lines . T.decodeUtf8 . LB.toStrict
+ <$> sh ("git show -p --name-only --format=''" & setWorkingDir cwd)
+
+gitLsFilesModifiedIn :: FilePath -> [FilePath] -> IO [FilePath]
+gitLsFilesModifiedIn cwd paths =
+ map T.unpack . T.lines . T.decodeUtf8 . LB.toStrict
+ <$> sh
+ ( proc "git ls-files --modified %" ("--" : paths)
+ & setWorkingDir cwd
+ )
+
+propagate :: CommitHash -> History -> Scramble -> IO History
+propagate commitHash oldHistory scramble = do
+ let issues = propagateIssues oldHistory.issues scramble
+ issueEvents <- propagateIssueEvents oldHistory.issueEvents oldHistory.issues commitHash issues
+ pure $ History {..}
+
+propagateIssues :: [I.Issue] -> Scramble -> [I.Issue]
+propagateIssues oldIssues partialCommitInfo =
+ mergeListsBy
+ eq
+ ( \old new ->
+ new
+ { I.provenance =
+ I.Provenance
+ { first = old.provenance.first,
+ last =
+ if ((/=) `on` (.rawText)) old new
+ then new.provenance.last
+ else old.provenance.last
+ },
+ I.closed = False
+ }
+ )
+ ( \old ->
+ if elemBy eq old partialCommitInfo.issues
+ || not (old.file `elem` partialCommitInfo.filesChanged)
+ then old
+ else old {I.closed = True}
+ )
+ id
+ oldIssues
+ partialCommitInfo.issues
+
+propagateIssueEvents :: [IssueEvent] -> [I.Issue] -> CommitHash -> [I.Issue] -> IO [IssueEvent]
+propagateIssueEvents oldIssueEvents oldIssues commitHash issues =
+ fmap (oldIssueEvents ++) $ newIssueEvents oldIssues commitHash issues
+
+newIssueEvents :: [I.Issue] -> CommitHash -> [I.Issue] -> IO [IssueEvent]
+newIssueEvents oldIssues' commitHash issues' =
+ sequence $
+ concat
+ [ [IssueCreated commitHash issue <$> patchCreated issue | issue <- deleteFirstsBy eq issues oldIssues],
+ [ IssueChanged commitHash oldIssue newIssue <$> patchChanged oldIssue newIssue
+ | (newIssue : oldIssue : _) <- intersectBy' eq issues oldIssues,
+ neq newIssue oldIssue
+ ],
+ [IssueDeleted commitHash issue <$> patchDeleted issue | issue <- deleteFirstsBy eq oldIssues issues]
+ ]
+ where
+ issues = filter (not . (.closed)) issues'
+ oldIssues = filter (not . (.closed)) oldIssues'
+
+ patchCreated new = diff "" new.rawText
+ patchChanged old new = diff old.rawText new.rawText
+ patchDeleted old = diff old.rawText ""
+
+ diff old new = withSystemTempDirectory "diff" $ \tmp -> do
+ let cwd = tmp
+ T.writeFile (tmp </> "old") old
+ T.writeFile (tmp </> "new") new
+ A.parse . LT.toStrict . LT.decodeUtf8 <$> sh ("git diff --no-index -- old new || :" & setWorkingDir cwd)
+
+unsafeAssume :: CommitHash -> Scramble -> IO History
+unsafeAssume commitHash scramble = do
+ let issues = scramble.issues
+ issueEvents <- propagateIssueEvents [] [] commitHash issues
+ pure $ History {..}
+
+eq :: I.Issue -> I.Issue -> Bool
+eq = (==) `on` (.id)
+
+neq :: I.Issue -> I.Issue -> Bool
+neq = (/=) `on` (.rawText)
+
+mergeListsBy :: (a -> a -> Bool) -> (a -> a -> b) -> (a -> b) -> (a -> b) -> [a] -> [a] -> [b]
+mergeListsBy eq onBoth onLeft onRight lefts rights =
+ concat
+ [ [ maybe (onLeft left) (onBoth left) right
+ | left <- lefts,
+ right <-
+ let rights' = filter (eq left) rights
+ in if null rights' then [Nothing] else (map Just rights')
+ ],
+ [ onRight right
+ | right <- rights,
+ not (elemBy eq right lefts)
+ ]
+ ]
+
+-- | A variant of `Data.List.intersectBy` that retuns the witnesses of the
+-- intersection.
+intersectBy' :: (a -> a -> Bool) -> [a] -> [a] -> [[a]]
+intersectBy' eq xs ys = filter (not . null) (map (\x -> x : filter (eq x) ys) xs)
+
+-- | A variant of `elem` that uses a custom comparison function.
+elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
+elemBy eq x xs = isJust $ find (eq x) xs
diff --git a/app/History/Cache.hs b/app/History/Cache.hs
index d0473e2..978f3d9 100644
--- a/app/History/Cache.hs
+++ b/app/History/Cache.hs
@@ -1,24 +1,33 @@
-module History.Cache (cached) where
+module History.Cache
+ ( cached,
+ cachedMaybe,
+ )
+where
import Data.Binary (Binary, decodeFileOrFail, encodeFile)
import Data.Text qualified as T
+import Debug
import Git qualified
import System.Directory (createDirectoryIfMissing, doesFileExist)
import System.FilePath ((</>))
-cached :: Binary a => T.Text -> (T.Text -> IO a) -> IO a
-cached hash func = do
+cached :: Binary a => T.Text -> IO a -> IO a
+cached key func = do
root <- Git.getRootDir
createDirectoryIfMissing True (root </> ".anissue")
- let file = (root </> ".anissue" </> T.unpack hash)
+ let file = (root </> ".anissue" </> T.unpack key)
doesFileExist file >>= \case
True ->
decodeFileOrFail file >>= \case
- Left _ -> generate file
+ Left e -> debug "e" e `seq` generate file
Right blob -> pure blob
False -> generate file
where
generate file = do
- blob <- func hash
- encodeFile file blob
+ blob <- func
+ encodeFile (debug "cache miss" file) blob
pure blob
+
+cachedMaybe :: Binary a => Maybe T.Text -> IO a -> IO a
+cachedMaybe Nothing func = func
+cachedMaybe (Just key) func = cached key func
diff --git a/app/History/CommitHash.hs b/app/History/CommitHash.hs
index cbe4db1..1075b2f 100644
--- a/app/History/CommitHash.hs
+++ b/app/History/CommitHash.hs
@@ -6,6 +6,7 @@ module History.CommitHash
where
import Data.Binary (Binary)
+import Data.Maybe (fromMaybe)
import Data.Text qualified as T
import GHC.Generics (Generic)
import Render qualified as P
@@ -15,13 +16,12 @@ data CommitHash
| Commit T.Text
deriving (Eq, Show, Binary, Generic)
-toShortText :: CommitHash -> T.Text
-toShortText WorkingTree = "<dirty>"
-toShortText (Commit hash) = T.take 7 hash
+toShortText :: CommitHash -> Maybe T.Text
+toShortText = fmap (T.take 7) . toText
-toText :: CommitHash -> T.Text
-toText WorkingTree = "<dirty>"
-toText (Commit hash) = hash
+toText :: CommitHash -> Maybe T.Text
+toText WorkingTree = Nothing
+toText (Commit hash) = Just hash
instance P.Render CommitHash where
render = P.render . P.Detailed
@@ -29,9 +29,9 @@ instance P.Render CommitHash where
instance P.Render (P.Detailed CommitHash) where
render (P.Detailed commitHash) =
P.styled [P.color P.Yellow] $
- P.render (toText commitHash)
+ P.render (fromMaybe "<dirty>" (toText commitHash))
instance P.Render (P.Summarized CommitHash) where
render (P.Summarized commitHash) =
P.styled [P.color P.Yellow] $
- P.render (toShortText commitHash)
+ P.render (fromMaybe "<dirty>" (toShortText commitHash))
diff --git a/app/History/CommitInfo.hs b/app/History/CommitInfo.hs
deleted file mode 100644
index 2c861a6..0000000
--- a/app/History/CommitInfo.hs
+++ /dev/null
@@ -1,137 +0,0 @@
-module History.CommitInfo
- ( CommitInfo (..),
- fromPartialCommitInfos,
- issueEvents,
- diffCommitInfos,
- )
-where
-
-import Data.Binary (Binary)
-import Data.Function (on, (&))
-import Data.List (deleteFirstsBy, find)
-import Data.Maybe (isJust)
-import Data.Text.IO qualified as T
-import Data.Text.Lazy qualified as LT
-import Data.Text.Lazy.Encoding qualified as LT
-import GHC.Generics (Generic)
-import History.CommitHash (CommitHash)
-import History.IssueEvent (IssueEvent (..))
-import History.PartialCommitInfo (PartialCommitInfo (..))
-import Issue (Issue (..))
-import Issue.Provenance qualified as I
-import Parallel (parSequence)
-import Patch qualified as A
-import Process (sh)
-import System.FilePath ((</>))
-import System.IO.Temp (withSystemTempDirectory)
-import System.Process.Typed (setWorkingDir)
-
--- TODO Change `CommitInfo` -> `CommitIssuesAll`
-data CommitInfo = CommitInfo
- { hash :: CommitHash,
- issues :: [Issue]
- }
- deriving (Show, Binary, Generic)
-
-fromPartialCommitInfos :: [PartialCommitInfo] -> [CommitInfo]
-fromPartialCommitInfos [] = []
-fromPartialCommitInfos (partialCommitInfo : partialCommitInfos) =
- scanl propagate (assume partialCommitInfo) partialCommitInfos
- where
- assume :: PartialCommitInfo -> CommitInfo
- assume (PartialCommitInfo {..}) = CommitInfo {..}
-
- propagate :: CommitInfo -> PartialCommitInfo -> CommitInfo
- propagate oldInfo newInfo@(PartialCommitInfo {..}) =
- CommitInfo
- { issues =
- mergeListsBy
- eq
- ( \old new ->
- new
- { provenance =
- I.Provenance
- { first = old.provenance.first,
- last =
- if ((/=) `on` (.rawText)) old new
- then new.provenance.last
- else old.provenance.last
- },
- closed = False
- }
- )
- ( \old ->
- if elemBy eq old newInfo.issues
- || not (old.file `elem` newInfo.filesChanged)
- then old
- else old {closed = True}
- )
- id
- oldInfo.issues
- newInfo.issues,
- ..
- }
-
- eq = (==) `on` (.id)
-
--- | We assume that [CommitInfo] is sorted starting with the oldest
--- commits.
-issueEvents :: [CommitInfo] -> IO [(CommitHash, [IssueEvent])]
-issueEvents xs = zip (map (.hash) xs) <$> parSequence (zipWith diffCommitInfos predecessors xs)
- where
- predecessors = Nothing : map Just xs
-
-diffCommitInfos :: Maybe CommitInfo -> CommitInfo -> IO [IssueEvent]
-diffCommitInfos maybeOldInfo newInfo =
- sequence $
- concat
- [ [IssueCreated newHash issue <$> patchCreated issue | issue <- deleteFirstsBy eq newIssues oldIssues],
- [ IssueChanged newHash oldIssue newIssue <$> patchChanged oldIssue newIssue
- | (newIssue : oldIssue : _) <- intersectBy' eq newIssues oldIssues,
- neq newIssue oldIssue
- ],
- [IssueDeleted newHash issue <$> patchDeleted issue | issue <- deleteFirstsBy eq oldIssues newIssues]
- ]
- where
- newHash = newInfo.hash
- newIssues' = newInfo.issues
- oldIssues' = maybe [] (.issues) maybeOldInfo
- newIssues = filter (not . (.closed)) newIssues'
- oldIssues = filter (not . (.closed)) oldIssues'
-
- eq = (==) `on` (.id)
- neq = (/=) `on` (.rawText)
-
- patchCreated new = diff "" new.rawText
- patchChanged old new = diff old.rawText new.rawText
- patchDeleted old = diff old.rawText ""
-
- diff old new = withSystemTempDirectory "diff" $ \tmp -> do
- let cwd = tmp
- T.writeFile (tmp </> "old") old
- T.writeFile (tmp </> "new") new
- A.parse . LT.toStrict . LT.decodeUtf8 <$> sh ("git diff --no-index -- old new || :" & setWorkingDir cwd)
-
-mergeListsBy :: (a -> a -> Bool) -> (a -> a -> b) -> (a -> b) -> (a -> b) -> [a] -> [a] -> [b]
-mergeListsBy eq onBoth onLeft onRight lefts rights =
- concat
- [ [ maybe (onLeft left) (onBoth left) right
- | left <- lefts,
- right <-
- let rights' = filter (eq left) rights
- in if null rights' then [Nothing] else (map Just rights')
- ],
- [ onRight right
- | right <- rights,
- not (elemBy eq right lefts)
- ]
- ]
-
--- | A variant of `Data.List.intersectBy` that retuns the witnesses of the
--- intersection.
-intersectBy' :: (a -> a -> Bool) -> [a] -> [a] -> [[a]]
-intersectBy' eq xs ys = filter (not . null) (map (\x -> x : filter (eq x) ys) xs)
-
--- | A variant of `elem` that uses a custom comparison function.
-elemBy :: (a -> a -> Bool) -> a -> [a] -> Bool
-elemBy eq x xs = isJust $ find (eq x) xs
diff --git a/app/History/IssueEvent.hs b/app/History/IssueEvent.hs
index 93bd133..932cfd9 100644
--- a/app/History/IssueEvent.hs
+++ b/app/History/IssueEvent.hs
@@ -1,5 +1,7 @@
module History.IssueEvent (IssueEvent (..)) where
+import Data.Binary (Binary)
+import GHC.Generics (Generic)
import History.CommitHash (CommitHash)
import Issue (Issue)
import Issue.Render qualified as I
@@ -24,7 +26,7 @@ data IssueEvent
issue :: Issue,
patch :: Patch
}
- deriving (Show)
+ deriving (Show, Generic, Binary)
instance P.Render IssueEvent where
render = P.render . P.Detailed
diff --git a/app/History/PartialCommitInfo.hs b/app/History/PartialCommitInfo.hs
deleted file mode 100644
index 6d93e88..0000000
--- a/app/History/PartialCommitInfo.hs
+++ /dev/null
@@ -1,138 +0,0 @@
-module History.PartialCommitInfo
- ( PartialCommitInfo (..),
- getPartialCommitInfos,
- )
-where
-
-import CMark qualified as D
-import Control.Exception (catch, handle)
-import Data.Binary (Binary)
-import Data.ByteString.Lazy.Char8 qualified as LB8
-import Data.Function ((&))
-import Data.List.NonEmpty qualified as N
-import Data.Maybe (catMaybes)
-import Data.Text qualified as T
-import Die (die)
-import Exception qualified as E
-import GHC.Generics (Generic)
-import Git qualified
-import History.Cache (cached)
-import History.CommitHash (CommitHash (..))
-import Issue (Issue (..))
-import Issue.Parser qualified as I
-import Issue.Provenance qualified as I
-import Issue.Tag qualified as I
-import Issue.Text qualified as I
-import Parallel (parMapM)
-import Process (proc, sh)
-import Render qualified as P
-import System.Directory (getCurrentDirectory)
-import System.FilePath ((</>))
-import System.IO.Temp (withSystemTempDirectory)
-import System.Process.Typed (setWorkingDir)
-import TreeGrepper.Comment qualified as G
-
--- | `PartialCommitInfo` records the complete issues ONLY in files that have
--- been changed in the commit.
--- TODO Change `PartialCommitInfo` -> `CommitIssuesChanged`
-data PartialCommitInfo = PartialCommitInfo
- { hash :: CommitHash,
- filesChanged :: [FilePath],
- issues :: [Issue]
- }
- deriving (Show, Binary, Generic)
-
-getPartialCommitInfos :: IO [PartialCommitInfo]
-getPartialCommitInfos = do
- commitHashes <- N.toList <$> Git.getCommitHashes
- parMapM getCommitInfoOf (map Commit commitHashes ++ [WorkingTree])
-
-getCommitInfoOf :: CommitHash -> IO PartialCommitInfo
-getCommitInfoOf WorkingTree = do
- (issues, filesChanged) <- getIssuesAndFilesWorkingTreeChanged []
- pure $
- PartialCommitInfo
- { hash = WorkingTree,
- ..
- }
-getCommitInfoOf (Commit hash) = cached (hash <> (T.pack ".changed")) $ \_ -> do
- (issues, filesChanged) <- getIssuesAndFilesCommitChanged hash
- pure $
- PartialCommitInfo
- { hash = Commit hash,
- ..
- }
-
--- | Given the hash of a commit, get all issues in the files which have
--- been changed by this commit, as well as all changed files.
-getIssuesAndFilesCommitChanged :: T.Text -> IO ([Issue], [FilePath])
-getIssuesAndFilesCommitChanged hash = do
- withSystemTempDirectory "history" $ \tmp -> do
- let cwd = tmp </> T.unpack hash
- Git.withWorkingTree cwd hash do
- files <- gitShowChanged cwd
- issues <- concat <$> catch (mapM (getIssues cwd) files) dieOfInvalidTreeGrepperResult
- pure (issues, files)
-
--- | Get all issues in the given directory and file.
-getIssues :: FilePath -> FilePath -> IO [Issue]
-getIssues cwd filename =
- handle (\(_ :: E.UnknownFileExtension) -> pure []) $
- fmap catMaybes . mapM (fromComment cwd)
- =<< G.getComments cwd filename
-
--- | Note that `provenance` is trivial and needs to be fixed up later.
-fromComment :: FilePath -> G.Comment -> IO (Maybe Issue)
-fromComment cwd comment = do
- commit <- I.commitFromHEAD cwd
- let provenance = I.Provenance commit commit
-
- pure $
- ( \parseResult ->
- let (markers, title) =
- I.stripIssueMarkers (T.pack (show (P.render parseResult.heading)))
- in Issue
- { title = title,
- description = N.nonEmpty parseResult.paragraphs,
- file = comment.file,
- provenance = provenance,
- start = comment.start,
- end = comment.end,
- tags = I.extractTags parseResult.tags,
- markers = markers,
- rawText = rawText,
- commentStyle = commentStyle,
- comments = N.nonEmpty parseResult.comments,
- closed = False
- }
- )
- <$> I.parse I.issueMarkers (D.commonmarkToNode [] rawText)
- where
- (commentStyle, rawText) = G.uncomment comment.file_type comment.text
-
-dieOfInvalidTreeGrepperResult :: E.InvalidTreeGrepperResult -> IO a
-dieOfInvalidTreeGrepperResult (E.InvalidTreeGrepperResult e) =
- die e
-
--- | Gets issues in all files which have been changed in your current
--- [working
--- - tree](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefworkingtreeaworkingtree)
-getIssuesAndFilesWorkingTreeChanged :: [FilePath] -> IO ([Issue], [FilePath])
-getIssuesAndFilesWorkingTreeChanged paths = do
- cwd <- getCurrentDirectory
- files <- gitLsFilesModifiedIn cwd paths
- issues <- concat <$> catch (mapM (getIssues cwd) files) dieOfInvalidTreeGrepperResult
- pure (issues, files)
-
-gitShowChanged :: FilePath -> IO [FilePath]
-gitShowChanged cwd =
- Prelude.lines . LB8.unpack
- <$> sh ("git show -p --name-only --format=''" & setWorkingDir cwd)
-
-gitLsFilesModifiedIn :: FilePath -> [FilePath] -> IO [FilePath]
-gitLsFilesModifiedIn cwd paths =
- Prelude.lines . LB8.unpack
- <$> sh
- ( proc "git ls-files --modified %" ("--" : paths)
- & setWorkingDir cwd
- )
diff --git a/app/Issue.hs b/app/Issue.hs
index 303862d..2b9e568 100644
--- a/app/Issue.hs
+++ b/app/Issue.hs
@@ -44,7 +44,7 @@ data Issue = Issue
--
-- @related reduce-cached-data-size
instance Binary D.Node where
- put = put . show . P.render
+ put = put . T.pack . show . P.render
get = D.commonmarkToNode [] <$> get
id :: Issue -> T.Text
diff --git a/app/Main.hs b/app/Main.hs
index df63624..fe802ad 100644
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -353,7 +353,7 @@ import Data.Text qualified as T
import Data.Text.IO qualified as T
import Data.Text.Lazy qualified as LT
import Data.Text.Lazy.IO qualified as LT
-import History (getHistory)
+import History (History (..), getHistory)
import Issue (Issue (..))
import Issue qualified as I
import Issue.Filter qualified as I
@@ -517,7 +517,10 @@ idArg =
O.strArgument
( O.metavar "ID"
<> O.completer
- (O.listIOCompleter $ map (T.unpack . I.id) . (._3) . last <$> getHistory)
+ ( O.listIOCompleter $
+ map (T.unpack . I.id) . (.issues)
+ <$> getHistory
+ )
)
editFlag :: O.Parser Bool
@@ -550,8 +553,7 @@ main = do
. I.applyFilters filters
. I.applyPath files
. I.applyClosed closed
- . (._3)
- . last
+ . (.issues)
<$> getHistory
let groupedIssues = I.groupIssuesByTag group ungroupedIssues
putDoc colorize noPager width (group, groupedIssues)
@@ -561,28 +563,26 @@ main = do
. I.applyFilters filters
. I.applyPath files
. I.applyClosed closed
- . (._3)
- . last
+ . (.issues)
<$> getHistory
putDoc colorize noPager width . (P.vsep . intersperse "") $
map (P.render . P.Summarized) issues
Options {colorize, noPager, width, command = Log {patch}} -> do
- ess <- concatMap (._2) . reverse <$> getHistory
+ es <- reverse . (.issueEvents) <$> getHistory
putDoc colorize noPager width . P.vsep $
if patch
- then map (P.render . P.Detailed) ess
- else map (P.render . P.Summarized) ess
+ then map (P.render . P.Detailed) es
+ else map (P.render . P.Summarized) es
Options {colorize, noPager, width, command = Show {id = Nothing}} -> do
issues <-
I.applySorts []
. I.applyFilters []
. I.applyClosed False
- . (._3)
- . last
+ . (.issues)
<$> getHistory
putDoc colorize noPager width . P.vsep $ map (showIssue issues) issues
Options {colorize, noPager, width, command = Show {id = Just id, edit}} -> do
- issues <- (._3) . last <$> getHistory
+ issues <- (.issues) <$> getHistory
issue <-
case find ((==) id . T.unpack . I.id) issues of
Nothing -> die (printf "no issue with id `%s'\n" id)
@@ -597,7 +597,7 @@ main = do
I.replaceText issue =<< T.readFile fp
else putDoc colorize noPager width $ showIssue issues issue
Options {colorize, noPager, width, internalTags, command = Tags} -> do
- issues <- (._3) . last <$> getHistory
+ issues <- (.issues) <$> getHistory
let tags =
concatMap
( \issue ->
diff --git a/app/Patch.hs b/app/Patch.hs
index 0600a34..9e6ed88 100644
--- a/app/Patch.hs
+++ b/app/Patch.hs
@@ -1,3 +1,5 @@
+{-# LANGUAGE DerivingStrategies #-}
+
module Patch
( Patch,
parse,
@@ -5,17 +7,21 @@ module Patch
where
import Control.Exception (throw)
+import Data.Binary (Binary (..))
import Data.Text qualified as T
import Exception qualified as E
+import GHC.Generics (Generic)
import Render ((<<<))
import Render qualified as P
+import Text.Diff.Extra ()
import Text.Diff.Parse qualified as D
import Text.Diff.Parse.Types qualified as D
newtype Patch = Patch
{ fileDeltas :: D.FileDeltas
}
- deriving (Show)
+ deriving (Show, Generic)
+ deriving newtype (Binary)
parse :: T.Text -> Patch
parse = either (throw . E.InvalidDiff) Patch . D.parseDiff
diff --git a/app/Text/Diff/Extra.hs b/app/Text/Diff/Extra.hs
new file mode 100644
index 0000000..f558495
--- /dev/null
+++ b/app/Text/Diff/Extra.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module Text.Diff.Extra where
+
+import Data.Binary (Binary)
+import Generics.Deriving.TH (deriveAll0)
+import Text.Diff.Parse.Types
+
+deriveAll0 ''FileDelta
+deriveAll0 ''FileStatus
+deriveAll0 ''Content
+deriveAll0 ''Hunk
+deriveAll0 ''Range
+deriveAll0 ''Line
+deriveAll0 ''Annotation
+
+instance Binary FileDelta
+
+instance Binary FileStatus
+
+instance Binary Content
+
+instance Binary Hunk
+
+instance Binary Range
+
+instance Binary Line
+
+instance Binary Annotation