content: {-# LANGUAGE NamedFieldPuns #-}
  {-# LANGUAGE NoImplicitPrelude #-}
  module Stackage.Database.Github
      ( cloneOrUpdate
      , lastGitFileUpdate
      , getStackageContentDir
      , getBackupCoreCabalFilesDir
      , GithubRepo(..)
      ) where
  
  import qualified Data.ByteString.Lazy.Char8 as LBS8
  import RIO
  import RIO.Directory
  import RIO.FilePath
  import RIO.Process
  import RIO.Time
  
  
  data GithubRepo = GithubRepo
      { grAccount :: !String
      , grName    :: !String
      } deriving Show
  
  gitLog
    :: (MonadReader env m, HasLogFunc env, HasProcessContext env,
        MonadIO m) =>
       FilePath -> String -> [String] -> m LBS8.ByteString
  gitLog gitDir filePath args =
      withWorkingDir gitDir $ proc "git" ("log" : (args ++ [filePath])) readProcessStdout_
  
  
  -- | From the git commit log infer the timestamp when the file was changed last .
  lastGitFileUpdate ::
         (MonadReader env m, HasLogFunc env, HasProcessContext env, MonadUnliftIO m)
      => FilePath -- ^ Root dir of the repository
      -> FilePath -- ^ Relative path of the file
      -> m (Maybe UTCTime)
  lastGitFileUpdate gitDir filePath = do
      lastCommitTimestamps <- gitLog gitDir filePath ["-1", "--format=%cD"]
      parseGitDate rfc822DateFormat lastCommitTimestamps
    where
      parseGitDate fmt dates =
          case listToMaybe $ LBS8.lines dates of
              Nothing -> do
                  logError "Git log is empty for the file"
                  return Nothing
              Just lbsDate -> do
                  let parseDateTime = parseTimeM False defaultTimeLocale fmt (LBS8.unpack lbsDate)
                  catchAny (Just <$> liftIO parseDateTime) $ \exc -> do
                      logError $
                          "Error parsing git commit date: " <> fromString (displayException exc)
                      pure Nothing
  
  -- | Clone a repository locally. In case when repository is already present sync it up with
  -- remote. Returns the full path where repository was cloned into.
  cloneOrUpdate ::
         (MonadReader env m, HasLogFunc env, HasProcessContext env, MonadIO m)
      => FilePath -- ^ Path where the repo should be cloned
      -> GithubRepo -- ^ Github user or organization name together with repository name
      -> m FilePath
  cloneOrUpdate root GithubRepo {grAccount, grName} = do
      exists <- doesDirectoryExist dest
      if exists
          then withWorkingDir dest $ do
              proc "git" ["fetch"] runProcess_
              proc "git" ["reset", "--hard", "origin/master"] runProcess_
          else withWorkingDir root $
              proc "git" ["clone", url, grName] runProcess_
      return dest
    where
      url = "https://github.com/" <> grAccount <> "/" <> grName <> ".git"
      dest = root </> grName
  
  
  
  getStackageContentDir ::
         (MonadReader env m, HasLogFunc env, HasProcessContext env, MonadIO m)
      => FilePath
      -> m FilePath
  getStackageContentDir rootDir =
      cloneOrUpdate rootDir (GithubRepo "commercialhaskell" "stackage-content")
  
  -- | Use backup location with cabal files, hackage doesn't have all of them.
  getBackupCoreCabalFilesDir ::
         (MonadReader env m, HasLogFunc env, HasProcessContext env, MonadIO m)
      => FilePath
      -> m FilePath
  getBackupCoreCabalFilesDir rootDir =
      cloneOrUpdate rootDir (GithubRepo "commercialhaskell" "core-cabal-files")
  