rework subcommand invocation logic

This commit is contained in:
Joey Hess 2010-11-01 14:49:05 -04:00
parent 4da551827f
commit 59e49ae083
3 changed files with 85 additions and 63 deletions

View file

@ -27,12 +27,18 @@ import Core
import qualified Remotes import qualified Remotes
import qualified TypeInternals import qualified TypeInternals
{- A subcommand can take one of several kinds of input parameters. -}
data SubCmdInput = FilesInGit FilePath | FilesNotInGit FilePath |
FilesMissing FilePath | Description String | Keys String |
Tempfile FilePath | FilesToBeCommitted FilePath
{- A subcommand runs in three stages. Each stage can return the next stage {- A subcommand runs in three stages. Each stage can return the next stage
- to run. - to run.
- -
- 1. The start stage is run before anything is printed about the - 1. The start stage is run before anything is printed about the
- subcommand, and can early abort it if the input does not make sense. - subcommand, is passed some input, and can early abort it
- It should run quickly and should not modify Annex state. - if the input does not make sense. It should run quickly and
- should not modify Annex state.
- -
- 2. The perform stage is run after a message is printed about the subcommand - 2. The perform stage is run after a message is printed about the subcommand
- being run, and it should be where the bulk of the work happens. - being run, and it should be where the bulk of the work happens.
@ -40,18 +46,18 @@ import qualified TypeInternals
- 3. The cleanup stage is run only if the perform stage succeeds, and it - 3. The cleanup stage is run only if the perform stage succeeds, and it
- returns the overall success/fail of the subcommand. - returns the overall success/fail of the subcommand.
-} -}
type SubCmdStart = String -> Annex (Maybe SubCmdPerform) type SubCmdStart = Annex (Maybe SubCmdPerform)
type SubCmdPerform = Annex (Maybe SubCmdCleanup) type SubCmdPerform = Annex (Maybe SubCmdCleanup)
type SubCmdCleanup = Annex Bool type SubCmdCleanup = Annex Bool
{- Runs a subcommand through its three stages. -} {- Runs a subcommand through its three stages. -}
doSubCmd :: String -> SubCmdStart -> String -> Annex Bool doSubCmd :: String -> SubCmdStart -> Annex Bool
doSubCmd cmdname start param = do doSubCmd cmdname start = do
startres <- start param :: Annex (Maybe SubCmdPerform) startres <- start :: Annex (Maybe SubCmdPerform)
case (startres) of case (startres) of
Nothing -> return True Nothing -> return True
Just perform -> do Just perform -> do
showStart cmdname param --showStart cmdname param
performres <- perform :: Annex (Maybe SubCmdCleanup) performres <- perform :: Annex (Maybe SubCmdCleanup)
case (performres) of case (performres) of
Nothing -> do Nothing -> do
@ -68,15 +74,10 @@ doSubCmd cmdname start param = do
return False return False
{- A subcommand can broadly want one of several kinds of input parameters.
- This allows a first stage of filtering before starting a subcommand. -}
data SubCmdWants = FilesInGit | FilesNotInGit | FilesMissing
| Description | Keys | Tempfile | FilesToBeCommitted
data SubCommand = SubCommand { data SubCommand = SubCommand {
subcmdname :: String, subcmdname :: String,
subcmdaction :: SubCmdStart, subcmdaction :: (SubCmdInput -> SubCmdStart),
subcmdwants :: SubCmdWants, subcmdinput :: (String -> SubCmdInput),
subcmddesc :: String subcmddesc :: String
} }
subCmds :: [SubCommand] subCmds :: [SubCommand]
@ -139,40 +140,53 @@ usage = usageInfo header options ++ "\nSubcommands:\n" ++ cmddescs
showcmd c = showcmd c =
(subcmdname c) ++ (subcmdname c) ++
(pad 11 (subcmdname c)) ++ (pad 11 (subcmdname c)) ++
(descWanted (subcmdwants c)) ++ (descSubCmdInput (subcmdinput c)) ++
(pad 13 (descWanted (subcmdwants c))) ++ (pad 13 (descSubCmdInput (subcmdinput c))) ++
(subcmddesc c) (subcmddesc c)
indent l = " " ++ l indent l = " " ++ l
pad n s = take (n - (length s)) $ repeat ' ' pad n s = take (n - (length s)) $ repeat ' '
{- Generate descriptions of wanted parameters for subcommands. -} {- Generate descriptions of wanted parameters for subcommands. -}
descWanted :: SubCmdWants -> String descSubCmdInput :: (String -> SubCmdInput) -> String
descWanted Description = "DESCRIPTION" descSubCmdInput Description = "DESCRIPTION"
descWanted Keys = "KEY ..." descSubCmdInput Keys = "KEY ..."
descWanted _ = "PATH ..." descSubCmdInput _ = "PATH ..."
{- Prepares a set of actions to run to handle a subcommand, based on
- the parameters passed to it. -}
prepSubCmd :: SubCommand -> Git.Repo -> [String] -> IO [Annex Bool]
prepSubCmd SubCommand { subcmdname = name, subcmdaction = action,
subcmdinput = input, subcmddesc = _ } repo params = do
input <- findInput input params repo
return $ map (doSubCmd name action) input
{- Finds the type of parameters a subcommand wants, from among the passed {- Finds the type of parameters a subcommand wants, from among the passed
- parameter list. -} - parameter list. -}
findWanted :: SubCmdWants -> [String] -> Git.Repo -> IO [String] findInput :: (String -> SubCmdInput) -> [String] -> Git.Repo -> IO [SubCmdInput]
findWanted FilesNotInGit params repo = do findInput FilesNotInGit params repo = do
files <- mapM (Git.notInRepo repo) params files <- mapM (Git.notInRepo repo) params
return $ foldl (++) [] files return $ map FilesNotInGit $ notState $ foldl (++) [] files
findWanted FilesInGit params repo = do findInput FilesInGit params repo = do
files <- mapM (Git.inRepo repo) params files <- mapM (Git.inRepo repo) params
return $ foldl (++) [] files return $ map FilesInGit $ notState $ foldl (++) [] files
findWanted FilesMissing params _ = do findInput FilesMissing params _ = do
files <- liftIO $ filterM missing params files <- liftIO $ filterM missing params
return $ files return $ map FilesMissing $ notState $ files
where where
missing f = do missing f = do
e <- doesFileExist f e <- doesFileExist f
return $ not e return $ not e
findWanted Description params _ = do findInput Description params _ = do
return $ [unwords params] return $ map Description $ [unwords params]
findWanted FilesToBeCommitted params repo = do findInput FilesToBeCommitted params repo = do
files <- mapM (Git.stagedFiles repo) params files <- mapM (Git.stagedFiles repo) params
return $ foldl (++) [] files return $ map FilesToBeCommitted $ notState $ foldl (++) [] files
findWanted _ params _ = return params findInput Keys params _ = return $ map Keys params
findInput Tempfile params _ = return $ map Tempfile params
{- filter out files from the state directory -}
notState :: [FilePath] -> [FilePath]
notState fs = filter (\f -> stateLoc /= take (length stateLoc) f) fs
{- Parses command line and returns two lists of actions to be {- Parses command line and returns two lists of actions to be
- run in the Annex monad. The first actions configure it - run in the Annex monad. The first actions configure it
@ -184,20 +198,15 @@ parseCmd argv state = do
when (null params) $ error usage when (null params) $ error usage
case lookupCmd (params !! 0) of case lookupCmd (params !! 0) of
[] -> error usage [] -> error usage
[SubCommand { subcmdname = name, subcmdaction = action, [subcommand] -> do
subcmdwants = want, subcmddesc = _ }] -> do let repo = TypeInternals.repo state
files <- findWanted want (drop 1 params) actions <- prepSubCmd subcommand repo (drop 1 params)
(TypeInternals.repo state)
let actions = map (doSubCmd name action) $
filter notstate files
let configactions = map (\flag -> do let configactions = map (\flag -> do
flag flag
return True) flags return True) flags
return (configactions, actions) return (configactions, actions)
_ -> error "internal error: multiple matching subcommands" _ -> error "internal error: multiple matching subcommands"
where where
-- never include files from the state directory
notstate f = stateLoc /= take (length stateLoc) f
getopt = case getOpt Permute options argv of getopt = case getOpt Permute options argv of
(flags, params, []) -> return (flags, params) (flags, params, []) -> return (flags, params)
(_, _, errs) -> ioError (userError (concat errs ++ usage)) (_, _, errs) -> ioError (userError (concat errs ++ usage))
@ -206,8 +215,8 @@ parseCmd argv state = do
{- The add subcommand annexes a file, storing it in a backend, and then {- The add subcommand annexes a file, storing it in a backend, and then
- moving it into the annex directory and setting up the symlink pointing - moving it into the annex directory and setting up the symlink pointing
- to its content. -} - to its content. -}
addStart :: FilePath -> Annex (Maybe SubCmdPerform) addStart :: SubCmdInput -> SubCmdStart
addStart file = notAnnexed file $ do addStart (FilesNotInGit file) = notAnnexed file $ do
s <- liftIO $ getSymbolicLinkStatus file s <- liftIO $ getSymbolicLinkStatus file
if ((isSymbolicLink s) || (not $ isRegularFile s)) if ((isSymbolicLink s) || (not $ isRegularFile s))
then return Nothing then return Nothing
@ -231,8 +240,8 @@ addCleanup file key = do
return True return True
{- The unannex subcommand undoes an add. -} {- The unannex subcommand undoes an add. -}
unannexStart :: FilePath -> Annex (Maybe SubCmdPerform) unannexStart :: SubCmdInput -> SubCmdStart
unannexStart file = isAnnexed file $ \(key, backend) -> do unannexStart (FilesInGit file) = isAnnexed file $ \(key, backend) -> do
return $ Just $ unannexPerform file key backend return $ Just $ unannexPerform file key backend
unannexPerform :: FilePath -> Key -> Backend -> Annex (Maybe SubCmdCleanup) unannexPerform :: FilePath -> Key -> Backend -> Annex (Maybe SubCmdCleanup)
unannexPerform file key backend = do unannexPerform file key backend = do
@ -255,8 +264,8 @@ unannexCleanup file key = do
return True return True
{- Gets an annexed file from one of the backends. -} {- Gets an annexed file from one of the backends. -}
getStart :: FilePath -> Annex (Maybe SubCmdPerform) getStart :: SubCmdInput -> Annex (Maybe SubCmdPerform)
getStart file = isAnnexed file $ \(key, backend) -> do getStart (FilesInGit file) = isAnnexed file $ \(key, backend) -> do
inannex <- inAnnex key inannex <- inAnnex key
if (inannex) if (inannex)
then return Nothing then return Nothing
@ -270,8 +279,8 @@ getPerform key backend = do
{- Indicates a file's content is not wanted anymore, and should be removed {- Indicates a file's content is not wanted anymore, and should be removed
- if it's safe to do so. -} - if it's safe to do so. -}
dropStart :: FilePath -> Annex (Maybe SubCmdPerform) dropStart :: SubCmdInput -> SubCmdStart
dropStart file = isAnnexed file $ \(key, backend) -> do dropStart (FilesInGit file) = isAnnexed file $ \(key, backend) -> do
inbackend <- Backend.hasKey key inbackend <- Backend.hasKey key
if (not inbackend) if (not inbackend)
then return Nothing then return Nothing
@ -295,8 +304,8 @@ dropCleanup key = do
else return True else return True
{- Drops cached content for a key. -} {- Drops cached content for a key. -}
dropKeyStart :: String -> Annex (Maybe SubCmdPerform) dropKeyStart :: SubCmdInput -> SubCmdStart
dropKeyStart keyname = do dropKeyStart (Keys keyname) = do
backends <- Backend.list backends <- Backend.list
let key = genKey (backends !! 0) keyname let key = genKey (backends !! 0) keyname
present <- inAnnex key present <- inAnnex key
@ -318,8 +327,8 @@ dropKeyCleanup key = do
return True return True
{- Sets cached content for a key. -} {- Sets cached content for a key. -}
setKeyStart :: FilePath -> Annex (Maybe SubCmdPerform) setKeyStart :: SubCmdInput -> SubCmdStart
setKeyStart tmpfile = do setKeyStart (Tempfile tmpfile) = do
keyname <- Annex.flagGet "key" keyname <- Annex.flagGet "key"
when (null keyname) $ error "please specify the key with --key" when (null keyname) $ error "please specify the key with --key"
backends <- Backend.list backends <- Backend.list
@ -339,8 +348,11 @@ setKeyCleanup key = do
return True return True
{- Fixes the symlink to an annexed file. -} {- Fixes the symlink to an annexed file. -}
fixStart :: FilePath -> Annex (Maybe SubCmdPerform) fixStart :: SubCmdInput -> SubCmdStart
fixStart file = isAnnexed file $ \(key, _) -> do fixStart (FilesInGit file) = fixStart' file
fixStart (FilesToBeCommitted file) = fixStart' file
fixStart' :: FilePath -> SubCmdStart
fixStart' file = isAnnexed file $ \(key, _) -> do
link <- calcGitLink file key link <- calcGitLink file key
l <- liftIO $ readSymbolicLink file l <- liftIO $ readSymbolicLink file
if (link == l) if (link == l)
@ -358,8 +370,8 @@ fixCleanup file = do
return True return True
{- Stores description for the repository etc. -} {- Stores description for the repository etc. -}
initStart :: String -> Annex (Maybe SubCmdPerform) initStart :: SubCmdInput -> SubCmdStart
initStart description = do initStart (Description description) = do
when (null description) $ error $ when (null description) $ error $
"please specify a description of this repository\n" ++ usage "please specify a description of this repository\n" ++ usage
return $ Just $ initPerform description return $ Just $ initPerform description
@ -380,8 +392,8 @@ initCleanup = do
return True return True
{- Adds a file pointing at a manually-specified key -} {- Adds a file pointing at a manually-specified key -}
fromKeyStart :: FilePath -> Annex (Maybe SubCmdPerform) fromKeyStart :: SubCmdInput -> SubCmdStart
fromKeyStart file = do fromKeyStart (FilesMissing file) = do
keyname <- Annex.flagGet "key" keyname <- Annex.flagGet "key"
when (null keyname) $ error "please specify the key with --key" when (null keyname) $ error "please specify the key with --key"
backends <- Backend.list backends <- Backend.list
@ -406,8 +418,8 @@ fromKeyCleanup file = do
- -
- This only operates on the cached file content; it does not involve - This only operates on the cached file content; it does not involve
- moving data in the key-value backend. -} - moving data in the key-value backend. -}
moveStart :: FilePath -> Annex (Maybe SubCmdPerform) moveStart :: SubCmdInput -> SubCmdStart
moveStart file = do moveStart (FilesInGit file) = do
fromName <- Annex.flagGet "fromrepository" fromName <- Annex.flagGet "fromrepository"
toName <- Annex.flagGet "torepository" toName <- Annex.flagGet "torepository"
case (fromName, toName) of case (fromName, toName) of

