{-| Functions for shelling out to @git@ to work with git repositories.
-}


module FeedGipeda.GitShell
  ( isRepositoryRoot
  , fetch
  , allCommits
  , firstCommit
  , remoteRepo
  , showHead
  , sync
  , SHA
  ) where


import           Data.Char        (isSpace)
import           Data.Functor
import           Data.Maybe       (listToMaybe)
import           Data.Set         (Set)
import qualified Data.Set         as Set
import           FeedGipeda.Repo  (Repo)
import qualified FeedGipeda.Repo  as Repo
import           System.Directory (createDirectoryIfMissing)
import           System.Exit      (ExitCode (..))
import           System.Process   (callProcess, readProcessWithExitCode)


type SHA
  = String


isRepositoryRoot :: FilePath -> IO Bool
isRepositoryRoot path = do
  (_, stdout, _)  <- readProcessWithExitCode
    "git" ["-C", path, "rev-parse", "--git-dir"] ""
  -- testing for ".git" and "." (bare repo) should be good enough.
  (return . maybe False (`elem` [".git", "."]) . listToMaybe . lines) stdout


mirror :: Repo -> FilePath -> IO ()
mirror repo path = do
  (_, _, _)  <- readProcessWithExitCode
    "git" ["clone", "--mirror", "--quiet", Repo.uri repo, path] ""
  return ()


remoteRepo :: FilePath -> IO Repo
remoteRepo path = do
  (_, stdout, _)  <- readProcessWithExitCode
    "git" ["-C", path, "ls-remote", "--get-url", "origin"] ""
  return (Repo.unsafeFromString (init stdout)) -- strip the \n with init


fetch :: FilePath -> IO ()
fetch path =
  callProcess "git" ["-C", path, "fetch", "--quiet"]


{-| @sync repo@ tries to fetch updates from the remote @repo@ or creates a
    mirror of @repo@ if there isn't already a local clone present.
-}
sync :: Repo -> IO ()
sync repo = do
  path <- Repo.cloneDir repo
  hasClone <- isRepositoryRoot path
  if hasClone
    then fetch path
    else do
      createDirectoryIfMissing True path
      mirror repo path


allCommits :: FilePath -> IO (Set SHA)
allCommits path =
  Set.fromList <$> gitLogImpl path []


firstCommit :: FilePath -> IO (Maybe SHA)
firstCommit path =
  listToMaybe <$> gitLogImpl path ["--reverse"]


showHead :: FilePath -> FilePath -> IO (Maybe String)
showHead path file = do
  let
    allArgs = ["-C", path, "show", "HEAD:" ++ file]
  (exitCode, stdout, stderr) <- readProcessWithExitCode "git" allArgs ""
  case exitCode of
    ExitSuccess -> return (Just stdout)
    ExitFailure _ -> return Nothing


gitLogImpl :: FilePath -> [String] -> IO [SHA]
gitLogImpl path additionalArgs = do
  let
    allArgs = ["-C", path, "log", "--format=format:%H"] ++ additionalArgs
  (_, stdout, _) <- readProcessWithExitCode "git" allArgs ""
  return (filter (not . null) (lines stdout))