git-annex/Commands.hs

308 lines
9.1 KiB
Haskell
Raw Normal View History

2010-10-14 18:38:29 +00:00
{- git-annex command line -}
2010-10-14 07:18:11 +00:00
module Commands (parseCmd) where
2010-10-14 07:18:11 +00:00
2010-10-14 18:38:29 +00:00
import System.Console.GetOpt
2010-10-14 07:18:11 +00:00
import Control.Monad.State (liftIO)
import System.Posix.Files
import System.Directory
2010-10-15 20:09:30 +00:00
import System.Path
2010-10-14 07:18:11 +00:00
import Data.String.Utils
import Control.Monad (filterM)
2010-10-14 07:18:11 +00:00
import List
2010-10-14 21:37:20 +00:00
import IO
2010-10-16 20:20:49 +00:00
2010-10-14 07:18:11 +00:00
import qualified GitRepo as Git
import qualified Annex
import Utility
import Locations
import qualified Backend
import UUID
import LocationLog
import Types
2010-10-14 18:38:29 +00:00
import Core
2010-10-14 21:37:20 +00:00
import qualified Remotes
2010-10-18 06:06:27 +00:00
import qualified TypeInternals
2010-10-21 21:59:32 +00:00
data CmdWants = FilesInGit | FilesNotInGit | FilesMissing | Description
data Command = Command {
cmdname :: String,
cmdaction :: (String -> Annex ()),
2010-10-17 21:10:20 +00:00
cmdwants :: CmdWants,
cmddesc :: String
}
cmds :: [Command]
2010-10-16 23:43:32 +00:00
cmds = [
2010-10-17 21:10:20 +00:00
(Command "add" addCmd FilesNotInGit
"add files to annex")
, (Command "get" getCmd FilesInGit
"make content of annexed files available")
, (Command "drop" dropCmd FilesInGit
"indicate content of files not currently wanted")
2010-10-21 21:59:32 +00:00
, (Command "move" moveCmd FilesInGit
"transfer content of files to another repository")
, (Command "init" initCmd Description
2010-10-17 21:10:20 +00:00
"initialize git-annex with repository description")
2010-10-21 21:59:32 +00:00
, (Command "unannex" unannexCmd FilesInGit
"undo accidential add command")
2010-10-17 21:10:20 +00:00
, (Command "fix" fixCmd FilesInGit
"fix up files' symlinks to point to annexed content")
, (Command "fromkey" fromKeyCmd FilesMissing
"adds a file using a specific key")
]
-- Each dashed command-line option results in generation of an action
-- in the Annex monad that performs the necessary setting.
options :: [OptDescr (Annex ())]
2010-10-16 23:43:32 +00:00
options = [
2010-10-21 21:59:32 +00:00
Option ['f'] ["force"] (NoArg (storebool "force" True))
"allow actions that may lose annexed data"
2010-10-21 21:59:32 +00:00
, Option ['b'] ["backend"] (ReqArg (storestring "backend") "NAME")
"specify default key-value backend to use"
2010-10-21 21:59:32 +00:00
, Option ['k'] ["key"] (ReqArg (storestring "key") "KEY")
"specify a key to use"
, Option ['t'] ["to"] (ReqArg (storestring "repository") "REPOSITORY")
"specify a repository to transfer content to"
, Option ['f'] ["from"] (ReqArg (storestring "repository") "REPOSITORY")
"specify a repository to transfer content from"
2010-10-16 23:43:32 +00:00
]
2010-10-21 21:59:32 +00:00
where
storebool n b = Annex.flagChange n $ FlagBool b
storestring n s = Annex.flagChange n $ FlagString s
2010-10-16 23:43:32 +00:00
header = "Usage: git-annex " ++ (join "|" $ map cmdname cmds)
2010-10-17 21:10:20 +00:00
usage :: String
2010-10-17 21:10:20 +00:00
usage = usageInfo header options ++ "\nSubcommands:\n" ++ cmddescs
where
cmddescs = unlines $ map (\c -> indent $ showcmd c) cmds
showcmd c =
(cmdname c) ++
(pad 10 (cmdname c)) ++
(descWanted (cmdwants c)) ++
(pad 13 (descWanted (cmdwants c))) ++
2010-10-17 21:10:20 +00:00
(cmddesc c)
indent l = " " ++ l
pad n s = take (n - (length s)) $ repeat ' '
{- Generate descrioptions of wanted parameters for subcommands. -}
descWanted :: CmdWants -> String
descWanted Description = "DESCRIPTION"
descWanted _ = "PATH ..."
2010-10-17 21:10:20 +00:00
{- Finds the type of parameters a command wants, from among the passed
- parameter list. -}
findWanted :: CmdWants -> [String] -> Git.Repo -> IO [String]
findWanted FilesNotInGit params repo = do
files <- mapM (Git.notInRepo repo) params
return $ foldl (++) [] files
findWanted FilesInGit params repo = do
files <- mapM (Git.inRepo repo) params
return $ foldl (++) [] files
findWanted FilesMissing params repo = do
files <- liftIO $ filterM missing params
return $ files
where
missing f = do
e <- doesFileExist f
if (e) then return False else return True
findWanted Description params _ = do
2010-10-16 20:15:31 +00:00
return $ [unwords params]
2010-10-14 18:38:29 +00:00
{- Parses command line and returns two lists of actions to be
- run in the Annex monad. The first actions configure it
- according to command line options, while the second actions
- handle subcommands. -}
parseCmd :: [String] -> AnnexState -> IO ([Annex ()], [Annex ()])
parseCmd argv state = do
(flags, params) <- getopt
case (length params) of
2010-10-17 00:03:41 +00:00
0 -> error usage
_ -> case (lookupCmd (params !! 0)) of
2010-10-17 00:03:41 +00:00
[] -> error usage
2010-10-17 21:10:20 +00:00
[Command _ action want _] -> do
f <- findWanted want (drop 1 params)
2010-10-18 06:06:27 +00:00
(TypeInternals.repo state)
return (flags, map action $ filter notstate f)
where
-- never include files from the state directory
notstate f = stateLoc /= take (length stateLoc) f
getopt = case getOpt Permute options argv of
(flags, params, []) -> return (flags, params)
2010-10-17 00:03:41 +00:00
(_, _, errs) -> ioError (userError (concat errs ++ usage))
lookupCmd cmd = filter (\c -> cmd == cmdname c) cmds
2010-10-14 18:38:29 +00:00
2010-10-14 07:18:11 +00:00
{- Annexes a file, storing it in a backend, and then moving it into
- the annex directory and setting up the symlink pointing to its content. -}
2010-10-14 16:36:40 +00:00
addCmd :: FilePath -> Annex ()
addCmd file = notInBackend file $ do
s <- liftIO $ getSymbolicLinkStatus file
if ((isSymbolicLink s) || (not $ isRegularFile s))
then return ()
else do
showStart "add" file
g <- Annex.gitRepo
stored <- Backend.storeFileKey file
case (stored) of
2010-10-19 16:55:40 +00:00
Nothing -> showEndFail
Just (key, backend) -> do
logStatus key ValuePresent
setup g key
2010-10-14 07:18:11 +00:00
where
2010-10-17 01:03:25 +00:00
setup g key = do
2010-10-14 23:36:11 +00:00
let dest = annexLocation g key
2010-10-16 23:43:32 +00:00
liftIO $ createDirectoryIfMissing True (parentDir dest)
liftIO $ renameFile file dest
2010-10-17 01:03:25 +00:00
link <- calcGitLink file key
liftIO $ createSymbolicLink link file
liftIO $ Git.run g ["add", file]
2010-10-17 17:13:49 +00:00
showEndOk
2010-10-14 07:18:11 +00:00
2010-10-17 01:03:25 +00:00
{- Undo addCmd. -}
2010-10-14 07:18:11 +00:00
unannexCmd :: FilePath -> Annex ()
unannexCmd file = inBackend file $ \(key, backend) -> do
2010-10-17 17:13:49 +00:00
showStart "unannex" file
Annex.flagChange "force" $ FlagBool True -- force backend to always remove
Backend.removeKey backend key
logStatus key ValueMissing
g <- Annex.gitRepo
let src = annexLocation g key
moveout g src
2010-10-14 07:18:11 +00:00
where
moveout g src = do
2010-10-17 02:59:19 +00:00
liftIO $ removeFile file
2010-10-17 17:13:49 +00:00
liftIO $ Git.run g ["rm", "--quiet", file]
2010-10-14 07:18:11 +00:00
-- git rm deletes empty directories;
-- put them back
2010-10-17 02:59:19 +00:00
liftIO $ createDirectoryIfMissing True (parentDir file)
liftIO $ renameFile src file
2010-10-17 17:13:49 +00:00
showEndOk
2010-10-14 07:18:11 +00:00
{- Gets an annexed file from one of the backends. -}
getCmd :: FilePath -> Annex ()
getCmd file = inBackend file $ \(key, backend) -> do
2010-10-14 23:36:11 +00:00
inannex <- inAnnex key
2010-10-14 07:18:11 +00:00
if (inannex)
then return ()
else do
2010-10-17 17:13:49 +00:00
showStart "get" file
2010-10-14 07:18:11 +00:00
g <- Annex.gitRepo
2010-10-14 23:36:11 +00:00
let dest = annexLocation g key
2010-10-17 20:39:30 +00:00
let tmp = (annexTmpLocation g) ++ (keyFile key)
liftIO $ createDirectoryIfMissing True (parentDir tmp)
success <- Backend.retrieveKeyFile backend key tmp
2010-10-14 07:18:11 +00:00
if (success)
then do
2010-10-17 20:39:30 +00:00
liftIO $ renameFile tmp dest
2010-10-14 07:18:11 +00:00
logStatus key ValuePresent
2010-10-17 17:13:49 +00:00
showEndOk
2010-10-17 20:39:30 +00:00
else do
2010-10-19 16:55:40 +00:00
showEndFail
2010-10-14 07:18:11 +00:00
2010-10-21 21:59:32 +00:00
{- Moves the content of an annexed file to another repository,
- removing it from the current repository, and updates locationlog
- information on both.
-
- Note that unlike drop, this does not honor annex.numcopies.
- A file's content can be moved even if there are insufficient copies to
- allow it to be dropped.
-}
moveCmd :: FilePath -> Annex ()
moveCmd file = inBackend file $ \(key, backend) -> do
error "TODO"
2010-10-16 20:15:31 +00:00
{- Indicates a file's content is not wanted anymore, and should be removed
- if it's safe to do so. -}
2010-10-14 07:18:11 +00:00
dropCmd :: FilePath -> Annex ()
dropCmd file = inBackend file $ \(key, backend) -> do
inbackend <- Backend.hasKey key
if (not inbackend)
then return () -- no-op
else do
2010-10-17 17:13:49 +00:00
showStart "drop" file
success <- Backend.removeKey backend key
if (success)
2010-10-17 17:13:49 +00:00
then do
cleanup key
showEndOk
2010-10-19 16:55:40 +00:00
else showEndFail
where
cleanup key = do
2010-10-14 18:14:19 +00:00
logStatus key ValueMissing
2010-10-14 23:36:11 +00:00
inannex <- inAnnex key
2010-10-14 18:14:19 +00:00
if (inannex)
then do
g <- Annex.gitRepo
2010-10-14 23:36:11 +00:00
let loc = annexLocation g key
2010-10-14 18:14:19 +00:00
liftIO $ removeFile loc
return ()
else return ()
2010-10-14 07:18:11 +00:00
2010-10-17 01:03:25 +00:00
{- Fixes the symlink to an annexed file. -}
fixCmd :: FilePath -> Annex ()
fixCmd file = inBackend file $ \(key, backend) -> do
2010-10-17 01:03:25 +00:00
link <- calcGitLink file key
2010-10-17 21:10:20 +00:00
l <- liftIO $ readSymbolicLink file
if (link == l)
then return ()
2010-10-17 21:10:20 +00:00
else do
showStart "fix" file
liftIO $ createDirectoryIfMissing True (parentDir file)
liftIO $ removeFile file
liftIO $ createSymbolicLink link file
g <- Annex.gitRepo
liftIO $ Git.run g ["add", file]
2010-10-17 21:10:20 +00:00
showEndOk
2010-10-17 01:03:25 +00:00
2010-10-16 20:15:31 +00:00
{- Stores description for the repository. -}
2010-10-17 21:10:20 +00:00
initCmd :: String -> Annex ()
initCmd description = do
if (0 == length description)
then error $
"please specify a description of this repository\n" ++
usage
else do
g <- Annex.gitRepo
u <- getUUID g
describeUUID u description
log <- uuidLog
liftIO $ Git.run g ["add", log]
2010-10-20 16:07:24 +00:00
liftIO $ Git.run g ["commit", "-m", "git annex init", log]
2010-10-17 21:10:20 +00:00
liftIO $ putStrLn "description set"
2010-10-16 20:15:31 +00:00
{- Adds a file pointing at a manually-specified key -}
fromKeyCmd :: FilePath -> Annex ()
fromKeyCmd file = do
keyname <- Annex.flagGet "key"
if (0 == length keyname)
then error "please specify the key with --key"
else return ()
backends <- Backend.list
let key = genKey (backends !! 0) keyname
inbackend <- Backend.hasKey key
if (not inbackend)
then error $ "key ("++keyname++") is not present in backend"
else return ()
link <- calcGitLink file key
showStart "fromkey" file
liftIO $ createDirectoryIfMissing True (parentDir file)
liftIO $ createSymbolicLink link file
g <- Annex.gitRepo
liftIO $ Git.run g ["add", file]
showEndOk
2010-10-17 16:08:59 +00:00
-- helpers
notInBackend file a = do
r <- Backend.lookupFile file
case (r) of
Just v -> return ()
Nothing -> a
inBackend file a = do
r <- Backend.lookupFile file
2010-10-14 07:40:26 +00:00
case (r) of
Just v -> a v
Nothing -> return ()