module History.PartialCommitInfo ( PartialCommitInfo (..), getPartialCommitInfos, ) where import Control.Exception (catch) import Data.Binary (Binary) import Data.ByteString.Lazy.Char8 qualified as LB8 import Data.Function ((&)) 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, getIssuesPar) import Process (proc, sh) import System.Directory (getCurrentDirectory) import System.FilePath (()) import System.IO.Temp (withSystemTempDirectory) import System.Process.Typed (setWorkingDir) -- | `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 -- TODO Revise `getCommitHashes` -- -- - Should throw if no commits. -- - Should always be reversed? -- -- @difficulty easy commitHashes <- reverse <$> Git.getCommitHashes mapM getCommitInfoOf (map Commit commitHashes ++ [WorkingTree]) getCommitInfoOf :: CommitHash -> IO PartialCommitInfo getCommitInfoOf WorkingTree = do (issuesWorkingTreeChanged, filesChanged) <- getIssuesAndFilesWorkingTreeChanged [] pure $ PartialCommitInfo { hash = WorkingTree, filesChanged = filesChanged, issues = issuesWorkingTreeChanged } getCommitInfoOf (Commit hash) = cached (hash <> (T.pack ".changed")) $ \_ -> do (issuesCommitChanged, filesChanged) <- getIssuesAndFilesCommitChanged hash pure $ PartialCommitInfo { hash = Commit hash, filesChanged = filesChanged, issues = issuesCommitChanged } -- | 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 (getIssuesPar cwd files) dieOfInvalidTreeGrepperResult pure (issues, files) 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 (getIssuesPar 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 )