rework subcommand invocation logic
This commit is contained in:
parent
4da551827f
commit
59e49ae083
3 changed files with 85 additions and 63 deletions
132
Commands.hs
132
Commands.hs
|
@ -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
2
debian/changelog
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue