diff options
| author | Roman Smrž <roman.smrz@seznam.cz> | 2025-03-23 12:40:53 +0100 | 
|---|---|---|
| committer | Roman Smrž <roman.smrz@seznam.cz> | 2025-03-29 19:16:37 +0100 | 
| commit | f3dbad3df9f8c9c1aca873d74a34c6f9169133b0 (patch) | |
| tree | 93d4a0114962c4062f5c6398aeb975a2f64da0e9 | |
| parent | a372c8cf51bce6179fe0d585a545b7f4f3910233 (diff) | |
Evaluate canonical job ids
Changelog: Added `jobid` command resolving job reference to canonical id
| -rw-r--r-- | minici.cabal | 1 | ||||
| -rw-r--r-- | src/Command/JobId.hs | 39 | ||||
| -rw-r--r-- | src/Config.hs | 10 | ||||
| -rw-r--r-- | src/Eval.hs | 57 | ||||
| -rw-r--r-- | src/Job/Types.hs | 9 | ||||
| -rw-r--r-- | src/Main.hs | 2 | ||||
| -rw-r--r-- | src/Repo.hs | 37 | 
7 files changed, 141 insertions, 14 deletions
| diff --git a/minici.cabal b/minici.cabal index 433576f..b8fa18a 100644 --- a/minici.cabal +++ b/minici.cabal @@ -49,6 +49,7 @@ executable minici      other-modules:          Command          Command.Checkout +        Command.JobId          Command.Run          Config          Eval diff --git a/src/Command/JobId.hs b/src/Command/JobId.hs new file mode 100644 index 0000000..9f531d6 --- /dev/null +++ b/src/Command/JobId.hs @@ -0,0 +1,39 @@ +module Command.JobId ( +    JobIdCommand, +) where + +import Control.Monad.IO.Class + +import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.IO qualified as T + +import Command +import Eval +import Job.Types + + +data JobIdCommand = JobIdCommand JobRef + +instance Command JobIdCommand where +    commandName _ = "jobid" +    commandDescription _ = "Resolve job reference to canonical job ID" + +    type CommandArguments JobIdCommand = Text + +    commandUsage _ = T.pack $ unlines $ +        [ "Usage: minici jobid <job ref>" +        ] + +    commandInit _ _ = JobIdCommand . JobRef . T.splitOn "." +    commandExec = cmdJobId + + +cmdJobId :: JobIdCommand -> CommandExec () +cmdJobId (JobIdCommand ref) = do +    config <- getConfig +    einput <- getEvalInput +    JobId ids <- either (tfail . textEvalError) return =<< +        liftIO (runEval (evalJobReference config ref) einput) + +    liftIO $ T.putStrLn $ T.intercalate "." $ map textJobIdPart ids diff --git a/src/Config.hs b/src/Config.hs index 08bc3f2..5631179 100644 --- a/src/Config.hs +++ b/src/Config.hs @@ -157,14 +157,14 @@ parseConfig contents = do              Left $ prettyPosWithSource pos contents err          Right conf -> Right conf -loadConfigForCommit :: MonadIO m => Commit -> m (Either String Config) -loadConfigForCommit commit = do -    readCommittedFile commit configFileName >>= return . \case +loadConfigForCommit :: MonadIO m => Tree -> m (Either String Config) +loadConfigForCommit tree = do +    readCommittedFile tree configFileName >>= return . \case          Just content -> either (\_ -> Left $ "failed to parse " <> configFileName) Right $ parseConfig content          Nothing -> Left $ configFileName <> " not found" -loadJobSetForCommit :: MonadIO m => Commit -> m DeclaredJobSet -loadJobSetForCommit commit = toJobSet <$> loadConfigForCommit commit +loadJobSetForCommit :: (MonadIO m, MonadFail m) => Commit -> m DeclaredJobSet +loadJobSetForCommit commit = return . toJobSet =<< loadConfigForCommit =<< getCommitTree commit    where      toJobSet configEither = JobSet          { jobsetCommit = Just commit diff --git a/src/Eval.hs b/src/Eval.hs index a071770..1828468 100644 --- a/src/Eval.hs +++ b/src/Eval.hs @@ -1,18 +1,23 @@  module Eval (      EvalInput(..),      EvalError(..), textEvalError, +    Eval, runEval,      evalJob,      evalJobSet, +    evalJobReference,  ) where  import Control.Monad  import Control.Monad.Except +import Control.Monad.Reader  import Data.Bifunctor +import Data.List  import Data.Text (Text)  import Data.Text qualified as T +import Config  import Job.Types  import Repo @@ -27,6 +32,13 @@ data EvalError  textEvalError :: EvalError -> Text  textEvalError (OtherEvalError text) = text + +type Eval a = ReaderT EvalInput (ExceptT EvalError IO) a + +runEval :: Eval a -> EvalInput -> IO (Either EvalError a) +runEval action einput = runExceptT $ flip runReaderT einput action + +  evalJob :: EvalInput -> DeclaredJob -> Except EvalError Job  evalJob EvalInput {..} decl = do      otherCheckout <- forM (jobOtherCheckout decl) $ \( DeclaredJobRepo name, revision, checkout ) -> do @@ -52,3 +64,48 @@ evalJobSet ei decl = do          }    where      runExceptStr = first (T.unpack . textEvalError) . runExcept + + +canonicalJobName :: [ Text ] -> Config -> Eval [ JobIdPart ] +canonicalJobName (r : rs) config = do +    einput <- ask +    let name = JobName r +    case find ((name ==) . jobName) (configJobs config) of +        Just djob -> do +            job <- either throwError return $ runExcept $ evalJob einput djob +            let repos = nub $ map (\( EvaluatedJobRepo repo, _, _ ) -> repo) $ jobOtherCheckout job +            (JobIdName name :) <$> canonicalOtherCheckouts rs repos +        Nothing -> throwError $ OtherEvalError $ "job ‘" <> r <> "’ not found" +canonicalJobName [] _ = throwError $ OtherEvalError "expected job name" + +canonicalOtherCheckouts :: [ Text ] -> [ Repo ] -> Eval [ JobIdPart ] +canonicalOtherCheckouts (r : rs) (repo : repos) = do +    tree <- tryReadCommit repo r >>= \case +        Just commit -> getCommitTree commit +        Nothing -> tryReadTree repo r >>= \case +            Just tree -> return tree +            Nothing -> throwError $ OtherEvalError $ "failed to resolve ‘" <> r <> "’ to a commit or tree in " <> T.pack (show repo) +    (JobIdTree (treeId tree) :) <$> canonicalOtherCheckouts rs repos +canonicalOtherCheckouts []       []       = return [] +canonicalOtherCheckouts []       (_ : _ ) = throwError $ OtherEvalError $ "expected commit or tree reference" +canonicalOtherCheckouts (r : _)  []       = throwError $ OtherEvalError $ "unexpected job ref part ‘" <> r <> "’" + +canonicalCommitConfig :: [ Text ] -> Repo -> Eval [ JobIdPart ] +canonicalCommitConfig (r : rs) repo = do +    tree <- tryReadCommit repo r >>= \case +        Just commit -> getCommitTree commit +        Nothing -> tryReadTree repo r >>= \case +            Just tree -> return tree +            Nothing -> throwError $ OtherEvalError $ "failed to resolve ‘" <> r <> "’ to a commit or tree in " <> T.pack (show repo) +    config <- either fail return =<< loadConfigForCommit tree +    (JobIdTree (treeId tree) :) <$> canonicalJobName rs config +canonicalCommitConfig [] _ = throwError $ OtherEvalError "expected commit or tree reference" + +evalJobReference :: Config -> JobRef -> Eval JobId +evalJobReference config (JobRef rs) = +    fmap JobId $ do +        asks eiContainingRepo >>= \case +            Just defRepo -> do +                canonicalCommitConfig rs defRepo +            Nothing -> do +                canonicalJobName rs config diff --git a/src/Job/Types.hs b/src/Job/Types.hs index 0f91b94..0447615 100644 --- a/src/Job/Types.hs +++ b/src/Job/Types.hs @@ -68,3 +68,12 @@ data JobIdPart      | JobIdCommit CommitId      | JobIdTree TreeId      deriving (Eq, Ord) + +newtype JobRef = JobRef [ Text ] +    deriving (Eq, Ord) + +textJobIdPart :: JobIdPart -> Text +textJobIdPart = \case +    JobIdName name -> textJobName name +    JobIdCommit cid -> textCommitId cid +    JobIdTree tid -> textTreeId tid diff --git a/src/Main.hs b/src/Main.hs index f98b274..9e9214f 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -19,6 +19,7 @@ import System.IO  import Command  import Command.Checkout +import Command.JobId  import Command.Run  import Config  import Repo @@ -73,6 +74,7 @@ commands :: NE.NonEmpty SomeCommandType  commands =      ( SC $ Proxy @RunCommand) NE.:|      [ SC $ Proxy @CheckoutCommand +    , SC $ Proxy @JobIdCommand      ]  lookupCommand :: String -> Maybe SomeCommandType diff --git a/src/Repo.hs b/src/Repo.hs index 702f09d..f22b211 100644 --- a/src/Repo.hs +++ b/src/Repo.hs @@ -9,7 +9,8 @@ module Repo (      Tag(..),      openRepo, -    readCommit, +    readCommit, tryReadCommit, +    readTree, tryReadTree,      readBranch,      readTag,      listCommits, @@ -63,6 +64,9 @@ data Repo          , gitWatchedBranches :: MVar (Map Text [ TVar (Maybe Commit) ])          } +instance Show Repo where +    show GitRepo {..} = gitDir +  data DeclaredRepo = DeclaredRepo      { repoName :: RepoName      , repoPath :: FilePath @@ -164,10 +168,25 @@ mkCommit commitRepo commitId_ = do      return $ Commit {..}  readCommit :: (MonadIO m, MonadFail m) => Repo -> Text -> m 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 -> mkCommit repo (CommitId $ BC.pack cid) -        _                                              -> fail $ "revision `" <> T.unpack ref <> "' not found in `" <> gitDir <> "'" +readCommit repo@GitRepo {..} ref = maybe (fail err) return =<< tryReadCommit repo ref +    where err = "revision `" <> T.unpack ref <> "' not found in `" <> gitDir <> "'" + +tryReadCommit :: (MonadIO m, MonadFail m) => Repo -> Text -> m (Maybe Commit) +tryReadCommit repo ref = sequence . fmap (mkCommit repo . CommitId) =<< tryReadObjectId repo "commit" ref + +readTree :: (MonadIO m, MonadFail m) => Repo -> Text -> m Tree +readTree repo@GitRepo {..} ref = maybe (fail err) return =<< tryReadTree repo ref +    where err = "tree `" <> T.unpack ref <> "' not found in `" <> gitDir <> "'" + +tryReadTree :: (MonadIO m, MonadFail m) => Repo -> Text -> m (Maybe Tree) +tryReadTree repo ref = return . fmap (Tree repo . TreeId) =<< tryReadObjectId repo "tree" ref + +tryReadObjectId :: (MonadIO m, MonadFail m) => Repo -> Text -> Text -> m (Maybe ByteString) +tryReadObjectId GitRepo {..} otype ref = do +    liftIO (readProcessWithExitCode "git" [ "--git-dir=" <> gitDir, "rev-parse", "--verify", "--quiet", T.unpack ref <> "^{" <> T.unpack otype <> "}" ] "") >>= \case +        ( ExitSuccess, out, _ ) | oid : _ <- lines out -> return $ Just $ BC.pack oid +        _                                              -> return Nothing +  readCommitFromFile :: MonadIO m => Repo -> FilePath -> m (Maybe Commit)  readCommitFromFile repo@GitRepo {..} path = liftIO $ do @@ -325,11 +344,11 @@ createWipCommit repo@GitRepo {..} = do              _ -> readCommit repo "HEAD" -readCommittedFile :: MonadIO m => Commit -> FilePath -> m (Maybe BL.ByteString) -readCommittedFile Commit {..} path = do -    let GitRepo {..} = commitRepo +readCommittedFile :: MonadIO m => Tree -> FilePath -> m (Maybe BL.ByteString) +readCommittedFile Tree {..} path = do +    let GitRepo {..} = treeRepo      liftIO $ withMVar gitLock $ \_ -> do -        let cmd = (proc "git" [ "--git-dir=" <> gitDir, "cat-file", "blob", showCommitId commitId_ <> ":" <> path ]) +        let cmd = (proc "git" [ "--git-dir=" <> gitDir, "cat-file", "blob", showTreeId treeId <> ":" <> path ])                  { std_in = NoStream                  , std_out = CreatePipe                  } |