{- git-annex command - - Copyright 2017-2023 Joey Hess - - Licensed under the GNU AGPL version 3 or higher. -} {-# LANGUAGE OverloadedStrings #-} module Command.Config where import Command import Logs.Config import Config import Types.GitConfig (globalConfigs) import Git.Types (fromConfigValue, fromConfigKey) import qualified Git.Command import Utility.SafeOutput import Annex.CheckAttr import Types.NumCopies import Logs.NumCopies import qualified Data.ByteString.Char8 as S8 cmd :: Command cmd = noMessages $ command "config" SectionSetup "configuration stored in git-annex branch" paramNothing (seek <$$> optParser) data Action = SetConfig ConfigKey ConfigValue | GetConfig ConfigKey | UnsetConfig ConfigKey | ShowOrigin ConfigKey (Maybe FilePath) type Name = String type Value = String optParser :: CmdParamsDesc -> Parser Action optParser _ = setconfig <|> getconfig <|> unsetconfig <|> showorigin where setconfig = SetConfig <$> strOption ( long "set" <> help "set configuration" <> metavar paramName ) <*> strArgument ( metavar paramValue ) getconfig = GetConfig <$> strOption ( long "get" <> help "get configuration" <> metavar paramName ) unsetconfig = UnsetConfig <$> strOption ( long "unset" <> help "unset configuration" <> metavar paramName ) showorigin = ShowOrigin <$> strOption ( long "show-origin" <> help "explain where a value is configured" <> metavar paramName ) <*> optional (strOption ( long "for-file" <> help "filename to check for in gitattributes" <> metavar paramFile )) seek :: Action -> CommandSeek seek (SetConfig ck@(ConfigKey name) val) = checkIsGlobalConfig ck $ \setter _unsetter _getter -> commandAction $ startingUsualMessages (decodeBS name) ai si $ do setter val when (needLocalUpdate ck) $ setConfig ck (fromConfigValue val) next $ return True where ai = ActionItemOther (Just (UnquotedString (fromConfigValue val))) si = SeekInput [decodeBS name] seek (UnsetConfig ck@(ConfigKey name)) = checkIsGlobalConfig ck $ \_setter unsetter _getter -> commandAction $ startingUsualMessages (decodeBS name) ai si $ do unsetter when (needLocalUpdate ck) $ unsetConfig ck next $ return True where ai = ActionItemOther (Just "unset") si = SeekInput [decodeBS name] seek (GetConfig ck) = checkIsGlobalConfig ck $ \_setter _unsetter getter -> commandAction $ startingCustomOutput ai $ do getter >>= \case Just (ConfigValue v) -> liftIO $ S8.putStrLn $ safeOutput v Just NoConfigValue -> return () Nothing -> return () next $ return True where ai = ActionItemOther Nothing seek (ShowOrigin ck@(ConfigKey name) forfile) = commandAction $ startingCustomOutput ai $ next $ checknotconfigured $ case checkIsGlobalConfig' ck of Just (_setter, _unsetter, getter) -> ifM gitconfigorigin ( return True , checkattrs (checkconfigbranch getter) ) Nothing -> ifM gitconfigorigin ( return True , checkattrs checkgitconfigunderride ) where ai = ActionItemOther Nothing gitconfigorigin | name `elem` gitconfigdoesnotoverride = return False | otherwise = gitconfigorigin' gitconfigorigin' = inRepo $ Git.Command.runBool [ Param "config" , Param "--show-origin" , Param (decodeBS name) ] -- git configs for these do not override values from git attributes -- or the branch gitconfigdoesnotoverride = [ "annex.numcopies" , "annex.mincopies" ] -- the git config for annex.numcopies is a special case; it's only -- used if not configured anywhere else checkgitconfigunderride | name == "annex.numcopies" = gitconfigorigin' | otherwise = return False -- Display similar to git config --show-origin showval loc v = liftIO $ do putStrLn $ loc ++ "\t" ++ v return True configbranch v | needLocalUpdate ck = checkgitconfigunderride | otherwise = showval "branch:git-annex" (decodeBS v) checkconfigbranch getter = getter >>= \case Just (ConfigValue v) -> configbranch v _ -> checkgitconfigunderride checkattrs cont | decodeBS name `elem` annexAttrs = case forfile of Just file -> do v <- checkAttr (decodeBS name) (toRawFilePath file) if null v then cont else showval "gitattributes" v Nothing -> do warnforfile cont | otherwise = cont warnforfile = warning $ UnquotedString $ configKeyMessage ck $ unwords [ "may be configured in gitattributes." , "Pass --for-file= with a filename to check" ] checknotconfigured a = do ok <- a unless ok $ warning $ UnquotedString $ configKeyMessage ck "is not configured" return ok type Setter = ConfigValue -> Annex () type Unsetter = Annex () type Getter = Annex (Maybe ConfigValue) checkIsGlobalConfig :: ConfigKey -> (Setter -> Unsetter -> Getter -> Annex a) -> Annex a checkIsGlobalConfig ck a = case checkIsGlobalConfig' ck of Just (setter, unsetter, getter) -> a setter unsetter getter Nothing -> giveup $ configKeyMessage ck "is not a configuration setting that can be stored in the git-annex branch" checkIsGlobalConfig' :: ConfigKey -> Maybe (Setter, Unsetter, Getter) checkIsGlobalConfig' ck | elem ck globalConfigs = Just ( setGlobalConfig ck , unsetGlobalConfig ck , getGlobalConfig ck ) -- These came before this command, but are also global configs, -- so support them here as well. | ck == ConfigKey "annex.numcopies" = Just ( mksetter (setGlobalNumCopies . configuredNumCopies) , error "unsetting annex.numcopies is not supported" , mkgetter fromNumCopies getGlobalNumCopies ) | ck == ConfigKey "annex.mincopies" = Just ( mksetter (setGlobalMinCopies . configuredMinCopies) , error "unsetting annex.mincopies is not supported" , mkgetter fromMinCopies getGlobalMinCopies ) | otherwise = Nothing where mksetter f = maybe (error ("invalid value for " ++ fromConfigKey ck)) f . readish . decodeBS . fromConfigValue mkgetter f g = fmap (ConfigValue . encodeBS . show . f) <$> g configKeyMessage :: ConfigKey -> String -> String configKeyMessage (ConfigKey name) msg = decodeBS name ++ " " ++ msg needLocalUpdate :: ConfigKey -> Bool needLocalUpdate (ConfigKey "annex.securehashesonly") = True needLocalUpdate _ = False