Merge branch 'master' into watch

This commit is contained in:
Joey Hess 2012-06-04 12:07:59 -04:00
commit eab3872d91
231 changed files with 2786 additions and 1112 deletions

6
.gitignore vendored
View file

@ -1,12 +1,8 @@
tmp tmp
*.hi
*.o
test test
configure configure
Build/SysConfig.hs Build/SysConfig.hs
git-annex git-annex
git-annex-shell
git-union-merge
git-annex.1 git-annex.1
git-annex-shell.1 git-annex-shell.1
git-union-merge.1 git-union-merge.1
@ -15,5 +11,5 @@ html
*.tix *.tix
.hpc .hpc
Utility/Touch.hs Utility/Touch.hs
Utility/StatFS.hs Utility/libdiskfree.o
dist dist

View file

@ -10,7 +10,6 @@
module Annex ( module Annex (
Annex, Annex,
AnnexState(..), AnnexState(..),
OutputType(..),
new, new,
newState, newState,
run, run,
@ -19,6 +18,7 @@ module Annex (
changeState, changeState,
setFlag, setFlag,
setField, setField,
setOutput,
getFlag, getFlag,
getField, getField,
addCleanup, addCleanup,
@ -37,12 +37,14 @@ import qualified Git
import qualified Git.Config import qualified Git.Config
import Git.CatFile import Git.CatFile
import Git.CheckAttr import Git.CheckAttr
import Git.SharedRepository
import qualified Git.Queue import qualified Git.Queue
import Types.Backend import Types.Backend
import qualified Types.Remote import qualified Types.Remote
import Types.Crypto import Types.Crypto
import Types.BranchState import Types.BranchState
import Types.TrustLevel import Types.TrustLevel
import Types.Messages
import Utility.State import Utility.State
import qualified Utility.Matcher import qualified Utility.Matcher
import qualified Data.Map as M import qualified Data.Map as M
@ -68,8 +70,6 @@ instance MonadBaseControl IO Annex where
where where
unStAnnex (StAnnex st) = st unStAnnex (StAnnex st) = st
data OutputType = NormalOutput | QuietOutput | JSONOutput
type Matcher a = Either [Utility.Matcher.Token a] (Utility.Matcher.Matcher a) type Matcher a = Either [Utility.Matcher.Token a] (Utility.Matcher.Matcher a)
-- internal state storage -- internal state storage
@ -77,7 +77,7 @@ data AnnexState = AnnexState
{ repo :: Git.Repo { repo :: Git.Repo
, backends :: [BackendA Annex] , backends :: [BackendA Annex]
, remotes :: [Types.Remote.RemoteA Annex] , remotes :: [Types.Remote.RemoteA Annex]
, output :: OutputType , output :: MessageState
, force :: Bool , force :: Bool
, fast :: Bool , fast :: Bool
, auto :: Bool , auto :: Bool
@ -88,9 +88,10 @@ data AnnexState = AnnexState
, forcebackend :: Maybe String , forcebackend :: Maybe String
, forcenumcopies :: Maybe Int , forcenumcopies :: Maybe Int
, limit :: Matcher (FilePath -> Annex Bool) , limit :: Matcher (FilePath -> Annex Bool)
, shared :: Maybe SharedRepository
, forcetrust :: TrustMap , forcetrust :: TrustMap
, trustmap :: Maybe TrustMap , trustmap :: Maybe TrustMap
, ciphers :: M.Map EncryptedCipher Cipher , ciphers :: M.Map StorableCipher Cipher
, lockpool :: M.Map FilePath Fd , lockpool :: M.Map FilePath Fd
, flags :: M.Map String Bool , flags :: M.Map String Bool
, fields :: M.Map String String , fields :: M.Map String String
@ -102,7 +103,7 @@ newState gitrepo = AnnexState
{ repo = gitrepo { repo = gitrepo
, backends = [] , backends = []
, remotes = [] , remotes = []
, output = NormalOutput , output = defaultMessageState
, force = False , force = False
, fast = False , fast = False
, auto = False , auto = False
@ -113,6 +114,7 @@ newState gitrepo = AnnexState
, forcebackend = Nothing , forcebackend = Nothing
, forcenumcopies = Nothing , forcenumcopies = Nothing
, limit = Left [] , limit = Left []
, shared = Nothing
, forcetrust = M.empty , forcetrust = M.empty
, trustmap = Nothing , trustmap = Nothing
, ciphers = M.empty , ciphers = M.empty
@ -122,7 +124,8 @@ newState gitrepo = AnnexState
, cleanup = M.empty , cleanup = M.empty
} }
{- Create and returns an Annex state object for the specified git repo. -} {- Makes an Annex state object for the specified git repo.
- Ensures the config is read, if it was not already. -}
new :: Git.Repo -> IO AnnexState new :: Git.Repo -> IO AnnexState
new gitrepo = newState <$> Git.Config.read gitrepo new gitrepo = newState <$> Git.Config.read gitrepo
@ -147,6 +150,11 @@ addCleanup :: String -> Annex () -> Annex ()
addCleanup uid a = changeState $ \s -> addCleanup uid a = changeState $ \s ->
s { cleanup = M.insertWith' const uid a $ cleanup s } s { cleanup = M.insertWith' const uid a $ cleanup s }
{- Sets the type of output to emit. -}
setOutput :: OutputType -> Annex ()
setOutput o = changeState $ \s ->
s { output = (output s) { outputType = o } }
{- Checks if a flag was set. -} {- Checks if a flag was set. -}
getFlag :: String -> Annex Bool getFlag :: String -> Annex Bool
getFlag flag = fromMaybe False . M.lookup flag <$> getState flags getFlag flag = fromMaybe False . M.lookup flag <$> getState flags

View file

@ -36,6 +36,7 @@ import qualified Git.UnionMerge
import Git.HashObject import Git.HashObject
import qualified Git.Index import qualified Git.Index
import Annex.CatFile import Annex.CatFile
import Annex.Perms
{- Name of the branch that is used to store git-annex's information. -} {- Name of the branch that is used to store git-annex's information. -}
name :: Git.Ref name :: Git.Ref
@ -64,9 +65,7 @@ siblingBranches = inRepo $ Git.Ref.matchingUniq name
{- Creates the branch, if it does not already exist. -} {- Creates the branch, if it does not already exist. -}
create :: Annex () create :: Annex ()
create = do create = void $ getBranch
_ <- getBranch
return ()
{- Returns the ref of the branch, creating it first if necessary. -} {- Returns the ref of the branch, creating it first if necessary. -}
getBranch :: Annex Git.Ref getBranch :: Annex Git.Ref
@ -308,6 +307,7 @@ setIndexSha :: Git.Ref -> Annex ()
setIndexSha ref = do setIndexSha ref = do
lock <- fromRepo gitAnnexIndexLock lock <- fromRepo gitAnnexIndexLock
liftIO $ writeFile lock $ show ref ++ "\n" liftIO $ writeFile lock $ show ref ++ "\n"
setAnnexPerm lock
{- Checks if there are uncommitted changes in the branch's index or journal. -} {- Checks if there are uncommitted changes in the branch's index or journal. -}
unCommitted :: Annex Bool unCommitted :: Annex Bool
@ -323,14 +323,14 @@ setUnCommitted = do
liftIO $ writeFile file "1" liftIO $ writeFile file "1"
setCommitted :: Annex () setCommitted :: Annex ()
setCommitted = do setCommitted = void $ do
file <- fromRepo gitAnnexIndexDirty file <- fromRepo gitAnnexIndexDirty
_ <- liftIO $ tryIO $ removeFile file liftIO $ tryIO $ removeFile file
return ()
{- Stages the journal into the index. -} {- Stages the journal into the index. -}
stageJournal :: Annex () stageJournal :: Annex ()
stageJournal = do stageJournal = do
showStoringStateAction
fs <- getJournalFiles fs <- getJournalFiles
g <- gitRepo g <- gitRepo
withIndex $ liftIO $ do withIndex $ liftIO $ do

View file

@ -23,16 +23,18 @@ module Annex.Content (
saveState, saveState,
downloadUrl, downloadUrl,
preseedTmp, preseedTmp,
freezeContent,
thawContent,
freezeContentDir,
) where ) where
import Control.Exception (bracket_)
import System.Posix.Types
import System.IO.Unsafe (unsafeInterleaveIO) import System.IO.Unsafe (unsafeInterleaveIO)
import Common.Annex import Common.Annex
import Logs.Location import Logs.Location
import Annex.UUID import Annex.UUID
import qualified Git import qualified Git
import qualified Git.Config
import qualified Annex import qualified Annex
import qualified Annex.Queue import qualified Annex.Queue
import qualified Annex.Branch import qualified Annex.Branch
@ -44,6 +46,8 @@ import Utility.DataUnits
import Utility.CopyFile import Utility.CopyFile
import Config import Config
import Annex.Exception import Annex.Exception
import Git.SharedRepository
import Annex.Perms
{- Checks if a given key's content is currently present. -} {- Checks if a given key's content is currently present. -}
inAnnex :: Key -> Annex Bool inAnnex :: Key -> Annex Bool
@ -57,8 +61,10 @@ inAnnex' a key = do
{- A safer check; the key's content must not only be present, but {- A safer check; the key's content must not only be present, but
- is not in the process of being removed. -} - is not in the process of being removed. -}
inAnnexSafe :: Key -> Annex (Maybe Bool) inAnnexSafe :: Key -> Annex (Maybe Bool)
inAnnexSafe = inAnnex' $ \f -> openForLock f False >>= check inAnnexSafe = inAnnex' $ \f -> openforlock f >>= check
where where
openforlock f = catchMaybeIO $
openFd f ReadOnly Nothing defaultFileFlags
check Nothing = return is_missing check Nothing = return is_missing
check (Just h) = do check (Just h) = do
v <- getLock h (ReadLock, AbsoluteSeek, 0, 0) v <- getLock h (ReadLock, AbsoluteSeek, 0, 0)
@ -75,30 +81,27 @@ inAnnexSafe = inAnnex' $ \f -> openForLock f False >>= check
lockContent :: Key -> Annex a -> Annex a lockContent :: Key -> Annex a -> Annex a
lockContent key a = do lockContent key a = do
file <- inRepo $ gitAnnexLocation key file <- inRepo $ gitAnnexLocation key
bracketIO (openForLock file True >>= lock) unlock a bracketIO (openforlock file >>= lock) unlock a
where where
{- Since files are stored with the write bit disabled, have
- to fiddle with permissions to open for an exclusive lock. -}
openforlock f = catchMaybeIO $ ifM (doesFileExist f)
( withModifiedFileMode f
(\cur -> cur `unionFileModes` ownerWriteMode)
open
, open
)
where
open = openFd f ReadWrite Nothing defaultFileFlags
lock Nothing = return Nothing lock Nothing = return Nothing
lock (Just l) = do lock (Just fd) = do
v <- tryIO $ setLock l (WriteLock, AbsoluteSeek, 0, 0) v <- tryIO $ setLock fd (WriteLock, AbsoluteSeek, 0, 0)
case v of case v of
Left _ -> error "content is locked" Left _ -> error "content is locked"
Right _ -> return $ Just l Right _ -> return $ Just fd
unlock Nothing = return () unlock Nothing = noop
unlock (Just l) = closeFd l unlock (Just l) = closeFd l
openForLock :: FilePath -> Bool -> IO (Maybe Fd)
openForLock file writelock = bracket_ prep cleanup go
where
go = catchMaybeIO $ openFd file mode Nothing defaultFileFlags
mode = if writelock then ReadWrite else ReadOnly
{- Since files are stored with the write bit disabled,
- have to fiddle with permissions to open for an
- exclusive lock. -}
forwritelock a =
when writelock $ whenM (doesFileExist file) a
prep = forwritelock $ allowWrite file
cleanup = forwritelock $ preventWrite file
{- Calculates the relative path to use to link a file to a key. -} {- Calculates the relative path to use to link a file to a key. -}
calcGitLink :: FilePath -> Key -> Annex FilePath calcGitLink :: FilePath -> Key -> Annex FilePath
calcGitLink file key = do calcGitLink file key = do
@ -127,20 +130,20 @@ getViaTmp key action = do
-- When the temp file already exists, count the space -- When the temp file already exists, count the space
-- it is using as free. -- it is using as free.
e <- liftIO $ doesFileExist tmp e <- liftIO $ doesFileExist tmp
if e alreadythere <- if e
then do then fromIntegral . fileSize <$> liftIO (getFileStatus tmp)
stat <- liftIO $ getFileStatus tmp else return 0
checkDiskSpace' (fromIntegral $ fileSize stat) key ifM (checkDiskSpace Nothing key alreadythere)
else checkDiskSpace key ( do
when e $ thawContent tmp
when e $ liftIO $ allowWrite tmp
getViaTmpUnchecked key action getViaTmpUnchecked key action
, return False
)
prepTmp :: Key -> Annex FilePath prepTmp :: Key -> Annex FilePath
prepTmp key = do prepTmp key = do
tmp <- fromRepo $ gitAnnexTmpLocation key tmp <- fromRepo $ gitAnnexTmpLocation key
liftIO $ createDirectoryIfMissing True (parentDir tmp) createAnnexDirectory (parentDir tmp)
return tmp return tmp
{- Like getViaTmp, but does not check that there is enough disk space {- Like getViaTmp, but does not check that there is enough disk space
@ -169,22 +172,24 @@ withTmp key action = do
return res return res
{- Checks that there is disk space available to store a given key, {- Checks that there is disk space available to store a given key,
- throwing an error if not. -} - in a destination (or the annex) printing a warning if not. -}
checkDiskSpace :: Key -> Annex () checkDiskSpace :: Maybe FilePath -> Key -> Integer -> Annex Bool
checkDiskSpace = checkDiskSpace' 0 checkDiskSpace destination key alreadythere = do
checkDiskSpace' :: Integer -> Key -> Annex ()
checkDiskSpace' adjustment key = do
reserve <- getDiskReserve reserve <- getDiskReserve
free <- inRepo $ getDiskFree . gitAnnexDir free <- liftIO . getDiskFree =<< dir
force <- Annex.getState Annex.force
case (free, keySize key) of case (free, keySize key) of
(Just have, Just need) -> (Just have, Just need) -> do
when (need + reserve > have + adjustment) $ let ok = (need + reserve <= have + alreadythere) || force
needmorespace (need + reserve - have - adjustment) unless ok $ do
_ -> return () liftIO $ print (need, reserve, have, alreadythere)
needmorespace (need + reserve - have - alreadythere)
return ok
_ -> return True
where where
needmorespace n = unlessM (Annex.getState Annex.force) $ dir = maybe (fromRepo gitAnnexDir) return destination
error $ "not enough free space, need " ++ needmorespace n =
warning $ "not enough free space, need " ++
roughSize storageUnits True n ++ roughSize storageUnits True n ++
" more" ++ forcemsg " more" ++ forcemsg
forcemsg = " (use --force to override this check or adjust annex.diskreserve)" forcemsg = " (use --force to override this check or adjust annex.diskreserve)"
@ -213,15 +218,13 @@ checkDiskSpace' adjustment key = do
moveAnnex :: Key -> FilePath -> Annex () moveAnnex :: Key -> FilePath -> Annex ()
moveAnnex key src = do moveAnnex key src = do
dest <- inRepo $ gitAnnexLocation key dest <- inRepo $ gitAnnexLocation key
let dir = parentDir dest ifM (liftIO $ doesFileExist dest)
liftIO $ ifM (doesFileExist dest) ( liftIO $ removeFile src
( removeFile src
, do , do
createDirectoryIfMissing True dir createContentDir dest
allowWrite dir -- in case the directory already exists liftIO $ moveFile src dest
moveFile src dest freezeContent dest
preventWrite dest freezeContentDir dest
preventWrite dir
) )
withObjectLoc :: Key -> ((FilePath, FilePath) -> Annex a) -> Annex a withObjectLoc :: Key -> ((FilePath, FilePath) -> Annex a) -> Annex a
@ -235,10 +238,10 @@ cleanObjectLoc key = do
file <- inRepo $ gitAnnexLocation key file <- inRepo $ gitAnnexLocation key
liftIO $ removeparents file (3 :: Int) liftIO $ removeparents file (3 :: Int)
where where
removeparents _ 0 = return () removeparents _ 0 = noop
removeparents file n = do removeparents file n = do
let dir = parentDir file let dir = parentDir file
maybe (return ()) (const $ removeparents dir (n-1)) maybe noop (const $ removeparents dir (n-1))
=<< catchMaybeIO (removeDirectory dir) =<< catchMaybeIO (removeDirectory dir)
{- Removes a key's file from .git/annex/objects/ -} {- Removes a key's file from .git/annex/objects/ -}
@ -252,10 +255,9 @@ removeAnnex key = withObjectLoc key $ \(dir, file) -> do
{- Moves a key's file out of .git/annex/objects/ -} {- Moves a key's file out of .git/annex/objects/ -}
fromAnnex :: Key -> FilePath -> Annex () fromAnnex :: Key -> FilePath -> Annex ()
fromAnnex key dest = withObjectLoc key $ \(dir, file) -> do fromAnnex key dest = withObjectLoc key $ \(dir, file) -> do
liftIO $ do liftIO $ allowWrite dir
allowWrite dir thawContent file
allowWrite file liftIO $ moveFile file dest
moveFile file dest
cleanObjectLoc key cleanObjectLoc key
{- Moves a key out of .git/annex/objects/ into .git/annex/bad, and {- Moves a key out of .git/annex/objects/ into .git/annex/bad, and
@ -265,8 +267,8 @@ moveBad key = do
src <- inRepo $ gitAnnexLocation key src <- inRepo $ gitAnnexLocation key
bad <- fromRepo gitAnnexBadDir bad <- fromRepo gitAnnexBadDir
let dest = bad </> takeFileName src let dest = bad </> takeFileName src
createAnnexDirectory (parentDir dest)
liftIO $ do liftIO $ do
createDirectoryIfMissing True (parentDir dest)
allowWrite (parentDir src) allowWrite (parentDir src)
moveFile src dest moveFile src dest
cleanObjectLoc key cleanObjectLoc key
@ -296,20 +298,21 @@ getKeysPresent = liftIO . traverse (2 :: Int) =<< fromRepo gitAnnexObjectDir
- especially if performing a short-lived action. - especially if performing a short-lived action.
-} -}
saveState :: Bool -> Annex () saveState :: Bool -> Annex ()
saveState oneshot = do saveState oneshot = doSideAction $ do
Annex.Queue.flush False Annex.Queue.flush
unless oneshot $ unless oneshot $
ifM alwayscommit ifM alwayscommit
( Annex.Branch.commit "update" , Annex.Branch.stage) ( Annex.Branch.commit "update" , Annex.Branch.stage)
where where
alwayscommit = fromMaybe True . Git.configTrue alwayscommit = fromMaybe True . Git.Config.isTrue
<$> getConfig "annex.alwayscommit" "" <$> getConfig (annexConfig "alwayscommit") ""
{- Downloads content from any of a list of urls. -} {- Downloads content from any of a list of urls. -}
downloadUrl :: [Url.URLString] -> FilePath -> Annex Bool downloadUrl :: [Url.URLString] -> FilePath -> Annex Bool
downloadUrl urls file = do downloadUrl urls file = do
o <- map Param . words <$> getConfig "annex.web-options" "" o <- map Param . words <$> getConfig (annexConfig "web-options") ""
liftIO $ anyM (\u -> Url.download u o file) urls headers <- getHttpHeaders
liftIO $ anyM (\u -> Url.download u headers o file) urls
{- Copies a key's content, when present, to a temp file. {- Copies a key's content, when present, to a temp file.
- This is used to speed up some rsyncs. -} - This is used to speed up some rsyncs. -}
@ -319,7 +322,7 @@ preseedTmp key file = go =<< inAnnex key
go False = return False go False = return False
go True = do go True = do
ok <- copy ok <- copy
when ok $ liftIO $ allowWrite file when ok $ thawContent file
return ok return ok
copy = ifM (liftIO $ doesFileExist file) copy = ifM (liftIO $ doesFileExist file)
( return True ( return True
@ -327,3 +330,50 @@ preseedTmp key file = go =<< inAnnex key
s <- inRepo $ gitAnnexLocation key s <- inRepo $ gitAnnexLocation key
liftIO $ copyFileExternal s file liftIO $ copyFileExternal s file
) )
{- Blocks writing to an annexed file. The file is made unwritable
- to avoid accidental edits. core.sharedRepository may change
- who can read it. -}
freezeContent :: FilePath -> Annex ()
freezeContent file = liftIO . go =<< fromRepo getSharedRepository
where
go GroupShared = modifyFileMode file $
removeModes writeModes .
addModes [ownerReadMode, groupReadMode]
go AllShared = modifyFileMode file $
removeModes writeModes .
addModes readModes
go _ = preventWrite file
{- Allows writing to an annexed file that freezeContent was called on
- before. -}
thawContent :: FilePath -> Annex ()
thawContent file = liftIO . go =<< fromRepo getSharedRepository
where
go GroupShared = groupWriteRead file
go AllShared = groupWriteRead file
go _ = allowWrite file
{- Blocks writing to the directory an annexed file is in, to prevent the
- file accidentially being deleted. However, if core.sharedRepository
- is set, this is not done, since the group must be allowed to delete the
- file.
-}
freezeContentDir :: FilePath -> Annex ()
freezeContentDir file = liftIO . go =<< fromRepo getSharedRepository
where
dir = parentDir file
go GroupShared = groupWriteRead dir
go AllShared = groupWriteRead dir
go _ = preventWrite dir
{- Makes the directory tree to store an annexed file's content,
- with appropriate permissions on each level. -}
createContentDir :: FilePath -> Annex ()
createContentDir dest = do
unlessM (liftIO $ doesDirectoryExist dir) $
createAnnexDirectory dir
-- might have already existed with restricted perms
liftIO $ allowWrite dir
where
dir = parentDir dest

View file

@ -16,6 +16,7 @@ import System.IO.Binary
import Common.Annex import Common.Annex
import Annex.Exception import Annex.Exception
import qualified Git import qualified Git
import Annex.Perms
{- Records content for a file in the branch to the journal. {- Records content for a file in the branch to the journal.
- -
@ -23,22 +24,20 @@ import qualified Git
- avoids git needing to rewrite the index after every change. -} - avoids git needing to rewrite the index after every change. -}
setJournalFile :: FilePath -> String -> Annex () setJournalFile :: FilePath -> String -> Annex ()
setJournalFile file content = do setJournalFile file content = do
g <- gitRepo createAnnexDirectory =<< fromRepo gitAnnexJournalDir
liftIO $ doRedo (write g) $ do createAnnexDirectory =<< fromRepo gitAnnexTmpDir
createDirectoryIfMissing True $ gitAnnexJournalDir g
createDirectoryIfMissing True $ gitAnnexTmpDir g
where
-- journal file is written atomically -- journal file is written atomically
write g = do jfile <- fromRepo $ journalFile file
let jfile = journalFile g file tmp <- fromRepo gitAnnexTmpDir
let tmpfile = gitAnnexTmpDir g </> takeFileName jfile let tmpfile = tmp </> takeFileName jfile
liftIO $ do
writeBinaryFile tmpfile content writeBinaryFile tmpfile content
moveFile tmpfile jfile moveFile tmpfile jfile
{- Gets any journalled content for a file in the branch. -} {- Gets any journalled content for a file in the branch. -}
getJournalFile :: FilePath -> Annex (Maybe String) getJournalFile :: FilePath -> Annex (Maybe String)
getJournalFile file = inRepo $ \g -> catchMaybeIO $ getJournalFile file = inRepo $ \g -> catchMaybeIO $
readFileStrict $ journalFile g file readFileStrict $ journalFile file g
{- List of files that have updated content in the journal. -} {- List of files that have updated content in the journal. -}
getJournalledFiles :: Annex [FilePath] getJournalledFiles :: Annex [FilePath]
@ -62,8 +61,8 @@ journalDirty = not . null <$> getJournalFiles
- used in the branch is not necessary, and all the files are put directly - used in the branch is not necessary, and all the files are put directly
- in the journal directory. - in the journal directory.
-} -}
journalFile :: Git.Repo -> FilePath -> FilePath journalFile :: FilePath -> Git.Repo -> FilePath
journalFile repo file = gitAnnexJournalDir repo </> concatMap mangle file journalFile file repo = gitAnnexJournalDir repo </> concatMap mangle file
where where
mangle '/' = "_" mangle '/' = "_"
mangle '_' = "__" mangle '_' = "__"
@ -79,16 +78,12 @@ fileJournal = replace "//" "_" . replace "_" "/"
lockJournal :: Annex a -> Annex a lockJournal :: Annex a -> Annex a
lockJournal a = do lockJournal a = do
file <- fromRepo gitAnnexJournalLock file <- fromRepo gitAnnexJournalLock
bracketIO (lock file) unlock a createAnnexDirectory $ takeDirectory file
mode <- annexFileMode
bracketIO (lock file mode) unlock a
where where
lock file = do lock file mode = do
l <- doRedo (createFile file stdFileMode) $ l <- noUmask mode $ createFile file mode
createDirectoryIfMissing True $ takeDirectory file
waitToSetLock l (WriteLock, AbsoluteSeek, 0, 0) waitToSetLock l (WriteLock, AbsoluteSeek, 0, 0)
return l return l
unlock = closeFd unlock = closeFd
{- Runs an action, catching failure and running something to fix it up, and
- retrying if necessary. -}
doRedo :: IO a -> IO b -> IO a
doRedo a b = catchIO a $ const $ b >> a

View file

@ -12,22 +12,24 @@ import System.Posix.Types (Fd)
import Common.Annex import Common.Annex
import Annex import Annex
import Annex.Perms
{- Create a specified lock file, and takes a shared lock. -} {- Create a specified lock file, and takes a shared lock. -}
lockFile :: FilePath -> Annex () lockFile :: FilePath -> Annex ()
lockFile file = go =<< fromPool file lockFile file = go =<< fromPool file
where where
go (Just _) = return () -- already locked go (Just _) = noop -- already locked
go Nothing = do go Nothing = do
fd <- liftIO $ openFd file ReadOnly (Just stdFileMode) defaultFileFlags mode <- annexFileMode
fd <- liftIO $ noUmask mode $
openFd file ReadOnly (Just mode) defaultFileFlags
liftIO $ waitToSetLock fd (ReadLock, AbsoluteSeek, 0, 0) liftIO $ waitToSetLock fd (ReadLock, AbsoluteSeek, 0, 0)
changePool $ M.insert file fd changePool $ M.insert file fd
unlockFile :: FilePath -> Annex () unlockFile :: FilePath -> Annex ()
unlockFile file = go =<< fromPool file unlockFile file = maybe noop go =<< fromPool file
where where
go Nothing = return () go fd = do
go (Just fd) = do
liftIO $ closeFd fd liftIO $ closeFd fd
changePool $ M.delete file changePool $ M.delete file

70
Annex/Perms.hs Normal file
View file

@ -0,0 +1,70 @@
{- git-annex file permissions
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Annex.Perms (
setAnnexPerm,
annexFileMode,
createAnnexDirectory,
noUmask,
) where
import Common.Annex
import Utility.FileMode
import Git.SharedRepository
import qualified Annex
import System.Posix.Types
withShared :: (SharedRepository -> Annex a) -> Annex a
withShared a = maybe startup a =<< Annex.getState Annex.shared
where
startup = do
shared <- fromRepo getSharedRepository
Annex.changeState $ \s -> s { Annex.shared = Just shared }
a shared
{- Sets appropriate file mode for a file or directory in the annex,
- other than the content files and content directory. Normally,
- use the default mode, but with core.sharedRepository set,
- allow the group to write, etc. -}
setAnnexPerm :: FilePath -> Annex ()
setAnnexPerm file = withShared $ liftIO . go
where
go GroupShared = groupWriteRead file
go AllShared = modifyFileMode file $ addModes $
[ ownerWriteMode, groupWriteMode ] ++ readModes
go _ = noop
{- Gets the appropriate mode to use for creating a file in the annex
- (other than content files, which are locked down more). -}
annexFileMode :: Annex FileMode
annexFileMode = withShared $ return . go
where
go GroupShared = sharedmode
go AllShared = combineModes (sharedmode:readModes)
go _ = stdFileMode
sharedmode = combineModes
[ ownerWriteMode, groupWriteMode
, ownerReadMode, groupReadMode
]
{- Creates a directory inside the gitAnnexDir, including any parent
- directories. Makes directories with appropriate permissions. -}
createAnnexDirectory :: FilePath -> Annex ()
createAnnexDirectory dir = traverse dir [] =<< top
where
top = parentDir <$> fromRepo gitAnnexDir
traverse d below stop
| d `equalFilePath` stop = done
| otherwise = ifM (liftIO $ doesDirectoryExist d)
( done
, traverse (parentDir d) (d:below) stop
)
where
done = forM_ below $ \p -> do
liftIO $ createDirectory p
setAnnexPerm p

View file

@ -26,15 +26,14 @@ add command params files = do
flushWhenFull :: Annex () flushWhenFull :: Annex ()
flushWhenFull = do flushWhenFull = do
q <- get q <- get
when (Git.Queue.full q) $ flush False when (Git.Queue.full q) flush
{- Runs (and empties) the queue. -} {- Runs (and empties) the queue. -}
flush :: Bool -> Annex () flush :: Annex ()
flush silent = do flush = do
q <- get q <- get
unless (0 == Git.Queue.size q) $ do unless (0 == Git.Queue.size q) $ do
unless silent $ showStoringStateAction
showSideAction "Recording state in git"
q' <- inRepo $ Git.Queue.flush q q' <- inRepo $ Git.Queue.flush q
store q' store q'
@ -47,7 +46,7 @@ new = do
store q store q
return q return q
where where
queuesize = readish <$> getConfig "annex.queuesize" "" queuesize = readish <$> getConfig (annexConfig "queuesize") ""
store :: Git.Queue.Queue -> Annex () store :: Git.Queue.Queue -> Annex ()
store q = changeState $ \s -> s { repoqueue = Just q } store q = changeState $ \s -> s { repoqueue = Just q }

View file

@ -14,9 +14,10 @@ import qualified Data.Map as M
import Common.Annex import Common.Annex
import Annex.LockPool import Annex.LockPool
import qualified Git import qualified Git.Config
import Config import Config
import qualified Build.SysConfig as SysConfig import qualified Build.SysConfig as SysConfig
import Annex.Perms
{- Generates parameters to ssh to a given host (or user@host) on a given {- Generates parameters to ssh to a given host (or user@host) on a given
- port, with connection caching. -} - port, with connection caching. -}
@ -46,8 +47,8 @@ sshInfo (host, port) = ifM caching
) )
where where
caching = fromMaybe SysConfig.sshconnectioncaching caching = fromMaybe SysConfig.sshconnectioncaching
. Git.configTrue . Git.Config.isTrue
<$> getConfig "annex.sshcaching" "" <$> getConfig (annexConfig "sshcaching") ""
cacheParams :: FilePath -> [CommandParam] cacheParams :: FilePath -> [CommandParam]
cacheParams socketfile = cacheParams socketfile =
@ -74,30 +75,29 @@ sshCleanup = do
-- be stopped. -- be stopped.
let lockfile = socket2lock socketfile let lockfile = socket2lock socketfile
unlockFile lockfile unlockFile lockfile
fd <- liftIO $ openFd lockfile ReadWrite (Just stdFileMode) defaultFileFlags mode <- annexFileMode
fd <- liftIO $ noUmask mode $
openFd lockfile ReadWrite (Just mode) defaultFileFlags
v <- liftIO $ tryIO $ v <- liftIO $ tryIO $
setLock fd (WriteLock, AbsoluteSeek, 0, 0) setLock fd (WriteLock, AbsoluteSeek, 0, 0)
case v of case v of
Left _ -> return () Left _ -> noop
Right _ -> stopssh socketfile Right _ -> stopssh socketfile
liftIO $ closeFd fd liftIO $ closeFd fd
stopssh socketfile = do stopssh socketfile = do
let (host, port) = socket2hostport socketfile let (host, port) = socket2hostport socketfile
(_, params) <- sshInfo (host, port) (_, params) <- sshInfo (host, port)
_ <- liftIO $ do void $ liftIO $ do
-- "ssh -O stop" is noisy on stderr even with -q -- "ssh -O stop" is noisy on stderr even with -q
let cmd = unwords $ toCommand $ let cmd = unwords $ toCommand $
[ Params "-O stop" [ Params "-O stop"
] ++ params ++ [Param host] ] ++ params ++ [Param host]
_ <- boolSystem "sh" boolSystem "sh"
[ Param "-c" [ Param "-c"
, Param $ "ssh " ++ cmd ++ " >/dev/null 2>/dev/null" , Param $ "ssh " ++ cmd ++ " >/dev/null 2>/dev/null"
] ]
--try $ removeFile socketfile
return ()
-- Cannot remove the lock file; other processes may -- Cannot remove the lock file; other processes may
-- be waiting on our exclusive lock to use it. -- be waiting on our exclusive lock to use it.
return ()
hostport2socket :: String -> Maybe Integer -> FilePath hostport2socket :: String -> Maybe Integer -> FilePath
hostport2socket host Nothing = host hostport2socket host Nothing = host

View file

@ -16,7 +16,8 @@ module Annex.UUID (
getRepoUUID, getRepoUUID,
getUncachedUUID, getUncachedUUID,
prepUUID, prepUUID,
genUUID genUUID,
removeRepoUUID,
) where ) where
import Common.Annex import Common.Annex
@ -25,8 +26,8 @@ import qualified Git.Config
import qualified Build.SysConfig as SysConfig import qualified Build.SysConfig as SysConfig
import Config import Config
configkey :: String configkey :: ConfigKey
configkey = "annex.uuid" configkey = annexConfig "uuid"
{- Generates a UUID. There is a library for this, but it's not packaged, {- Generates a UUID. There is a library for this, but it's not packaged,
- so use the command line tool. -} - so use the command line tool. -}
@ -61,13 +62,18 @@ getRepoUUID r = do
when (g /= r) $ storeUUID cachekey u when (g /= r) $ storeUUID cachekey u
cachekey = remoteConfig r "uuid" cachekey = remoteConfig r "uuid"
removeRepoUUID :: Annex ()
removeRepoUUID = unsetConfig configkey
getUncachedUUID :: Git.Repo -> UUID getUncachedUUID :: Git.Repo -> UUID
getUncachedUUID = toUUID . Git.Config.get configkey "" getUncachedUUID = toUUID . Git.Config.get key ""
where
(ConfigKey key) = configkey
{- Make sure that the repo has an annex.uuid setting. -} {- Make sure that the repo has an annex.uuid setting. -}
prepUUID :: Annex () prepUUID :: Annex ()
prepUUID = whenM ((==) NoUUID <$> getUUID) $ prepUUID = whenM ((==) NoUUID <$> getUUID) $
storeUUID configkey =<< liftIO genUUID storeUUID configkey =<< liftIO genUUID
storeUUID :: String -> UUID -> Annex () storeUUID :: ConfigKey -> UUID -> Annex ()
storeUUID configfield = setConfig configfield . fromUUID storeUUID configfield = setConfig configfield . fromUUID

