git-annex/Command.hs

272 lines
9.4 KiB
Haskell
Raw Normal View History

2011-09-15 20:57:02 +00:00
{- git-annex command infrastructure
-
2011-09-19 05:37:04 +00:00
- Copyright 2010-2011 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Command where
2011-10-05 20:02:51 +00:00
import Common.Annex
2010-11-04 17:28:49 +00:00
import qualified Backend
import qualified Annex
import qualified Git
import qualified Git.LsFiles as LsFiles
import Types.Key
2011-10-15 20:21:08 +00:00
import Logs.Trust
import Logs.Location
import Config
2011-09-15 20:57:02 +00:00
import Backend
import Limit
import Init
{- A command runs in these stages.
-
- a. The check stage runs checks, that error out if
- anything prevents the command from running. -}
data CommandCheck = CommandCheck { idCheck :: Int, runCheck :: Annex () }
instance Eq CommandCheck where
a == b = idCheck a == idCheck b
{- b. The seek stage takes the parameters passed to the command,
- looks through the repo to find the ones that are relevant
- to that command (ie, new files to add), and generates
- a list of start stage actions. -}
type CommandSeek = [String] -> Annex [CommandStart]
{- c. The start stage is run before anything is printed about the
- command, is passed some input, and can early abort it
- if the input does not make sense. It should run quickly and
- should not modify Annex state. -}
type CommandStart = Annex (Maybe CommandPerform)
{- d. The perform stage is run after a message is printed about the command
- being run, and it should be where the bulk of the work happens. -}
type CommandPerform = Annex (Maybe CommandCleanup)
{- e. The cleanup stage is run only if the perform stage succeeds, and it
- returns the overall success/fail of the command. -}
type CommandCleanup = Annex Bool
data Command = Command {
cmdcheck :: [CommandCheck],
cmdname :: String,
cmdparams :: String,
cmdseek :: [CommandSeek],
2011-05-15 06:12:17 +00:00
cmddesc :: String
2010-11-04 17:28:49 +00:00
}
2011-05-15 06:02:46 +00:00
{- For start and perform stages to indicate what step to run next. -}
next :: a -> Annex (Maybe a)
next a = return $ Just a
{- Or to indicate nothing needs to be done. -}
stop :: Annex (Maybe a)
stop = return Nothing
{- Generates a command with the common checks. -}
command :: String -> String -> [CommandSeek] -> String -> Command
command = Command commonChecks
{- Prepares a list of actions to run to perform a command, based on
2010-11-04 17:28:49 +00:00
- the parameters passed to it. -}
prepCommand :: Command -> [String] -> Annex [Annex Bool]
2011-09-19 05:37:04 +00:00
prepCommand Command { cmdseek = seek } params =
return . map doCommand . concat =<< mapM (\s -> s params) seek
2010-11-04 17:28:49 +00:00
{- Runs a command through the start, perform and cleanup stages -}
doCommand :: CommandStart -> CommandCleanup
2011-05-15 16:25:58 +00:00
doCommand = start
where
2011-07-05 18:58:33 +00:00
start = stage $ maybe success perform
perform = stage $ maybe failure cleanup
2011-05-15 16:25:58 +00:00
cleanup = stage $ \r -> showEndResult r >> return r
2011-09-19 05:37:04 +00:00
stage = (=<<)
2011-07-05 18:58:33 +00:00
success = return True
2011-09-19 05:37:04 +00:00
failure = showEndFail >> return False
2010-11-04 17:28:49 +00:00
2010-11-11 22:54:52 +00:00
{- These functions find appropriate files or other things based on a
2011-09-19 05:37:04 +00:00
user's parameters, and prepare actions operating on them. -}
withFilesInGit :: (FilePath -> CommandStart) -> CommandSeek
2010-11-11 22:54:52 +00:00
withFilesInGit a params = do
repo <- gitRepo
runFiltered a $ liftIO $ runPreserveOrder (LsFiles.inRepo repo) params
withAttrFilesInGit :: String -> ((FilePath, String) -> CommandStart) -> CommandSeek
withAttrFilesInGit attr a params = do
repo <- gitRepo
files <- liftIO $ runPreserveOrder (LsFiles.inRepo repo) params
2011-09-19 03:09:40 +00:00
runFilteredGen a fst $ liftIO $ Git.checkAttr repo attr files
withNumCopies :: (FilePath -> Maybe Int -> CommandStart) -> CommandSeek
withNumCopies a params = withAttrFilesInGit "annex.numcopies" go params
where
2011-09-19 03:09:40 +00:00
go (file, v) = a file (readMaybe v)
withBackendFilesInGit :: (BackendFile -> CommandStart) -> CommandSeek
withBackendFilesInGit a params = do
repo <- gitRepo
files <- liftIO $ runPreserveOrder (LsFiles.inRepo repo) params
backendPairs a files
withFilesMissing :: (String -> CommandStart) -> CommandSeek
withFilesMissing a params = runFiltered a $ liftIO $ filterM missing params
2010-11-11 22:54:52 +00:00
where
missing = liftM not . doesFileExist
withFilesNotInGit :: (BackendFile -> CommandStart) -> CommandSeek
2010-11-11 22:54:52 +00:00
withFilesNotInGit a params = do
repo <- gitRepo
force <- Annex.getState Annex.force
newfiles <- liftIO $ runPreserveOrder (LsFiles.notInRepo repo force) params
backendPairs a newfiles
withWords :: ([String] -> CommandStart) -> CommandSeek
withWords a params = return [a params]
withStrings :: (String -> CommandStart) -> CommandSeek
2010-11-15 22:04:19 +00:00
withStrings a params = return $ map a params
withFilesToBeCommitted :: (String -> CommandStart) -> CommandSeek
2010-11-11 22:54:52 +00:00
withFilesToBeCommitted a params = do
repo <- gitRepo
runFiltered a $
liftIO $ runPreserveOrder (LsFiles.stagedNotDeleted repo) params
withFilesUnlocked :: (BackendFile -> CommandStart) -> CommandSeek
withFilesUnlocked = withFilesUnlocked' LsFiles.typeChanged
withFilesUnlockedToBeCommitted :: (BackendFile -> CommandStart) -> CommandSeek
withFilesUnlockedToBeCommitted = withFilesUnlocked' LsFiles.typeChangedStaged
withFilesUnlocked' :: (Git.Repo -> [FilePath] -> IO [FilePath]) -> (BackendFile -> CommandStart) -> CommandSeek
withFilesUnlocked' typechanged a params = do
-- unlocked files have changed type from a symlink to a regular file
repo <- gitRepo
2011-02-01 00:14:08 +00:00
typechangedfiles <- liftIO $ runPreserveOrder (typechanged repo) params
unlockedfiles <- liftIO $ filterM notSymlink $
map (\f -> Git.workTree repo ++ "/" ++ f) typechangedfiles
backendPairs a unlockedfiles
withKeys :: (Key -> CommandStart) -> CommandSeek
withKeys a params = return $ map (a . parse) params
where
parse p = fromMaybe (error "bad key") $ readKey p
withNothing :: CommandStart -> CommandSeek
2010-11-13 20:15:45 +00:00
withNothing a [] = return [a]
withNothing _ _ = error "This command takes no parameters."
2010-11-11 22:54:52 +00:00
runFiltered :: (FilePath -> Annex (Maybe a)) -> Annex [FilePath] -> Annex [Annex (Maybe a)]
2011-09-21 03:24:48 +00:00
runFiltered a = runFilteredGen a id
backendPairs :: (BackendFile -> CommandStart) -> CommandSeek
2011-09-19 03:09:40 +00:00
backendPairs a fs = runFilteredGen a snd (Backend.chooseBackends fs)
2011-09-19 05:37:04 +00:00
runFilteredGen :: (b -> Annex (Maybe a)) -> (b -> FilePath) -> Annex [b] -> Annex [Annex (Maybe a)]
2011-09-19 03:09:40 +00:00
runFilteredGen a d fs = do
matcher <- Limit.getMatcher
2011-10-29 22:47:53 +00:00
runActions (proc matcher) fs
where
2011-09-19 03:09:40 +00:00
proc matcher v = do
let f = d v
ok <- matcher f
2011-09-19 03:09:40 +00:00
if ok then a v else stop
2011-10-29 22:47:53 +00:00
runActions :: (b -> Annex (Maybe a)) -> Annex [b] -> Annex [Annex (Maybe a)]
runActions a fs = liftM (map a) fs
notAnnexed :: FilePath -> Annex (Maybe a) -> Annex (Maybe a)
notAnnexed file a = maybe a (const $ return Nothing) =<< Backend.lookupFile file
isAnnexed :: FilePath -> ((Key, Backend Annex) -> Annex (Maybe a)) -> Annex (Maybe a)
isAnnexed file a = maybe (return Nothing) a =<< Backend.lookupFile file
2011-10-29 22:47:53 +00:00
isBareRepo :: Annex Bool
isBareRepo = Git.repoIsLocalBare <$> gitRepo
notBareRepo :: Annex a -> Annex a
notBareRepo a = do
2011-10-29 22:47:53 +00:00
whenM isBareRepo $
error "You cannot run this subcommand in a bare repository."
a
2010-11-11 22:54:52 +00:00
notSymlink :: FilePath -> IO Bool
notSymlink f = liftIO $ not . isSymbolicLink <$> getSymbolicLinkStatus f
{- Descriptions of params used in usage messages. -}
2011-09-15 18:33:37 +00:00
paramPaths :: String
paramPaths = paramOptional $ paramRepeating paramPath -- most often used
paramPath :: String
paramPath = "PATH"
paramKey :: String
paramKey = "KEY"
paramDesc :: String
paramDesc = "DESC"
2011-09-15 16:36:27 +00:00
paramUrl :: String
paramUrl = "URL"
paramNumber :: String
paramNumber = "NUMBER"
paramRemote :: String
paramRemote = "REMOTE"
paramGlob :: String
paramGlob = "GLOB"
paramName :: String
paramName = "NAME"
paramUUID :: String
paramUUID = "UUID"
2011-03-29 03:22:31 +00:00
paramType :: String
paramType = "TYPE"
paramKeyValue :: String
paramKeyValue = "K=V"
paramNothing :: String
paramNothing = ""
2011-09-15 18:33:37 +00:00
paramRepeating :: String -> String
paramRepeating s = s ++ " ..."
paramOptional :: String -> String
paramOptional s = "[" ++ s ++ "]"
paramPair :: String -> String -> String
paramPair a b = a ++ " " ++ b
{- The Key specified by the --key parameter. -}
cmdlineKey :: Annex Key
cmdlineKey = do
k <- Annex.getState Annex.defaultkey
case k of
Nothing -> nokey
Just "" -> nokey
2011-05-15 06:49:43 +00:00
Just kstring -> maybe badkey return $ readKey kstring
where
nokey = error "please specify the key with --key"
2011-05-15 06:49:43 +00:00
badkey = error "bad key"
{- Used for commands that have an auto mode that checks the number of known
- copies of a key.
-
- In auto mode, first checks that the number of known
- copies of the key is > or < than the numcopies setting, before running
- the action. -}
autoCopies :: Key -> (Int -> Int -> Bool) -> Maybe Int -> CommandStart -> CommandStart
autoCopies key vs numcopiesattr a = do
auto <- Annex.getState Annex.auto
if auto
then do
needed <- getNumCopies numcopiesattr
(_, have) <- trustPartition UnTrusted =<< keyLocations key
if length have `vs` needed then a else stop
else a
{- Common checks for commands, and an interface to selectively remove them,
- or add others. -}
commonChecks :: [CommandCheck]
commonChecks = [fromOpt, toOpt, repoExists]
repoExists :: CommandCheck
repoExists = CommandCheck 0 ensureInitialized
fromOpt :: CommandCheck
fromOpt = CommandCheck 1 $ do
v <- Annex.getState Annex.fromremote
unless (v == Nothing) $ error "cannot use --from with this command"
toOpt :: CommandCheck
toOpt = CommandCheck 2 $ do
v <- Annex.getState Annex.toremote
unless (v == Nothing) $ error "cannot use --to with this command"
checkCommand :: Command -> Annex ()
checkCommand Command { cmdcheck = c } = sequence_ $ map runCheck c
dontCheck :: CommandCheck -> Command -> Command
dontCheck check cmd = mutateCheck cmd $ \c -> filter (/= check) c
addCheck :: Annex () -> Command -> Command
addCheck check cmd = mutateCheck cmd $
\c -> CommandCheck (length c + 100) check : c
mutateCheck :: Command -> ([CommandCheck] -> [CommandCheck]) -> Command
mutateCheck cmd@(Command { cmdcheck = c }) a = cmd { cmdcheck = a c }