Merge branch 'master' into watch
This commit is contained in:
commit
eab3872d91
231 changed files with 2786 additions and 1112 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -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
|
||||||
|
|
22
Annex.hs
22
Annex.hs
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
180
Annex/Content.hs
180
Annex/Content.hs
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
70
Annex/Perms.hs
Normal 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
|
|
@ -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 }
|
||||||
|
|
20
Annex/Ssh.hs
20
Annex/Ssh.hs
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
10
Backend.hs
10
Backend.hs
|
@ -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 ++
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
12
CmdLine.hs
12
CmdLine.hs
|
@ -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
34
Command/AddUnused.hs
Normal 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"
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
39
Command/Import.hs
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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 $
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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!"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
53
Config.hs
53
Config.hs
|
@ -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])
|
||||||
|
|
89
Crypto.hs
89
Crypto.hs
|
@ -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
77
Git.hs
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
58
Git/CurrentRepo.hs
Normal 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 }
|
|
@ -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
27
Git/SharedRepository.hs
Normal 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)
|
20
Git/Types.hs
20
Git/Types.hs
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ()
|
||||||
|
|
6
Init.hs
6
Init.hs
|
@ -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 =
|
||||||
|
|
20
Locations.hs
20
Locations.hs
|
@ -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.
|
||||||
-
|
-
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) ++ ";"
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
91
Logs/Unused.hs
Normal 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
|
4
Makefile
4
Makefile
|
@ -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
|
||||||
|
|
||||||
|
|
57
Messages.hs
57
Messages.hs
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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 ()
|
|
||||||
|
|
|
@ -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 ","
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 $
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
10
Seek.hs
|
@ -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]
|
||||||
|
|
||||||
|
|
17
Setup.hs
17
Setup.hs
|
@ -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)
|
||||||
|
|
|
@ -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
20
Types/Messages.hs
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
2
Usage.hs
2
Usage.hs
|
@ -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
|
||||||
|
|
|
@ -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 ""
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ()
|
|
||||||
|
|
|
@ -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
38
Utility/Percentage.hs
Normal 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)
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
61
debian/changelog
vendored
|
@ -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
6
debian/copyright
vendored
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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]]
|
17
doc/bugs/GIT_DIR_support_incomplete.mdwn
Normal file
17
doc/bugs/GIT_DIR_support_incomplete.mdwn
Normal 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]]
|
|
@ -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.
|
||||||
|
|
20
doc/bugs/case-insensitive.mdwn
Normal file
20
doc/bugs/case-insensitive.mdwn
Normal 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]]
|
|
@ -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.
|
||||||
|
"""]]
|
|
@ -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
20
doc/design/assistant.mdwn
Normal 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]]
|
64
doc/design/assistant/android.mdwn
Normal file
64
doc/design/assistant/android.mdwn
Normal 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.)
|
|
@ -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
|
||||||
|
"""]]
|
43
doc/design/assistant/cloud.mdwn
Normal file
43
doc/design/assistant/cloud.mdwn
Normal 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.
|
|
@ -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.
|
||||||
|
"""]]
|
18
doc/design/assistant/configurators.mdwn
Normal file
18
doc/design/assistant/configurators.mdwn
Normal 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
Loading…
Add table
Reference in a new issue