git-annex/Commands.hs
2010-10-14 14:49:19 -04:00

185 lines
5.6 KiB
Haskell

{- git-annex command line -}
module Commands (
argvToActions
) where
import System.Console.GetOpt
import Control.Monad.State (liftIO)
import System.Posix.Files
import System.Directory
import Data.String.Utils
import List
import qualified GitRepo as Git
import qualified Annex
import Utility
import Locations
import qualified Backend
import BackendList
import UUID
import LocationLog
import Types
import Core
options :: [OptDescr (String -> Annex ())]
options =
[ Option ['a'] ["add"] (NoArg addCmd) "add files to annex"
, Option ['p'] ["push"] (NoArg pushCmd) "push annex to repos"
, Option ['P'] ["pull"] (NoArg pullCmd) "pull annex from repos"
, Option ['w'] ["want"] (NoArg wantCmd) "request file contents"
, Option ['g'] ["get"] (NoArg getCmd) "transfer file contents"
, Option ['d'] ["drop"] (NoArg dropCmd) "indicate file contents not needed"
, Option ['u'] ["unannex"] (NoArg unannexCmd) "undo --add"
]
{- Parses command line and returns a list of actons to be run in the Annex
- monad. -}
argvToActions :: [String] -> IO [Annex ()]
argvToActions argv = do
case getOpt Permute options argv of
([],files,[]) -> return $ map defaultCmd files
-- one mode is normal case
(m:[],files,[]) -> return $ map m files
-- multiple modes is an error
(ms,files,[]) -> ioError (userError ("only one mode should be specified\n" ++ usageInfo header options))
-- error case
(_,files,errs) -> ioError (userError (concat errs ++ usageInfo header options))
where header = "Usage: git-annex [mode] file"
{- Default mode is to annex a file if it is not already, and otherwise
- get its content. -}
defaultCmd :: FilePath -> Annex ()
defaultCmd file = do
r <- liftIO $ Backend.lookupFile file
case (r) of
Just v -> getCmd file
Nothing -> addCmd file
{- 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. -}
addCmd :: FilePath -> Annex ()
addCmd file = inBackend file err $ do
liftIO $ checkLegal file
stored <- Backend.storeFileKey file
g <- Annex.gitRepo
case (stored) of
Nothing -> error $ "no backend could store: " ++ file
Just (key, backend) -> do
logStatus key ValuePresent
liftIO $ setup g key backend
where
err = error $ "already annexed " ++ file
checkLegal file = do
s <- getSymbolicLinkStatus file
if ((isSymbolicLink s) || (not $ isRegularFile s))
then error $ "not a regular file: " ++ file
else return ()
setup g key backend = do
let dest = annexLocation g backend key
let reldest = annexLocationRelative g backend key
createDirectoryIfMissing True (parentDir dest)
renameFile file dest
createSymbolicLink ((linkTarget file) ++ reldest) file
Git.run g ["add", file]
Git.run g ["commit", "-m",
("git-annex annexed " ++ file), file]
linkTarget file =
-- relies on file being relative to the top of the
-- git repo; just replace each subdirectory with ".."
if (subdirs > 0)
then (join "/" $ take subdirs $ repeat "..") ++ "/"
else ""
where
subdirs = (length $ split "/" file) - 1
{- Inverse of addCmd. -}
unannexCmd :: FilePath -> Annex ()
unannexCmd file = notinBackend file err $ \(key, backend) -> do
Backend.removeKey backend key
logStatus key ValueMissing
g <- Annex.gitRepo
let src = annexLocation g backend key
liftIO $ moveout g src
where
err = error $ "not annexed " ++ file
moveout g src = do
removeFile file
Git.run g ["rm", file]
Git.run g ["commit", "-m",
("git-annex unannexed " ++ file), file]
-- git rm deletes empty directories;
-- put them back
createDirectoryIfMissing True (parentDir file)
renameFile src file
return ()
{- Gets an annexed file from one of the backends. -}
getCmd :: FilePath -> Annex ()
getCmd file = notinBackend file err $ \(key, backend) -> do
inannex <- inAnnex backend key
if (inannex)
then return ()
else do
g <- Annex.gitRepo
let dest = annexLocation g backend key
liftIO $ createDirectoryIfMissing True (parentDir dest)
success <- Backend.retrieveKeyFile backend key dest
if (success)
then do
logStatus key ValuePresent
return ()
else error $ "failed to get " ++ file
where
err = error $ "not annexed " ++ file
{- Indicates a file is wanted. -}
wantCmd :: FilePath -> Annex ()
wantCmd file = do error "not implemented" -- TODO
{- Indicates a file is not wanted. -}
dropCmd :: FilePath -> Annex ()
dropCmd file = notinBackend file err $ \(key, backend) -> do
-- TODO only remove if enough copies are present elsewhere
success <- Backend.removeKey backend key
if (success)
then do
logStatus key ValueMissing
inannex <- inAnnex backend key
if (inannex)
then do
g <- Annex.gitRepo
let loc = annexLocation g backend key
liftIO $ removeFile loc
return ()
else return ()
else error $ "backend refused to drop " ++ file
where
err = error $ "not annexed " ++ file
{- Pushes all files to a remote repository. -}
pushCmd :: String -> Annex ()
pushCmd reponame = do error "not implemented" -- TODO
{- Pulls all files from a remote repository. -}
pullCmd :: String -> Annex ()
pullCmd reponame = do error "not implemented" -- TODO
{- Updates the LocationLog when a key's presence changes. -}
logStatus :: Key -> LogStatus -> Annex ()
logStatus key status = do
g <- Annex.gitRepo
u <- getUUID g
f <- liftIO $ logChange g key u status
liftIO $ commit g f
where
commit g f = do
Git.run g ["add", f]
Git.run g ["commit", "-m", "git-annex log update", f]
inBackend file yes no = do
r <- liftIO $ Backend.lookupFile file
case (r) of
Just v -> yes v
Nothing -> no
notinBackend file yes no = inBackend file no yes