git-annex/Command.hs

243 lines
8.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
2010-11-11 22:54:52 +00:00
import Control.Monad.State (liftIO)
import System.Directory
import System.Posix.Files
2011-09-19 05:37:04 +00:00
import Control.Monad (filterM, liftM)
import Control.Applicative
2011-05-15 06:49:43 +00:00
import Data.Maybe
2010-11-11 22:54:52 +00:00
import Types
2010-11-04 17:28:49 +00:00
import qualified Backend
2010-11-08 19:15:21 +00:00
import Messages
2010-11-04 17:28:49 +00:00
import qualified Annex
import qualified Git
import qualified Git.LsFiles as LsFiles
2011-02-01 00:14:08 +00:00
import Utility
2011-09-19 05:37:04 +00:00
import Utility.Conditional
import Utility.Path
import Types.Key
import Trust
import LocationLog
import Config
2011-09-15 20:57:02 +00:00
import Backend
import Limit
{- A command runs in four stages.
-
- 0. 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]
{- 1. 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)
{- 2. 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)
{- 3. 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 {
2011-05-15 06:12:17 +00:00
cmdusesrepo :: Bool,
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
{- Most commands operate on files in a git repo. -}
repoCommand :: String -> String -> [CommandSeek] -> String -> Command
2011-05-15 06:12:17 +00:00
repoCommand = Command True
2011-05-15 06:02:46 +00:00
{- Others can run anywhere. -}
standaloneCommand :: String -> String -> [CommandSeek] -> String -> Command
2011-05-15 06:12:17 +00:00
standaloneCommand = Command False
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
{- 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
notAnnexed :: FilePath -> Annex (Maybe a) -> Annex (Maybe a)
2011-05-15 06:49:43 +00:00
notAnnexed file a = maybe a (const $ return Nothing) =<< Backend.lookupFile file
isAnnexed :: FilePath -> ((Key, Backend Annex) -> Annex (Maybe a)) -> Annex (Maybe a)
2011-05-15 06:49:43 +00:00
isAnnexed file a = maybe (return Nothing) a =<< Backend.lookupFile file
2010-11-11 22:54:52 +00:00
notBareRepo :: Annex a -> Annex a
notBareRepo a = do
2011-09-19 05:37:04 +00:00
whenM (Git.repoIsLocalBare <$> Annex.gitRepo) $
error "You cannot run this subcommand in a bare repository."
a
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 <- Annex.gitRepo
runFiltered a $ liftIO $ runPreserveOrder (LsFiles.inRepo repo) params
withAttrFilesInGit :: String -> ((FilePath, String) -> CommandStart) -> CommandSeek
withAttrFilesInGit attr a params = do
repo <- Annex.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 <- Annex.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 <- Annex.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 <- Annex.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
2010-11-11 22:54:52 +00:00
repo <- Annex.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-09-19 03:09:40 +00:00
liftM (map $ 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
2010-11-11 22:54:52 +00:00
{- filter out symlinks -}
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"
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