git-annex/Command.hs

277 lines
9.2 KiB
Haskell
Raw Normal View History

2010-11-11 22:54:52 +00:00
{- git-annex commands
-
- Copyright 2010 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
import Control.Monad (filterM, liftM, when)
import System.Path.WildMatch
import Text.Regex.PCRE.Light.Char8
2011-01-27 20:31:29 +00:00
import Data.List
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
2010-11-11 22:54:52 +00:00
import qualified GitRepo as Git
import Locations
2011-02-01 00:14:08 +00:00
import Utility
import Key
{- 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
{- Some helper functions are used to build up CommandSeek and CommandStart
- functions. -}
type CommandSeekStrings = CommandStartString -> CommandSeek
type CommandStartString = String -> CommandStart
type CommandSeekKeys = CommandStartKey -> CommandSeek
type CommandStartKey = Key -> CommandStart
type BackendFile = (FilePath, Maybe (Backend Annex))
type CommandSeekBackendFiles = CommandStartBackendFile -> CommandSeek
type CommandStartBackendFile = BackendFile -> CommandStart
type AttrFile = (FilePath, String)
type CommandSeekAttrFiles = CommandStartAttrFile -> CommandSeek
type CommandStartAttrFile = AttrFile -> CommandStart
type CommandSeekNothing = CommandStart -> CommandSeek
type CommandStartNothing = CommandStart
data Command = Command {
cmdname :: String,
cmdparams :: String,
cmdseek :: [CommandSeek],
cmddesc :: String
2010-11-04 17:28:49 +00:00
}
{- 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. -}
prepCmd :: Command -> [String] -> Annex [Annex Bool]
prepCmd Command { cmdseek = seek } params = do
lists <- mapM (\s -> s params) seek
2011-01-09 22:23:58 +00:00
return $ map doCommand $ concat lists
2010-11-04 17:28:49 +00:00
{- Runs a command through the start, perform and cleanup stages -}
doCommand :: CommandStart -> CommandCleanup
doCommand start = do
2010-11-04 17:28:49 +00:00
s <- start
case s of
2010-11-04 17:28:49 +00:00
Nothing -> return True
Just perform -> do
p <- perform
case p of
2010-11-04 17:28:49 +00:00
Nothing -> do
showEndFail
return False
Just cleanup -> do
c <- cleanup
if c
2010-11-04 17:28:49 +00:00
then do
showEndOk
return True
else do
showEndFail
return False
notAnnexed :: FilePath -> Annex (Maybe a) -> Annex (Maybe a)
notAnnexed file a = do
r <- Backend.lookupFile file
case r of
Just _ -> return Nothing
Nothing -> a
isAnnexed :: FilePath -> ((Key, Backend Annex) -> Annex (Maybe a)) -> Annex (Maybe a)
isAnnexed file a = do
r <- Backend.lookupFile file
case r of
Just v -> a v
Nothing -> return Nothing
2010-11-11 22:54:52 +00:00
notBareRepo :: Annex a -> Annex a
notBareRepo a = do
g <- Annex.gitRepo
when (Git.repoIsLocalBare g) $ do
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
user's parameters, and run a specified action on them. -}
withFilesInGit :: CommandSeekStrings
2010-11-11 22:54:52 +00:00
withFilesInGit a params = do
repo <- Annex.gitRepo
2011-02-01 00:14:08 +00:00
files <- liftIO $ runPreserveOrder (Git.inRepo repo) params
2011-02-19 21:00:40 +00:00
liftM (map a) $ filterFiles files
withAttrFilesInGit :: String -> CommandSeekAttrFiles
withAttrFilesInGit attr a params = do
repo <- Annex.gitRepo
2011-02-01 00:14:08 +00:00
files <- liftIO $ runPreserveOrder (Git.inRepo repo) params
files' <- filterFiles files
2011-02-19 21:00:40 +00:00
liftM (map a) $ liftIO $ Git.checkAttr repo attr files'
withBackendFilesInGit :: CommandSeekBackendFiles
withBackendFilesInGit a params = do
repo <- Annex.gitRepo
2011-02-01 00:14:08 +00:00
files <- liftIO $ runPreserveOrder (Git.inRepo repo) params
files' <- filterFiles files
backendPairs a files'
withFilesMissing :: CommandSeekStrings
2010-11-11 22:54:52 +00:00
withFilesMissing a params = do
files <- liftIO $ filterM missing params
2011-02-19 21:00:40 +00:00
liftM (map a) $ filterFiles files
2010-11-11 22:54:52 +00:00
where
missing f = do
e <- doesFileExist f
return $ not e
withFilesNotInGit :: CommandSeekBackendFiles
2010-11-11 22:54:52 +00:00
withFilesNotInGit a params = do
repo <- Annex.gitRepo
2011-02-01 00:14:08 +00:00
newfiles <- liftIO $ runPreserveOrder (Git.notInRepo repo) params
newfiles' <- filterFiles newfiles
backendPairs a newfiles'
withString :: CommandSeekStrings
withString a params = return [a $ unwords params]
withStrings :: CommandSeekStrings
2010-11-15 22:04:19 +00:00
withStrings a params = return $ map a params
withFilesToBeCommitted :: CommandSeekStrings
2010-11-11 22:54:52 +00:00
withFilesToBeCommitted a params = do
repo <- Annex.gitRepo
2011-02-02 01:58:47 +00:00
tocommit <- liftIO $ runPreserveOrder (Git.stagedFilesNotDeleted repo) params
2011-02-19 21:00:40 +00:00
liftM (map a) $ filterFiles tocommit
withFilesUnlocked :: CommandSeekBackendFiles
withFilesUnlocked = withFilesUnlocked' Git.typeChangedFiles
withFilesUnlockedToBeCommitted :: CommandSeekBackendFiles
withFilesUnlockedToBeCommitted = withFilesUnlocked' Git.typeChangedStagedFiles
withFilesUnlocked' :: (Git.Repo -> [FilePath] -> IO [FilePath]) -> CommandSeekBackendFiles
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
unlockedfiles' <- filterFiles unlockedfiles
backendPairs a unlockedfiles'
withKeys :: CommandSeekKeys
withKeys a params = return $ map a $ map parse params
where
parse p = case readKey p of
Just k -> k
Nothing -> error "bad key"
withTempFile :: CommandSeekStrings
2010-11-11 22:54:52 +00:00
withTempFile a params = return $ map a params
withNothing :: CommandSeekNothing
2010-11-13 20:15:45 +00:00
withNothing a [] = return [a]
withNothing _ _ = return []
2010-11-11 22:54:52 +00:00
backendPairs :: CommandSeekBackendFiles
2011-02-19 21:00:40 +00:00
backendPairs a files = liftM (map a) $ Backend.chooseBackends files
{- Filter out files from the state directory, and those matching the
- exclude glob pattern, if it was specified. -}
filterFiles :: [FilePath] -> Annex [FilePath]
filterFiles l = do
let l' = filter notState l
exclude <- Annex.getState Annex.exclude
if null exclude
then return l'
2011-01-30 04:08:17 +00:00
else return $ filter (notExcluded $ wildsRegex exclude) l'
where
2011-01-27 21:58:30 +00:00
notState f = not $ stateDir `isPrefixOf` f
notExcluded r f = case match r f [] of
Nothing -> True
Just _ -> False
2011-01-30 03:47:10 +00:00
2011-01-30 04:08:17 +00:00
wildsRegex :: [String] -> Regex
wildsRegex ws = compile regex []
where regex = "^(" ++ wildsRegex' ws "" ++ ")"
2011-01-30 03:47:10 +00:00
wildsRegex' :: [String] -> String -> String
wildsRegex' [] c = c
wildsRegex' (w:ws) "" = wildsRegex' ws (wildToRegex w)
wildsRegex' (w:ws) c = wildsRegex' ws (c ++ "|" ++ wildToRegex w)
2010-11-11 22:54:52 +00:00
{- filter out symlinks -}
notSymlink :: FilePath -> IO Bool
2011-02-19 21:00:40 +00:00
notSymlink f = liftM (not . isSymbolicLink) $ liftIO $ getSymbolicLinkStatus f
{- Descriptions of params used in usage messages. -}
paramRepeating :: String -> String
paramRepeating s = s ++ " ..."
paramOptional :: String -> String
paramOptional s = "[" ++ s ++ "]"
2011-03-03 21:21:00 +00:00
paramPair :: String -> String -> String
paramPair a b = a ++ " " ++ b
paramPath :: String
paramPath = "PATH"
paramKey :: String
paramKey = "KEY"
paramDesc :: String
paramDesc = "DESC"
paramNumber :: String
paramNumber = "NUMBER"
paramRemote :: String
paramRemote = "REMOTE"
paramGlob :: String
paramGlob = "GLOB"
paramName :: String
paramName = "NAME"
paramNothing :: String
paramNothing = ""
{- The Key specified by the --key parameter. -}
cmdlineKey :: Annex Key
cmdlineKey = do
k <- Annex.getState Annex.defaultkey
case k of
Nothing -> nokey
Just "" -> nokey
Just kstring -> case readKey kstring of
Nothing -> error "bad key"
Just key -> return key
where
nokey = error "please specify the key with --key"
{- Given an original list of files, and an expanded list derived from it,
- ensures that the original list's ordering is preserved.
-
- The input list may contain a directory, like "dir" or "dir/". Any
- items in the expanded list that are contained in that directory will
- appear at the same position as it did in the input list.
-}
preserveOrder :: [FilePath] -> [FilePath] -> [FilePath]
-- optimisation, only one item in original list, so no reordering needed
preserveOrder [_] new = new
preserveOrder orig new = collect orig new
where
collect [] n = n
collect [_] n = n -- optimisation
collect (l:ls) n = found ++ collect ls rest
where (found, rest)=partition (l `dirContains`) n
2011-02-01 00:14:08 +00:00
{- Runs an action that takes a list of FilePaths, and ensures that
- its return list preserves order.
-
- This assumes that it's cheaper to call preserveOrder on the result,
2011-02-19 21:00:40 +00:00
- than it would be to run the action separately with each param. In the case
2011-02-01 00:14:08 +00:00
- of git file list commands, that assumption tends to hold.
-}
runPreserveOrder :: ([FilePath] -> IO [FilePath]) -> [FilePath] -> IO [FilePath]
2011-02-19 21:00:40 +00:00
runPreserveOrder a files = liftM (preserveOrder files) (a files)