2010-10-14 18:38:29 +00:00
|
|
|
{- git-annex command line -}
|
2010-10-14 07:18:11 +00:00
|
|
|
|
2010-10-15 01:10:59 +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
|
2010-10-21 20:30:16 +00:00
|
|
|
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-16 18:58:35 +00:00
|
|
|
|
2010-10-21 21:59:32 +00:00
|
|
|
data CmdWants = FilesInGit | FilesNotInGit | FilesMissing | Description
|
2010-10-16 18:58:35 +00:00
|
|
|
data Command = Command {
|
|
|
|
cmdname :: String,
|
|
|
|
cmdaction :: (String -> Annex ()),
|
2010-10-17 21:10:20 +00:00
|
|
|
cmdwants :: CmdWants,
|
|
|
|
cmddesc :: String
|
2010-10-16 18:58:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
2010-10-21 20:30:16 +00:00
|
|
|
, (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")
|
2010-10-21 20:30:16 +00:00
|
|
|
, (Command "fromkey" fromKeyCmd FilesMissing
|
|
|
|
"adds a file using a specific key")
|
2010-10-16 18:58:35 +00:00
|
|
|
]
|
|
|
|
|
2010-10-21 20:30:16 +00:00
|
|
|
-- 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))
|
2010-10-21 20:30:16 +00:00
|
|
|
"allow actions that may lose annexed data"
|
2010-10-21 21:59:32 +00:00
|
|
|
, Option ['b'] ["backend"] (ReqArg (storestring "backend") "NAME")
|
2010-10-21 20:30:16 +00:00
|
|
|
"specify default key-value backend to use"
|
2010-10-21 21:59:32 +00:00
|
|
|
, Option ['k'] ["key"] (ReqArg (storestring "key") "KEY")
|
2010-10-21 20:30:16 +00:00
|
|
|
"specify a key to use"
|
2010-10-23 00:35:39 +00:00
|
|
|
, 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
|
|
|
|
2010-10-21 20:30:16 +00:00
|
|
|
header = "Usage: git-annex " ++ (join "|" $ map cmdname cmds)
|
2010-10-17 21:10:20 +00:00
|
|
|
|
2010-10-21 20:30:16 +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) ++
|
2010-10-21 20:30:16 +00:00
|
|
|
(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
|
2010-10-21 20:30:16 +00:00
|
|
|
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
|
|
|
|
2010-10-16 18:58:35 +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
|
2010-10-17 04:33:05 +00:00
|
|
|
return $ foldl (++) [] files
|
2010-10-16 18:58:35 +00:00
|
|
|
findWanted FilesInGit params repo = do
|
|
|
|
files <- mapM (Git.inRepo repo) params
|
|
|
|
return $ foldl (++) [] files
|
2010-10-21 20:30:16 +00:00
|
|
|
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
|
|
|
|
2010-10-21 20:30:16 +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 ()])
|
2010-10-16 18:58:35 +00:00
|
|
|
parseCmd argv state = do
|
2010-10-16 17:38:59 +00:00
|
|
|
(flags, params) <- getopt
|
|
|
|
case (length params) of
|
2010-10-17 00:03:41 +00:00
|
|
|
0 -> error usage
|
2010-10-16 17:59:48 +00:00
|
|
|
_ -> 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
|
2010-10-16 18:58:35 +00:00
|
|
|
f <- findWanted want (drop 1 params)
|
2010-10-18 06:06:27 +00:00
|
|
|
(TypeInternals.repo state)
|
2010-10-17 04:33:05 +00:00
|
|
|
return (flags, map action $ filter notstate f)
|
2010-10-15 01:10:59 +00:00
|
|
|
where
|
2010-10-17 04:33:05 +00:00
|
|
|
-- never include files from the state directory
|
|
|
|
notstate f = stateLoc /= take (length stateLoc) f
|
2010-10-15 01:10:59 +00:00
|
|
|
getopt = case getOpt Permute options argv of
|
2010-10-16 18:58:35 +00:00
|
|
|
(flags, params, []) -> return (flags, params)
|
2010-10-17 00:03:41 +00:00
|
|
|
(_, _, errs) -> ioError (userError (concat errs ++ usage))
|
2010-10-16 18:58:35 +00:00
|
|
|
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 ()
|
2010-10-21 20:30:16 +00:00
|
|
|
addCmd file = notInBackend file $ do
|
2010-10-19 05:46:20 +00:00
|
|
|
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
|
2010-10-19 05:46:20 +00:00
|
|
|
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
|
2010-10-18 05:52:06 +00:00
|
|
|
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 ()
|
2010-10-21 20:30:16 +00:00
|
|
|
unannexCmd file = inBackend file $ \(key, backend) -> do
|
2010-10-17 17:13:49 +00:00
|
|
|
showStart "unannex" file
|
2010-10-21 20:30:16 +00:00
|
|
|
Annex.flagChange "force" $ FlagBool True -- force backend to always remove
|
2010-10-17 02:49:09 +00:00
|
|
|
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 ()
|
2010-10-21 20:30:16 +00:00
|
|
|
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 ()
|
2010-10-21 20:30:16 +00:00
|
|
|
dropCmd file = inBackend file $ \(key, backend) -> do
|
2010-10-17 14:47:46 +00:00
|
|
|
inbackend <- Backend.hasKey key
|
|
|
|
if (not inbackend)
|
|
|
|
then return () -- no-op
|
|
|
|
else do
|
2010-10-17 17:13:49 +00:00
|
|
|
showStart "drop" file
|
2010-10-17 14:47:46 +00:00
|
|
|
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
|
2010-10-17 14:47:46 +00:00
|
|
|
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. -}
|
2010-10-21 20:30:16 +00:00
|
|
|
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)
|
2010-10-17 22:27:37 +00:00
|
|
|
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
|
2010-10-18 05:52:06 +00:00
|
|
|
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
|
2010-10-18 05:52:06 +00:00
|
|
|
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
|
|
|
|
2010-10-21 20:30:16 +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
|
2010-10-21 20:30:16 +00:00
|
|
|
notInBackend file a = do
|
2010-10-17 22:27:37 +00:00
|
|
|
r <- Backend.lookupFile file
|
|
|
|
case (r) of
|
|
|
|
Just v -> return ()
|
|
|
|
Nothing -> a
|
2010-10-21 20:30:16 +00:00
|
|
|
inBackend file a = do
|
2010-10-17 15:47:36 +00:00
|
|
|
r <- Backend.lookupFile file
|
2010-10-14 07:40:26 +00:00
|
|
|
case (r) of
|
2010-10-17 22:27:37 +00:00
|
|
|
Just v -> a v
|
|
|
|
Nothing -> return ()
|