View file

@ -21,8 +21,8 @@ supportedVersions = [defaultVersion]
upgradableVersions :: [Version] upgradableVersions :: [Version]
upgradableVersions = ["0", "1", "2"] upgradableVersions = ["0", "1", "2"]
versionField :: String versionField :: ConfigKey
versionField = "annex.version" versionField = annexConfig "version"
getVersion :: Annex (Maybe Version) getVersion :: Annex (Maybe Version)
getVersion = handle <$> getConfig versionField "" getVersion = handle <$> getConfig versionField ""
@ -35,7 +35,7 @@ setVersion = setConfig versionField defaultVersion
checkVersion :: Version -> Annex () checkVersion :: Version -> Annex ()
checkVersion v checkVersion v
| v `elem` supportedVersions = return () | v `elem` supportedVersions = noop
| v `elem` upgradableVersions = err "Upgrade this repository: git-annex upgrade" | v `elem` upgradableVersions = err "Upgrade this repository: git-annex upgrade"
| otherwise = err "Upgrade git-annex." | otherwise = err "Upgrade git-annex."
where where

View file

@ -46,7 +46,7 @@ orderedList = do
l' <- (lookupBackendName name :) <$> standard l' <- (lookupBackendName name :) <$> standard
Annex.changeState $ \s -> s { Annex.backends = l' } Annex.changeState $ \s -> s { Annex.backends = l' }
return l' return l'
standard = parseBackendList <$> getConfig "annex.backends" "" standard = parseBackendList <$> getConfig (annexConfig "backends") ""
parseBackendList [] = list parseBackendList [] = list
parseBackendList s = map lookupBackendName $ words s parseBackendList s = map lookupBackendName $ words s
@ -75,16 +75,16 @@ genKey' (b:bs) file = do
- by examining what the file symlinks to. -} - by examining what the file symlinks to. -}
lookupFile :: FilePath -> Annex (Maybe (Key, Backend)) lookupFile :: FilePath -> Annex (Maybe (Key, Backend))
lookupFile file = do lookupFile file = do
tl <- liftIO $ tryIO getsymlink tl <- liftIO $ tryIO $ readSymbolicLink file
case tl of case tl of
Left _ -> return Nothing Left _ -> return Nothing
Right l -> makekey l Right l -> makekey l
where where
getsymlink = takeFileName <$> readSymbolicLink file makekey l = maybe (return Nothing) (makeret l) (fileKey $ takeFileName l)
makekey l = maybe (return Nothing) (makeret l) (fileKey l)
makeret l k = let bname = keyBackendName k in makeret l k = let bname = keyBackendName k in
case maybeLookupBackendName bname of case maybeLookupBackendName bname of
Just backend -> return $ Just (k, backend) Just backend -> do
return $ Just (k, backend)
Nothing -> do Nothing -> do
when (isLinkToAnnex l) $ warning $ when (isLinkToAnnex l) $ warning $
"skipping " ++ file ++ "skipping " ++ file ++

View file

@ -45,7 +45,7 @@ genBackendE size =
shaCommand :: SHASize -> Maybe String shaCommand :: SHASize -> Maybe String
shaCommand 1 = SysConfig.sha1 shaCommand 1 = SysConfig.sha1
shaCommand 256 = SysConfig.sha256 shaCommand 256 = Just SysConfig.sha256
shaCommand 224 = SysConfig.sha224 shaCommand 224 = SysConfig.sha224
shaCommand 384 = SysConfig.sha384 shaCommand 384 = SysConfig.sha384
shaCommand 512 = SysConfig.sha512 shaCommand 512 = SysConfig.sha512

View file

@ -6,6 +6,7 @@ import System.Directory
import Data.List import Data.List
import System.Cmd.Utils import System.Cmd.Utils
import Control.Applicative import Control.Applicative
import System.FilePath
import Build.TestConfig import Build.TestConfig
import Utility.SafeCommand import Utility.SafeCommand
@ -26,15 +27,21 @@ tests =
, TestCase "bup" $ testCmd "bup" "bup --version >/dev/null" , TestCase "bup" $ testCmd "bup" "bup --version >/dev/null"
, TestCase "gpg" $ testCmd "gpg" "gpg --version >/dev/null" , TestCase "gpg" $ testCmd "gpg" "gpg --version >/dev/null"
, TestCase "ssh connection caching" getSshConnectionCaching , TestCase "ssh connection caching" getSshConnectionCaching
] ++ shaTestCases [1, 256, 512, 224, 384] ] ++ shaTestCases False [1, 512, 224, 384] ++ shaTestCases True [256]
shaTestCases :: [Int] -> [TestCase] shaTestCases :: Bool -> [Int] -> [TestCase]
shaTestCases l = map make l shaTestCases required l = map make l
where make n = where
let make n = TestCase key $ selector key (shacmds n) "</dev/null"
cmds = map (\x -> "sha" ++ show n ++ x) ["", "sum"] where
key = "sha" ++ show n key = "sha" ++ show n
in TestCase key $ maybeSelectCmd key cmds "</dev/null" selector = if required then selectCmd else maybeSelectCmd
shacmds n = concatMap (\x -> [x, osxpath </> x]) $
map (\x -> "sha" ++ show n ++ x) ["", "sum"]
-- Max OSX puts GNU tools outside PATH, so look in
-- the location it uses, and remember where to run them
-- from.
osxpath = "/opt/local/libexec/gnubin"
tmpDir :: String tmpDir :: String
tmpDir = "tmp" tmpDir = "tmp"

View file

@ -46,19 +46,19 @@ dispatch fuzzyok allargs allcmds commonoptions header getgitrepo = do
where where
err msg = msg ++ "\n\n" ++ usage header allcmds commonoptions err msg = msg ++ "\n\n" ++ usage header allcmds commonoptions
cmd = Prelude.head cmds cmd = Prelude.head cmds
(cmds, name, args) = findCmd fuzzyok allargs allcmds err (fuzzy, cmds, name, args) = findCmd fuzzyok allargs allcmds err
(flags, params) = getOptCmd args cmd commonoptions err (flags, params) = getOptCmd args cmd commonoptions err
checkfuzzy = when (length cmds > 1) $ checkfuzzy = when fuzzy $
inRepo $ Git.AutoCorrect.prepare name cmdname cmds inRepo $ Git.AutoCorrect.prepare name cmdname cmds
{- Parses command line params far enough to find the Command to run, and {- Parses command line params far enough to find the Command to run, and
- returns the remaining params. - returns the remaining params.
- Does fuzzy matching if necessary, which may result in multiple Commands. -} - Does fuzzy matching if necessary, which may result in multiple Commands. -}
findCmd :: Bool -> Params -> [Command] -> (String -> String) -> ([Command], String, Params) findCmd :: Bool -> Params -> [Command] -> (String -> String) -> (Bool, [Command], String, Params)
findCmd fuzzyok argv cmds err findCmd fuzzyok argv cmds err
| isNothing name = error $ err "missing command" | isNothing name = error $ err "missing command"
| not (null exactcmds) = (exactcmds, fromJust name, args) | not (null exactcmds) = (False, exactcmds, fromJust name, args)
| fuzzyok && not (null inexactcmds) = (inexactcmds, fromJust name, args) | fuzzyok && not (null inexactcmds) = (True, inexactcmds, fromJust name, args)
| otherwise = error $ err $ "unknown command " ++ fromJust name | otherwise = error $ err $ "unknown command " ++ fromJust name
where where
(name, args) = findname argv [] (name, args) = findname argv []
@ -88,7 +88,7 @@ tryRun = tryRun' 0
tryRun' :: Integer -> Annex.AnnexState -> Command -> [CommandCleanup] -> IO () tryRun' :: Integer -> Annex.AnnexState -> Command -> [CommandCleanup] -> IO ()
tryRun' errnum _ cmd [] tryRun' errnum _ cmd []
| errnum > 0 = error $ cmdname cmd ++ ": " ++ show errnum ++ " failed" | errnum > 0 = error $ cmdname cmd ++ ": " ++ show errnum ++ " failed"
| otherwise = return () | otherwise = noop
tryRun' errnum state cmd (a:as) = do tryRun' errnum state cmd (a:as) = do
r <- run r <- run
handle $! r handle $! r

34
Command/AddUnused.hs Normal file
View file