2
debian/changelog vendored
View file

@ -9,6 +9,8 @@ git-annex (0.03) UNRELEASED; urgency=low
from git before starting, and will be much faster with large repos. from git before starting, and will be much faster with large repos.
* Fix crash on unknown symlinks. * Fix crash on unknown symlinks.
* Added remote.annex-scp-options and remote.annex-ssh-options. * Added remote.annex-scp-options and remote.annex-ssh-options.
* The backends to use when adding different sets of files can be configured
via gitattributes.
-- Joey Hess <joeyh@debian.org> Thu, 28 Oct 2010 13:46:59 -0400 -- Joey Hess <joeyh@debian.org> Thu, 28 Oct 2010 13:46:59 -0400

View file

@ -159,8 +159,7 @@ Many git-annex subcommands will stage changes for later `git commit` by you.
* --backend=name * --backend=name
Specify the default key-value backend to use, adding it to the front Specifies the key-value backend to use when adding a file.
of the list normally configured by `annex.backends`.
* --key=name * --key=name
@ -186,7 +185,7 @@ Here are all the supported configuration settings.
repositories (default: 1) repositories (default: 1)
* `annex.backends` -- space-separated list of names of * `annex.backends` -- space-separated list of names of
the key-value backends to use. The first listed is used to store the key-value backends to use. The first listed is used to store
new files. (default: "WORM SHA1 URL") new files by default. (default: "WORM SHA1 URL")
* `remote.<name>.annex-cost` -- When determining which repository to * `remote.<name>.annex-cost` -- When determining which repository to
transfer annexed files from or to, ones with lower costs are preferred. transfer annexed files from or to, ones with lower costs are preferred.
The default cost is 100 for local repositories, and 200 for remote The default cost is 100 for local repositories, and 200 for remote
@ -204,6 +203,15 @@ Here are all the supported configuration settings.
* `annex.scp-options` and `annex.ssh-options` -- Default scp and ssh * `annex.scp-options` and `annex.ssh-options` -- Default scp and ssh
options to use if a remote does not have specific options. options to use if a remote does not have specific options.
The backend used when adding a new file to the annex can be configured
on a per-file-type basis via the `.gitattributes` file. In the file,
the `git-annex-backend` attribute can be set to the name of the backend to
use. For example, this here's how to use the WORM backend by default,
but the SHA1 backend for ogg files:
* git-annex-backend=WORM
*.ogg git-annex-backend=SHA1
# FILES # FILES
These files are used, in your git repository: These files are used, in your git repository: