diff options
author | Roman Smrž <roman.smrz@seznam.cz> | 2025-03-05 20:42:14 +0100 |
---|---|---|
committer | Roman Smrž <roman.smrz@seznam.cz> | 2025-03-06 20:04:14 +0100 |
commit | 0658710f7fcd2ac57abfaf1c387ef363a4a889da (patch) | |
tree | 7ceeba0d9b72d5a96a0add32f8b299088f211108 | |
parent | a8deb42b4899ce11d1937bda0b59c8b56f230bce (diff) |
Checkout command
Changelog: Added `checkout` command
-rw-r--r-- | minici.cabal | 1 | ||||
-rw-r--r-- | src/Command.hs | 30 | ||||
-rw-r--r-- | src/Command/Checkout.hs | 34 | ||||
-rw-r--r-- | src/Main.hs | 54 | ||||
-rw-r--r-- | src/Repo.hs | 27 |
5 files changed, 131 insertions, 15 deletions
diff --git a/minici.cabal b/minici.cabal index fc3c3da..7c05311 100644 --- a/minici.cabal +++ b/minici.cabal @@ -48,6 +48,7 @@ executable minici other-modules: Command + Command.Checkout Command.Run Config Job diff --git a/src/Command.hs b/src/Command.hs index 2114d90..8ca0655 100644 --- a/src/Command.hs +++ b/src/Command.hs @@ -10,6 +10,7 @@ module Command ( getCommonOptions, getConfigPath, getConfig, + getRepo, getDefaultRepo, getTerminalOutput, ) where @@ -25,15 +26,18 @@ import System.Exit import System.IO import Config +import Repo import Terminal data CommonOptions = CommonOptions { optJobs :: Int + , optRepo :: [ DeclaredRepo ] } defaultCommonOptions :: CommonOptions defaultCommonOptions = CommonOptions { optJobs = 2 + , optRepo = [] } class CommandArgumentsType (CommandArguments c) => Command c where @@ -79,10 +83,16 @@ instance CommandArgumentsType [ Text ] where newtype CommandExec a = CommandExec (ReaderT CommandInput IO a) deriving (Functor, Applicative, Monad, MonadIO) +instance MonadFail CommandExec where + fail err = liftIO $ do + hPutStrLn stderr err + exitFailure + data CommandInput = CommandInput { ciOptions :: CommonOptions , ciConfigPath :: Maybe FilePath , ciConfig :: Either String Config + , ciRepos :: [ ( Maybe RepoName, Repo ) ] , ciTerminalOutput :: TerminalOutput } @@ -92,18 +102,26 @@ getCommonOptions = CommandExec (asks ciOptions) getConfigPath :: CommandExec FilePath getConfigPath = CommandExec $ do asks ciConfigPath >>= \case - Nothing -> liftIO $ do - hPutStrLn stderr "no job file found" - exitFailure + Nothing -> fail $ "no job file found" Just path -> return path getConfig :: CommandExec Config getConfig = CommandExec $ do asks ciConfig >>= \case - Left err -> liftIO $ do - hPutStrLn stderr err - exitFailure + Left err -> fail err Right config -> return config +getRepo :: RepoName -> CommandExec Repo +getRepo name = CommandExec $ do + asks (lookup (Just name) . ciRepos) >>= \case + Just repo -> return repo + Nothing -> fail $ "repo `" <> showRepoName name <> "' not declared" + +getDefaultRepo :: CommandExec Repo +getDefaultRepo = CommandExec $ do + asks (lookup Nothing . ciRepos) >>= \case + Just repo -> return repo + Nothing -> fail $ "no defalut repo" + getTerminalOutput :: CommandExec TerminalOutput getTerminalOutput = CommandExec (asks ciTerminalOutput) diff --git a/src/Command/Checkout.hs b/src/Command/Checkout.hs new file mode 100644 index 0000000..c180a34 --- /dev/null +++ b/src/Command/Checkout.hs @@ -0,0 +1,34 @@ +module Command.Checkout ( + CheckoutCommand, +) where + +import Data.Text (Text) +import Data.Text qualified as T + +import Command +import Repo + + +data CheckoutCommand = CheckoutCommand (Maybe RepoName) Text + +instance Command CheckoutCommand where + commandName _ = "checkout" + commandDescription _ = "Checkout (part of) a given repository" + + type CommandArguments CheckoutCommand = [ Text ] + + commandUsage _ = T.pack $ unlines $ + [ "Usage: minici checkout [<repo> [<revision>]] [<option>...]" + ] + + commandInit _ _ = \case + (name : revision : _) -> CheckoutCommand (Just (RepoName name)) revision + [ name ] -> CheckoutCommand (Just (RepoName name)) "HEAD" + [] -> CheckoutCommand Nothing "HEAD" + commandExec = cmdCheckout + +cmdCheckout :: CheckoutCommand -> CommandExec () +cmdCheckout (CheckoutCommand name revision) = do + repo <- maybe getDefaultRepo getRepo name + commit <- maybe (fail $ T.unpack $ "revision `" <> revision <> "' not found") return =<< readCommit repo revision + checkoutAt commit "." diff --git a/src/Main.hs b/src/Main.hs index 6a7cf8d..f86bd77 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -16,8 +16,10 @@ import System.FilePath import System.IO import Command +import Command.Checkout import Command.Run import Config +import Repo import Terminal import Version @@ -34,17 +36,28 @@ defaultCmdlineOptions = CmdlineOptions , optCommon = defaultCommonOptions } -options :: [OptDescr (CmdlineOptions -> CmdlineOptions)] +options :: [ OptDescr (CmdlineOptions -> Except String CmdlineOptions) ] options = - [ Option ['h'] ["help"] - (NoArg $ \opts -> opts { optShowHelp = True }) + [ Option [ 'h' ] [ "help" ] + (NoArg $ \opts -> return opts { optShowHelp = True }) "show this help and exit" - , Option ['V'] ["version"] - (NoArg $ \opts -> opts { optShowVersion = True }) + , Option [ 'V' ] [ "version" ] + (NoArg $ \opts -> return opts { optShowVersion = True }) "show version and exit" - , Option ['j'] ["jobs"] - (ReqArg (\num opts -> opts { optCommon = (optCommon opts) { optJobs = read num }}) "<num>") + , Option [ 'j' ] [ "jobs" ] + (ReqArg (\num opts -> return opts { optCommon = (optCommon opts) { optJobs = read num }}) "<num>") ("number of jobs to run simultaneously (default " <> show (optJobs defaultCommonOptions) <> ")") + , Option [] [ "repo" ] + (ReqArg (\value opts -> + case span (/= ':') value of + ( repo, ':' : path ) -> return opts + { optCommon = (optCommon opts) + { optRepo = DeclaredRepo (RepoName $ T.pack repo) path : optRepo (optCommon opts) + } + } + _ -> throwError $ "--repo: invalid value `" <> value <> "'" + ) "<repo>:<path>") + ("override or declare repo path") ] data SomeCommandType = forall c. Command c => SC (Proxy c) @@ -52,6 +65,7 @@ data SomeCommandType = forall c. Command c => SC (Proxy c) commands :: [ SomeCommandType ] commands = [ SC $ Proxy @RunCommand + , SC $ Proxy @CheckoutCommand ] lookupCommand :: String -> Maybe SomeCommandType @@ -68,7 +82,17 @@ main = do _ -> ( Nothing, args ) (opts, cmdargs) <- case getOpt RequireOrder options args' of - (o, cmdargs, []) -> return (foldl (flip id) defaultCmdlineOptions o, cmdargs) + (os, cmdargs, []) -> do + let merge :: ([String], CmdlineOptions) -> (CmdlineOptions -> Except String CmdlineOptions) -> ([String], CmdlineOptions) + merge ( errs, o ) f = case runExcept $ f o of + Left err -> ( err : errs, o ) + Right o' -> ( errs, o' ) + + case foldl merge ( [], defaultCmdlineOptions ) os of + ( [], opts ) -> return ( opts , cmdargs ) + ( errs, _ ) -> do + hPutStrLn stderr $ unlines (reverse errs) <> "Try `minici --help' for more information." + exitFailure (_, _, errs) -> do hPutStrLn stderr $ concat errs <> "Try `minici --help' for more information." exitFailure @@ -159,5 +183,19 @@ runSomeCommand ciConfigPath ciOptions (SC tproxy) args = do let cmd = commandInit tproxy (fcoSpecific opts) cmdargs let CommandExec exec = commandExec cmd + + namedRepos <- forM (optRepo ciOptions) $ \decl -> do + openRepo (repoPath decl) >>= \case + Just repo -> return ( Just (repoName decl), repo ) + Nothing -> do + hPutStrLn stderr $ "Failed to open repo `" <> showRepoName (repoName decl) <> "' at " <> repoPath decl + exitFailure + + defaultRepo <- maybe (return Nothing) (openRepo . takeDirectory) ciConfigPath + let ciRepos = concat + [ maybe [] (\r -> [ ( Nothing, r ) ]) defaultRepo + , namedRepos + ] + ciTerminalOutput <- initTerminalOutput flip runReaderT CommandInput {..} exec diff --git a/src/Repo.hs b/src/Repo.hs index c8e818c..1053248 100644 --- a/src/Repo.hs +++ b/src/Repo.hs @@ -1,10 +1,14 @@ module Repo ( - Repo, Commit, commitId, + Repo, + DeclaredRepo(..), + RepoName(..), textRepoName, showRepoName, + Commit, commitId, CommitId, textCommitId, showCommitId, TreeId, textTreeId, showTreeId, Tag(..), openRepo, + readCommit, readBranch, readTag, listCommits, @@ -53,6 +57,21 @@ data Repo , gitWatchedBranches :: MVar (Map Text [ TVar (Maybe Commit) ]) } +data DeclaredRepo = DeclaredRepo + { repoName :: RepoName + , repoPath :: FilePath + } + +newtype RepoName = RepoName Text + deriving (Eq, Ord) + +textRepoName :: RepoName -> Text +textRepoName (RepoName text) = text + +showRepoName :: RepoName -> String +showRepoName = T.unpack . textRepoName + + data Commit = Commit { commitRepo :: Repo , commitId_ :: CommitId @@ -133,6 +152,12 @@ mkCommit commitRepo commitId_ = do commitDetails <- newMVar Nothing return $ Commit {..} +readCommit :: MonadIO m => Repo -> Text -> m (Maybe Commit) +readCommit repo@GitRepo {..} ref = liftIO $ do + readProcessWithExitCode "git" [ "--git-dir=" <> gitDir, "rev-parse", "--verify", "--quiet", T.unpack ref <> "^{commit}" ] "" >>= \case + ( ExitSuccess, out, _ ) | cid : _ <- lines out -> Just <$> mkCommit repo (CommitId $ BC.pack cid) + _ -> return Nothing + readCommitFromFile :: MonadIO m => Repo -> FilePath -> m (Maybe Commit) readCommitFromFile repo@GitRepo {..} path = liftIO $ do try @IOException (BC.readFile $ gitDir </> path) >>= \case |