@ -0,0 +1,34 @@
{- git-annex command
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Command.AddUnused where
import Common.Annex
import Logs.Unused
import Command
import qualified Command.Add
def :: [Command]
def = [command "addunused" (paramRepeating paramNumRange)
seek "add back unused files"]
seek :: [CommandSeek]
seek = [withUnusedMaps start]
start :: UnusedMaps -> Int -> CommandStart
start = startUnused "addunused" perform (performOther "bad") (performOther "tmp")
perform :: Key -> CommandPerform
perform key = next $ Command.Add.cleanup file key True
where
file = "unused." ++ show key
{- The content is not in the annex, but in another directory, and
- it seems better to error out, rather than moving bad/tmp content into
- the annex. -}
performOther :: String -> Key -> CommandPerform
performOther other _ = error $ "cannot addunused " ++ other ++ "content"

View file

@ -20,6 +20,7 @@ import Annex.Content
import Logs.Web import Logs.Web
import qualified Option import qualified Option
import Types.Key import Types.Key
import Config
def :: [Command] def :: [Command]
def = [withOptions [fileOption, pathdepthOption] $ def = [withOptions [fileOption, pathdepthOption] $
@ -53,8 +54,9 @@ perform url file = ifAnnexed file addurl geturl
liftIO $ createDirectoryIfMissing True (parentDir file) liftIO $ createDirectoryIfMissing True (parentDir file)
ifM (Annex.getState Annex.fast) ifM (Annex.getState Annex.fast)
( nodownload url file , download url file ) ( nodownload url file , download url file )
addurl (key, _backend) = addurl (key, _backend) = do
ifM (liftIO $ Url.check url $ keySize key) headers <- getHttpHeaders
ifM (liftIO $ Url.check url headers $ keySize key)
( do ( do
setUrlPresent key url setUrlPresent key url
next $ return True next $ return True
@ -81,7 +83,8 @@ download url file = do
nodownload :: String -> FilePath -> CommandPerform nodownload :: String -> FilePath -> CommandPerform
nodownload url file = do nodownload url file = do
(exists, size) <- liftIO $ Url.exists url headers <- getHttpHeaders
(exists, size) <- liftIO $ Url.exists url headers
if exists if exists
then do then do
let key = Backend.URL.fromUrl url size let key = Backend.URL.fromUrl url size

View file

@ -1,14 +1,13 @@
{- git-annex command {- git-annex command
- -
- Copyright 2010 Joey Hess <joey@kitenet.net> - Copyright 2010,2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Command.DropUnused where module Command.DropUnused where
import qualified Data.Map as M import Logs.Unused
import Common.Annex import Common.Annex
import Command import Command
import qualified Annex import qualified Annex
@ -16,40 +15,17 @@ import qualified Command.Drop
import qualified Remote import qualified Remote
import qualified Git import qualified Git
import qualified Option import qualified Option
import Types.Key
type UnusedMap = M.Map String Key
def :: [Command] def :: [Command]
def = [withOptions [Command.Drop.fromOption] $ def = [withOptions [Command.Drop.fromOption] $
command "dropunused" (paramRepeating paramNumber) command "dropunused" (paramRepeating paramNumRange)
seek "drop unused file content"] seek "drop unused file content"]
seek :: [CommandSeek] seek :: [CommandSeek]
seek = [withUnusedMaps] seek = [withUnusedMaps start]
{- Read unused logs once, and pass the maps to each start action. -} start :: UnusedMaps -> Int -> CommandStart
withUnusedMaps :: CommandSeek start = startUnused "dropunused" perform (performOther gitAnnexBadLocation) (performOther gitAnnexTmpLocation)
withUnusedMaps params = do
unused <- readUnusedLog ""
unusedbad <- readUnusedLog "bad"
unusedtmp <- readUnusedLog "tmp"
return $ map (start (unused, unusedbad, unusedtmp)) params
start :: (UnusedMap, UnusedMap, UnusedMap) -> FilePath -> CommandStart
start (unused, unusedbad, unusedtmp) s = search
[ (unused, perform)
, (unusedbad, performOther gitAnnexBadLocation)
, (unusedtmp, performOther gitAnnexTmpLocation)
]
where
search [] = stop
search ((m, a):rest) =
case M.lookup s m of
Nothing -> search rest
Just key -> do
showStart "dropunused" s
next $ a key
perform :: Key -> CommandPerform perform :: Key -> CommandPerform
perform key = maybe droplocal dropremote =<< Remote.byName =<< from perform key = maybe droplocal dropremote =<< Remote.byName =<< from
@ -66,15 +42,3 @@ performOther filespec key = do
f <- fromRepo $ filespec key f <- fromRepo $ filespec key
liftIO $ whenM (doesFileExist f) $ removeFile f liftIO $ whenM (doesFileExist f) $ removeFile f
next $ return True next $ return True
readUnusedLog :: FilePath -> Annex UnusedMap
readUnusedLog prefix = do
f <- fromRepo $ gitAnnexUnusedLog prefix
e <- liftIO $ doesFileExist f
if e
then M.fromList . map parse . lines <$> liftIO (readFile f)
else return M.empty
where
parse line = (num, fromJust $ readKey rest)
where
(num, rest) = separate (== ' ') line

View file

@ -85,7 +85,7 @@ performRemote key file backend numcopies remote =
t <- fromRepo gitAnnexTmpDir t <- fromRepo gitAnnexTmpDir
let tmp = t </> "fsck" ++ show pid ++ "." ++ keyFile key let tmp = t </> "fsck" ++ show pid ++ "." ++ keyFile key
liftIO $ createDirectoryIfMissing True t liftIO $ createDirectoryIfMissing True t
let cleanup = liftIO $ catchIO (removeFile tmp) (const $ return ()) let cleanup = liftIO $ catchIO (removeFile tmp) (const noop)
cleanup cleanup
cleanup `after` a tmp cleanup `after` a tmp
getfile tmp = getfile tmp =
@ -166,10 +166,9 @@ verifyLocationLog key desc = do
-- Since we're checking that a key's file is present, throw -- Since we're checking that a key's file is present, throw
-- in a permission fixup here too. -- in a permission fixup here too.
when present $ do when present $ do
f <- inRepo $ gitAnnexLocation key file <- inRepo $ gitAnnexLocation key
liftIO $ do freezeContent file
preventWrite f freezeContentDir file
preventWrite (parentDir f)
u <- getUUID u <- getUUID
verifyLocationLog' key desc present u (logChange key u) verifyLocationLog' key desc present u (logChange key u)

39
Command/Import.hs Normal file
View file

@ -0,0 +1,39 @@
{- git-annex command
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Command.Import where
import Common.Annex
import Command
import qualified Annex
import qualified Command.Add
def :: [Command]
def = [command "import" paramPaths seek "move and add files from outside git working copy"]
seek :: [CommandSeek]
seek = [withPathContents start]
start :: (FilePath, FilePath) -> CommandStart
start (srcfile, destfile) = notBareRepo $
ifM (liftIO $ isRegularFile <$> getSymbolicLinkStatus srcfile)
( do
showStart "import" destfile
next $ perform srcfile destfile
, stop
)
perform :: FilePath -> FilePath -> CommandPerform
perform srcfile destfile = do
whenM (liftIO $ doesFileExist destfile) $
unlessM (Annex.getState Annex.force) $
error $ "not overwriting existing " ++ destfile ++
" (use --force to override)"
liftIO $ createDirectoryIfMissing True (parentDir destfile)
liftIO $ moveFile srcfile destfile
Command.Add.perform destfile

View file

@ -24,9 +24,5 @@ start file = do
perform :: FilePath -> CommandPerform perform :: FilePath -> CommandPerform
perform file = do perform file = do
liftIO $ removeFile file Annex.Queue.add "checkout" [Param "--"] [file]
-- Checkout from HEAD to get rid of any changes that might be
-- staged in the index, and get back to the previous symlink to
-- the content.
Annex.Queue.add "checkout" [Param "HEAD", Param "--"] [file]
next $ return True -- no cleanup needed next $ return True -- no cleanup needed

View file

@ -133,7 +133,7 @@ compareChanges format changes = concatMap diff $ zip changes (drop 1 changes)
- *lot* for newish files. -} - *lot* for newish files. -}
getLog :: Key -> [CommandParam] -> Annex [String] getLog :: Key -> [CommandParam] -> Annex [String]
getLog key os = do getLog key os = do
top <- fromRepo Git.workTree top <- fromRepo Git.repoPath
p <- liftIO $ relPathCwdToFile top p <- liftIO $ relPathCwdToFile top
let logfile = p </> Logs.Location.logFile key let logfile = p </> Logs.Location.logFile key
inRepo $ pipeNullSplit $ inRepo $ pipeNullSplit $

View file

@ -156,14 +156,14 @@ absRepo :: Git.Repo -> Git.Repo -> Annex Git.Repo
absRepo reference r absRepo reference r
| Git.repoIsUrl reference = return $ Git.Construct.localToUrl reference r | Git.repoIsUrl reference = return $ Git.Construct.localToUrl reference r
| Git.repoIsUrl r = return r | Git.repoIsUrl r = return r
| otherwise = liftIO $ Git.Construct.fromAbsPath =<< absPath (Git.workTree r) | otherwise = liftIO $ Git.Construct.fromAbsPath =<< absPath (Git.repoPath r)
{- Checks if two repos are the same. -} {- Checks if two repos are the same. -}
same :: Git.Repo -> Git.Repo -> Bool same :: Git.Repo -> Git.Repo -> Bool
same a b same a b
| both Git.repoIsSsh = matching Git.Url.authority && matching Git.workTree | both Git.repoIsSsh = matching Git.Url.authority && matching Git.repoPath
| both Git.repoIsUrl && neither Git.repoIsSsh = matching show | both Git.repoIsUrl && neither Git.repoIsSsh = matching show
| neither Git.repoIsSsh = matching Git.workTree | neither Git.repoIsSsh = matching Git.repoPath
| otherwise = False | otherwise = False
where where
@ -210,7 +210,7 @@ tryScan r
where where
sshcmd = cddir ++ " && " ++ sshcmd = cddir ++ " && " ++
"git config --null --list" "git config --null --list"
dir = Git.workTree r dir = Git.repoPath r
cddir cddir
| "/~" `isPrefixOf` dir = | "/~" `isPrefixOf` dir =
let (userhome, reldir) = span (/= '/') (drop 1 dir) let (userhome, reldir) = span (/= '/') (drop 1 dir)

View file

@ -30,6 +30,7 @@ import Logs.UUID
import Logs.Trust import Logs.Trust
import Remote import Remote
import Config import Config
import Utility.Percentage
-- a named computation that produces a statistic -- a named computation that produces a statistic
type Stat = StatState (Maybe (String, StatState String)) type Stat = StatState (Maybe (String, StatState String))
@ -69,6 +70,7 @@ fast_stats =
, remote_list SemiTrusted "semitrusted" , remote_list SemiTrusted "semitrusted"
, remote_list UnTrusted "untrusted" , remote_list UnTrusted "untrusted"
, remote_list DeadTrusted "dead" , remote_list DeadTrusted "dead"
, disk_size
] ]
slow_stats :: [Stat] slow_stats :: [Stat]
slow_stats = slow_stats =
@ -78,7 +80,6 @@ slow_stats =
, local_annex_size , local_annex_size
, known_annex_keys , known_annex_keys
, known_annex_size , known_annex_size
, disk_size
, bloom_info , bloom_info
, backend_usage , backend_usage
] ]
@ -108,12 +109,11 @@ nojson :: StatState String -> String -> StatState String
nojson a _ = a nojson a _ = a
showStat :: Stat -> StatState () showStat :: Stat -> StatState ()
showStat s = calc =<< s showStat s = maybe noop calc =<< s
where where
calc (Just (desc, a)) = do calc (desc, a) = do
(lift . showHeader) desc (lift . showHeader) desc
lift . showRaw =<< a lift . showRaw =<< a
calc Nothing = return ()
supported_backends :: Stat supported_backends :: Stat
supported_backends = stat "supported backends" $ json unwords $ supported_backends = stat "supported backends" $ json unwords $
@ -161,7 +161,7 @@ bloom_info = stat "bloom filter size" $ json id $ do
let note = aside $ let note = aside $
if localkeys >= capacity if localkeys >= capacity
then "appears too small for this repository; adjust annex.bloomcapacity" then "appears too small for this repository; adjust annex.bloomcapacity"
else "has room for " ++ show (capacity - localkeys) ++ " more local annex keys" else showPercentage 1 (percentage capacity localkeys) ++ " full"
-- Two bloom filters are used at the same time, so double the size -- Two bloom filters are used at the same time, so double the size
-- of one. -- of one.
@ -176,8 +176,12 @@ disk_size = stat "available local disk space" $ json id $ lift $
<$> getDiskReserve <$> getDiskReserve
<*> inRepo (getDiskFree . gitAnnexDir) <*> inRepo (getDiskFree . gitAnnexDir)
where where
calcfree reserve (Just have) = calcfree reserve (Just have) = unwords
roughSize storageUnits False $ nonneg $ have - reserve [ roughSize storageUnits False $ nonneg $ have - reserve
, "(+" ++ roughSize storageUnits False reserve
, "reserved)"
]
calcfree _ _ = "unknown" calcfree _ _ = "unknown"
nonneg x nonneg x
| x >= 0 = x | x >= 0 = x

View file

@ -57,10 +57,17 @@ syncRemotes rs = ifM (Annex.getState Annex.fast) ( nub <$> pickfast , wanted )
wanted wanted
| null rs = good =<< concat . byspeed <$> available | null rs = good =<< concat . byspeed <$> available
| otherwise = listed | otherwise = listed
listed = catMaybes <$> mapM (Remote.byName . Just) rs listed = do
l <- catMaybes <$> mapM (Remote.byName . Just) rs
let s = filter special l
unless (null s) $
error $ "cannot sync special remotes: " ++
unwords (map Types.Remote.name s)
return l
available = filter nonspecial <$> Remote.enabledRemoteList available = filter nonspecial <$> Remote.enabledRemoteList
good = filterM $ Remote.Git.repoAvail . Types.Remote.repo good = filterM $ Remote.Git.repoAvail . Types.Remote.repo
nonspecial r = Types.Remote.remotetype r == Remote.Git.remote nonspecial r = Types.Remote.remotetype r == Remote.Git.remote
special = not . nonspecial
fastest = fromMaybe [] . headMaybe . byspeed fastest = fromMaybe [] . headMaybe . byspeed
byspeed = map snd . sort . M.toList . costmap byspeed = map snd . sort . M.toList . costmap
costmap = M.fromListWith (++) . map costpair costmap = M.fromListWith (++) . map costpair

View file

@ -10,7 +10,6 @@ module Command.Unannex where
import Common.Annex import Common.Annex
import Command import Command
import qualified Annex import qualified Annex
import Utility.FileMode
import Logs.Location import Logs.Location
import Annex.Content import Annex.Content
import qualified Git.Command import qualified Git.Command
@ -51,9 +50,8 @@ cleanup file key = do
( do ( do
-- fast mode: hard link to content in annex -- fast mode: hard link to content in annex
src <- inRepo $ gitAnnexLocation key src <- inRepo $ gitAnnexLocation key
liftIO $ do liftIO $ createLink src file
createLink src file thawContent file
allowWrite file
, do , do
fromAnnex key file fromAnnex key file
logStatus key InfoMissing logStatus key InfoMissing

View file

@ -11,7 +11,6 @@ import Common.Annex
import Command import Command
import Annex.Content import Annex.Content
import Utility.CopyFile import Utility.CopyFile
import Utility.FileMode
def :: [Command] def :: [Command]
def = def =
@ -34,8 +33,7 @@ start file (key, _) = do
perform :: FilePath -> Key -> CommandPerform perform :: FilePath -> Key -> CommandPerform
perform dest key = do perform dest key = do
unlessM (inAnnex key) $ error "content not present" unlessM (inAnnex key) $ error "content not present"
unlessM (checkDiskSpace Nothing key 0) $ error "cannot unlock"
checkDiskSpace key
src <- inRepo $ gitAnnexLocation key src <- inRepo $ gitAnnexLocation key
tmpdest <- fromRepo $ gitAnnexTmpLocation key tmpdest <- fromRepo $ gitAnnexTmpLocation key
@ -47,6 +45,6 @@ perform dest key = do
liftIO $ do liftIO $ do
removeFile dest removeFile dest
moveFile tmpdest dest moveFile tmpdest dest
allowWrite dest thawContent dest
next $ return True next $ return True
else error "copy failed!" else error "copy failed!"

View file

@ -19,9 +19,9 @@ import Control.Monad.ST
import Common.Annex import Common.Annex
import Command import Command
import Logs.Unused
import Annex.Content import Annex.Content
import Utility.FileMode import Utility.FileMode
import Utility.TempFile
import Logs.Location import Logs.Location
import Config import Config
import qualified Annex import qualified Annex
@ -91,19 +91,13 @@ check file msg a c = do
l <- a l <- a
let unusedlist = number c l let unusedlist = number c l
unless (null l) $ showLongNote $ msg unusedlist unless (null l) $ showLongNote $ msg unusedlist
writeUnusedFile file unusedlist writeUnusedLog file unusedlist
return $ c + length l return $ c + length l
number :: Int -> [a] -> [(Int, a)] number :: Int -> [a] -> [(Int, a)]
number _ [] = [] number _ [] = []
number n (x:xs) = (n+1, x) : number (n+1) xs number n (x:xs) = (n+1, x) : number (n+1) xs
writeUnusedFile :: FilePath -> [(Int, Key)] -> Annex ()
writeUnusedFile prefix l = do
logfile <- fromRepo $ gitAnnexUnusedLog prefix
liftIO $ viaTmp writeFile logfile $
unlines $ map (\(n, k) -> show n ++ " " ++ show k) l
table :: [(Int, Key)] -> [String] table :: [(Int, Key)] -> [String]
table l = " NUMBER KEY" : map cols l table l = " NUMBER KEY" : map cols l
where where
@ -189,10 +183,10 @@ exclude smaller larger = S.toList $ remove larger $ S.fromList smaller
-} -}
bloomCapacity :: Annex Int bloomCapacity :: Annex Int
bloomCapacity = fromMaybe 500000 . readish bloomCapacity = fromMaybe 500000 . readish
<$> getConfig "annex.bloomcapacity" "" <$> getConfig (annexConfig "bloomcapacity") ""
bloomAccuracy :: Annex Int bloomAccuracy :: Annex Int
bloomAccuracy = fromMaybe 1000 . readish bloomAccuracy = fromMaybe 1000 . readish
<$> getConfig "annex.bloomaccuracy" "" <$> getConfig (annexConfig "bloomaccuracy") ""
bloomBitsHashes :: Annex (Int, Int) bloomBitsHashes :: Annex (Int, Int)
bloomBitsHashes = do bloomBitsHashes = do
capacity <- bloomCapacity capacity <- bloomCapacity
@ -237,7 +231,7 @@ withKeysReferenced' :: v -> (Key -> v -> Annex v) -> Annex v
withKeysReferenced' initial a = go initial =<< files withKeysReferenced' initial a = go initial =<< files
where where
files = do files = do
top <- fromRepo Git.workTree top <- fromRepo Git.repoPath
inRepo $ LsFiles.inRepo [top] inRepo $ LsFiles.inRepo [top]
go v [] = return v go v [] = return v
go v (f:fs) = do go v (f:fs) = do
@ -268,7 +262,7 @@ withKeysReferencedInGitRef a ref = do
showAction $ "checking " ++ Git.Ref.describe ref showAction $ "checking " ++ Git.Ref.describe ref
go =<< inRepo (LsTree.lsTree ref) go =<< inRepo (LsTree.lsTree ref)
where where
go [] = return () go [] = noop
go (l:ls) go (l:ls)
| isSymLink (LsTree.mode l) = do | isSymLink (LsTree.mode l) = do
content <- L.decodeUtf8 <$> catFile ref (LsTree.file l) content <- L.decodeUtf8 <$> catFile ref (LsTree.file l)

View file

@ -46,9 +46,9 @@ perform remotemap key = do
untrustedheader = "The following untrusted locations may also have copies:\n" untrustedheader = "The following untrusted locations may also have copies:\n"
performRemote :: Key -> Remote -> Annex () performRemote :: Key -> Remote -> Annex ()
performRemote key remote = case whereisKey remote of performRemote key remote = maybe noop go $ whereisKey remote
Nothing -> return () where
Just a -> do go a = do
ls <- a key ls <- a key
unless (null ls) $ showLongNote $ unless (null ls) $ showLongNote $ unlines $
unlines $ map (\l -> name remote ++ ": " ++ l) ls map (\l -> name remote ++ ": " ++ l) ls

View file

@ -1,6 +1,6 @@
{- Git configuration {- Git configuration
- -
- Copyright 2011 Joey Hess <joey@kitenet.net> - Copyright 2011-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -14,29 +14,39 @@ import qualified Git.Command
import qualified Annex import qualified Annex
import Utility.DataUnits import Utility.DataUnits
type ConfigKey = String type UnqualifiedConfigKey = String
data ConfigKey = ConfigKey String
{- Changes a git config setting in both internal state and .git/config -} {- Changes a git config setting in both internal state and .git/config -}
setConfig :: ConfigKey -> String -> Annex () setConfig :: ConfigKey -> String -> Annex ()
setConfig k value = do setConfig (ConfigKey key) value = do
inRepo $ Git.Command.run "config" [Param k, Param value] inRepo $ Git.Command.run "config" [Param key, Param value]
-- re-read git config and update the repo's state newg <- inRepo Git.Config.reRead
newg <- inRepo Git.Config.read
Annex.changeState $ \s -> s { Annex.repo = newg } Annex.changeState $ \s -> s { Annex.repo = newg }
{- Looks up a git config setting in git config. -} {- Unsets a git config setting. (Leaves it in state currently.) -}
unsetConfig :: ConfigKey -> Annex ()
unsetConfig (ConfigKey key) = inRepo $ Git.Command.run "config"
[Param "--unset", Param key]
{- Looks up a setting in git config. -}
getConfig :: ConfigKey -> String -> Annex String getConfig :: ConfigKey -> String -> Annex String
getConfig key def = fromRepo $ Git.Config.get key def getConfig (ConfigKey key) def = fromRepo $ Git.Config.get key def
{- Looks up a per-remote config setting in git config. {- Looks up a per-remote config setting in git config.
- Failing that, tries looking for a global config option. -} - Failing that, tries looking for a global config option. -}
getRemoteConfig :: Git.Repo -> ConfigKey -> String -> Annex String getRemoteConfig :: Git.Repo -> UnqualifiedConfigKey -> String -> Annex String
getRemoteConfig r key def = getRemoteConfig r key def =
getConfig (remoteConfig r key) =<< getConfig key def getConfig (remoteConfig r key) =<< getConfig (annexConfig key) def
{- A per-remote config setting in git config. -} {- A per-remote config setting in git config. -}
remoteConfig :: Git.Repo -> ConfigKey -> String remoteConfig :: Git.Repo -> UnqualifiedConfigKey -> ConfigKey
remoteConfig r key = "remote." ++ fromMaybe "" (Git.remoteName r) ++ ".annex-" ++ key remoteConfig r key = ConfigKey $
"remote." ++ fromMaybe "" (Git.remoteName r) ++ ".annex-" ++ key
{- A global annex setting in git config. -}
annexConfig :: UnqualifiedConfigKey -> ConfigKey
annexConfig key = ConfigKey $ "annex." ++ key
{- Calculates cost for a remote. Either the default, or as configured {- Calculates cost for a remote. Either the default, or as configured
- by remote.<name>.annex-cost, or if remote.<name>.annex-cost-command - by remote.<name>.annex-cost, or if remote.<name>.annex-cost-command
@ -73,7 +83,7 @@ prop_cost_sane = False `notElem`
{- Checks if a repo should be ignored. -} {- Checks if a repo should be ignored. -}
repoNotIgnored :: Git.Repo -> Annex Bool repoNotIgnored :: Git.Repo -> Annex Bool
repoNotIgnored r = not . fromMaybe False . Git.configTrue repoNotIgnored r = not . fromMaybe False . Git.Config.isTrue
<$> getRemoteConfig r "ignore" "" <$> getRemoteConfig r "ignore" ""
{- If a value is specified, it is used; otherwise the default is looked up {- If a value is specified, it is used; otherwise the default is looked up
@ -83,16 +93,27 @@ getNumCopies v = perhaps (use v) =<< Annex.getState Annex.forcenumcopies
where where
use (Just n) = return n use (Just n) = return n
use Nothing = perhaps (return 1) =<< use Nothing = perhaps (return 1) =<<
readish <$> getConfig "annex.numcopies" "1" readish <$> getConfig (annexConfig "numcopies") "1"
perhaps fallback = maybe fallback (return . id) perhaps fallback = maybe fallback (return . id)
{- Gets the trust level set for a remote in git config. -} {- Gets the trust level set for a remote in git config. -}
getTrustLevel :: Git.Repo -> Annex (Maybe String) getTrustLevel :: Git.Repo -> Annex (Maybe String)
getTrustLevel r = fromRepo $ Git.Config.getMaybe $ remoteConfig r "trustlevel" getTrustLevel r = fromRepo $ Git.Config.getMaybe key
where
(ConfigKey key) = remoteConfig r "trustlevel"
{- Gets annex.diskreserve setting. -} {- Gets annex.diskreserve setting. -}
getDiskReserve :: Annex Integer getDiskReserve :: Annex Integer
getDiskReserve = fromMaybe megabyte . readSize dataUnits getDiskReserve = fromMaybe megabyte . readSize dataUnits
<$> getConfig "diskreserve" "" <$> getConfig (annexConfig "diskreserve") ""
where where
megabyte = 1000000 megabyte = 1000000
{- Gets annex.httpheaders or annex.httpheaders-command setting,
- splitting it into lines. -}
getHttpHeaders :: Annex [String]
getHttpHeaders = do
cmd <- getConfig (annexConfig "http-headers-command") ""
if (null cmd)
then fromRepo $ Git.Config.getList "annex.http-headers"
else lines . snd <$> liftIO (pipeFrom "sh" ["-c", cmd])

View file

@ -3,19 +3,19 @@
- Currently using gpg; could later be modified to support different - Currently using gpg; could later be modified to support different
- crypto backends if neccessary. - crypto backends if neccessary.
- -
- Copyright 2011 Joey Hess <joey@kitenet.net> - Copyright 2011-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Crypto ( module Crypto (
Cipher, Cipher,
EncryptedCipher, KeyIds(..),
genCipher, StorableCipher(..),
updateCipher, genEncryptedCipher,
genSharedCipher,
updateEncryptedCipher,
describeCipher, describeCipher,
storeCipher,
extractCipher,
decryptCipher, decryptCipher,
encryptKey, encryptKey,
withEncryptedHandle, withEncryptedHandle,
@ -27,7 +27,6 @@ module Crypto (
) where ) where
import qualified Data.ByteString.Lazy.Char8 as L import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.Map as M
import Data.ByteString.Lazy.UTF8 (fromString) import Data.ByteString.Lazy.UTF8 (fromString)
import Data.Digest.Pure.SHA import Data.Digest.Pure.SHA
import Control.Applicative import Control.Applicative
@ -35,8 +34,6 @@ import Control.Applicative
import Common.Annex import Common.Annex
import qualified Utility.Gpg as Gpg import qualified Utility.Gpg as Gpg
import Types.Key import Types.Key
import Types.Remote
import Utility.Base64
import Types.Crypto import Types.Crypto
{- The first half of a Cipher is used for HMAC; the remainder {- The first half of a Cipher is used for HMAC; the remainder
@ -60,59 +57,37 @@ cipherPassphrase (Cipher c) = drop cipherHalf c
cipherHmac :: Cipher -> String cipherHmac :: Cipher -> String
cipherHmac (Cipher c) = take cipherHalf c cipherHmac (Cipher c) = take cipherHalf c
{- Creates a new Cipher, encrypted as specified in the remote's configuration -} {- Creates a new Cipher, encrypted to the specificed key id. -}
genCipher :: RemoteConfig -> IO EncryptedCipher genEncryptedCipher :: String -> IO StorableCipher
genCipher c = do genEncryptedCipher keyid = do
ks <- configKeyIds c ks <- Gpg.findPubKeys keyid
random <- genrandom random <- Gpg.genRandom cipherSize
encryptCipher (Cipher random) ks encryptCipher (Cipher random) ks
where
genrandom = Gpg.readStrict
-- Armor the random data, to avoid newlines,
-- since gpg only reads ciphers up to the first
-- newline.
[ Params "--gen-random --armor"
, Param $ show randomquality
, Param $ show cipherSize
]
-- 1 is /dev/urandom; 2 is /dev/random
randomquality = 1 :: Int
{- Updates an existing Cipher, re-encrypting it to add KeyIds specified in {- Creates a new, shared Cipher. -}
- the remote's configuration. -} genSharedCipher :: IO StorableCipher
updateCipher :: RemoteConfig -> EncryptedCipher -> IO EncryptedCipher genSharedCipher = SharedCipher <$> Gpg.genRandom cipherSize
updateCipher c encipher@(EncryptedCipher _ ks) = do
ks' <- configKeyIds c {- Updates an existing Cipher, re-encrypting it to add a keyid. -}
cipher <- decryptCipher c encipher updateEncryptedCipher :: String -> StorableCipher -> IO StorableCipher
updateEncryptedCipher _ (SharedCipher _) = undefined
updateEncryptedCipher keyid encipher@(EncryptedCipher _ ks) = do
ks' <- Gpg.findPubKeys keyid
cipher <- decryptCipher encipher
encryptCipher cipher (merge ks ks') encryptCipher cipher (merge ks ks')
where where
merge (KeyIds a) (KeyIds b) = KeyIds $ a ++ b merge (KeyIds a) (KeyIds b) = KeyIds $ a ++ b
describeCipher :: EncryptedCipher -> String describeCipher :: StorableCipher -> String
describeCipher (SharedCipher _) = "shared cipher"
describeCipher (EncryptedCipher _ (KeyIds ks)) = describeCipher (EncryptedCipher _ (KeyIds ks)) =
"with gpg " ++ keys ks ++ " " ++ unwords ks "with gpg " ++ keys ks ++ " " ++ unwords ks
where where
keys [_] = "key" keys [_] = "key"
keys _ = "keys" keys _ = "keys"
{- Stores an EncryptedCipher in a remote's configuration. -}
storeCipher :: RemoteConfig -> EncryptedCipher -> RemoteConfig
storeCipher c (EncryptedCipher t ks) =
M.insert "cipher" (toB64 t) $ M.insert "cipherkeys" (showkeys ks) c
where
showkeys (KeyIds l) = join "," l
{- Extracts an EncryptedCipher from a remote's configuration. -}
extractCipher :: RemoteConfig -> Maybe EncryptedCipher
extractCipher c =
case (M.lookup "cipher" c, M.lookup "cipherkeys" c) of
(Just t, Just ks) -> Just $ EncryptedCipher (fromB64 t) (readkeys ks)
_ -> Nothing
where
readkeys = KeyIds . split ","
{- Encrypts a Cipher to the specified KeyIds. -} {- Encrypts a Cipher to the specified KeyIds. -}
encryptCipher :: Cipher -> KeyIds -> IO EncryptedCipher encryptCipher :: Cipher -> KeyIds -> IO StorableCipher
encryptCipher (Cipher c) (KeyIds ks) = do encryptCipher (Cipher c) (KeyIds ks) = do
let ks' = nub $ sort ks -- gpg complains about duplicate recipient keyids let ks' = nub $ sort ks -- gpg complains about duplicate recipient keyids
encipher <- Gpg.pipeStrict (encrypt++recipients ks') c encipher <- Gpg.pipeStrict (encrypt++recipients ks') c
@ -126,9 +101,9 @@ encryptCipher (Cipher c) (KeyIds ks) = do
force_recipients = Params "--no-encrypt-to --no-default-recipient" force_recipients = Params "--no-encrypt-to --no-default-recipient"
{- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -} {- Decrypting an EncryptedCipher is expensive; the Cipher should be cached. -}
decryptCipher :: RemoteConfig -> EncryptedCipher -> IO Cipher decryptCipher :: StorableCipher -> IO Cipher
decryptCipher _ (EncryptedCipher encipher _) = decryptCipher (SharedCipher t) = return $ Cipher t
Cipher <$> Gpg.pipeStrict decrypt encipher decryptCipher (EncryptedCipher t _) = Cipher <$> Gpg.pipeStrict decrypt t
where where
decrypt = [ Param "--decrypt" ] decrypt = [ Param "--decrypt" ]
@ -163,15 +138,7 @@ withDecryptedContent = pass withDecryptedHandle
pass :: (Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a) pass :: (Cipher -> IO L.ByteString -> (Handle -> IO a) -> IO a)
-> Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a -> Cipher -> IO L.ByteString -> (L.ByteString -> IO a) -> IO a
pass to c i a = to c i $ \h -> a =<< L.hGetContents h pass to n s a = to n s $ \h -> a =<< L.hGetContents h
configKeyIds :: RemoteConfig -> IO KeyIds
configKeyIds c = Gpg.findPubKeys $ configGet c "encryption"
configGet :: RemoteConfig -> String -> String
configGet c key = fromMaybe missing $ M.lookup key c
where
missing = error $ "missing " ++ key ++ " in remote config"
hmacWithCipher :: Cipher -> String -> String hmacWithCipher :: Cipher -> String -> String
hmacWithCipher c = hmacWithCipher' (cipherHmac c) hmacWithCipher c = hmacWithCipher' (cipherHmac c)

77
Git.hs
View file

@ -3,7 +3,7 @@
- This is written to be completely independant of git-annex and should be - This is written to be completely independant of git-annex and should be
- suitable for other uses. - suitable for other uses.
- -
- Copyright 2010, 2011 Joey Hess <joey@kitenet.net> - Copyright 2010-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -17,19 +17,17 @@ module Git (
repoIsUrl, repoIsUrl,
repoIsSsh, repoIsSsh,
repoIsHttp, repoIsHttp,
repoIsLocal,
repoIsLocalBare, repoIsLocalBare,
repoDescribe, repoDescribe,
repoLocation, repoLocation,
workTree, repoPath,
gitDir, localGitDir,
configTrue,
attributes, attributes,
hookPath, hookPath,
assertLocal, assertLocal,
) where ) where
import qualified Data.Map as M
import Data.Char
import Network.URI (uriPath, uriScheme, unEscapeString) import Network.URI (uriPath, uriScheme, unEscapeString)
import System.Posix.Files import System.Posix.Files
@ -41,15 +39,34 @@ import Utility.FileMode
repoDescribe :: Repo -> String repoDescribe :: Repo -> String
repoDescribe Repo { remoteName = Just name } = name repoDescribe Repo { remoteName = Just name } = name
repoDescribe Repo { location = Url url } = show url repoDescribe Repo { location = Url url } = show url
repoDescribe Repo { location = Dir dir } = dir repoDescribe Repo { location = Local { worktree = Just dir } } = dir
repoDescribe Repo { location = Local { gitdir = dir } } = dir
repoDescribe Repo { location = LocalUnknown dir } = dir
repoDescribe Repo { location = Unknown } = "UNKNOWN" repoDescribe Repo { location = Unknown } = "UNKNOWN"
{- Location of the repo, either as a path or url. -} {- Location of the repo, either as a path or url. -}
repoLocation :: Repo -> String repoLocation :: Repo -> String
repoLocation Repo { location = Url url } = show url repoLocation Repo { location = Url url } = show url
repoLocation Repo { location = Dir dir } = dir repoLocation Repo { location = Local { worktree = Just dir } } = dir
repoLocation Repo { location = Local { gitdir = dir } } = dir
repoLocation Repo { location = LocalUnknown dir } = dir
repoLocation Repo { location = Unknown } = undefined repoLocation Repo { location = Unknown } = undefined
{- Path to a repository. For non-bare, this is the worktree, for bare,
- it's the gitdir, and for URL repositories, is the path on the remote
- host. -}
repoPath :: Repo -> FilePath
repoPath Repo { location = Url u } = unEscapeString $ uriPath u
repoPath Repo { location = Local { worktree = Just d } } = d
repoPath Repo { location = Local { gitdir = d } } = d
repoPath Repo { location = LocalUnknown dir } = dir
repoPath Repo { location = Unknown } = undefined
{- Path to a local repository's .git directory. -}
localGitDir :: Repo -> FilePath
localGitDir Repo { location = Local { gitdir = d } } = d
localGitDir _ = undefined
{- Some code needs to vary between URL and normal repos, {- Some code needs to vary between URL and normal repos,
- or bare and non-bare, these functions help with that. -} - or bare and non-bare, these functions help with that. -}
repoIsUrl :: Repo -> Bool repoIsUrl :: Repo -> Bool
@ -74,11 +91,12 @@ repoIsHttp Repo { location = Url url }
| otherwise = False | otherwise = False
repoIsHttp _ = False repoIsHttp _ = False
configAvail ::Repo -> Bool repoIsLocal :: Repo -> Bool
configAvail Repo { config = c } = c /= M.empty repoIsLocal Repo { location = Local { } } = True
repoIsLocal _ = False
repoIsLocalBare :: Repo -> Bool repoIsLocalBare :: Repo -> Bool
repoIsLocalBare r@(Repo { location = Dir _ }) = configAvail r && configBare r repoIsLocalBare Repo { location = Local { worktree = Nothing } } = True
repoIsLocalBare _ = False repoIsLocalBare _ = False
assertLocal :: Repo -> a -> a assertLocal :: Repo -> a -> a
@ -90,49 +108,18 @@ assertLocal repo action
] ]
| otherwise = action | otherwise = action
configBare :: Repo -> Bool
configBare repo = maybe unknown (fromMaybe False . configTrue) $
M.lookup "core.bare" $ config repo
where
unknown = error $ "it is not known if git repo " ++
repoDescribe repo ++
" is a bare repository; config not read"
{- Path to a repository's gitattributes file. -} {- Path to a repository's gitattributes file. -}
attributes :: Repo -> FilePath attributes :: Repo -> FilePath
attributes repo attributes repo
| configBare repo = workTree repo ++ "/info/.gitattributes" | repoIsLocalBare repo = repoPath repo ++ "/info/.gitattributes"
| otherwise = workTree repo ++ "/.gitattributes" | otherwise = repoPath repo ++ "/.gitattributes"
{- Path to a repository's .git directory. -}
gitDir :: Repo -> FilePath
gitDir repo
| configBare repo = workTree repo
| otherwise = workTree repo </> ".git"
{- Path to a given hook script in a repository, only if the hook exists {- Path to a given hook script in a repository, only if the hook exists
- and is executable. -} - and is executable. -}
hookPath :: String -> Repo -> IO (Maybe FilePath) hookPath :: String -> Repo -> IO (Maybe FilePath)
hookPath script repo = do hookPath script repo = do
let hook = gitDir repo </> "hooks" </> script let hook = localGitDir repo </> "hooks" </> script
ifM (catchBoolIO $ isexecutable hook) ifM (catchBoolIO $ isexecutable hook)
( return $ Just hook , return Nothing ) ( return $ Just hook , return Nothing )
where where
isexecutable f = isExecutable . fileMode <$> getFileStatus f isexecutable f = isExecutable . fileMode <$> getFileStatus f
{- Path to a repository's --work-tree, that is, its top.
-
- Note that for URL repositories, this is the path on the remote host. -}
workTree :: Repo -> FilePath
workTree Repo { location = Url u } = unEscapeString $ uriPath u
workTree Repo { location = Dir d } = d
workTree Repo { location = Unknown } = undefined
{- Checks if a string from git config is a true value. -}
configTrue :: String -> Maybe Bool
configTrue s
| s' == "true" = Just True
| s' == "false" = Just False
| otherwise = Nothing
where
s' = map toLower s

View file

@ -34,7 +34,7 @@ fuzzymatches :: String -> (c -> String) -> [c] -> [c]
fuzzymatches input showchoice choices = fst $ unzip $ fuzzymatches input showchoice choices = fst $ unzip $
sortBy comparecost $ filter similarEnough $ zip choices costs sortBy comparecost $ filter similarEnough $ zip choices costs
where where
distance v = restrictedDamerauLevenshteinDistance gitEditCosts v input distance = restrictedDamerauLevenshteinDistance gitEditCosts input
costs = map (distance . showchoice) choices costs = map (distance . showchoice) choices
comparecost a b = compare (snd a) (snd b) comparecost a b = compare (snd a) (snd b)
similarEnough (_, cst) = cst < similarityFloor similarEnough (_, cst) = cst < similarityFloor

View file

@ -1,6 +1,6 @@
{- running git commands {- running git commands
- -
- Copyright 2010, 2011 Joey Hess <joey@kitenet.net> - Copyright 2010-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -18,11 +18,12 @@ import Git.Types
{- Constructs a git command line operating on the specified repo. -} {- Constructs a git command line operating on the specified repo. -}
gitCommandLine :: [CommandParam] -> Repo -> [CommandParam] gitCommandLine :: [CommandParam] -> Repo -> [CommandParam]
gitCommandLine params repo@(Repo { location = Dir _ } ) = gitCommandLine params Repo { location = l@(Local _ _ ) } = setdir : settree ++ params
-- force use of specified repo via --git-dir and --work-tree where
[ Param ("--git-dir=" ++ gitDir repo) setdir = Param $ "--git-dir=" ++ gitdir l
, Param ("--work-tree=" ++ workTree repo) settree = case worktree l of
] ++ params Nothing -> []
Just t -> [Param $ "--work-tree=" ++ t]
gitCommandLine _ repo = assertLocal repo $ error "internal" gitCommandLine _ repo = assertLocal repo $ error "internal"
{- Runs git in the specified repo. -} {- Runs git in the specified repo. -}
@ -79,5 +80,5 @@ pipeNullSplit params repo =
reap :: IO () reap :: IO ()
reap = do reap = do
-- throws an exception when there are no child processes -- throws an exception when there are no child processes
r <- catchDefaultIO (getAnyProcessStatus False True) Nothing catchDefaultIO (getAnyProcessStatus False True) Nothing
maybe (return ()) (const reap) r >>= maybe noop (const reap)

View file

@ -1,15 +1,14 @@
{- git repository configuration handling {- git repository configuration handling
- -
- Copyright 2010,2011 Joey Hess <joey@kitenet.net> - Copyright 2010-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Git.Config where module Git.Config where
import System.Posix.Directory
import Control.Exception (bracket_)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Char
import Common import Common
import Git import Git
@ -20,23 +19,37 @@ import qualified Git.Construct
get :: String -> String -> Repo -> String get :: String -> String -> Repo -> String
get key defaultValue repo = M.findWithDefault defaultValue key (config repo) get key defaultValue repo = M.findWithDefault defaultValue key (config repo)
{- Returns a list with each line of a multiline config setting. -}
getList :: String -> Repo -> [String]
getList key repo = M.findWithDefault [] key (fullconfig repo)
{- Returns a single git config setting, if set. -} {- Returns a single git config setting, if set. -}
getMaybe :: String -> Repo -> Maybe String getMaybe :: String -> Repo -> Maybe String
getMaybe key repo = M.lookup key (config repo) getMaybe key repo = M.lookup key (config repo)
{- Runs git config and populates a repo with its config. -} {- Runs git config and populates a repo with its config.
- Avoids re-reading config when run repeatedly. -}
read :: Repo -> IO Repo read :: Repo -> IO Repo
read repo@(Repo { location = Dir d }) = bracketcd d $ read repo@(Repo { config = c })
{- Cannot use pipeRead because it relies on the config having | c == M.empty = read' repo
been already read. Instead, chdir to the repo. -} | otherwise = return repo
pOpen ReadFromPipe "git" ["config", "--null", "--list"] $ hRead repo
{- Reads config even if it was read before. -}
reRead :: Repo -> IO Repo
reRead = read'
{- Cannot use pipeRead because it relies on the config having been already
- read. Instead, chdir to the repo.
-}
read' :: Repo -> IO Repo
read' repo = go repo
where where
bracketcd to a = bracketcd' to a =<< getCurrentDirectory go Repo { location = Local { gitdir = d } } = git_config d
bracketcd' to a cwd go Repo { location = LocalUnknown d } = git_config d
| dirContains to cwd = a go _ = assertLocal repo $ error "internal"
| otherwise = bracket_ (changeWorkingDirectory to) (changeWorkingDirectory cwd) a git_config d = bracketCd d $
read r = assertLocal r $ pOpen ReadFromPipe "git" ["config", "--null", "--list"] $
error $ "internal error; trying to read config of " ++ show r hRead repo
{- Reads git config from a handle and populates a repo with it. -} {- Reads git config from a handle and populates a repo with it. -}
hRead :: Repo -> Handle -> IO Repo hRead :: Repo -> Handle -> IO Repo
@ -44,19 +57,37 @@ hRead repo h = do
val <- hGetContentsStrict h val <- hGetContentsStrict h
store val repo store val repo
{- Stores a git config into a repo, returning the new version of the repo. {- Stores a git config into a Repo, returning the new version of the Repo.
- The git config may be multiple lines, or a single line. Config settings - The git config may be multiple lines, or a single line.
- can be updated inrementally. -} - Config settings can be updated incrementally.
-}
store :: String -> Repo -> IO Repo store :: String -> Repo -> IO Repo
store s repo = do store s repo = do
let c = parse s let c = parse s
let repo' = repo let repo' = updateLocation $ repo
{ config = (M.map Prelude.head c) `M.union` config repo { config = (M.map Prelude.head c) `M.union` config repo
, fullconfig = M.unionWith (++) c (fullconfig repo) , fullconfig = M.unionWith (++) c (fullconfig repo)
} }
rs <- Git.Construct.fromRemotes repo' rs <- Git.Construct.fromRemotes repo'
return $ repo' { remotes = rs } return $ repo' { remotes = rs }
{- Updates the location of a repo, based on its configuration.
-
- Git.Construct makes LocalUknown repos, of which only a directory is
- known. Once the config is read, this can be fixed up to a Local repo,
- based on the core.bare and core.worktree settings.
-}
updateLocation :: Repo -> Repo
updateLocation r@(Repo { location = LocalUnknown d })
| isBare r = newloc $ Local d Nothing
| otherwise = newloc $ Local (d </> ".git") (Just d)
where
newloc l = r { location = getworktree l }
getworktree l = case workTree r of
Nothing -> l
wt -> l { worktree = wt }
updateLocation r = r
{- Parses git config --list or git config --null --list output into a {- Parses git config --list or git config --null --list output into a
- config map. -} - config map. -}
parse :: String -> M.Map String [String] parse :: String -> M.Map String [String]
@ -70,3 +101,18 @@ parse s
ls = lines s ls = lines s
sep c = M.fromListWith (++) . map (\(k,v) -> (k, [v])) . sep c = M.fromListWith (++) . map (\(k,v) -> (k, [v])) .
map (separate (== c)) map (separate (== c))
{- Checks if a string from git config is a true value. -}
isTrue :: String -> Maybe Bool
isTrue s
| s' == "true" = Just True
| s' == "false" = Just False
| otherwise = Nothing
where
s' = map toLower s
isBare :: Repo -> Bool
isBare r = fromMaybe False $ isTrue =<< getMaybe "core.bare" r
workTree :: Repo -> Maybe FilePath
workTree = getMaybe "core.worktree"

View file

@ -1,12 +1,11 @@
{- Construction of Git Repo objects {- Construction of Git Repo objects
- -
- Copyright 2010,2011 Joey Hess <joey@kitenet.net> - Copyright 2010-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Git.Construct ( module Git.Construct (
fromCurrent,
fromCwd, fromCwd,
fromAbsPath, fromAbsPath,
fromPath, fromPath,
@ -21,8 +20,6 @@ module Git.Construct (
) where ) where
import System.Posix.User import System.Posix.User
import System.Posix.Env (getEnv, unsetEnv)
import System.Posix.Directory (changeWorkingDirectory)
import qualified Data.Map as M hiding (map, split) import qualified Data.Map as M hiding (map, split)
import Network.URI import Network.URI
@ -31,34 +28,12 @@ import Git.Types
import Git import Git
import qualified Git.Url as Url import qualified Git.Url as Url
{- Finds the current git repository.
-
- GIT_DIR can override the location of the .git directory.
-
- When GIT_WORK_TREE is set, chdir to it, so that anything using
- this repository runs in the right location. However, this chdir is
- done after determining GIT_DIR; git does not let GIT_WORK_TREE
- influence the git directory.
-
- Both environment variables are unset, to avoid confusing other git
- commands that also look at them. This would particularly be a problem
- when GIT_DIR is relative and we chdir for GIT_WORK_TREE. Instead,
- the Git module passes --work-tree and --git-dir to git commands it runs.
-}
fromCurrent :: IO Repo
fromCurrent = do
r <- maybe fromCwd fromPath =<< getEnv "GIT_DIR"
maybe (return ()) changeWorkingDirectory =<< getEnv "GIT_WORK_TREE"
unsetEnv "GIT_DIR"
unsetEnv "GIT_WORK_TREE"
return r
{- Finds the git repository used for the Cwd, which may be in a parent {- Finds the git repository used for the Cwd, which may be in a parent
- directory. -} - directory. -}
fromCwd :: IO Repo fromCwd :: IO Repo
fromCwd = getCurrentDirectory >>= seekUp isRepoTop >>= maybe norepo makerepo fromCwd = getCurrentDirectory >>= seekUp isRepoTop >>= maybe norepo makerepo
where where
makerepo = newFrom . Dir makerepo = newFrom . LocalUnknown
norepo = error "Not in a git repository." norepo = error "Not in a git repository."
{- Local Repo constructor, accepts a relative or absolute path. -} {- Local Repo constructor, accepts a relative or absolute path. -}
@ -74,7 +49,7 @@ fromAbsPath dir
| otherwise = | otherwise =
error $ "internal error, " ++ dir ++ " is not absolute" error $ "internal error, " ++ dir ++ " is not absolute"
where where
ret = newFrom . Dir ret = newFrom . LocalUnknown
{- Git always looks for "dir.git" in preference to {- Git always looks for "dir.git" in preference to
- to "dir", even if dir ends in a "/". -} - to "dir", even if dir ends in a "/". -}
canondir = dropTrailingPathSeparator dir canondir = dropTrailingPathSeparator dir
@ -122,7 +97,7 @@ localToUrl reference r
absurl = absurl =
Url.scheme reference ++ "//" ++ Url.scheme reference ++ "//" ++
Url.authority reference ++ Url.authority reference ++
workTree r repoPath r
{- Calculates a list of a repo's configured remotes, by parsing its config. -} {- Calculates a list of a repo's configured remotes, by parsing its config. -}
fromRemotes :: Repo -> IO [Repo] fromRemotes :: Repo -> IO [Repo]
@ -191,7 +166,7 @@ fromRemoteLocation s repo = gen $ calcloc s
fromRemotePath :: FilePath -> Repo -> IO Repo fromRemotePath :: FilePath -> Repo -> IO Repo
fromRemotePath dir repo = do fromRemotePath dir repo = do
dir' <- expandTilde dir dir' <- expandTilde dir
fromAbsPath $ workTree repo </> dir' fromAbsPath $ repoPath repo </> dir'
{- Git remotes can have a directory that is specified relative {- Git remotes can have a directory that is specified relative
- to the user's home directory, or that contains tilde expansions. - to the user's home directory, or that contains tilde expansions.
@ -251,3 +226,5 @@ newFrom l = return Repo
, remotes = [] , remotes = []
, remoteName = Nothing , remoteName = Nothing
} }

58
Git/CurrentRepo.hs Normal file
View file

@ -0,0 +1,58 @@
{- The current git repository.
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Git.CurrentRepo where
import System.Posix.Directory (changeWorkingDirectory)
import System.Posix.Env (getEnv, unsetEnv)
import Common
import Git.Types
import Git.Construct
import qualified Git.Config
{- Gets the current git repository.
-
- Honors GIT_DIR and GIT_WORK_TREE.
- Both environment variables are unset, to avoid confusing other git
- commands that also look at them. Instead, the Git module passes
- --work-tree and --git-dir to git commands it runs.
-
- When GIT_WORK_TREE or core.worktree are set, changes the working
- directory if necessary to ensure it is within the repository's work
- tree. While not needed for git commands, this is useful for anything
- else that looks for files in the worktree.
-}
get :: IO Repo
get = do
gd <- pathenv "GIT_DIR"
r <- configure gd =<< maybe fromCwd fromPath gd
wt <- maybe (Git.Config.workTree r) Just <$> pathenv "GIT_WORK_TREE"
case wt of
Nothing -> return r
Just d -> do
cwd <- getCurrentDirectory
unless (d `dirContains` cwd) $
changeWorkingDirectory d
return $ addworktree wt r
where
pathenv s = do
v <- getEnv s
when (isJust v) $
unsetEnv s
case v of
Nothing -> return Nothing
Just d -> Just <$> absPath d
configure Nothing r = Git.Config.read r
configure (Just d) r = do
r' <- Git.Config.read r
-- Let GIT_DIR override the default gitdir.
return $ changelocation r' $
Local { gitdir = d, worktree = worktree (location r') }
addworktree w r = changelocation r $
Local { gitdir = gitdir (location r), worktree = w }
changelocation r l = r { location = l }

View file

@ -69,7 +69,7 @@ typeChanged' ps l repo = do
fs <- pipeNullSplit (prefix ++ ps ++ suffix) repo fs <- pipeNullSplit (prefix ++ ps ++ suffix) repo
-- git diff returns filenames relative to the top of the git repo; -- git diff returns filenames relative to the top of the git repo;
-- convert to filenames relative to the cwd, like git ls-files. -- convert to filenames relative to the cwd, like git ls-files.
let top = workTree repo let top = repoPath repo
cwd <- getCurrentDirectory cwd <- getCurrentDirectory
return $ map (\f -> relPathDirToFile cwd $ top </> f) fs return $ map (\f -> relPathDirToFile cwd $ top </> f) fs
where where

27
Git/SharedRepository.hs Normal file
View file

@ -0,0 +1,27 @@
{- git core.sharedRepository handling
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Git.SharedRepository where
import Data.Char
import Common
import Git
import qualified Git.Config
data SharedRepository = UnShared | GroupShared | AllShared | UmaskShared Int
getSharedRepository :: Repo -> SharedRepository
getSharedRepository r =
case map toLower $ Git.Config.get "core.sharedrepository" "" r of
"1" -> GroupShared
"group" -> GroupShared
"true" -> GroupShared
"all" -> AllShared
"world" -> AllShared
"everybody" -> AllShared
v -> maybe UnShared UmaskShared (readish v)

View file

@ -1,6 +1,6 @@
{- git data types {- git data types
- -
- Copyright 2010,2011 Joey Hess <joey@kitenet.net> - Copyright 2010-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -10,9 +10,21 @@ module Git.Types where
import Network.URI import Network.URI
import qualified Data.Map as M import qualified Data.Map as M
{- There are two types of repositories; those on local disk and those {- Support repositories on local disk, and repositories accessed via an URL.
- accessed via an URL. -} -
data RepoLocation = Dir FilePath | Url URI | Unknown - Repos on local disk have a git directory, and unless bare, a worktree.
-
- A local repo may not have had its config read yet, in which case all
- that's known about it is its path.
-
- Finally, an Unknown repository may be known to exist, but nothing
- else known about it.
-}
data RepoLocation
= Local { gitdir :: FilePath, worktree :: Maybe FilePath }
| LocalUnknown FilePath
| Url URI
| Unknown
deriving (Show, Eq) deriving (Show, Eq)
data Repo = Repo { data Repo = Repo {

View file

@ -97,7 +97,7 @@ calc_merge :: CatFileHandle -> [String] -> Repo -> Streamer
calc_merge ch differ repo streamer = gendiff >>= go calc_merge ch differ repo streamer = gendiff >>= go
where where
gendiff = pipeNullSplit (map Param differ) repo gendiff = pipeNullSplit (map Param differ) repo
go [] = return () go [] = noop
go (info:file:rest) = mergeFile info file ch repo >>= go (info:file:rest) = mergeFile info file ch repo >>=
maybe (go rest) (\l -> streamer l >> go rest) maybe (go rest) (\l -> streamer l >> go rest)
go (_:[]) = error "calc_merge parse error" go (_:[]) = error "calc_merge parse error"

View file

@ -11,7 +11,7 @@ import System.Console.GetOpt
import Common.Annex import Common.Annex
import qualified Git.Config import qualified Git.Config
import qualified Git.Construct import qualified Git.CurrentRepo
import CmdLine import CmdLine
import Command import Command
import Types.TrustLevel import Types.TrustLevel
@ -37,6 +37,7 @@ import qualified Command.InitRemote
import qualified Command.Fsck import qualified Command.Fsck
import qualified Command.Unused import qualified Command.Unused
import qualified Command.DropUnused import qualified Command.DropUnused
import qualified Command.AddUnused
import qualified Command.Unlock import qualified Command.Unlock
import qualified Command.Lock import qualified Command.Lock
import qualified Command.PreCommit import qualified Command.PreCommit
@ -53,6 +54,7 @@ import qualified Command.Semitrust
import qualified Command.Dead import qualified Command.Dead
import qualified Command.Sync import qualified Command.Sync
import qualified Command.AddUrl import qualified Command.AddUrl
import qualified Command.Import
import qualified Command.Map import qualified Command.Map
import qualified Command.Upgrade import qualified Command.Upgrade
import qualified Command.Version import qualified Command.Version
@ -69,6 +71,7 @@ cmds = concat
, Command.Lock.def , Command.Lock.def
, Command.Sync.def , Command.Sync.def
, Command.AddUrl.def , Command.AddUrl.def
, Command.Import.def
, Command.Init.def , Command.Init.def
, Command.Describe.def , Command.Describe.def
, Command.InitRemote.def , Command.InitRemote.def
@ -87,6 +90,7 @@ cmds = concat
, Command.Fsck.def , Command.Fsck.def
, Command.Unused.def , Command.Unused.def
, Command.DropUnused.def , Command.DropUnused.def
, Command.AddUnused.def
, Command.Find.def , Command.Find.def
, Command.Whereis.def , Command.Whereis.def
, Command.Log.def , Command.Log.def
@ -133,4 +137,4 @@ header :: String
header = "Usage: git-annex command [option ..]" header = "Usage: git-annex command [option ..]"
run :: [String] -> IO () run :: [String] -> IO ()
run args = dispatch True args cmds options header Git.Construct.fromCurrent run args = dispatch True args cmds options header Git.CurrentRepo.get

View file

@ -52,7 +52,7 @@ options = Option.common ++
where where
checkuuid expected = getUUID >>= check checkuuid expected = getUUID >>= check
where where
check u | u == toUUID expected = return () check u | u == toUUID expected = noop
check NoUUID = unexpected "uninitialized repository" check NoUUID = unexpected "uninitialized repository"
check u = unexpected $ "UUID " ++ fromUUID u check u = unexpected $ "UUID " ++ fromUUID u
unexpected s = error $ unexpected s = error $
@ -107,7 +107,7 @@ checkNotLimited = checkEnv "GIT_ANNEX_SHELL_LIMITED"
checkNotReadOnly :: String -> IO () checkNotReadOnly :: String -> IO ()
checkNotReadOnly cmd checkNotReadOnly cmd
| cmd `elem` map cmdname cmds_readonly = return () | cmd `elem` map cmdname cmds_readonly = noop
| otherwise = checkEnv "GIT_ANNEX_SHELL_READONLY" | otherwise = checkEnv "GIT_ANNEX_SHELL_READONLY"
checkEnv :: String -> IO () checkEnv :: String -> IO ()

View file

@ -29,7 +29,9 @@ initialize mdescription = do
maybe (recordUUID u) (describeUUID u) mdescription maybe (recordUUID u) (describeUUID u) mdescription
uninitialize :: Annex () uninitialize :: Annex ()
uninitialize = gitPreCommitHookUnWrite uninitialize = do
gitPreCommitHookUnWrite
removeRepoUUID
{- Will automatically initialize if there is already a git-annex {- Will automatically initialize if there is already a git-annex
branch from somewhere. Otherwise, require a manual init branch from somewhere. Otherwise, require a manual init
@ -70,7 +72,7 @@ unlessBare :: Annex () -> Annex ()
unlessBare = unlessM $ fromRepo Git.repoIsLocalBare unlessBare = unlessM $ fromRepo Git.repoIsLocalBare
preCommitHook :: Annex FilePath preCommitHook :: Annex FilePath
preCommitHook = (</>) <$> fromRepo Git.gitDir <*> pure "hooks/pre-commit" preCommitHook = (</>) <$> fromRepo Git.localGitDir <*> pure "hooks/pre-commit"
preCommitScript :: String preCommitScript :: String
preCommitScript = preCommitScript =

View file

@ -85,28 +85,24 @@ gitAnnexLocation key r
| Git.repoIsLocalBare r = | Git.repoIsLocalBare r =
{- Bare repositories default to hashDirLower for new {- Bare repositories default to hashDirLower for new
- content, as it's more portable. -} - content, as it's more portable. -}
check (map inrepo $ annexLocations key) check $ map inrepo $ annexLocations key
| otherwise = | otherwise =
{- Non-bare repositories only use hashDirMixed, so {- Non-bare repositories only use hashDirMixed, so
- don't need to do any work to check if the file is - don't need to do any work to check if the file is
- present. -} - present. -}
return $ inrepo ".git" </> annexLocation key hashDirMixed return $ inrepo $ annexLocation key hashDirMixed
where where
inrepo d = Git.workTree r </> d inrepo d = Git.localGitDir r </> d
check locs@(l:_) = fromMaybe l <$> firstM doesFileExist locs check locs@(l:_) = fromMaybe l <$> firstM doesFileExist locs
check [] = error "internal" check [] = error "internal"
{- The annex directory of a repository. -} {- The annex directory of a repository. -}
gitAnnexDir :: Git.Repo -> FilePath gitAnnexDir :: Git.Repo -> FilePath
gitAnnexDir r gitAnnexDir r = addTrailingPathSeparator $ Git.localGitDir r </> annexDir
| Git.repoIsLocalBare r = addTrailingPathSeparator $ Git.workTree r </> annexDir
| otherwise = addTrailingPathSeparator $ Git.workTree r </> ".git" </> annexDir
{- The part of the annex directory where file contents are stored. -} {- The part of the annex directory where file contents are stored. -}
gitAnnexObjectDir :: Git.Repo -> FilePath gitAnnexObjectDir :: Git.Repo -> FilePath
gitAnnexObjectDir r gitAnnexObjectDir r = addTrailingPathSeparator $ Git.localGitDir r </> objectDir
| Git.repoIsLocalBare r = addTrailingPathSeparator $ Git.workTree r </> objectDir
| otherwise = addTrailingPathSeparator $ Git.workTree r </> ".git" </> objectDir
{- .git/annex/tmp/ is used for temp files -} {- .git/annex/tmp/ is used for temp files -}
gitAnnexTmpDir :: Git.Repo -> FilePath gitAnnexTmpDir :: Git.Repo -> FilePath
@ -124,7 +120,7 @@ gitAnnexBadDir r = addTrailingPathSeparator $ gitAnnexDir r </> "bad"
gitAnnexBadLocation :: Key -> Git.Repo -> FilePath gitAnnexBadLocation :: Key -> Git.Repo -> FilePath
gitAnnexBadLocation key r = gitAnnexBadDir r </> keyFile key gitAnnexBadLocation key r = gitAnnexBadDir r </> keyFile key
{- .git/annex/*unused is used to number possibly unused keys -} {- .git/annex/foounused is used to number possibly unused keys -}
gitAnnexUnusedLog :: FilePath -> Git.Repo -> FilePath gitAnnexUnusedLog :: FilePath -> Git.Repo -> FilePath
gitAnnexUnusedLog prefix r = gitAnnexDir r </> (prefix ++ "unused") gitAnnexUnusedLog prefix r = gitAnnexDir r </> (prefix ++ "unused")
@ -159,7 +155,9 @@ gitAnnexRemotesDir r = addTrailingPathSeparator $ gitAnnexDir r </> "remotes"
{- Checks a symlink target to see if it appears to point to annexed content. -} {- Checks a symlink target to see if it appears to point to annexed content. -}
isLinkToAnnex :: FilePath -> Bool isLinkToAnnex :: FilePath -> Bool
isLinkToAnnex s = ("/.git/" ++ objectDir) `isInfixOf` s isLinkToAnnex s = ("/" ++ d) `isInfixOf` s || d `isPrefixOf` s
where
d = ".git" </> objectDir
{- Converts a key into a filename fragment without any directory. {- Converts a key into a filename fragment without any directory.
- -

View file

@ -30,7 +30,7 @@ import Logs.Presence
{- Log a change in the presence of a key's value in a repository. -} {- Log a change in the presence of a key's value in a repository. -}
logChange :: Key -> UUID -> LogStatus -> Annex () logChange :: Key -> UUID -> LogStatus -> Annex ()
logChange key (UUID u) s = addLog (logFile key) =<< logNow s u logChange key (UUID u) s = addLog (logFile key) =<< logNow s u
logChange _ NoUUID _ = return () logChange _ NoUUID _ = noop
{- Returns a list of repository UUIDs that, according to the log, have {- Returns a list of repository UUIDs that, according to the log, have
- the value of a key. - the value of a key.

View file

@ -36,7 +36,7 @@ configSet u c = do
{- Map of remotes by uuid containing key/value config maps. -} {- Map of remotes by uuid containing key/value config maps. -}
readRemoteLog :: Annex (M.Map UUID RemoteConfig) readRemoteLog :: Annex (M.Map UUID RemoteConfig)
readRemoteLog = (simpleMap . parseLog parseConfig) <$> Annex.Branch.get remoteLog readRemoteLog = simpleMap . parseLog parseConfig <$> Annex.Branch.get remoteLog
parseConfig :: String -> Maybe RemoteConfig parseConfig :: String -> Maybe RemoteConfig
parseConfig = Just . keyValToConfig . words parseConfig = Just . keyValToConfig . words
@ -59,7 +59,7 @@ configToKeyVal m = map toword $ sort $ M.toList m
toword (k, v) = k ++ "=" ++ configEscape v toword (k, v) = k ++ "=" ++ configEscape v
configEscape :: String -> String configEscape :: String -> String
configEscape = (>>= escape) configEscape = concatMap escape
where where
escape c escape c
| isSpace c || c `elem` "&" = "&" ++ show (ord c) ++ ";" | isSpace c || c `elem` "&" = "&" ++ show (ord c) ++ ";"

View file

@ -73,7 +73,7 @@ recordUUID u = go . M.lookup u =<< uuidMap
where where
go (Just "") = set go (Just "") = set
go Nothing = set go Nothing = set
go _ = return () go _ = noop
set = describeUUID u "" set = describeUUID u ""
{- Read the uuidLog into a simple Map. {- Read the uuidLog into a simple Map.

View file

@ -83,7 +83,7 @@ changeLog t u v = M.insert u $ LogEntry (Date t) v
{- Only add an LogEntry if it's newer (or at least as new as) than any {- Only add an LogEntry if it's newer (or at least as new as) than any
- existing LogEntry for a UUID. -} - existing LogEntry for a UUID. -}
addLog :: UUID -> LogEntry a -> Log a -> Log a addLog :: UUID -> LogEntry a -> Log a -> Log a
addLog = M.insertWith best addLog = M.insertWith' best
{- Converts a Log into a simple Map without the timestamp information. {- Converts a Log into a simple Map without the timestamp information.
- This is a one-way trip, but useful for code that never needs to change - This is a one-way trip, but useful for code that never needs to change

91
Logs/Unused.hs Normal file
View file

@ -0,0 +1,91 @@
{- git-annex unused log file
-
- Copyright 2010,2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Logs.Unused (
UnusedMap,
UnusedMaps(..),
writeUnusedLog,
readUnusedLog,
withUnusedMaps,
startUnused,
) where
import qualified Data.Map as M
import Common.Annex
import Command
import Types.Key
import Utility.TempFile
writeUnusedLog :: FilePath -> [(Int, Key)] -> Annex ()
writeUnusedLog prefix l = do
logfile <- fromRepo $ gitAnnexUnusedLog prefix
liftIO $ viaTmp writeFile logfile $
unlines $ map (\(n, k) -> show n ++ " " ++ show k) l
readUnusedLog :: FilePath -> Annex UnusedMap
readUnusedLog prefix = do
f <- fromRepo $ gitAnnexUnusedLog prefix
ifM (liftIO $ doesFileExist f)
( M.fromList . catMaybes . map parse . lines
<$> liftIO (readFile f)
, return M.empty
)
where
parse line =
case (readish tag, readKey rest) of
(Just num, Just key) -> Just (num, key)
_ -> Nothing
where
(tag, rest) = separate (== ' ') line
type UnusedMap = M.Map Int Key
data UnusedMaps = UnusedMaps
{ unusedMap :: UnusedMap
, unusedBadMap :: UnusedMap
, unusedTmpMap :: UnusedMap
}
{- Read unused logs once, and pass the maps to each start action. -}
withUnusedMaps :: (UnusedMaps -> Int -> CommandStart) -> CommandSeek
withUnusedMaps a params = do
unused <- readUnusedLog ""
unusedbad <- readUnusedLog "bad"
unusedtmp <- readUnusedLog "tmp"
return $ map (a $ UnusedMaps unused unusedbad unusedtmp) $
concatMap unusedSpec params
unusedSpec :: String -> [Int]
unusedSpec spec
| "-" `isInfixOf` spec = range $ separate (== '-') spec
| otherwise = catMaybes [readish spec]
where
range (a, b) = case (readish a, readish b) of
(Just x, Just y) -> [x..y]
_ -> []
{- Start action for unused content. Finds the number in the maps, and
- calls either of 3 actions, depending on the type of unused file. -}
startUnused :: String
-> (Key -> CommandPerform)
-> (Key -> CommandPerform)
-> (Key -> CommandPerform)
-> UnusedMaps -> Int -> CommandStart
startUnused message unused badunused tmpunused maps n = search
[ (unusedMap maps, unused)
, (unusedBadMap maps, badunused)
, (unusedTmpMap maps, tmpunused)
]
where
search [] = stop
search ((m, a):rest) =
case M.lookup n m of
Nothing -> search rest
Just key -> do
showStart message (show n)
next $ a key

View file

@ -1,6 +1,6 @@
PREFIX=/usr PREFIX=/usr
IGNORE=-ignore-package monads-fd IGNORE=-ignore-package monads-fd
BASEFLAGS=-Wall $(IGNORE) -outputdir tmp -IUtility BASEFLAGS=-Wall $(IGNORE) -outputdir tmp -IUtility -DWITH_S3
GHCFLAGS=-O2 $(BASEFLAGS) GHCFLAGS=-O2 $(BASEFLAGS)
ifdef PROFILE ifdef PROFILE
@ -12,7 +12,7 @@ GHCMAKE=ghc $(GHCFLAGS) --make
bins=git-annex bins=git-annex
mans=git-annex.1 git-annex-shell.1 mans=git-annex.1 git-annex-shell.1
sources=Build/SysConfig.hs Utility/Touch.hs sources=Build/SysConfig.hs Utility/Touch.hs
clibs=Utility/diskfree.o clibs=Utility/libdiskfree.o
all=$(bins) $(mans) docs all=$(bins) $(mans) docs

View file

@ -13,6 +13,9 @@ module Messages (
metered, metered,
MeterUpdate, MeterUpdate,
showSideAction, showSideAction,
doSideAction,
doQuietSideAction,
showStoringStateAction,
showOutput, showOutput,
showLongNote, showLongNote,
showEndOk, showEndOk,
@ -37,6 +40,7 @@ import Data.Quantity
import Common import Common
import Types import Types
import Types.Messages
import Types.Key import Types.Key
import qualified Annex import qualified Annex
import qualified Messages.JSON as JSON import qualified Messages.JSON as JSON
@ -61,9 +65,9 @@ showProgress = handle q $
- The action is passed a callback to use to update the meter. -} - The action is passed a callback to use to update the meter. -}
type MeterUpdate = Integer -> IO () type MeterUpdate = Integer -> IO ()
metered :: Key -> (MeterUpdate -> Annex a) -> Annex a metered :: Key -> (MeterUpdate -> Annex a) -> Annex a
metered key a = Annex.getState Annex.output >>= go (keySize key) metered key a = withOutputType $ go (keySize key)
where where
go (Just size) Annex.NormalOutput = do go (Just size) NormalOutput = do
progress <- liftIO $ newProgress "" size progress <- liftIO $ newProgress "" size
meter <- liftIO $ newMeter progress "B" 25 (renderNums binaryOpts 1) meter <- liftIO $ newMeter progress "B" 25 (renderNums binaryOpts 1)
showOutput showOutput
@ -73,11 +77,37 @@ metered key a = Annex.getState Annex.output >>= go (keySize key)
displayMeter stdout meter displayMeter stdout meter
liftIO $ clearMeter stdout meter liftIO $ clearMeter stdout meter
return r return r
go _ _ = a (const $ return ()) go _ _ = a (const noop)
showSideAction :: String -> Annex () showSideAction :: String -> Annex ()
showSideAction s = handle q $ showSideAction m = Annex.getState Annex.output >>= go
putStrLn $ "(" ++ s ++ "...)" where
go (MessageState v StartBlock) = do
p
Annex.changeState $ \s -> s { Annex.output = MessageState v InBlock }
go (MessageState _ InBlock) = return ()
go _ = p
p = handle q $ putStrLn $ "(" ++ m ++ "...)"
showStoringStateAction :: Annex ()
showStoringStateAction = showSideAction "Recording state in git"
{- Performs an action, supressing showSideAction messages. -}
doQuietSideAction :: Annex a -> Annex a
doQuietSideAction = doSideAction' InBlock
{- Performs an action, that may call showSideAction multiple times.
- Only the first will be displayed. -}
doSideAction :: Annex a -> Annex a
doSideAction = doSideAction' StartBlock
doSideAction' :: SideActionBlock -> Annex a -> Annex a
doSideAction' b a = do
o <- Annex.getState Annex.output
set $ o { sideActionBlock = b }
set o `after` a
where
set o = Annex.changeState $ \s -> s { Annex.output = o }
showOutput :: Annex () showOutput :: Annex ()
showOutput = handle q $ showOutput = handle q $
@ -122,9 +152,9 @@ maybeShowJSON v = handle (JSON.add v) q
{- Shows a complete JSON value, only when in json mode. -} {- Shows a complete JSON value, only when in json mode. -}
showFullJSON :: JSON a => [(String, a)] -> Annex Bool showFullJSON :: JSON a => [(String, a)] -> Annex Bool
showFullJSON v = Annex.getState Annex.output >>= liftIO . go showFullJSON v = withOutputType $ liftIO . go
where where
go Annex.JSONOutput = JSON.complete v >> return True go JSONOutput = JSON.complete v >> return True
go _ = return False go _ = return False
{- Performs an action that outputs nonstandard/customized output, and {- Performs an action that outputs nonstandard/customized output, and
@ -153,14 +183,17 @@ setupConsole = do
fileEncoding stderr fileEncoding stderr
handle :: IO () -> IO () -> Annex () handle :: IO () -> IO () -> Annex ()
handle json normal = Annex.getState Annex.output >>= go handle json normal = withOutputType $ go
where where
go Annex.NormalOutput = liftIO normal go NormalOutput = liftIO normal
go Annex.QuietOutput = q go QuietOutput = q
go Annex.JSONOutput = liftIO $ flushed json go JSONOutput = liftIO $ flushed json
q :: Monad m => m () q :: Monad m => m ()
q = return () q = noop
flushed :: IO () -> IO () flushed :: IO () -> IO ()
flushed a = a >> hFlush stdout flushed a = a >> hFlush stdout
withOutputType :: (OutputType -> Annex a) -> Annex a
withOutputType a = outputType <$> Annex.getState Annex.output >>= a

View file

@ -20,6 +20,7 @@ import System.Log.Logger
import Common.Annex import Common.Annex
import qualified Annex import qualified Annex
import Types.Messages
import Limit import Limit
import Usage import Usage
@ -31,11 +32,11 @@ common =
"avoid slow operations" "avoid slow operations"
, Option ['a'] ["auto"] (NoArg (setauto True)) , Option ['a'] ["auto"] (NoArg (setauto True))
"automatic mode" "automatic mode"
, Option ['q'] ["quiet"] (NoArg (setoutput Annex.QuietOutput)) , Option ['q'] ["quiet"] (NoArg (Annex.setOutput QuietOutput))
"avoid verbose output" "avoid verbose output"
, Option ['v'] ["verbose"] (NoArg (setoutput Annex.NormalOutput)) , Option ['v'] ["verbose"] (NoArg (Annex.setOutput NormalOutput))
"allow verbose output (default)" "allow verbose output (default)"
, Option ['j'] ["json"] (NoArg (setoutput Annex.JSONOutput)) , Option ['j'] ["json"] (NoArg (Annex.setOutput JSONOutput))
"enable JSON output" "enable JSON output"
, Option ['d'] ["debug"] (NoArg setdebug) , Option ['d'] ["debug"] (NoArg setdebug)
"show debug messages" "show debug messages"
@ -46,7 +47,6 @@ common =
setforce v = Annex.changeState $ \s -> s { Annex.force = v } setforce v = Annex.changeState $ \s -> s { Annex.force = v }
setfast v = Annex.changeState $ \s -> s { Annex.fast = v } setfast v = Annex.changeState $ \s -> s { Annex.fast = v }
setauto v = Annex.changeState $ \s -> s { Annex.auto = v } setauto v = Annex.changeState $ \s -> s { Annex.auto = v }
setoutput v = Annex.changeState $ \s -> s { Annex.output = v }
setforcebackend v = Annex.changeState $ \s -> s { Annex.forcebackend = Just v } setforcebackend v = Annex.changeState $ \s -> s { Annex.forcebackend = Just v }
setdebug = liftIO $ updateGlobalLogger rootLoggerName $ setdebug = liftIO $ updateGlobalLogger rootLoggerName $
setLevel DEBUG setLevel DEBUG

View file

@ -194,7 +194,7 @@ showLocations key exclude = do
message rs us = message rs [] ++ message [] us message rs us = message rs [] ++ message [] us
showTriedRemotes :: [Remote] -> Annex () showTriedRemotes :: [Remote] -> Annex ()
showTriedRemotes [] = return () showTriedRemotes [] = noop
showTriedRemotes remotes = showTriedRemotes remotes =
showLongNote $ "Unable to access these remotes: " ++ showLongNote $ "Unable to access these remotes: " ++
join ", " (map name remotes) join ", " (map name remotes)

View file

@ -184,7 +184,7 @@ storeBupUUID u buprepo = do
onBupRemote :: Git.Repo -> (FilePath -> [CommandParam] -> IO a) -> FilePath -> [CommandParam] -> Annex a onBupRemote :: Git.Repo -> (FilePath -> [CommandParam] -> IO a) -> FilePath -> [CommandParam] -> Annex a
onBupRemote r a command params = do onBupRemote r a command params = do
let dir = shellEscape (Git.workTree r) let dir = shellEscape (Git.repoPath r)
sshparams <- sshToRepo r [Param $ sshparams <- sshToRepo r [Param $
"cd " ++ dir ++ " && " ++ unwords (command : toCommand params)] "cd " ++ dir ++ " && " ++ unwords (command : toCommand params)]
liftIO $ a "ssh" sshparams liftIO $ a "ssh" sshparams

View file

@ -10,7 +10,7 @@ module Remote.Directory (remote) where
import qualified Data.ByteString.Lazy.Char8 as L import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.ByteString.Char8 as S import qualified Data.ByteString.Char8 as S
import qualified Data.Map as M import qualified Data.Map as M
import Control.Exception (bracket) import qualified Control.Exception as E
import Common.Annex import Common.Annex
import Types.Remote import Types.Remote
@ -22,6 +22,7 @@ import Remote.Helper.Encryptable
import Crypto import Crypto
import Utility.DataUnits import Utility.DataUnits
import Data.Int import Data.Int
import Annex.Content
remote :: RemoteType remote :: RemoteType
remote = RemoteType { remote = RemoteType {
@ -125,7 +126,7 @@ store :: FilePath -> ChunkSize -> Key -> Annex Bool
store d chunksize k = do store d chunksize k = do
src <- inRepo $ gitAnnexLocation k src <- inRepo $ gitAnnexLocation k
metered k $ \meterupdate -> metered k $ \meterupdate ->
liftIO $ catchBoolIO $ storeHelper d chunksize k $ \dests -> storeHelper d chunksize k $ \dests ->
case chunksize of case chunksize of
Nothing -> do Nothing -> do
let dest = Prelude.head dests let dest = Prelude.head dests
@ -140,7 +141,7 @@ storeEncrypted :: FilePath -> ChunkSize -> (Cipher, Key) -> Key -> Annex Bool
storeEncrypted d chunksize (cipher, enck) k = do storeEncrypted d chunksize (cipher, enck) k = do
src <- inRepo $ gitAnnexLocation k src <- inRepo $ gitAnnexLocation k
metered k $ \meterupdate -> metered k $ \meterupdate ->
liftIO $ catchBoolIO $ storeHelper d chunksize enck $ \dests -> storeHelper d chunksize enck $ \dests ->
withEncryptedContent cipher (L.readFile src) $ \s -> withEncryptedContent cipher (L.readFile src) $ \s ->
case chunksize of case chunksize of
Nothing -> do Nothing -> do
@ -165,7 +166,7 @@ storeSplit' :: MeterUpdate -> Int64 -> [FilePath] -> [S.ByteString] -> [FilePath
storeSplit' _ _ [] _ _ = error "ran out of dests" storeSplit' _ _ [] _ _ = error "ran out of dests"
storeSplit' _ _ _ [] c = return $ reverse c storeSplit' _ _ _ [] c = return $ reverse c
storeSplit' meterupdate chunksize (d:dests) bs c = do storeSplit' meterupdate chunksize (d:dests) bs c = do
bs' <- bracket (openFile d WriteMode) hClose (feed chunksize bs) bs' <- E.bracket (openFile d WriteMode) hClose (feed chunksize bs)
storeSplit' meterupdate chunksize dests bs' (d:c) storeSplit' meterupdate chunksize dests bs' (d:c)
where where
feed _ [] _ = return [] feed _ [] _ = return []
@ -190,11 +191,12 @@ meteredWriteFile meterupdate dest b =
- meter after each chunk. The feeder is called to get more chunks. -} - meter after each chunk. The feeder is called to get more chunks. -}
meteredWriteFile' :: MeterUpdate -> FilePath -> s -> (s -> IO (s, [S.ByteString])) -> IO () meteredWriteFile' :: MeterUpdate -> FilePath -> s -> (s -> IO (s, [S.ByteString])) -> IO ()
meteredWriteFile' meterupdate dest startstate feeder = meteredWriteFile' meterupdate dest startstate feeder =
bracket (openFile dest WriteMode) hClose (feed startstate []) E.bracket (openFile dest WriteMode) hClose (feed startstate [])
where where
feed state [] h = do feed state [] h = do
(state', cs) <- feeder state (state', cs) <- feeder state
if null cs then return () else feed state' cs h unless (null cs) $
feed state' cs h
feed state (c:cs) h = do feed state (c:cs) h = do
S.hPut h c S.hPut h c
meterupdate $ toInteger $ S.length c meterupdate $ toInteger $ S.length c
@ -207,11 +209,26 @@ meteredWriteFile' meterupdate dest startstate feeder =
- The stored files are only put into their final place once storage is - The stored files are only put into their final place once storage is
- complete. - complete.
-} -}
storeHelper :: FilePath -> ChunkSize -> Key -> ([FilePath] -> IO [FilePath]) -> IO Bool storeHelper :: FilePath -> ChunkSize -> Key -> ([FilePath] -> IO [FilePath]) -> Annex Bool
storeHelper d chunksize key a = do storeHelper d chunksize key a = prep <&&> check <&&> go
let dir = parentDir desttemplate where
desttemplate = Prelude.head $ locations d key
dir = parentDir desttemplate
tmpdests = case chunksize of
Nothing -> [desttemplate ++ tmpprefix]
Just _ -> map (++ tmpprefix) (chunkStream desttemplate)
tmpprefix = ".tmp"
detmpprefix f = take (length f - tmpprefixlen) f
tmpprefixlen = length tmpprefix
prep = liftIO $ catchBoolIO $ do
createDirectoryIfMissing True dir createDirectoryIfMissing True dir
allowWrite dir allowWrite dir
return True
{- The size is not exactly known when encrypting the key;
- this assumes that at least the size of the key is
- needed as free space. -}
check = checkDiskSpace (Just dir) key 0
go = liftIO $ catchBoolIO $ do
stored <- a tmpdests stored <- a tmpdests
forM_ stored $ \f -> do forM_ stored $ \f -> do
let dest = detmpprefix f let dest = detmpprefix f
@ -224,14 +241,6 @@ storeHelper d chunksize key a = do
preventWrite chunkcount preventWrite chunkcount
preventWrite dir preventWrite dir
return (not $ null stored) return (not $ null stored)
where
desttemplate = Prelude.head $ locations d key
tmpdests = case chunksize of
Nothing -> [desttemplate ++ tmpprefix]
Just _ -> map (++ tmpprefix) (chunkStream desttemplate)
tmpprefix = ".tmp"
detmpprefix f = take (length f - tmpprefixlen) f
tmpprefixlen = length tmpprefix
retrieve :: FilePath -> ChunkSize -> Key -> FilePath -> Annex Bool retrieve :: FilePath -> ChunkSize -> Key -> FilePath -> Annex Bool
retrieve d chunksize k f = metered k $ \meterupdate -> retrieve d chunksize k f = metered k $ \meterupdate ->

View file

@ -94,7 +94,9 @@ tryGitConfigRead :: Git.Repo -> Annex Git.Repo
tryGitConfigRead r tryGitConfigRead r
| not $ M.null $ Git.config r = return r -- already read | not $ M.null $ Git.config r = return r -- already read
| Git.repoIsSsh r = store $ onRemote r (pipedconfig, r) "configlist" [] | Git.repoIsSsh r = store $ onRemote r (pipedconfig, r) "configlist" []
| Git.repoIsHttp r = store $ safely geturlconfig | Git.repoIsHttp r = do
headers <- getHttpHeaders
store $ safely $ geturlconfig headers
| Git.repoIsUrl r = return r | Git.repoIsUrl r = return r
| otherwise = store $ safely $ onLocal r $ do | otherwise = store $ safely $ onLocal r $ do
ensureInitialized ensureInitialized
@ -109,8 +111,8 @@ tryGitConfigRead r
pOpen ReadFromPipe cmd (toCommand params) $ pOpen ReadFromPipe cmd (toCommand params) $
Git.Config.hRead r Git.Config.hRead r
geturlconfig = do geturlconfig headers = do
s <- Url.get (Git.repoLocation r ++ "/config") s <- Url.get (Git.repoLocation r ++ "/config") headers
withTempFile "git-annex.tmp" $ \tmpfile h -> do withTempFile "git-annex.tmp" $ \tmpfile h -> do
hPutStr h s hPutStr h s
hClose h hClose h
@ -136,16 +138,16 @@ tryGitConfigRead r
-} -}
inAnnex :: Git.Repo -> Key -> Annex (Either String Bool) inAnnex :: Git.Repo -> Key -> Annex (Either String Bool)
inAnnex r key inAnnex r key
| Git.repoIsHttp r = checkhttp | Git.repoIsHttp r = checkhttp =<< getHttpHeaders
| Git.repoIsUrl r = checkremote | Git.repoIsUrl r = checkremote
| otherwise = checklocal | otherwise = checklocal
where where
checkhttp = liftIO $ go undefined $ keyUrls r key checkhttp headers = liftIO $ go undefined $ keyUrls r key
where where
go e [] = return $ Left e go e [] = return $ Left e
go _ (u:us) = do go _ (u:us) = do
res <- catchMsgIO $ res <- catchMsgIO $
Url.check u (keySize key) Url.check u headers (keySize key)
case res of case res of
Left e -> go e us Left e -> go e us
v -> return v v -> return v
@ -177,12 +179,8 @@ repoAvail r
- monad using that repository. -} - monad using that repository. -}
onLocal :: Git.Repo -> Annex a -> IO a onLocal :: Git.Repo -> Annex a -> IO a
onLocal r a = do onLocal r a = do
-- Avoid re-reading the repository's configuration if it was s <- Annex.new r
-- already read. Annex.eval s $ do
state <- if M.null $ Git.config r
then Annex.new r
else return $ Annex.newState r
Annex.eval state $ do
-- No need to update the branch; its data is not used -- No need to update the branch; its data is not used
-- for anything onLocal is used to do. -- for anything onLocal is used to do.
Annex.BranchState.disableUpdate Annex.BranchState.disableUpdate
@ -312,8 +310,9 @@ commitOnCleanup r a = go `after` a
go = Annex.addCleanup (Git.repoLocation r) cleanup go = Annex.addCleanup (Git.repoLocation r) cleanup
cleanup cleanup
| not $ Git.repoIsUrl r = liftIO $ onLocal r $ | not $ Git.repoIsUrl r = liftIO $ onLocal r $
doQuietSideAction $
Annex.Branch.commit "update" Annex.Branch.commit "update"
| otherwise = do | otherwise = void $ do
Just (shellcmd, shellparams) <- Just (shellcmd, shellparams) <-
git_annex_shell r "commit" [] git_annex_shell r "commit" []
-- Throw away stderr, since the remote may not -- Throw away stderr, since the remote may not
@ -322,6 +321,4 @@ commitOnCleanup r a = go `after` a
let cmd = shellcmd ++ " " let cmd = shellcmd ++ " "
++ unwords (map shellEscape $ toCommand shellparams) ++ unwords (map shellEscape $ toCommand shellparams)
++ ">/dev/null 2>/dev/null" ++ ">/dev/null 2>/dev/null"
_ <- liftIO $ liftIO $ boolSystem "sh" [Param "-c", Param cmd]
boolSystem "sh" [Param "-c", Param cmd]
return ()

View file

@ -14,20 +14,26 @@ import Types.Remote
import Crypto import Crypto
import qualified Annex import qualified Annex
import Config import Config
import Utility.Base64
{- Encryption setup for a remote. The user must specify whether to use {- Encryption setup for a remote. The user must specify whether to use
- an encryption key, or not encrypt. An encrypted cipher is created, or is - an encryption key, or not encrypt. An encrypted cipher is created, or is
- updated to be accessible to an additional encryption key. -} - updated to be accessible to an additional encryption key. Or the user
- could opt to use a shared cipher, which is stored unencrypted. -}
encryptionSetup :: RemoteConfig -> Annex RemoteConfig encryptionSetup :: RemoteConfig -> Annex RemoteConfig
encryptionSetup c = encryptionSetup c = case (M.lookup "encryption" c, extractCipher c) of
case (M.lookup "encryption" c, extractCipher c) of (Nothing, Nothing) -> error "Specify encryption=key or encryption=none or encryption=shared"
(Nothing, Nothing) -> error "Specify encryption=key or encryption=none"
(Just "none", Nothing) -> return c (Just "none", Nothing) -> return c
(Just "none", Just _) -> error "Cannot change encryption type of existing remote."
(Nothing, Just _) -> return c (Nothing, Just _) -> return c
(Just _, Nothing) -> use "encryption setup" $ genCipher c (Just "shared", Just (SharedCipher _)) -> return c
(Just _, Just v) -> use "encryption updated" $ updateCipher c v (Just "none", Just _) -> cannotchange
(Just "shared", Just (EncryptedCipher _ _)) -> cannotchange
(Just _, Just (SharedCipher _)) -> cannotchange
(Just "shared", Nothing) -> use "encryption setup" $ genSharedCipher
(Just keyid, Nothing) -> use "encryption setup" $ genEncryptedCipher keyid
(Just keyid, Just v) -> use "encryption updated" $ updateEncryptedCipher keyid v
where where
cannotchange = error "Cannot change encryption type of existing remote."
use m a = do use m a = do
cipher <- liftIO a cipher <- liftIO a
showNote $ m ++ " " ++ describeCipher cipher showNote $ m ++ " " ++ describeCipher cipher
@ -78,7 +84,7 @@ remoteCipher c = go $ extractCipher c
Nothing -> decrypt encipher cache Nothing -> decrypt encipher cache
decrypt encipher cache = do decrypt encipher cache = do
showNote "gpg" showNote "gpg"
cipher <- liftIO $ decryptCipher c encipher cipher <- liftIO $ decryptCipher encipher
Annex.changeState (\s -> s { Annex.ciphers = M.insert encipher cipher cache }) Annex.changeState (\s -> s { Annex.ciphers = M.insert encipher cipher cache })
return $ Just cipher return $ Just cipher
@ -88,3 +94,21 @@ cipherKey Nothing _ = return Nothing
cipherKey (Just c) k = maybe Nothing encrypt <$> remoteCipher c cipherKey (Just c) k = maybe Nothing encrypt <$> remoteCipher c
where where
encrypt ciphertext = Just (ciphertext, encryptKey ciphertext k) encrypt ciphertext = Just (ciphertext, encryptKey ciphertext k)
{- Stores an StorableCipher in a remote's configuration. -}
storeCipher :: RemoteConfig -> StorableCipher -> RemoteConfig
storeCipher c (SharedCipher t) = M.insert "cipher" (toB64 t) c
storeCipher c (EncryptedCipher t ks) =
M.insert "cipher" (toB64 t) $ M.insert "cipherkeys" (showkeys ks) c
where
showkeys (KeyIds l) = join "," l
{- Extracts an StorableCipher from a remote's configuration. -}
extractCipher :: RemoteConfig -> Maybe StorableCipher
extractCipher c =
case (M.lookup "cipher" c, M.lookup "cipherkeys" c) of
(Just t, Just ks) -> Just $ EncryptedCipher (fromB64 t) (readkeys ks)
(Just t, Nothing) -> Just $ SharedCipher (fromB64 t)
_ -> Nothing
where
readkeys = KeyIds . split ","

View file

@ -14,6 +14,7 @@ import Types.Remote
import qualified Annex import qualified Annex
import Annex.LockPool import Annex.LockPool
import Config import Config
import Annex.Perms
{- Modifies a remote's access functions to first run the {- Modifies a remote's access functions to first run the
- annex-start-command hook, and trigger annex-stop-command on shutdown. - annex-start-command hook, and trigger annex-stop-command on shutdown.
@ -45,10 +46,9 @@ runHooks r starthook stophook a = do
a a
where where
remoteid = show (uuid r) remoteid = show (uuid r)
run Nothing = return () run Nothing = noop
run (Just command) = liftIO $ do run (Just command) = void $ liftIO $
_ <- boolSystem "sh" [Param "-c", Param command] boolSystem "sh" [Param "-c", Param command]
return ()
firstrun lck = do firstrun lck = do
-- Take a shared lock; This indicates that git-annex -- Take a shared lock; This indicates that git-annex
-- is using the remote, and prevents other instances -- is using the remote, and prevents other instances
@ -75,11 +75,13 @@ runHooks r starthook stophook a = do
-- succeeds, we're the only process using this remote, -- succeeds, we're the only process using this remote,
-- so can stop it. -- so can stop it.
unlockFile lck unlockFile lck
fd <- liftIO $ openFd lck ReadWrite (Just stdFileMode) defaultFileFlags mode <- annexFileMode
fd <- liftIO $ noUmask mode $
openFd lck ReadWrite (Just mode) defaultFileFlags
v <- liftIO $ tryIO $ v <- liftIO $ tryIO $
setLock fd (WriteLock, AbsoluteSeek, 0, 0) setLock fd (WriteLock, AbsoluteSeek, 0, 0)
case v of case v of
Left _ -> return () Left _ -> noop
Right _ -> run stophook Right _ -> run stophook
liftIO $ closeFd fd liftIO $ closeFd fd

View file

@ -34,7 +34,7 @@ git_annex_shell r command params
return $ Just ("ssh", sshparams) return $ Just ("ssh", sshparams)
| otherwise = return Nothing | otherwise = return Nothing
where where
dir = Git.workTree r dir = Git.repoPath r
shellcmd = "git-annex-shell" shellcmd = "git-annex-shell"
shellopts = Param command : File dir : params shellopts = Param command : File dir : params
sshcmd uuid = unwords $ sshcmd uuid = unwords $

View file

@ -74,14 +74,14 @@ hookEnv k f = Just $ fileenv f ++ keyenv
lookupHook :: String -> String -> Annex (Maybe String) lookupHook :: String -> String -> Annex (Maybe String)
lookupHook hooktype hook =do lookupHook hooktype hook =do
command <- getConfig hookname "" command <- getConfig (annexConfig hookname) ""
if null command if null command
then do then do
warning $ "missing configuration for " ++ hookname warning $ "missing configuration for " ++ hookname
return Nothing return Nothing
else return $ Just command else return $ Just command
where where
hookname = "annex." ++ hooktype ++ "-" ++ hook ++ "-hook" hookname = hooktype ++ "-" ++ hook ++ "-hook"
runHook :: String -> String -> Key -> Maybe FilePath -> Annex Bool -> Annex Bool runHook :: String -> String -> Key -> Maybe FilePath -> Annex Bool -> Annex Bool
runHook hooktype hook k f a = maybe (return False) run =<< lookupHook hooktype hook runHook hooktype hook k f a = maybe (return False) run =<< lookupHook hooktype hook

View file

@ -1,3 +1,5 @@
{-# LANGUAGE CPP #-}
{- git-annex remote list {- git-annex remote list
- -
- Copyright 2011 Joey Hess <joey@kitenet.net> - Copyright 2011 Joey Hess <joey@kitenet.net>
@ -18,7 +20,9 @@ import Config
import Remote.Helper.Hooks import Remote.Helper.Hooks
import qualified Remote.Git import qualified Remote.Git
#ifdef WITH_S3
import qualified Remote.S3 import qualified Remote.S3
#endif
import qualified Remote.Bup import qualified Remote.Bup
import qualified Remote.Directory import qualified Remote.Directory
import qualified Remote.Rsync import qualified Remote.Rsync
@ -28,7 +32,9 @@ import qualified Remote.Hook
remoteTypes :: [RemoteType] remoteTypes :: [RemoteType]
remoteTypes = remoteTypes =
[ Remote.Git.remote [ Remote.Git.remote
#ifdef WITH_S3
, Remote.S3.remote , Remote.S3.remote
#endif
, Remote.Bup.remote , Remote.Bup.remote
, Remote.Directory.remote , Remote.Directory.remote
, Remote.Rsync.remote , Remote.Rsync.remote

View file

@ -22,9 +22,10 @@ import Utility.RsyncFile
type RsyncUrl = String type RsyncUrl = String
data RsyncOpts = RsyncOpts { data RsyncOpts = RsyncOpts
rsyncUrl :: RsyncUrl, { rsyncUrl :: RsyncUrl
rsyncOptions :: [CommandParam] , rsyncOptions :: [CommandParam]
, rsyncShellEscape :: Bool
} }
remote :: RemoteType remote :: RemoteType
@ -37,7 +38,7 @@ remote = RemoteType {
gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote gen :: Git.Repo -> UUID -> Maybe RemoteConfig -> Annex Remote
gen r u c = do gen r u c = do
o <- genRsyncOpts r o <- genRsyncOpts r c
cst <- remoteCost r expensiveRemoteCost cst <- remoteCost r expensiveRemoteCost
return $ encryptableRemote c return $ encryptableRemote c
(storeEncrypted o) (storeEncrypted o)
@ -58,11 +59,13 @@ gen r u c = do
remotetype = remote remotetype = remote
} }
genRsyncOpts :: Git.Repo -> Annex RsyncOpts genRsyncOpts :: Git.Repo -> Maybe RemoteConfig -> Annex RsyncOpts
genRsyncOpts r = do genRsyncOpts r c = do
url <- getRemoteConfig r "rsyncurl" (error "missing rsyncurl") url <- getRemoteConfig r "rsyncurl" (error "missing rsyncurl")
opts <- getRemoteConfig r "rsync-options" "" opts <- map Param . filter safe . words
return $ RsyncOpts url $ map Param $ filter safe $ words opts <$> getRemoteConfig r "rsync-options" ""
let escape = maybe True (\m -> M.lookup "shellescape" m /= Just "no") c
return $ RsyncOpts url opts escape
where where
safe o safe o
-- Don't allow user to pass --delete to rsync; -- Don't allow user to pass --delete to rsync;
@ -86,7 +89,7 @@ rsyncSetup u c = do
rsyncEscape :: RsyncOpts -> String -> String rsyncEscape :: RsyncOpts -> String -> String
rsyncEscape o s rsyncEscape o s
| rsyncUrlIsShell (rsyncUrl o) = shellEscape s | rsyncShellEscape o && rsyncUrlIsShell (rsyncUrl o) = shellEscape s
| otherwise = s | otherwise = s
rsyncUrls :: RsyncOpts -> Key -> [String] rsyncUrls :: RsyncOpts -> Key -> [String]

View file

@ -93,7 +93,7 @@ s3Setup u c = handlehost $ M.lookup "host" c
archiveorg = do archiveorg = do
showNote "Internet Archive mode" showNote "Internet Archive mode"
maybe (error "specify bucket=") (const $ return ()) $ maybe (error "specify bucket=") (const noop) $
M.lookup "bucket" archiveconfig M.lookup "bucket" archiveconfig
use archiveconfig use archiveconfig
where where
@ -237,13 +237,13 @@ genBucket c = do
showAction "checking bucket" showAction "checking bucket"
loc <- liftIO $ getBucketLocation conn bucket loc <- liftIO $ getBucketLocation conn bucket
case loc of case loc of
Right _ -> return () Right _ -> noop
Left err@(NetworkError _) -> s3Error err Left err@(NetworkError _) -> s3Error err
Left (AWSError _ _) -> do Left (AWSError _ _) -> do
showAction $ "creating bucket in " ++ datacenter showAction $ "creating bucket in " ++ datacenter
res <- liftIO $ createBucketIn conn bucket datacenter res <- liftIO $ createBucketIn conn bucket datacenter
case res of case res of
Right _ -> return () Right _ -> noop
Left err -> s3Error err Left err -> s3Error err
where where
bucket = fromJust $ M.lookup "bucket" c bucket = fromJust $ M.lookup "bucket" c

View file

@ -83,4 +83,5 @@ checkKey key = do
checkKey' :: Key -> [URLString] -> Annex Bool checkKey' :: Key -> [URLString] -> Annex Bool
checkKey' key us = untilTrue us $ \u -> do checkKey' key us = untilTrue us $ \u -> do
showAction $ "checking " ++ u showAction $ "checking " ++ u
liftIO $ Url.check u (keySize key) headers <- getHttpHeaders
liftIO $ Url.check u headers (keySize key)

10
Seek.hs
View file

@ -4,7 +4,7 @@
- the values a user passes to a command, and prepare actions operating - the values a user passes to a command, and prepare actions operating
- on them. - on them.
- -
- Copyright 2010-2011 Joey Hess <joey@kitenet.net> - Copyright 2010-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -41,6 +41,14 @@ withFilesNotInGit a params = do
g <- gitRepo g <- gitRepo
liftIO $ (\p -> LsFiles.notInRepo force p g) l liftIO $ (\p -> LsFiles.notInRepo force p g) l
withPathContents :: ((FilePath, FilePath) -> CommandStart) -> CommandSeek
withPathContents a params = map a . concat <$> liftIO (mapM get params)
where
get p = ifM (isDirectory <$> getFileStatus p)
( map (\f -> (f, makeRelative p f)) <$> dirContentsRecursive p
, return [(p, takeFileName p)]
)
withWords :: ([String] -> CommandStart) -> CommandSeek withWords :: ([String] -> CommandStart) -> CommandSeek
withWords a params = return [a params] withWords a params = return [a params]

View file

@ -1,12 +1,27 @@
{- cabal setup file -} {- cabal setup file -}
import Distribution.Simple import Distribution.Simple
import Distribution.Simple.LocalBuildInfo
import Distribution.Simple.Setup
import System.Cmd import System.Cmd
import System.FilePath
import qualified Build.Configure as Configure import qualified Build.Configure as Configure
main = defaultMainWithHooks simpleUserHooks { preConf = configure } main = defaultMainWithHooks simpleUserHooks
{ preConf = configure
, instHook = install
}
configure _ _ = do configure _ _ = do
Configure.run Configure.tests Configure.run Configure.tests
return (Nothing, []) return (Nothing, [])
install pkg_descr lbi userhooks flags = do
r <- (instHook simpleUserHooks) pkg_descr lbi userhooks flags
_ <- rawSystem "ln" ["-sf", "git-annex",
bindir installDirs </> "git-annex-shell"]
return r
where
installDirs = absoluteInstallDirs pkg_descr lbi $
fromFlag (copyDest defaultCopyFlags)

View file

@ -1,13 +1,13 @@
{- git-annex crypto types {- git-annex crypto types
- -
- Copyright 2011 Joey Hess <joey@kitenet.net> - Copyright 2011-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Types.Crypto ( module Types.Crypto (
Cipher(..), Cipher(..),
EncryptedCipher(..), StorableCipher(..),
KeyIds(..), KeyIds(..),
) where ) where
@ -16,5 +16,5 @@ import Utility.Gpg (KeyIds(..))
-- XXX ideally, this would be a locked memory region -- XXX ideally, this would be a locked memory region
newtype Cipher = Cipher String newtype Cipher = Cipher String
data EncryptedCipher = EncryptedCipher String KeyIds data StorableCipher = EncryptedCipher String KeyIds | SharedCipher String
deriving (Ord, Eq) deriving (Ord, Eq)

20
Types/Messages.hs Normal file
View file

@ -0,0 +1,20 @@
{- git-annex Messages data types
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Types.Messages where
data OutputType = NormalOutput | QuietOutput | JSONOutput
data SideActionBlock = NoBlock | StartBlock | InBlock
data MessageState = MessageState
{ outputType :: OutputType
, sideActionBlock :: SideActionBlock
}
defaultMessageState :: MessageState
defaultMessageState = MessageState NormalOutput NoBlock

View file

@ -59,7 +59,7 @@ upgrade = do
updateSymlinks updateSymlinks
moveLocationLogs moveLocationLogs
Annex.Queue.flush True Annex.Queue.flush
setVersion setVersion
) )
@ -82,14 +82,14 @@ moveContent = do
updateSymlinks :: Annex () updateSymlinks :: Annex ()
updateSymlinks = do updateSymlinks = do
showAction "updating symlinks" showAction "updating symlinks"
top <- fromRepo Git.workTree top <- fromRepo Git.repoPath
files <- inRepo $ LsFiles.inRepo [top] files <- inRepo $ LsFiles.inRepo [top]
forM_ files fixlink forM_ files fixlink
where where
fixlink f = do fixlink f = do
r <- lookupFile1 f r <- lookupFile1 f
case r of case r of
Nothing -> return () Nothing -> noop
Just (k, _) -> do Just (k, _) -> do
link <- calcGitLink f k link <- calcGitLink f k
liftIO $ removeFile f liftIO $ removeFile f
@ -236,4 +236,4 @@ stateDir :: FilePath
stateDir = addTrailingPathSeparator ".git-annex" stateDir = addTrailingPathSeparator ".git-annex"
gitStateDir :: Git.Repo -> FilePath gitStateDir :: Git.Repo -> FilePath
gitStateDir repo = addTrailingPathSeparator $ Git.workTree repo </> stateDir gitStateDir repo = addTrailingPathSeparator $ Git.repoPath repo </> stateDir

View file

@ -134,4 +134,4 @@ gitAttributesUnWrite repo = do
stateDir :: FilePath stateDir :: FilePath
stateDir = addTrailingPathSeparator ".git-annex" stateDir = addTrailingPathSeparator ".git-annex"
gitStateDir :: Git.Repo -> FilePath gitStateDir :: Git.Repo -> FilePath
gitStateDir repo = addTrailingPathSeparator $ Git.workTree repo </> stateDir gitStateDir repo = addTrailingPathSeparator $ Git.repoPath repo </> stateDir

View file

@ -61,6 +61,8 @@ paramUrl :: String
paramUrl = "URL" paramUrl = "URL"
paramNumber :: String paramNumber :: String
paramNumber = "NUMBER" paramNumber = "NUMBER"
paramNumRange :: String
paramNumRange = "NUM|RANGE"
paramRemote :: String paramRemote :: String
paramRemote = "REMOTE" paramRemote = "REMOTE"
paramGlob :: String paramGlob :: String

View file

@ -1,16 +1,13 @@
{- git-annex file copying {- git-annex file copying
- -
- Copyright 2010 Joey Hess <joey@kitenet.net> - Copyright 2010,2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Utility.CopyFile (copyFileExternal) where module Utility.CopyFile (copyFileExternal) where
import System.Directory (doesFileExist, removeFile) import Common
import Control.Monad.IfElse
import Utility.SafeCommand
import qualified Build.SysConfig as SysConfig import qualified Build.SysConfig as SysConfig
{- The cp command is used, because I hate reinventing the wheel, {- The cp command is used, because I hate reinventing the wheel,
@ -19,10 +16,10 @@ copyFileExternal :: FilePath -> FilePath -> IO Bool
copyFileExternal src dest = do copyFileExternal src dest = do
whenM (doesFileExist dest) $ whenM (doesFileExist dest) $
removeFile dest removeFile dest
boolSystem "cp" [params, File src, File dest] boolSystem "cp" $ params ++ [File src, File dest]
where where
params params = map snd $ filter fst
| SysConfig.cp_reflink_auto = Params "--reflink=auto" [ (SysConfig.cp_reflink_auto, Param "--reflink=auto")
| SysConfig.cp_a = Params "-a" , (SysConfig.cp_a, Param "-a")
| SysConfig.cp_p = Params "-p" , (SysConfig.cp_p && not SysConfig.cp_a, Param "-p")
| otherwise = Params "" ]

View file

@ -15,26 +15,54 @@ import Control.Monad
import Control.Monad.IfElse import Control.Monad.IfElse
import System.FilePath import System.FilePath
import Control.Applicative import Control.Applicative
import Control.Exception (bracket_)
import System.Posix.Directory
import System.IO.Unsafe (unsafeInterleaveIO)
import Utility.SafeCommand import Utility.SafeCommand
import Utility.TempFile import Utility.TempFile
import Utility.Exception import Utility.Exception
import Utility.Monad
import Utility.Path
dirCruft :: FilePath -> Bool
dirCruft "." = True
dirCruft ".." = True
dirCruft _ = False
{- Lists the contents of a directory. {- Lists the contents of a directory.
- Unlike getDirectoryContents, paths are not relative to the directory. -} - Unlike getDirectoryContents, paths are not relative to the directory. -}
dirContents :: FilePath -> IO [FilePath] dirContents :: FilePath -> IO [FilePath]
dirContents d = map (d </>) . filter notcruft <$> getDirectoryContents d dirContents d = map (d </>) . filter (not . dirCruft) <$> getDirectoryContents d
{- Gets contents of directory, and then its subdirectories, recursively,
- and lazily. -}
dirContentsRecursive :: FilePath -> IO [FilePath]
dirContentsRecursive topdir = dirContentsRecursive' topdir [""]
dirContentsRecursive' :: FilePath -> [FilePath] -> IO [FilePath]
dirContentsRecursive' _ [] = return []
dirContentsRecursive' topdir (dir:dirs) = unsafeInterleaveIO $ do
(files, dirs') <- collect [] [] =<< dirContents (topdir </> dir)
files' <- dirContentsRecursive' topdir (dirs' ++ dirs)
return (files ++ files')
where where
notcruft "." = False collect files dirs' [] = return (reverse files, reverse dirs')
notcruft ".." = False collect files dirs' (entry:entries)
notcruft _ = True | dirCruft entry = collect files dirs' entries
| otherwise = do
let dirEntry = dir </> entry
ifM (doesDirectoryExist $ topdir </> dirEntry)
( collect files (dirEntry:dirs') entries
, collect (dirEntry:files) dirs' entries
)
{- Moves one filename to another. {- Moves one filename to another.
- First tries a rename, but falls back to moving across devices if needed. -} - First tries a rename, but falls back to moving across devices if needed. -}
moveFile :: FilePath -> FilePath -> IO () moveFile :: FilePath -> FilePath -> IO ()
moveFile src dest = tryIO (rename src dest) >>= onrename moveFile src dest = tryIO (rename src dest) >>= onrename
where where
onrename (Right _) = return () onrename (Right _) = noop
onrename (Left e) onrename (Left e)
| isPermissionError e = rethrow | isPermissionError e = rethrow
| isDoesNotExistError e = rethrow | isDoesNotExistError e = rethrow
@ -59,3 +87,14 @@ moveFile src dest = tryIO (rename src dest) >>= onrename
case r of case r of
(Left _) -> return False (Left _) -> return False
(Right s) -> return $ isDirectory s (Right s) -> return $ isDirectory s
{- Runs an action in another directory. -}
bracketCd :: FilePath -> IO a -> IO a
bracketCd dir a = go =<< getCurrentDirectory
where
go cwd
| dirContains dir cwd = a
| otherwise = bracket_
(changeWorkingDirectory dir)
(changeWorkingDirectory cwd)
a

View file

@ -15,10 +15,10 @@ import Foreign.C.Types
import Foreign.C.String import Foreign.C.String
import Foreign.C.Error import Foreign.C.Error
foreign import ccall unsafe "diskfree.h diskfree" c_diskfree foreign import ccall unsafe "libdiskfree.h diskfree" c_diskfree
:: CString -> IO CULLong :: CString -> IO CULLong
getDiskFree :: String -> IO (Maybe Integer) getDiskFree :: FilePath -> IO (Maybe Integer)
getDiskFree path = withFilePath path $ \c_path -> do getDiskFree path = withFilePath path $ \c_path -> do
free <- c_diskfree c_path free <- c_diskfree c_path
ifM (safeErrno <$> getErrno) ifM (safeErrno <$> getErrno)

View file

@ -1,35 +1,67 @@
{- File mode utilities. {- File mode utilities.
- -
- Copyright 2010 Joey Hess <joey@kitenet.net> - Copyright 2010-2012 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Utility.FileMode where module Utility.FileMode where
import System.Posix.Files import Common
import Control.Exception (bracket)
import System.Posix.Types import System.Posix.Types
import Foreign (complement) import Foreign (complement)
{- Removes a FileMode from a file. {- Applies a conversion function to a file's mode. -}
- For example, call with otherWriteMode to chmod o-w -} modifyFileMode :: FilePath -> (FileMode -> FileMode) -> IO ()
unsetFileMode :: FilePath -> FileMode -> IO () modifyFileMode f convert = void $ modifyFileMode' f convert
unsetFileMode f m = do modifyFileMode' :: FilePath -> (FileMode -> FileMode) -> IO FileMode
modifyFileMode' f convert = do
s <- getFileStatus f s <- getFileStatus f
setFileMode f $ fileMode s `intersectFileModes` complement m let old = fileMode s
let new = convert old
when (new /= old) $
setFileMode f new
return old
{- Adds the specified FileModes to the input mode, leaving the rest
- unchanged. -}
addModes :: [FileMode] -> FileMode -> FileMode
addModes ms m = combineModes (m:ms)
{- Removes the specified FileModes from the input mode. -}
removeModes :: [FileMode] -> FileMode -> FileMode
removeModes ms m = m `intersectFileModes` complement (combineModes ms)
{- Runs an action after changing a file's mode, then restores the old mode. -}
withModifiedFileMode :: FilePath -> (FileMode -> FileMode) -> IO a -> IO a
withModifiedFileMode file convert a = bracket setup cleanup go
where
setup = modifyFileMode' file convert
cleanup oldmode = modifyFileMode file (const oldmode)
go _ = a
writeModes :: [FileMode]
writeModes = [ownerWriteMode, groupWriteMode, otherWriteMode]
readModes :: [FileMode]
readModes = [ownerReadMode, groupReadMode, otherReadMode]
{- Removes the write bits from a file. -} {- Removes the write bits from a file. -}
preventWrite :: FilePath -> IO () preventWrite :: FilePath -> IO ()
preventWrite f = unsetFileMode f writebits preventWrite f = modifyFileMode f $ removeModes writeModes
where
writebits = foldl unionFileModes ownerWriteMode
[groupWriteMode, otherWriteMode]
{- Turns a file's write bit back on. -} {- Turns a file's owner write bit back on. -}
allowWrite :: FilePath -> IO () allowWrite :: FilePath -> IO ()
allowWrite f = do allowWrite f = modifyFileMode f $ addModes [ownerWriteMode]
s <- getFileStatus f
setFileMode f $ fileMode s `unionFileModes` ownerWriteMode {- Allows owner and group to read and write to a file. -}
groupWriteRead :: FilePath -> IO ()
groupWriteRead f = modifyFileMode f $ addModes
[ ownerWriteMode, groupWriteMode
, ownerReadMode, groupReadMode
]
{- Checks if a file mode indicates it's a symlink. -} {- Checks if a file mode indicates it's a symlink. -}
isSymLink :: FileMode -> Bool isSymLink :: FileMode -> Bool
@ -37,7 +69,22 @@ isSymLink mode = symbolicLinkMode `intersectFileModes` mode == symbolicLinkMode
{- Checks if a file has any executable bits set. -} {- Checks if a file has any executable bits set. -}
isExecutable :: FileMode -> Bool isExecutable :: FileMode -> Bool
isExecutable mode = ebits `intersectFileModes` mode /= 0 isExecutable mode = combineModes ebits `intersectFileModes` mode /= 0
where where
ebits = ownerExecuteMode `unionFileModes` ebits = [ownerExecuteMode, groupExecuteMode, otherExecuteMode]
groupExecuteMode `unionFileModes` otherExecuteMode
{- Runs an action without that pesky umask influencing it, unless the
- passed FileMode is the standard one. -}
noUmask :: FileMode -> IO a -> IO a
noUmask mode a
| mode == stdFileMode = a
| otherwise = bracket setup cleanup go
where
setup = setFileCreationMask nullFileMode
cleanup = setFileCreationMask
go _ = a
combineModes :: [FileMode] -> FileMode
combineModes [] = undefined
combineModes [m] = m
combineModes (m:ms) = foldl unionFileModes m ms

View file

@ -94,7 +94,18 @@ findPubKeys for = KeyIds . parse <$> readStrict params
pubKey = isPrefixOf "pub:" pubKey = isPrefixOf "pub:"
keyIdField s = split ":" s !! 4 keyIdField s = split ":" s !! 4
{- Creates a block of high-quality random data suitable to use as a cipher.
- It is armored, to avoid newlines, since gpg only reads ciphers up to the
- first newline. -}
genRandom :: Int -> IO String
genRandom size = readStrict
[ Params "--gen-random --armor"
, Param $ show randomquality
, Param $ show size
]
where
-- 1 is /dev/urandom; 2 is /dev/random
randomquality = 1 :: Int
{- A test key. This is provided pre-generated since generating a new gpg {- A test key. This is provided pre-generated since generating a new gpg
- key is too much work (requires too much entropy) for a test suite to - key is too much work (requires too much entropy) for a test suite to

View file

@ -53,11 +53,10 @@ watchDir i test add del dir = watchDir' False i test add del dir
watchDir' :: Bool -> INotify -> (FilePath -> Bool) -> Maybe (FilePath -> IO ()) -> Maybe (FilePath -> IO ()) -> FilePath -> IO () watchDir' :: Bool -> INotify -> (FilePath -> Bool) -> Maybe (FilePath -> IO ()) -> Maybe (FilePath -> IO ()) -> FilePath -> IO ()
watchDir' scan i test add del dir = do watchDir' scan i test add del dir = do
if test dir if test dir
then do then void $ do
_ <- addWatch i watchevents dir go _ <- addWatch i watchevents dir go
_ <- mapM walk =<< dirContents dir mapM walk =<< dirContents dir
return () else noop
else return ()
where where
watchevents watchevents
| isJust add && isJust del = | isJust add && isJust del =
@ -69,19 +68,19 @@ watchDir' scan i test add del dir = do
recurse = watchDir' scan i test add del recurse = watchDir' scan i test add del
walk f = ifM (catchBoolIO $ Files.isDirectory <$> getFileStatus f) walk f = ifM (catchBoolIO $ Files.isDirectory <$> getFileStatus f)
( recurse f ( recurse f
, if scan && isJust add then fromJust add f else return () , when (scan && isJust add) $ fromJust add f
) )
go (Created { isDirectory = False }) = return () go (Created { isDirectory = False }) = noop
go (Created { filePath = subdir }) = Just recurse <@> subdir go (Created { filePath = subdir }) = Just recurse <@> subdir
go (Closed { maybeFilePath = Just f }) = add <@> f go (Closed { maybeFilePath = Just f }) = add <@> f
go (MovedIn { isDirectory = False, filePath = f }) = add <@> f go (MovedIn { isDirectory = False, filePath = f }) = add <@> f
go (MovedOut { isDirectory = False, filePath = f }) = del <@> f go (MovedOut { isDirectory = False, filePath = f }) = del <@> f
go (Deleted { isDirectory = False, filePath = f }) = del <@> f go (Deleted { isDirectory = False, filePath = f }) = del <@> f
go _ = return () go _ = noop
Just a <@> f = a $ dir </> f Just a <@> f = a $ dir </> f
Nothing <@> _ = return () Nothing <@> _ = noop
{- Pauses the main thread, letting children run until program termination. -} {- Pauses the main thread, letting children run until program termination. -}
waitForTermination :: IO () waitForTermination :: IO ()
@ -92,6 +91,5 @@ waitForTermination = do
check keyboardSignal mv check keyboardSignal mv
takeMVar mv takeMVar mv
where where
check sig mv = do check sig mv = void $
_ <- installHandler sig (CatchOnce $ putMVar mv ()) Nothing installHandler sig (CatchOnce $ putMVar mv ()) Nothing
return ()

View file

@ -49,3 +49,7 @@ observe observer a = do
{- b `after` a runs first a, then b, and returns the value of a -} {- b `after` a runs first a, then b, and returns the value of a -}
after :: Monad m => m b -> m a -> m a after :: Monad m => m b -> m a -> m a
after = observe . const after = observe . const
{- do nothing -}
noop :: Monad m => m ()
noop = return ()

38
Utility/Percentage.hs Normal file
View file

@ -0,0 +1,38 @@
{- percentages
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Utility.Percentage (
Percentage,
percentage,
showPercentage
) where
import Data.Ratio
newtype Percentage = Percentage (Ratio Integer)
instance Show Percentage where
show = showPercentage 0
{- Normally the big number comes first. But 110% is allowed if desired. :) -}
percentage :: Integer -> Integer -> Percentage
percentage 0 _ = Percentage 0
percentage full have = Percentage $ have * 100 % full
{- Pretty-print a Percentage, with a specified level of precision. -}
showPercentage :: Int -> Percentage -> String
showPercentage precision (Percentage p)
| precision == 0 || remainder == 0 = go $ show int
| otherwise = go $ show int ++ "." ++ strip0s (show remainder)
where
go v = v ++ "%"
int :: Integer
(int, frac) = properFraction (fromRational p)
remainder = floor (frac * multiplier) :: Integer
strip0s = reverse . dropWhile (== '0') . reverse
multiplier :: Float
multiplier = 10 ** (fromIntegral precision)

View file

@ -58,7 +58,7 @@ rsyncUrlIsShell s
| "rsync://" `isPrefixOf` s = False | "rsync://" `isPrefixOf` s = False
| otherwise = go s | otherwise = go s
where where
-- host:dir is rsync protocol, while host:dir is ssh/rsh -- host::dir is rsync protocol, while host:dir is ssh/rsh
go [] = False go [] = False
go (c:cs) go (c:cs)
| c == '/' = False -- got to directory with no colon | c == '/' = False -- got to directory with no colon

View file

@ -106,9 +106,8 @@ touchBoth file atime mtime follow =
withFilePath file $ \f -> do withFilePath file $ \f -> do
pokeArray ptr [atime, mtime] pokeArray ptr [atime, mtime]
r <- syscall f ptr r <- syscall f ptr
if (r /= 0) when (r /= 0) $
then throwErrno "touchBoth" throwErrno "touchBoth"
else return ()
where where
syscall = if follow syscall = if follow
then c_lutimes then c_lutimes

View file

@ -17,13 +17,16 @@ import Common
import qualified Network.Browser as Browser import qualified Network.Browser as Browser
import Network.HTTP import Network.HTTP
import Network.URI import Network.URI
import Data.Either
type URLString = String type URLString = String
type Headers = [String]
{- Checks that an url exists and could be successfully downloaded, {- Checks that an url exists and could be successfully downloaded,
- also checking that its size, if available, matches a specified size. -} - also checking that its size, if available, matches a specified size. -}
check :: URLString -> Maybe Integer -> IO Bool check :: URLString -> Headers -> Maybe Integer -> IO Bool
check url expected_size = handle <$> exists url check url headers expected_size = handle <$> exists url headers
where where
handle (False, _) = False handle (False, _) = False
handle (True, Nothing) = True handle (True, Nothing) = True
@ -31,12 +34,12 @@ check url expected_size = handle <$> exists url
{- Checks that an url exists and could be successfully downloaded, {- Checks that an url exists and could be successfully downloaded,
- also returning its size if available. -} - also returning its size if available. -}
exists :: URLString -> IO (Bool, Maybe Integer) exists :: URLString -> Headers -> IO (Bool, Maybe Integer)
exists url = exists url headers =
case parseURI url of case parseURI url of
Nothing -> return (False, Nothing) Nothing -> return (False, Nothing)
Just u -> do Just u -> do
r <- request u HEAD r <- request u headers HEAD
case rspCode r of case rspCode r of
(2,_,_) -> return (True, size r) (2,_,_) -> return (True, size r)
_ -> return (False, Nothing) _ -> return (False, Nothing)
@ -50,26 +53,27 @@ exists url =
- would not be appropriate to test at configure time and build support - would not be appropriate to test at configure time and build support
- for only one in. - for only one in.
-} -}
download :: URLString -> [CommandParam] -> FilePath -> IO Bool download :: URLString -> Headers -> [CommandParam] -> FilePath -> IO Bool
download url options file = ifM (inPath "wget") (wget , curl) download url headers options file = ifM (inPath "wget") (wget , curl)
where where
wget = go "wget" [Params "-c -O"] headerparams = map (\h -> Param $ "--header=" ++ h) headers
wget = go "wget" $ headerparams ++ [Params "-c -O"]
{- Uses the -# progress display, because the normal {- Uses the -# progress display, because the normal
- one is very confusing when resuming, showing - one is very confusing when resuming, showing
- the remainder to download as the whole file, - the remainder to download as the whole file,
- and not indicating how much percent was - and not indicating how much percent was
- downloaded before the resume. -} - downloaded before the resume. -}
curl = go "curl" [Params "-L -C - -# -o"] curl = go "curl" $ headerparams ++ [Params "-L -C - -# -o"]
go cmd opts = boolSystem cmd $ go cmd opts = boolSystem cmd $
options++opts++[File file, File url] options++opts++[File file, File url]
{- Downloads a small file. -} {- Downloads a small file. -}
get :: URLString -> IO String get :: URLString -> Headers -> IO String
get url = get url headers =
case parseURI url of case parseURI url of
Nothing -> error "url parse error" Nothing -> error "url parse error"
Just u -> do Just u -> do
r <- request u GET r <- request u headers GET
case rspCode r of case rspCode r of
(2,_,_) -> return $ rspBody r (2,_,_) -> return $ rspBody r
_ -> error $ rspReason r _ -> error $ rspReason r
@ -81,8 +85,8 @@ get url =
- This does its own redirect following because Browser's is buggy for HEAD - This does its own redirect following because Browser's is buggy for HEAD
- requests. - requests.
-} -}
request :: URI -> RequestMethod -> IO (Response String) request :: URI -> Headers -> RequestMethod -> IO (Response String)
request url requesttype = go 5 url request url headers requesttype = go 5 url
where where
go :: Int -> URI -> IO (Response String) go :: Int -> URI -> IO (Response String)
go 0 _ = error "Too many redirects " go 0 _ = error "Too many redirects "
@ -91,11 +95,12 @@ request url requesttype = go 5 url
Browser.setErrHandler ignore Browser.setErrHandler ignore
Browser.setOutHandler ignore Browser.setOutHandler ignore
Browser.setAllowRedirects False Browser.setAllowRedirects False
snd <$> Browser.request (mkRequest requesttype u :: Request_String) let req = mkRequest requesttype u :: Request_String
snd <$> Browser.request (addheaders req)
case rspCode rsp of case rspCode rsp of
(3,0,x) | x /= 5 -> redir (n - 1) u rsp (3,0,x) | x /= 5 -> redir (n - 1) u rsp
_ -> return rsp _ -> return rsp
ignore = const $ return () ignore = const noop
redir n u rsp = case retrieveHeaders HdrLocation rsp of redir n u rsp = case retrieveHeaders HdrLocation rsp of
[] -> return rsp [] -> return rsp
(Header _ newu:_) -> (Header _ newu:_) ->
@ -104,3 +109,5 @@ request url requesttype = go 5 url
Just newURI -> go n newURI_abs Just newURI -> go n newURI_abs
where where
newURI_abs = fromMaybe newURI (newURI `relativeTo` u) newURI_abs = fromMaybe newURI (newURI `relativeTo` u)
addheaders req = setHeaders req (rqHeaders req ++ userheaders)
userheaders = rights $ map parseHeader headers

View file

@ -58,9 +58,10 @@ unsigned long long int diskfree(const char *path) {
unsigned long long int available, blocksize; unsigned long long int available, blocksize;
struct STATSTRUCT buf; struct STATSTRUCT buf;
errno = 0;
if (STATCALL(path, &buf) != 0) if (STATCALL(path, &buf) != 0)
return 0; /* errno is set */ return 0; /* errno is set */
else
errno = 0;
available = buf.f_bavail; available = buf.f_bavail;
blocksize = buf.f_bsize; blocksize = buf.f_bsize;

61
debian/changelog vendored
View file

@ -1,4 +1,58 @@
git-annex (3.20120407) UNRELEASED; urgency=low git-annex (3.20120523) UNRELEASED; urgency=low
* sync: Show a nicer message if a user tries to sync to a special remote.
* lock: Reset unlocked file to index, rather than to branch head.
* import: New subcommand, pulls files from a directory outside the annex
and adds them.
* Fix display of warning message when encountering a file that uses an
unsupported backend.
* Require that the SHA256 backend can be used when building, since it's the
default.
-- Joey Hess <joeyh@debian.org> Sun, 27 May 2012 20:55:29 -0400
git-annex (3.20120522) unstable; urgency=low
* Pass -a to cp even when it supports --reflink=auto, to preserve
permissions.
* Clean up handling of git directory and git worktree.
* Add support for core.worktree, and fix support for GIT_WORK_TREE and
GIT_DIR.
-- Joey Hess <joeyh@debian.org> Tue, 22 May 2012 11:16:13 -0400
git-annex (3.20120511) unstable; urgency=low
* Rsync special remotes can be configured with shellescape=no
to avoid shell quoting that is normally done when using rsync over ssh.
This is known to be needed for certian rsync hosting providers
(specificially hidrive.strato.com) that use rsync over ssh but do not
pass it through the shell.
* dropunused: Allow specifying ranges to drop.
* addunused: New command, the opposite of dropunused, it relinks unused
content into the git repository.
* Fix use of several config settings: annex.ssh-options,
annex.rsync-options, annex.bup-split-options. (And adjust types to avoid
the bugs that broke several config settings.)
-- Joey Hess <joeyh@debian.org> Fri, 11 May 2012 12:29:30 -0400
git-annex (3.20120430) unstable; urgency=low
* Fix use of annex.diskreserve config setting.
* Directory special remotes now check annex.diskreserve.
* Support git's core.sharedRepository configuration.
* Add annex.http-headers and annex.http-headers-command config
settings, to allow custom headers to be sent with all HTTP requests.
(Requested by the Internet Archive)
* uninit: Clear annex.uuid from .git/config. Closes: #670639
* Added shared cipher mode to encryptable special remotes. This option
avoids gpg key distribution, at the expense of flexability, and with
the requirement that all clones of the git repository be equally trusted.
-- Joey Hess <joeyh@debian.org> Mon, 30 Apr 2012 13:16:10 -0400
git-annex (3.20120418) unstable; urgency=low
* bugfix: Adding a dotfile also caused all non-dotfiles to be added. * bugfix: Adding a dotfile also caused all non-dotfiles to be added.
* bup: Properly handle key names with spaces or other things that are * bup: Properly handle key names with spaces or other things that are
@ -7,8 +61,11 @@ git-annex (3.20120407) UNRELEASED; urgency=low
configuration setting, doing fuzzy matching using the restricted configuration setting, doing fuzzy matching using the restricted
Damerau-Levenshtein edit distance, just as git does. This adds a build Damerau-Levenshtein edit distance, just as git does. This adds a build
dependency on the haskell edit-distance library. dependency on the haskell edit-distance library.
* Renamed diskfree.c to avoid OSX case insensativity bug.
* cabal now installs git-annex-shell as a symlink to git-annex.
* cabal file now autodetects whether S3 support is available.
-- Joey Hess <joeyh@debian.org> Sun, 08 Apr 2012 12:23:42 -0400 -- Joey Hess <joeyh@debian.org> Wed, 18 Apr 2012 12:11:32 -0400
git-annex (3.20120406) unstable; urgency=low git-annex (3.20120406) unstable; urgency=low

6
debian/copyright vendored
View file

@ -7,3 +7,9 @@ License: GPL-3+
The full text of version 3 of the GPL is distributed as doc/GPL in The full text of version 3 of the GPL is distributed as doc/GPL in
this package's source, or in /usr/share/common-licenses/GPL-3 on this package's source, or in /usr/share/common-licenses/GPL-3 on
Debian systems. Debian systems.
Files: doc/logo.png doc/logo_small.png doc/favicon.png
Copyright: 2007 Henrik Nyh <http://henrik.nyh.se/>
2010 Joey Hess <joey@kitenet.net>
License: other
Free to modify and redistribute with due credit, and obviously free to use.

View file

@ -26,6 +26,7 @@ Here is a quick example of how to set this up, using `origin` as the remote name
On the server: On the server:
mkdir bare-annex mkdir bare-annex
cd bare-annex
git init --bare git init --bare
git annex init origin git annex init origin

View file

@ -0,0 +1,79 @@
What steps will reproduce the problem?
$ git annex initremote rsyncremote type=rsync rsyncurl=myuser@rsync.hidrive.strato.com:/users/myuser/git-annex/Music/ encryption=0xC597DECC177AFD7C
$ git annex get --from rsyncremote "file"
What is the expected output? What do you see instead?
I expect that the requested file is copied as for every other remote, but instead I get this error:
----------------------------------------
get <file> (from rsyncremote...) (gpg)
rsync: change_dir "/users/myuser/git-annex/Music/0e5/a5b/'GPGHMACSHA1--3afd32ab8e70ac329262adeb770c330b0845b1e0" failed: No such file or directory (2)
sent 8 bytes received 10 bytes 7.20 bytes/sec
total size is 0 speedup is 0.00
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1518) [Receiver=3.0.9]
rsync failed -- run git annex again to resume file transfer
rsync: change_dir "/users/myuser/git-annex/Music/8k/QZ/'GPGHMACSHA1--3afd32ab8e70ac329262adeb770c330b0845b1e0" failed: No such file or directory (2)
sent 8 bytes received 10 bytes 36.00 bytes/sec
total size is 0 speedup is 0.00
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1518) [Receiver=3.0.9]
rsync failed -- run git annex again to resume file transfer
failed
git-annex: get: 1 failed
----------------------------------------
I can verify that the directory /users/myuser/git-annex/Music/0e5/a5b/GPGHMACSHA1--3afd32ab8e70ac329262adeb770c330b0845b1e0 exists in the rsync remote, without the ' character.
What version of git-annex are you using? On what operating system?
I tried versions 3.20120315 and 3.20120430 on Gentoo linux.
$ uname -a
Linux odin 3.3.1-gentoo-odin #1 SMP Sat Apr 7 21:18:11 CEST 2012 x86_64 Intel(R) Core(TM) i5 CPU M 560 @ 2.67GHz GenuineIntel GNU/Linux
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.4.1
Please provide any additional information below.
The rsync remote config in .git/config:
[remote "rsyncremote"]
annex-rsyncurl = myuser@rsync.hidrive.strato.com:/users/myuser/git-annex/Music/
annex-uuid = "UUID"
> Here's what the --debug flag shows is being run: --[[Joey]]
Running: rsync ["--progress","--inplace","joey@localhost:/tmp/Music/d98/a3c/'GPGHMACSHA1--878c3a3f59965bd87b4738ab29562efd215b954c/GPGHMACSHA1--878c3a3f59965bd87b4738ab29562efd215b954c'","/home/joey/tmp/x/.git/annex/tmp/GPGHMACSHA1--878c3a3f59965bd87b4738ab29562efd215b954c"]
> But, this works for me, here, despite containing the quoting!
> That's because here it's using rsync over ssh, which actually requires
> that quoting. Are you using rsync
> over the rsync protocol? If so, the workaround is to explicitly make
> the rsyncurl start with `rsync://`
>
> And if this is the case, I need
> to adjust the code in git-annex that determines if it's using ssh or
> the rsync protocol. It assumes that (and this is what the rsync man
> says AFAICS) that the rsync protocol is only used if the url starts
> with `rsync://` or contains `::`.
>
>> Nope, it is indeed using rsync over ssh as git-annex thought.
>
> Hmm, I see that `hidrive.strato.com` is some kind of rsync provider?
> Perhaps they do something with rsync over ssh that
> avoids the need for shell quoting. For example, they might pass incoming
> ssh connections directly into rsync, bypassing the shell
> -- which avoids the need for this quoting. Any details you can provide
> about them would probably be useful then. Ie, do they really use rsync
> over ssh, is it really a `rsync.net` type rsync provider?
> --[[Joey]]
>
>> This was the case, and the shellescape=no config option has been added
>> to rsync special remotes to deal with it. [[done]] --[[Joey]]

View file

@ -0,0 +1,17 @@
`GIT_DIR` support isn't right. Git does not look for `GIT_DIR/.git`;
git-annex does.
Also, to support this scenario, support for core.worktree needs to be added
as well:
mkdir repo workdir
git --work-tree=$PWD/workdir --git-dir=$PWD/repo init
export GIT_DIR=$PWD/repo
git status
# ok
git annex init "new repo"
# fail
--[[Joey]]
> [[fixed|done]] --[[Joey]]

View file

@ -16,3 +16,6 @@ I work around this lack as I want to drop all unused files anyway by something l
> I don't see adding my own range operations to be an improvement worth > I don't see adding my own range operations to be an improvement worth
> making; it'd arguably only be a complication. --[[Joey]] [[done]] > making; it'd arguably only be a complication. --[[Joey]] [[done]]
>> Actually, this did get implemented, since using seq could fall afoul
>> of command-line length limits in extreme cases.

View file

@ -0,0 +1,20 @@
What steps will reproduce the problem?
> Building git-annex on the ghc7.0 branch on a Mac with the default case-insensitive file system
What is the expected output? What do you see instead?
> Expected: build successfully; instead:
ld: duplicate symbol _UtilityziDiskFree_zdwa_info in dist/build/git-annex/git-annex-tmp/Utility/diskfree.o and dist/build/git-annex/git-annex-tmp/Utility/DiskFree.o for architecture x86_64
What version of git-annex are you using? On what operating system?
> commit `0bd5c90ef0518f75d52f0c5889422d8233df847d` on a Mac OS 10.6 and 10.7, using the Haskell Platform 2012.04
Please provide any additional information below.
> The problem is that since `DiskFree.hs` generates `DiskFree.o` and `diskfree.c` generates `diskfree.o`, a case-insensitive file system overwrites one object file with the other. Renaming `diskfree.c` to `diskfreec.c` and changing the corresponding filenames in `git-annex.cabal` fixes the problem.
>> Man, not this again. The 80's called, they want their
>> unix portability wars back. [[fixed|done]]. --[[Joey]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="https://www.google.com/accounts/o8/id?id=AItOawnpdM9F8VbtQ_H5PaPMpGSxPe_d5L1eJ6w"
nickname="Rafael"
subject="comment 10"
date="2012-05-15T07:36:25Z"
content="""
Won't git itself be fixed on this issue? It was on my plans to look into that, however I don't know how difficult it will be.
"""]]

View file

@ -0,0 +1,7 @@
Add a file (do not commit), then unlock it, and then lock it.
There is an error and the symlink gets deleted.
The file will still be staged in the index, and the file content is still
in the annex. --[[Joey]]
[[done]]

20
doc/design/assistant.mdwn Normal file
View file

@ -0,0 +1,20 @@
The git-annex assistant is being
[crowd funded on Kickstarter](http://www.kickstarter.com/projects/joeyh/git-annex-assistant-like-dropbox-but-with-your-own/).
This is my design and plan for developing it.
Still being fleshed out, still many ideas and use cases to add.
Feel free to chip in with comments! --[[Joey]]
## roadmap
* Month 1 "like dropbox": [[!traillink inotify]] [[!traillink syncing]]
* Month 2 "shiny webapp": [[!traillink webapp]] [[!traillink progressbars]]
* Month 3 "easy setup": [[!traillink configurators]] [[!traillink pairing]]
* Month 4 "polishing": [[!traillink cloud]] [[!traillink leftovers]]
* Months 5-6 "9k bonus round": [[!traillink Android]] [[!traillink partial_content]]
## not yet on the map:
* [[desymlink]]
* [[deltas]]
* In my overfunded nighmares: [[Windows]]

View file

@ -0,0 +1,64 @@
Porting git-annex to Android will use the Android native SDK.
A hopefully small Java app will be developed, which runs the webapp
daemon, and a web browser to display it.
### programs to port
These will probably need to be bundled into the Android app, unless already
available in the App Store.
* ssh (native ssh needed for scp, not a client like ConnectBot)
* rsync
* gpg
* git (not all git commands are needed,
but core plumbing and a few like `git-add` are.)
### Android specific features
The app should be aware of power status, and avoid expensive background
jobs when low on battery or run flat out when plugged in.
The app should be aware of network status, and avoid expensive data
transfers when not on wifi. This may need to be configurable.
### FAT sucks
The main media partition will use some awful FAT filesystem format from
1982 that cannot support git-annex's symlinks. (Hopefully it can at least
handle all of git's filenames.) Possible approaches to this follow.
(May want to consider which of these would make a Windows port easier too.)
#### bare git repo with file browser
Keep only a bare git repo on Android. The app would then need to include
a file browser to access the files in there, and adding a file would move
it into the repo.
Not ideal.
Could be improved some by registering git-annex as a file handling app on
Android, allowing you to "send to" git-annex.
#### implement git smudge filters
See [[todo/smudge]].
Difficult. Would make git-annex generally better.
#### keep files outside bare git repo
Use a bare git repo but don't keep files in `annex/objects`, instead
leave them outside the repo, and add some local mapping to find them.
Problem: Would leave files unlocked to modification, which might lose a
version git-annex dependend upon existing on the phone. (Maybe the phone
would have to be always considered an untrusted repo, which probably
makes sense anyway.)
Problem:
#### crazy `LD_PRELOAD` wrapper
Need I say more? (Also, Android's linker may not even support it.)

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="https://launchpad.net/~gdr-go2"
nickname="gdr-go2"
subject="FAT symlinks"
date="2012-05-28T18:12:10Z"
content="""
It's a linux kernel so perhaps another option would be to create a big file and mount -o loop
"""]]

View file

@ -0,0 +1,43 @@
The [[syncing]] design assumes the network is connected. But it's often
not in these pre-IPV6 days, so the cloud needs to be used to bridge between
LANS.
## more cloud providers
Git-annex already supports storing large files in
several cloud providers via [[special_remotes]].
More should be added, such as:
* Google drive (attractive because it's free, only 5 gb tho)
* OpenStack Swift (teh future)
* Box.com (it's free, and current method is hard to set up and a sorta
shakey; a better method would be to use its API)
* Dropbox? That would be ironic.. Via its API, presumably.
## limited space
When syncing via the cloud, space there is probably limited, so
users with more files than cloud space will want to be able to use the
cloud as a temporary transfer point, which files are removed from after
they've propigated out.
Other users will want to use the cloud as the canonical or backup location
of their data, and would want a copy of all their files to be kept there.
That's also ok.
git-annex will need a way to tell the difference between these, either
heuristically, or via configuration.
Also needed for USB keys and Android gadgets.
## storing git repos in the cloud
Of course, one option is to just use github etc to store the git repo.
Two things can store git repos in Anazon S3:
* <http://gabrito.com/post/storing-git-repositories-in-amazon-s3-for-high-availability>
* <http://wiki.cs.pdx.edu/oss2009/index/projects/gits3.html>
Another option is to not store the git repo in the cloud, but push/pull
peer-to-peer. When peers cannot directly talk to one-another, this could be
bounced through something like XMPP.

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="https://www.google.com/accounts/o8/id?id=AItOawkSq2FDpK2n66QRUxtqqdbyDuwgbQmUWus"
nickname="Jimmy"
subject="comment 1"
date="2012-06-02T12:06:37Z"
content="""
Will statically linked binaries be provided for say Linux, OSX and *BSD? I think having some statically linked binaries will certainly help and appeal to a lot of users.
"""]]

View file

@ -0,0 +1,18 @@
Add to the [[webapp]] some configuration of git-annex.
There are some basic settings that pass through to `git config`, things
like how much disk space to leave free, how many copies to ensure are kept
of files, etc.
The meat of the configuration will be in configuration assistants that walk
through setting up common use cases.
* Create a repository (run when the web app is started without a configured
repository too).
* Clone this repo to a USB drive.
* Clone this repo to another host. (Needs [[pairing]])
* Set up Amazon S3.
* Set up rsync remote.
* Set up encryption.
* I lost my USB drive!
* etc -- many more possibilities

Some files were not shown because too many files have changed in this diff Show more