git-annex 8.20200330 → 8.20200501
raw patch · 136 files changed
+1833/−872 lines, 136 files
Files
- Annex.hs +12/−11
- Annex/Action.hs +12/−1
- Annex/AdjustedBranch.hs +5/−3
- Annex/AdjustedBranch/Name.hs +18/−13
- Annex/Branch.hs +69/−40
- Annex/BranchState.hs +47/−6
- Annex/CatFile.hs +63/−30
- Annex/ChangedRefs.hs +4/−3
- Annex/CheckAttr.hs +32/−12
- Annex/CheckIgnore.hs +34/−14
- Annex/Concurrent.hs +37/−17
- Annex/Concurrent/Utility.hs +23/−0
- Annex/Content.hs +1/−1
- Annex/Export.hs +1/−2
- Annex/GitOverlay.hs +40/−23
- Annex/Import.hs +3/−1
- Annex/Multicast.hs +2/−2
- Annex/Path.hs +12/−1
- Annex/SpecialRemote.hs +1/−2
- Annex/SpecialRemote/Config.hs +9/−2
- Annex/TaggedPush.hs +7/−3
- Annex/Transfer.hs +1/−1
- Annex/UUID.hs +1/−1
- Annex/Url.hs +2/−6
- Annex/View.hs +1/−3
- Annex/WorkerPool.hs +126/−0
- Assistant.hs +2/−1
- Assistant/Sync.hs +2/−1
- Assistant/Threads/Merger.hs +3/−2
- Assistant/Threads/Watcher.hs +1/−1
- Assistant/Upgrade.hs +17/−16
- Assistant/WebApp/Configurators/Pairing.hs +2/−2
- Benchmark.hs +1/−1
- CHANGELOG +49/−0
- CmdLine/Action.hs +1/−8
- CmdLine/Batch.hs +19/−7
- CmdLine/GitAnnex/Options.hs +3/−4
- Command/AddUrl.hs +10/−5
- Command/CheckPresentKey.hs +1/−1
- Command/Config.hs +2/−1
- Command/DropKey.hs +2/−1
- Command/EnableTor.hs +2/−2
- Command/Export.hs +2/−1
- Command/FindRef.hs +1/−1
- Command/Forget.hs +1/−1
- Command/FromKey.hs +7/−4
- Command/Import.hs +1/−1
- Command/ImportFeed.hs +8/−5
- Command/Info.hs +7/−5
- Command/Log.hs +2/−1
- Command/Map.hs +4/−4
- Command/Merge.hs +2/−2
- Command/MetaData.hs +18/−12
- Command/Move.hs +4/−10
- Command/PreCommit.hs +0/−1
- Command/ReKey.hs +9/−5
- Command/ResolveMerge.hs +3/−1
- Command/RmUrl.hs +8/−4
- Command/SetPresentKey.hs +1/−1
- Command/Sync.hs +9/−9
- Command/TestRemote.hs +189/−109
- Command/TransferKeys.hs +2/−0
- Command/Uninit.hs +1/−1
- Command/Unused.hs +12/−10
- Config/Cost.hs +3/−3
- Config/Files.hs +0/−21
- Database/Keys.hs +17/−27
- Database/Types.hs +3/−2
- Git.hs +1/−0
- Git/Branch.hs +12/−10
- Git/CatFile.hs +48/−40
- Git/Command.hs +7/−2
- Git/Config.hs +35/−22
- Git/ConfigTypes.hs +3/−1
- Git/Credential.hs +1/−1
- Git/DiffTree.hs +27/−22
- Git/DiffTreeItem.hs +2/−1
- Git/FilePath.hs +1/−1
- Git/Fsck.hs +2/−1
- Git/GCrypt.hs +1/−0
- Git/HashObject.hs +2/−1
- Git/History.hs +7/−3
- Git/LsFiles.hs +12/−11
- Git/LsTree.hs +1/−1
- Git/Objects.hs +1/−1
- Git/Ref.hs +32/−22
- Git/RefLog.hs +4/−1
- Git/Remote.hs +10/−5
- Git/Repair.hs +9/−8
- Git/Sha.hs +23/−9
- Git/Tree.hs +2/−1
- Git/Types.hs +24/−6
- Git/UnionMerge.hs +18/−14
- Git/UpdateIndex.hs +4/−4
- Logs/Config.hs +5/−1
- Logs/Export.hs +2/−2
- Logs/FsckResults.hs +1/−1
- Logs/PreferredContent.hs +3/−2
- Logs/Transfer.hs +3/−3
- Logs/View.hs +10/−6
- Logs/Web.hs +1/−1
- Messages/JSON.hs +19/−10
- Remote/Adb.hs +38/−25
- Remote/Bup.hs +4/−2
- Remote/External.hs +28/−10
- Remote/GCrypt.hs +1/−1
- Remote/Git.hs +58/−42
- RemoteDaemon/Core.hs +4/−1
- Test.hs +67/−9
- Test/Framework.hs +11/−9
- Types/BranchState.hs +11/−3
- Types/CatFileHandles.hs +30/−0
- Types/Concurrency.hs +1/−0
- Types/GitConfig.hs +1/−1
- Types/IndexFiles.hs +11/−0
- Types/RefSpec.hs +1/−1
- Types/UUID.hs +1/−0
- Types/View.hs +2/−0
- Upgrade.hs +2/−2
- Upgrade/V2.hs +5/−4
- Upgrade/V5/Direct.hs +1/−1
- Utility/CoProcess.hs +2/−1
- Utility/Gpg.hs +24/−8
- Utility/Misc.hs +8/−0
- Utility/Process.hs +6/−10
- Utility/ResourcePool.hs +94/−0
- Utility/Url.hs +32/−15
- doc/git-annex-copy.mdwn +4/−1
- doc/git-annex-drop.mdwn +6/−2
- doc/git-annex-fsck.mdwn +9/−4
- doc/git-annex-get.mdwn +6/−2
- doc/git-annex-move.mdwn +3/−0
- doc/git-annex-preferred-content.mdwn +8/−0
- doc/git-annex-sync.mdwn +6/−3
- doc/git-annex.mdwn +14/−7
- git-annex.cabal +7/−2
Annex.hs view
@@ -43,7 +43,6 @@ import qualified Git.Config import qualified Git.Construct import Annex.Fixup-import Git.CatFile import Git.HashObject import Git.CheckAttr import Git.CheckIgnore@@ -67,9 +66,12 @@ import Types.CleanupActions import Types.AdjustedBranch import Types.WorkerPool+import Types.IndexFiles+import Types.CatFileHandles import qualified Database.Keys.Handle as Keys import Utility.InodeCache import Utility.Url+import Utility.ResourcePool import "mtl" Control.Monad.Reader import Control.Concurrent@@ -108,7 +110,6 @@ , gitremotes :: Maybe [Git.Repo] , backend :: Maybe (BackendA Annex) , remotes :: [Types.Remote.RemoteA Annex]- , remoteannexstate :: M.Map UUID AnnexState , output :: MessageState , concurrency :: Concurrency , force :: Bool@@ -116,10 +117,10 @@ , daemon :: Bool , branchstate :: BranchState , repoqueue :: Maybe (Git.Queue.Queue Annex)- , catfilehandles :: M.Map FilePath CatFileHandle+ , catfilehandles :: CatFileHandles , hashobjecthandle :: Maybe HashObjectHandle- , checkattrhandle :: Maybe CheckAttrHandle- , checkignorehandle :: Maybe CheckIgnoreHandle+ , checkattrhandle :: Maybe (ResourcePool CheckAttrHandle)+ , checkignorehandle :: Maybe (ResourcePool CheckIgnoreHandle) , forcebackend :: Maybe String , globalnumcopies :: Maybe NumCopies , forcenumcopies :: Maybe NumCopies@@ -146,9 +147,9 @@ , workers :: Maybe (TMVar (WorkerPool AnnexState)) , activekeys :: TVar (M.Map Key ThreadId) , activeremotes :: MVar (M.Map (Types.Remote.RemoteA Annex) Integer)- , keysdbhandle :: Maybe Keys.DbHandle+ , keysdbhandle :: Keys.DbHandle , cachedcurrentbranch :: (Maybe (Maybe Git.Branch, Maybe Adjustment))- , cachedgitenv :: Maybe (FilePath, [(String, String)])+ , cachedgitenv :: Maybe (AltIndexFile, FilePath, [(String, String)]) , urloptions :: Maybe UrlOptions } @@ -158,6 +159,7 @@ emptyactivekeys <- newTVarIO M.empty o <- newMessageState sc <- newTMVarIO False+ kh <- Keys.newDbHandle return $ AnnexState { repo = r , repoadjustment = return@@ -166,7 +168,6 @@ , gitremotes = Nothing , backend = Nothing , remotes = []- , remoteannexstate = M.empty , output = o , concurrency = NonConcurrent , force = False@@ -174,7 +175,7 @@ , daemon = False , branchstate = startBranchState , repoqueue = Nothing- , catfilehandles = M.empty+ , catfilehandles = catFileHandlesNonConcurrent , hashobjecthandle = Nothing , checkattrhandle = Nothing , checkignorehandle = Nothing@@ -204,7 +205,7 @@ , workers = Nothing , activekeys = emptyactivekeys , activeremotes = emptyactiveremotes- , keysdbhandle = Nothing+ , keysdbhandle = kh , cachedcurrentbranch = Nothing , cachedgitenv = Nothing , urloptions = Nothing@@ -232,7 +233,7 @@ flush s' return (r, s') where- flush = maybe noop Keys.flushDbQueue . keysdbhandle+ flush = Keys.flushDbQueue . keysdbhandle {- Performs an action in the Annex monad from a starting state, - and throws away the new state. -}
Annex/Action.hs view
@@ -23,7 +23,10 @@ import Annex.Common import qualified Annex import Annex.Content-import Annex.Concurrent+import Annex.CatFile+import Annex.CheckAttr+import Annex.HashObject+import Annex.CheckIgnore {- Actions to perform each time ran. -} startup :: Annex ()@@ -36,6 +39,14 @@ sequence_ =<< M.elems <$> Annex.getState Annex.cleanup stopCoProcesses liftIO reapZombies -- zombies from long-running git processes++{- Stops all long-running git query processes. -}+stopCoProcesses :: Annex ()+stopCoProcesses = do+ catFileStop+ checkAttrStop+ hashObjectStop+ checkIgnoreStop {- Reaps any zombie processes that may be hanging around. -
Annex/AdjustedBranch.hs view
@@ -5,7 +5,7 @@ - Licensed under the GNU AGPL version 3 or higher. -} -{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BangPatterns, OverloadedStrings #-} module Annex.AdjustedBranch ( Adjustment(..),@@ -61,6 +61,7 @@ import Config import qualified Data.Map as M+import qualified Data.ByteString as S -- How to perform various adjustments to a TreeItem. class AdjustTreeItem t where@@ -128,7 +129,7 @@ -- refs/basis/adjusted/master(unlocked). basisBranch :: AdjBranch -> BasisBranch basisBranch (AdjBranch adjbranch) = BasisBranch $- Ref ("refs/basis/" ++ fromRef (Git.Ref.base adjbranch))+ Ref ("refs/basis/" <> fromRef' (Git.Ref.base adjbranch)) getAdjustment :: Branch -> Maybe Adjustment getAdjustment = fmap fst . adjustedToOriginal@@ -405,7 +406,8 @@ <||> (resolveMerge (Just updatedorig) tomerge True <&&> commitResolvedMerge commitmode) if merged then do- !mergecommit <- liftIO $ extractSha <$> readFile (tmpgit </> "HEAD")+ !mergecommit <- liftIO $ extractSha+ <$> S.readFile (tmpgit </> "HEAD") -- This is run after the commit lock is dropped. return $ postmerge mergecommit else return $ return False
Annex/AdjustedBranch/Name.hs view
@@ -5,6 +5,8 @@ - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE OverloadedStrings #-}+ module Annex.AdjustedBranch.Name ( originalToAdjusted, adjustedToOriginal,@@ -18,14 +20,15 @@ import Utility.Misc import Control.Applicative-import Data.List+import Data.Char+import qualified Data.ByteString as S -adjustedBranchPrefix :: String+adjustedBranchPrefix :: S.ByteString adjustedBranchPrefix = "refs/heads/adjusted/" class SerializeAdjustment t where- serializeAdjustment :: t -> String- deserializeAdjustment :: String -> Maybe t+ serializeAdjustment :: t -> S.ByteString+ deserializeAdjustment :: S.ByteString -> Maybe t instance SerializeAdjustment Adjustment where serializeAdjustment (LinkAdjustment l) =@@ -33,7 +36,7 @@ serializeAdjustment (PresenceAdjustment p Nothing) = serializeAdjustment p serializeAdjustment (PresenceAdjustment p (Just l)) = - serializeAdjustment p ++ "-" ++ serializeAdjustment l+ serializeAdjustment p <> "-" <> serializeAdjustment l deserializeAdjustment s = (LinkAdjustment <$> deserializeAdjustment s) <|>@@ -41,7 +44,7 @@ <|> (PresenceAdjustment <$> deserializeAdjustment s <*> pure Nothing) where- (s1, s2) = separate (== '-') s+ (s1, s2) = separate' (== (fromIntegral (ord '-'))) s instance SerializeAdjustment LinkAdjustment where serializeAdjustment UnlockAdjustment = "unlocked"@@ -65,19 +68,21 @@ originalToAdjusted :: OrigBranch -> Adjustment -> AdjBranch originalToAdjusted orig adj = AdjBranch $ Ref $- adjustedBranchPrefix ++ base ++ '(' : serializeAdjustment adj ++ ")"+ adjustedBranchPrefix <> base <> "(" <> serializeAdjustment adj <> ")" where- base = fromRef (Git.Ref.base orig)+ base = fromRef' (Git.Ref.base orig) type OrigBranch = Branch adjustedToOriginal :: Branch -> Maybe (Adjustment, OrigBranch) adjustedToOriginal b- | adjustedBranchPrefix `isPrefixOf` bs = do- let (base, as) = separate (== '(') (drop prefixlen bs)- adj <- deserializeAdjustment (takeWhile (/= ')') as)+ | adjustedBranchPrefix `S.isPrefixOf` bs = do+ let (base, as) = separate' (== openparen) (S.drop prefixlen bs)+ adj <- deserializeAdjustment (S.takeWhile (/= closeparen) as) Just (adj, Git.Ref.branchRef (Ref base)) | otherwise = Nothing where- bs = fromRef b- prefixlen = length adjustedBranchPrefix+ bs = fromRef' b+ prefixlen = S.length adjustedBranchPrefix+ openparen = fromIntegral (ord '(')+ closeparen = fromIntegral (ord ')')
Annex/Branch.hs view
@@ -1,10 +1,12 @@ {- management of the git-annex branch -- - Copyright 2011-2019 Joey Hess <id@joeyh.name>+ - Copyright 2011-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE OverloadedStrings #-}+ module Annex.Branch ( fullname, name,@@ -12,6 +14,7 @@ hasSibling, siblingBranches, create,+ UpdateMade(..), update, forceUpdate, updateTo,@@ -29,7 +32,9 @@ withIndex, ) where +import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Char8 as B8 import qualified Data.Set as S import qualified Data.Map as M import Data.Function@@ -38,6 +43,7 @@ import Control.Concurrent (threadDelay) import Annex.Common+import Types.BranchState import Annex.BranchState import Annex.Journal import Annex.GitOverlay@@ -55,7 +61,7 @@ import Git.LsTree (lsTreeParams) import qualified Git.HashObject import Annex.HashObject-import Git.Types (Ref(..), fromRef, RefDate, TreeItemType(..))+import Git.Types (Ref(..), fromRef, fromRef', RefDate, TreeItemType(..)) import Git.FilePath import Annex.CatFile import Annex.Perms@@ -77,11 +83,11 @@ {- Fully qualified name of the branch. -} fullname :: Git.Ref-fullname = Git.Ref $ "refs/heads/" ++ fromRef name+fullname = Git.Ref $ "refs/heads/" <> fromRef' name {- Branch's name in origin. -} originname :: Git.Ref-originname = Git.Ref $ "origin/" ++ fromRef name+originname = Git.Ref $ "origin/" <> fromRef' name {- Does origin/git-annex exist? -} hasOrigin :: Annex Bool@@ -119,13 +125,18 @@ {- Ensures that the branch and index are up-to-date; should be - called before data is read from it. Runs only once per git-annex run. -}-update :: Annex ()-update = runUpdateOnce $ void $ updateTo =<< siblingBranches+update :: Annex BranchState+update = runUpdateOnce $ journalClean <$$> updateTo =<< siblingBranches {- Forces an update even if one has already been run. -}-forceUpdate :: Annex Bool+forceUpdate :: Annex UpdateMade forceUpdate = updateTo =<< siblingBranches +data UpdateMade = UpdateMade+ { refsWereMerged :: Bool+ , journalClean :: Bool+ }+ {- Merges the specified Refs into the index, if they have any changes not - already in it. The Branch names are only used in the commit message; - it's even possible that the provided Branches have not been updated to@@ -144,13 +155,13 @@ - - Returns True if any refs were merged in, False otherwise. -}-updateTo :: [(Git.Sha, Git.Branch)] -> Annex Bool+updateTo :: [(Git.Sha, Git.Branch)] -> Annex UpdateMade updateTo pairs = ifM (annexMergeAnnexBranches <$> Annex.getGitConfig) ( updateTo' pairs- , return False+ , return (UpdateMade False False) ) -updateTo' :: [(Git.Sha, Git.Branch)] -> Annex Bool+updateTo' :: [(Git.Sha, Git.Branch)] -> Annex UpdateMade updateTo' pairs = do -- ensure branch exists, and get its current ref branchref <- getBranch@@ -162,20 +173,35 @@ else do mergedrefs <- getMergedRefs filterM isnewer (excludeset mergedrefs unignoredrefs)- if null tomerge+ journalclean <- if null tomerge {- Even when no refs need to be merged, the index - may still be updated if the branch has gotten ahead - - of the index. -}- then do- whenM (needUpdateIndex branchref) $ lockJournal $ \jl -> do+ - of the index, or just if the journal is dirty. -}+ then ifM (needUpdateIndex branchref)+ ( lockJournal $ \jl -> do forceUpdateIndex jl branchref {- When there are journalled changes - as well as the branch being updated, - a commit needs to be done. -} when dirty $- go branchref True [] jl- else lockJournal $ go branchref dirty tomerge- return $ not $ null tomerge+ go branchref dirty [] jl+ return True+ , if dirty+ then ifM (annexAlwaysCommit <$> Annex.getGitConfig)+ ( do+ lockJournal $ go branchref dirty []+ return True+ , return False+ )+ else return True+ )+ else do+ lockJournal $ go branchref dirty tomerge+ return True+ return $ UpdateMade+ { refsWereMerged = not (null tomerge)+ , journalClean = journalclean+ } where excludeset s = filter (\(r, _) -> S.notMember r s) isnewer (r, _) = inRepo $ Git.Branch.changed fullname r@@ -217,8 +243,10 @@ - Returns an empty string if the file doesn't exist yet. -} get :: RawFilePath -> Annex L.ByteString get file = do- update- getLocal file+ st <- update+ if journalIgnorable st+ then getRef fullname file+ else getLocal file {- Like get, but does not merge the branch, so the info returned may not - reflect changes in remotes.@@ -270,7 +298,9 @@ {- Records new content of a file into the journal -} set :: Journalable content => JournalLocked -> RawFilePath -> content -> Annex ()-set = setJournalFile+set jl f c = do+ journalChanged+ setJournalFile jl f c {- Commit message used when making a commit of whatever data has changed - to the git-annex brach. -}@@ -327,9 +357,9 @@ where -- look for "parent ref" lines and return the refs commitparents = map (Git.Ref . snd) . filter isparent .- map (toassoc . decodeBL) . L.split newline+ map (toassoc . L.toStrict) . L.split newline newline = fromIntegral (ord '\n')- toassoc = separate (== ' ')+ toassoc = separate' (== (fromIntegral (ord ' '))) isparent (k,_) = k == "parent" {- The race can be detected by checking the commit's@@ -355,7 +385,7 @@ - that have not been committed yet. There may be duplicates in the list. -} files :: Annex [RawFilePath] files = do- update+ _ <- update -- ++ forces the content of the first list to be buffered in memory, -- so use getJournalledFilesStale which should be much smaller most -- of the time. branchFiles will stream as the list is consumed.@@ -388,8 +418,8 @@ mergeIndex jl branches = do prepareModifyIndex jl hashhandle <- hashObjectHandle- ch <- catFileHandle- inRepo $ \g -> Git.UnionMerge.mergeIndex hashhandle ch g branches+ withCatFileHandle $ \ch ->+ inRepo $ \g -> Git.UnionMerge.mergeIndex hashhandle ch g branches {- Removes any stale git lock file, to avoid git falling over when - updating the index.@@ -410,14 +440,12 @@ withIndex :: Annex a -> Annex a withIndex = withIndex' False withIndex' :: Bool -> Annex a -> Annex a-withIndex' bootstrapping a = do- f <- fromRepo gitAnnexIndex- withIndexFile f $ do- checkIndexOnce $ unlessM (liftIO $ doesFileExist f) $ do- unless bootstrapping create- createAnnexDirectory $ takeDirectory f- unless bootstrapping $ inRepo genIndex- a+withIndex' bootstrapping a = withIndexFile AnnexIndexFile $ \f -> do+ checkIndexOnce $ unlessM (liftIO $ doesFileExist f) $ do+ unless bootstrapping create+ createAnnexDirectory $ takeDirectory f+ unless bootstrapping $ inRepo genIndex+ a {- Updates the branch's index to reflect the current contents of the branch. - Any changes staged in the index will be preserved.@@ -438,8 +466,8 @@ needUpdateIndex :: Git.Ref -> Annex Bool needUpdateIndex branchref = do f <- fromRepo gitAnnexIndexStatus- committedref <- Git.Ref . firstLine <$>- liftIO (catchDefaultIO "" $ readFileStrict f)+ committedref <- Git.Ref . firstLine' <$>+ liftIO (catchDefaultIO mempty $ B.readFile f) return (committedref /= branchref) {- Record that the branch's index has been updated to correspond to a@@ -621,11 +649,12 @@ unlines $ map fromRef $ S.elems s getIgnoredRefs :: Annex (S.Set Git.Sha)-getIgnoredRefs = S.fromList . mapMaybe Git.Sha.extractSha . lines <$> content+getIgnoredRefs = + S.fromList . mapMaybe Git.Sha.extractSha . B8.lines <$> content where content = do f <- fromRepo gitAnnexIgnoredRefs- liftIO $ catchDefaultIO "" $ readFile f+ liftIO $ catchDefaultIO mempty $ B.readFile f addMergedRefs :: [(Git.Sha, Git.Branch)] -> Annex () addMergedRefs [] = return ()@@ -643,11 +672,11 @@ getMergedRefs' :: Annex [(Git.Sha, Git.Branch)] getMergedRefs' = do f <- fromRepo gitAnnexMergedRefs- s <- liftIO $ catchDefaultIO "" $ readFile f- return $ map parse $ lines s+ s <- liftIO $ catchDefaultIO mempty $ B.readFile f+ return $ map parse $ B8.lines s where parse l = - let (s, b) = separate (== '\t') l+ let (s, b) = separate' (== (fromIntegral (ord '\t'))) l in (Ref s, Ref b) {- Grafts a treeish into the branch at the specified location,
Annex/BranchState.hs view
@@ -2,7 +2,7 @@ - - Runtime state about the git-annex branch. -- - Copyright 2011-2012 Joey Hess <id@joeyh.name>+ - Copyright 2011-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -28,14 +28,55 @@ changeState $ \s -> s { indexChecked = True } {- Runs an action to update the branch, if it's not been updated before- - in this run of git-annex. -}-runUpdateOnce :: Annex () -> Annex ()-runUpdateOnce a = unlessM (branchUpdated <$> getState) $ do- a- disableUpdate+ - in this run of git-annex. + -+ - The action should return True if anything that was in the journal+ - before got staged (or if the journal was empty). That lets an opmisation+ - be done: The journal then does not need to be checked going forward,+ - until new information gets written to it.+ -}+runUpdateOnce :: Annex Bool -> Annex BranchState+runUpdateOnce a = do+ st <- getState+ if branchUpdated st+ then return st+ else do+ journalstaged <- a+ let stf = \st' -> st'+ { branchUpdated = True+ , journalIgnorable = journalstaged + && not (journalNeverIgnorable st')+ }+ changeState stf+ return (stf st) {- Avoids updating the branch. A useful optimisation when the branch - is known to have not changed, or git-annex won't be relying on info - from it. -} disableUpdate :: Annex () disableUpdate = changeState $ \s -> s { branchUpdated = True }++{- Called when a change is made to the journal. -}+journalChanged :: Annex ()+journalChanged = do+ -- Optimisation: Typically journalIgnorable will already be True+ -- (when one thing gets journalled, often other things do to),+ -- so avoid an unnecessary write to the MVar that changeState+ -- would do.+ --+ -- This assumes that another thread is not changing journalIgnorable+ -- at the same time, but since runUpdateOnce is the only+ -- thing that changes it, and it only runs once, that+ -- should not happen.+ st <- getState + when (journalIgnorable st) $+ changeState $ \st' -> st' { journalIgnorable = False }++{- When git-annex is somehow interactive, eg in --batch mode,+ - and needs to always notice changes made to the journal by other+ - processes, this disables optimisations that avoid normally reading the+ - journal.+ -}+enableInteractiveJournalAccess :: Annex ()+enableInteractiveJournalAccess = changeState $+ \s -> s { journalNeverIgnorable = True }
Annex/CatFile.hs view
@@ -1,10 +1,12 @@ {- git cat-file interface, with handle automatically stored in the Annex monad -- - Copyright 2011-2019 Joey Hess <id@joeyh.name>+ - Copyright 2011-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE BangPatterns #-}+ module Annex.CatFile ( catFile, catFileDetails,@@ -12,7 +14,7 @@ catTree, catCommit, catObjectDetails,- catFileHandle,+ withCatFileHandle, catObjectMetaData, catFileStop, catKey,@@ -27,6 +29,7 @@ import qualified Data.ByteString.Lazy as L import qualified Data.Map as M import System.PosixCompat.Types+import Control.Concurrent.STM import Annex.Common import qualified Git@@ -39,64 +42,94 @@ import Annex.Link import Annex.CurrentBranch import Types.AdjustedBranch+import Types.CatFileHandles+import Utility.ResourcePool catFile :: Git.Branch -> RawFilePath -> Annex L.ByteString-catFile branch file = do- h <- catFileHandle+catFile branch file = withCatFileHandle $ \h -> liftIO $ Git.CatFile.catFile h branch file catFileDetails :: Git.Branch -> RawFilePath -> Annex (Maybe (L.ByteString, Sha, ObjectType))-catFileDetails branch file = do- h <- catFileHandle+catFileDetails branch file = withCatFileHandle $ \h -> liftIO $ Git.CatFile.catFileDetails h branch file catObject :: Git.Ref -> Annex L.ByteString-catObject ref = do- h <- catFileHandle+catObject ref = withCatFileHandle $ \h -> liftIO $ Git.CatFile.catObject h ref catObjectMetaData :: Git.Ref -> Annex (Maybe (Sha, Integer, ObjectType))-catObjectMetaData ref = do- h <- catFileHandle+catObjectMetaData ref = withCatFileHandle $ \h -> liftIO $ Git.CatFile.catObjectMetaData h ref catTree :: Git.Ref -> Annex [(FilePath, FileMode)]-catTree ref = do- h <- catFileHandle+catTree ref = withCatFileHandle $ \h -> liftIO $ Git.CatFile.catTree h ref catCommit :: Git.Ref -> Annex (Maybe Commit)-catCommit ref = do- h <- catFileHandle+catCommit ref = withCatFileHandle $ \h -> liftIO $ Git.CatFile.catCommit h ref catObjectDetails :: Git.Ref -> Annex (Maybe (L.ByteString, Sha, ObjectType))-catObjectDetails ref = do- h <- catFileHandle+catObjectDetails ref = withCatFileHandle $ \h -> liftIO $ Git.CatFile.catObjectDetails h ref {- There can be multiple index files, and a different cat-file is needed- - for each. This is selected by setting GIT_INDEX_FILE in the gitEnv. -}-catFileHandle :: Annex Git.CatFile.CatFileHandle-catFileHandle = do- m <- Annex.getState Annex.catfilehandles+ - for each. That is selected by setting GIT_INDEX_FILE in the gitEnv+ - before running this. -}+withCatFileHandle :: (Git.CatFile.CatFileHandle -> Annex a) -> Annex a+withCatFileHandle a = do+ cfh <- Annex.getState Annex.catfilehandles indexfile <- fromMaybe "" . maybe Nothing (lookup indexEnv) <$> fromRepo gitEnv- case M.lookup indexfile m of- Just h -> return h- Nothing -> do- h <- inRepo Git.CatFile.catFileStart- let m' = M.insert indexfile h m- Annex.changeState $ \s -> s { Annex.catfilehandles = m' }- return h+ p <- case cfh of+ CatFileHandlesNonConcurrent m -> case M.lookup indexfile m of+ Just p -> return p+ Nothing -> do+ p <- mkResourcePoolNonConcurrent startcatfile+ let !m' = M.insert indexfile p m+ Annex.changeState $ \s -> s { Annex.catfilehandles = CatFileHandlesNonConcurrent m' }+ return p+ CatFileHandlesPool tm -> do+ m <- liftIO $ atomically $ takeTMVar tm+ case M.lookup indexfile m of+ Just p -> do+ liftIO $ atomically $ putTMVar tm m+ return p+ Nothing -> do+ p <- mkResourcePool maxCatFiles+ let !m' = M.insert indexfile p m+ liftIO $ atomically $ putTMVar tm m'+ return p+ withResourcePool p startcatfile a+ where+ startcatfile = inRepo Git.CatFile.catFileStart +{- A lot of git cat-file processes are unlikely to improve concurrency,+ - because a query to them takes only a little bit of CPU, and tends to be+ - bottlenecked on disk. Also, they each open a number of files, so+ - using too many might run out of file handles. So, only start a maximum+ - of 2.+ -+ - Note that each different index file gets its own pool of cat-files;+ - this is the size of each pool. In all, 4 times this many cat-files+ - may end up running.+ -}+maxCatFiles :: Int+maxCatFiles = 2+ {- Stops all running cat-files. Should only be run when it's known that - nothing is using the handles, eg at shutdown. -} catFileStop :: Annex () catFileStop = do- m <- Annex.withState $ pure . \s ->- (s { Annex.catfilehandles = M.empty }, Annex.catfilehandles s)- liftIO $ mapM_ Git.CatFile.catFileStop (M.elems m)+ cfh <- Annex.getState Annex.catfilehandles+ m <- case cfh of+ CatFileHandlesNonConcurrent m -> do+ Annex.changeState $ \s -> s { Annex.catfilehandles = CatFileHandlesNonConcurrent M.empty }+ return m+ CatFileHandlesPool tm ->+ liftIO $ atomically $ swapTMVar tm M.empty+ liftIO $ forM_ (M.elems m) $ \p ->+ freeResourcePool p Git.CatFile.catFileStop {- From ref to a symlink or a pointer file, get the key. -} catKey :: Ref -> Annex (Maybe Key)
Annex/ChangedRefs.hs view
@@ -1,6 +1,6 @@ {- Waiting for changed git refs -- - Copyright 2014-216 Joey Hess <id@joeyh.name>+ - Copyright 2014-2016 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -24,13 +24,14 @@ import Control.Concurrent import Control.Concurrent.STM import Control.Concurrent.STM.TBMChan+import qualified Data.ByteString as S newtype ChangedRefs = ChangedRefs [Git.Ref] deriving (Show) instance Proto.Serializable ChangedRefs where serialize (ChangedRefs l) = unwords $ map Git.fromRef l- deserialize = Just . ChangedRefs . map Git.Ref . words+ deserialize = Just . ChangedRefs . map (Git.Ref . encodeBS) . words data ChangedRefsHandle = ChangedRefsHandle DirWatcherHandle (TBMChan Git.Sha) @@ -97,7 +98,7 @@ | ".lock" `isSuffixOf` reffile = noop | otherwise = void $ do sha <- catchDefaultIO Nothing $- extractSha <$> readFile reffile+ extractSha <$> S.readFile reffile -- When the channel is full, there is probably no reader -- running, or ref changes have been occuring very fast, -- so it's ok to not write the change to it.
Annex/CheckAttr.hs view
@@ -1,19 +1,22 @@ {- git check-attr interface, with handle automatically stored in the Annex monad -- - Copyright 2012 Joey Hess <id@joeyh.name>+ - Copyright 2012-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} module Annex.CheckAttr ( checkAttr,- checkAttrHandle, checkAttrStop,+ mkConcurrentCheckAttrHandle, ) where import Annex.Common import qualified Git.CheckAttr as Git import qualified Annex+import Utility.ResourcePool+import Types.Concurrency+import Annex.Concurrent.Utility {- All gitattributes used by git-annex. -} annexAttrs :: [Git.Attr]@@ -24,21 +27,38 @@ ] checkAttr :: Git.Attr -> FilePath -> Annex String-checkAttr attr file = do- h <- checkAttrHandle+checkAttr attr file = withCheckAttrHandle $ \h -> liftIO $ Git.checkAttr h attr file -checkAttrHandle :: Annex Git.CheckAttrHandle-checkAttrHandle = maybe startup return =<< Annex.getState Annex.checkattrhandle+withCheckAttrHandle :: (Git.CheckAttrHandle -> Annex a) -> Annex a+withCheckAttrHandle a = + maybe mkpool go =<< Annex.getState Annex.checkattrhandle where- startup = do- h <- inRepo $ Git.checkAttrStart annexAttrs- Annex.changeState $ \s -> s { Annex.checkattrhandle = Just h }- return h+ go p = withResourcePool p start a+ start = inRepo $ Git.checkAttrStart annexAttrs+ mkpool = do+ -- This only runs in non-concurrent code paths;+ -- a concurrent pool is set up earlier when needed.+ p <- mkResourcePoolNonConcurrent start+ Annex.changeState $ \s -> s { Annex.checkattrhandle = Just p }+ go p +mkConcurrentCheckAttrHandle :: Concurrency -> Annex (ResourcePool Git.CheckAttrHandle)+mkConcurrentCheckAttrHandle c =+ Annex.getState Annex.checkattrhandle >>= \case+ Just p@(ResourcePool {}) -> return p+ _ -> mkResourcePool =<< liftIO (maxCheckAttrs c)++{- git check-attr is typically CPU bound, and is not likely to be the main+ - bottleneck for any command. So limit to the number of CPU cores, maximum,+ - while respecting the -Jn value.+ -}+maxCheckAttrs :: Concurrency -> IO Int+maxCheckAttrs = concurrencyUpToCpus+ checkAttrStop :: Annex () checkAttrStop = maybe noop stop =<< Annex.getState Annex.checkattrhandle where- stop h = do- liftIO $ Git.checkAttrStop h+ stop p = do+ liftIO $ freeResourcePool p Git.checkAttrStop Annex.changeState $ \s -> s { Annex.checkattrhandle = Nothing }
Annex/CheckIgnore.hs view
@@ -1,37 +1,57 @@ {- git check-ignore interface, with handle automatically stored in - the Annex monad -- - Copyright 2013 Joey Hess <id@joeyh.name>+ - Copyright 2013-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} module Annex.CheckIgnore ( checkIgnored,- checkIgnoreHandle,- checkIgnoreStop+ checkIgnoreStop,+ mkConcurrentCheckIgnoreHandle, ) where import Annex.Common import qualified Git.CheckIgnore as Git import qualified Annex+import Utility.ResourcePool+import Types.Concurrency+import Annex.Concurrent.Utility checkIgnored :: FilePath -> Annex Bool-checkIgnored file = go =<< checkIgnoreHandle- where- go h = liftIO $ Git.checkIgnored h file+checkIgnored file = withCheckIgnoreHandle $ \h ->+ liftIO $ Git.checkIgnored h file -checkIgnoreHandle :: Annex Git.CheckIgnoreHandle-checkIgnoreHandle = maybe startup return =<< Annex.getState Annex.checkignorehandle+withCheckIgnoreHandle :: (Git.CheckIgnoreHandle -> Annex a) -> Annex a+withCheckIgnoreHandle a =+ maybe mkpool go =<< Annex.getState Annex.checkignorehandle where- startup = do- h <- inRepo Git.checkIgnoreStart- Annex.changeState $ \s -> s { Annex.checkignorehandle = Just h }- return h+ go p = withResourcePool p start a+ start = inRepo Git.checkIgnoreStart+ mkpool = do+ -- This only runs in non-concurrent code paths;+ -- a concurrent pool is set up earlier when needed.+ p <- mkResourcePoolNonConcurrent start+ Annex.changeState $ \s -> s { Annex.checkignorehandle = Just p }+ go p +mkConcurrentCheckIgnoreHandle :: Concurrency -> Annex (ResourcePool Git.CheckIgnoreHandle)+mkConcurrentCheckIgnoreHandle c =+ Annex.getState Annex.checkignorehandle >>= \case+ Just p@(ResourcePool {}) -> return p+ _ -> mkResourcePool =<< liftIO (maxCheckIgnores c)++{- git check-ignore is typically CPU bound, and is not likely to be the main+ - bottleneck for any command. So limit to the number of CPU cores, maximum,+ - while respecting the -Jn value.+ -}+maxCheckIgnores :: Concurrency -> IO Int+maxCheckIgnores = concurrencyUpToCpus+ checkIgnoreStop :: Annex () checkIgnoreStop = maybe noop stop =<< Annex.getState Annex.checkignorehandle where- stop h = do- liftIO $ Git.checkIgnoreStop h+ stop p = do+ liftIO $ freeResourcePool p Git.checkIgnoreStop Annex.changeState $ \s -> s { Annex.checkignorehandle = Nothing }
Annex/Concurrent.hs view
@@ -1,6 +1,6 @@ {- git-annex concurrent state -- - Copyright 2015-2019 Joey Hess <id@joeyh.name>+ - Copyright 2015-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -10,16 +10,36 @@ import Annex import Annex.Common import qualified Annex.Queue-import Annex.CatFile+import Annex.Action+import Types.Concurrency+import Types.WorkerPool+import Types.CatFileHandles import Annex.CheckAttr-import Annex.HashObject import Annex.CheckIgnore-import Types.WorkerPool+import Remote.List import Control.Concurrent import Control.Concurrent.STM import qualified Data.Map as M +setConcurrency :: Concurrency -> Annex ()+setConcurrency NonConcurrent = Annex.changeState $ \s -> s + { Annex.concurrency = NonConcurrent+ }+setConcurrency c = do+ cfh <- Annex.getState Annex.catfilehandles+ cfh' <- case cfh of+ CatFileHandlesNonConcurrent _ -> liftIO catFileHandlesPool+ CatFileHandlesPool _ -> pure cfh+ cah <- mkConcurrentCheckAttrHandle c+ cih <- mkConcurrentCheckIgnoreHandle c+ Annex.changeState $ \s -> s+ { Annex.concurrency = c+ , Annex.catfilehandles = cfh'+ , Annex.checkattrhandle = Just cah+ , Annex.checkignorehandle = Just cih+ }+ {- Allows forking off a thread that uses a copy of the current AnnexState - to run an Annex action. -@@ -46,14 +66,22 @@ -} dupState :: Annex AnnexState dupState = do+ -- Make sure that some expensive actions have been done before+ -- starting threads. This way the state has them already run,+ -- and each thread won't try to do them.+ _ <- remoteList+ st <- Annex.getState id- return $ st+ -- Make sure that concurrency is enabled, if it was not already,+ -- so the concurrency-safe resource pools are set up.+ st' <- case Annex.concurrency st of+ NonConcurrent -> do+ setConcurrency (Concurrent 1)+ Annex.getState id+ _ -> return st+ return $ st' -- each thread has its own repoqueue { Annex.repoqueue = Nothing- -- avoid sharing eg, open file handles- , Annex.catfilehandles = M.empty- , Annex.checkattrhandle = Nothing- , Annex.checkignorehandle = Nothing } {- Merges the passed AnnexState into the current Annex state.@@ -65,14 +93,6 @@ uncurry addCleanup Annex.Queue.mergeFrom st' changeState $ \s -> s { errcounter = errcounter s + errcounter st' }--{- Stops all long-running git query processes. -}-stopCoProcesses :: Annex ()-stopCoProcesses = do- catFileStop- checkAttrStop- hashObjectStop- checkIgnoreStop {- Runs an action and makes the current thread have the specified stage - while doing so. If too many other threads are running in the specified
+ Annex/Concurrent/Utility.hs view
@@ -0,0 +1,23 @@+{- git-annex concurrency utilities+ -+ - Copyright 2020 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Annex.Concurrent.Utility where++import Types.Concurrency++import GHC.Conc++{- Honor the requested level of concurrency, but only up to the number of+ - CPU cores. Useful for things that are known to be CPU bound. -}+concurrencyUpToCpus :: Concurrency -> IO Int+concurrencyUpToCpus c = do+ let cn = case c of+ Concurrent n -> n+ NonConcurrent -> 1+ ConcurrentPerCpu -> 1+ pn <- getNumProcessors+ return (min cn pn)
Annex/Content.hs view
@@ -74,6 +74,7 @@ import Annex.Perms import Annex.Link import Annex.LockPool+import Annex.WorkerPool import Messages.Progress import Types.Remote (unVerified, Verification(..), RetrievalSecurityPolicy(..)) import qualified Types.Remote@@ -87,7 +88,6 @@ import Utility.InodeCache import Annex.Content.LowLevel import Annex.Content.PointerFile-import Annex.Concurrent import Types.WorkerPool import qualified Utility.RawFilePath as R
Annex/Export.hs view
@@ -14,7 +14,6 @@ import qualified Git import qualified Types.Remote as Remote import Messages-import Utility.FileSystemEncoding import Control.Applicative import Data.Maybe@@ -34,7 +33,7 @@ where mk (Just k) = AnnexKey k mk Nothing = GitKey $ mkKey $ \k -> k- { keyName = encodeBS $ Git.fromRef sha+ { keyName = Git.fromRef' sha , keyVariety = SHA1Key (HasExt False) , keySize = Nothing , keyMtime = Nothing
Annex/GitOverlay.hs view
@@ -1,15 +1,19 @@ {- Temporarily changing the files git uses. -- - Copyright 2014-2016 Joey Hess <id@joeyh.name>+ - Copyright 2014-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} -module Annex.GitOverlay where+module Annex.GitOverlay (+ module Annex.GitOverlay,+ AltIndexFile(..),+) where import qualified Control.Exception as E import Annex.Common+import Types.IndexFiles import Git import Git.Types import Git.Index@@ -18,13 +22,8 @@ import qualified Annex.Queue {- Runs an action using a different git index file. -}-withIndexFile :: FilePath -> Annex a -> Annex a-withIndexFile f a = do- f' <- liftIO $ indexEnvVal f- withAltRepo- (usecachedgitenv f' $ \g -> addGitEnv g indexEnv f')- (\g g' -> g' { gitEnv = gitEnv g })- a+withIndexFile :: AltIndexFile -> (FilePath -> Annex a) -> Annex a+withIndexFile i = withAltRepo usecachedgitenv restoregitenv where -- This is an optimisation. Since withIndexFile is run repeatedly, -- typically with the same file, and addGitEnv uses the slow@@ -37,22 +36,40 @@ -- Git object in the first place, but it's more efficient to let -- the environment be inherited in all calls to git where it -- does not need to be modified.)- usecachedgitenv f' m g = case gitEnv g of- Just _ -> liftIO $ m g+ --+ -- Also, the use of AltIndexFile avoids needing to construct+ -- the FilePath each time, which saves enough time to be worth the+ -- added complication.+ usecachedgitenv g = case gitEnv g of Nothing -> Annex.withState $ \s -> case Annex.cachedgitenv s of- Just (cachedf, cachede) | f' == cachedf ->- return (s, g { gitEnv = Just cachede })+ Just (cachedi, cachedf, cachede) | i == cachedi ->+ return (s, (g { gitEnv = Just cachede }, cachedf)) _ -> do- g' <- m g- return (s { Annex.cachedgitenv = (,) <$> Just f' <*> gitEnv g' }, g')+ r@(g', f) <- addindex g+ let cache = (,,)+ <$> Just i+ <*> Just f+ <*> gitEnv g'+ return (s { Annex.cachedgitenv = cache }, r)+ Just _ -> liftIO $ addindex g+ + addindex g = do+ f <- indexEnvVal $ case i of+ AnnexIndexFile -> gitAnnexIndex g+ ViewIndexFile -> gitAnnexViewIndex g+ g' <- addGitEnv g indexEnv f+ return (g', f)+ + restoregitenv g g' = g' { gitEnv = gitEnv g } {- Runs an action using a different git work tree. - - Smudge and clean filters are disabled in this work tree. -} withWorkTree :: FilePath -> Annex a -> Annex a-withWorkTree d = withAltRepo- (\g -> return $ g { location = modlocation (location g), gitGlobalOpts = gitGlobalOpts g ++ disableSmudgeConfig })+withWorkTree d a = withAltRepo+ (\g -> return $ (g { location = modlocation (location g), gitGlobalOpts = gitGlobalOpts g ++ disableSmudgeConfig }, ())) (\g g' -> g' { location = location g, gitGlobalOpts = gitGlobalOpts g })+ (const a) where modlocation l@(Local {}) = l { worktree = Just (toRawFilePath d) } modlocation _ = error "withWorkTree of non-local git repo"@@ -70,29 +87,29 @@ - Needs git 2.2.0 or newer. -} withWorkTreeRelated :: FilePath -> Annex a -> Annex a-withWorkTreeRelated d = withAltRepo modrepo unmodrepo+withWorkTreeRelated d a = withAltRepo modrepo unmodrepo (const a) where modrepo g = liftIO $ do g' <- addGitEnv g "GIT_COMMON_DIR" =<< absPath (fromRawFilePath (localGitDir g)) g'' <- addGitEnv g' "GIT_DIR" d- return (g'' { gitEnvOverridesGitDir = True })+ return (g'' { gitEnvOverridesGitDir = True }, ()) unmodrepo g g' = g' { gitEnv = gitEnv g , gitEnvOverridesGitDir = gitEnvOverridesGitDir g } withAltRepo - :: (Repo -> Annex Repo)+ :: (Repo -> Annex (Repo, t)) -- ^ modify Repo -> (Repo -> Repo -> Repo) -- ^ undo modifications; first Repo is the original and second -- is the one after running the action.- -> Annex a+ -> (t -> Annex a) -> Annex a withAltRepo modrepo unmodrepo a = do g <- gitRepo- g' <- modrepo g+ (g', t) <- modrepo g q <- Annex.Queue.get v <- tryNonAsync $ do Annex.changeState $ \s -> s@@ -101,7 +118,7 @@ -- with the modified repo. , Annex.repoqueue = Nothing }- a+ a t void $ tryNonAsync Annex.Queue.flush Annex.changeState $ \s -> s { Annex.repo = unmodrepo g (Annex.repo s)
Annex/Import.hs view
@@ -122,7 +122,9 @@ Nothing -> pure committedtree Just dir -> let subtreeref = Ref $- fromRef committedtree ++ ":" ++ fromRawFilePath (getTopFilePath dir)+ fromRef' committedtree + <> ":"+ <> getTopFilePath dir in fromMaybe emptyTree <$> inRepo (Git.Ref.tree subtreeref) updateexportdb importedtree
Annex/Multicast.hs view
@@ -7,7 +7,7 @@ module Annex.Multicast where -import Config.Files+import Annex.Path import Utility.Env import Utility.PartialPrelude @@ -22,7 +22,7 @@ multicastCallbackEnv :: IO (FilePath, [(String, String)], Handle) multicastCallbackEnv = do- gitannex <- readProgramFile+ gitannex <- programPath -- This will even work on Windows (rfd, wfd) <- createPipeFd rh <- fdToHandle rfd
Annex/Path.hs view
@@ -32,5 +32,16 @@ exe <- getExecutablePath p <- if isAbsolute exe then return exe- else readProgramFile+ else fromMaybe exe <$> readProgramFile maybe cannotFindProgram return =<< searchPath p++{- Returns the path for git-annex that is recorded in the programFile. -}+readProgramFile :: IO (Maybe FilePath)+readProgramFile = do+ programfile <- programFile+ headMaybe . lines <$> readFile programfile++cannotFindProgram :: IO a+cannotFindProgram = do+ f <- programFile+ giveup $ "cannot find git-annex program in PATH or in " ++ f
Annex/SpecialRemote.hs view
@@ -22,7 +22,6 @@ import Remote.List import Logs.Remote import Logs.Trust-import qualified Git.Config import qualified Types.Remote as Remote import Git.Types (RemoteName) @@ -99,7 +98,7 @@ _ -> return () where configured rc = fromMaybe False $- Git.Config.isTrueFalse . fromProposedAccepted+ trueFalseParser' . fromProposedAccepted =<< M.lookup autoEnableField rc canenable u = (/= DeadTrusted) <$> lookupTrust u getenabledremotes = M.fromList
Annex/SpecialRemote/Config.hs view
@@ -17,7 +17,6 @@ import Types.ProposedAccepted import Types.RemoteConfig import Types.GitConfig-import qualified Git.Config import qualified Data.Map as M import qualified Data.Set as S@@ -242,8 +241,16 @@ yesno _ = Nothing trueFalseParser :: RemoteConfigField -> Bool -> FieldDesc -> RemoteConfigFieldParser-trueFalseParser f v fd = genParser Git.Config.isTrueFalse f v fd+trueFalseParser f v fd = genParser trueFalseParser' f v fd (Just (ValueDesc "true or false"))++-- Not using Git.Config.isTrueFalse because git supports+-- a lot of other values for true and false in its configs,+-- and this is not a git config and we want to avoid that mess.+trueFalseParser' :: String -> Maybe Bool+trueFalseParser' "true" = Just True+trueFalseParser' "false" = Just False+trueFalseParser' _ = Nothing genParser :: Typeable t
Annex/TaggedPush.hs view
@@ -5,6 +5,8 @@ - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE OverloadedStrings #-}+ module Annex.TaggedPush where import Annex.Common@@ -16,6 +18,8 @@ import qualified Git.Branch import Utility.Base64 +import qualified Data.ByteString as S+ {- Converts a git branch into a branch that is tagged with a UUID, typically - the UUID of the repo that will be pushing it, and possibly with other - information.@@ -31,11 +35,11 @@ - refs, per git-check-ref-format. -} toTaggedBranch :: UUID -> Maybe String -> Git.Branch -> Git.Ref-toTaggedBranch u info b = Git.Ref $ intercalate "/" $ catMaybes+toTaggedBranch u info b = Git.Ref $ S.intercalate "/" $ catMaybes [ Just "refs/synced" , Just $ fromUUID u- , toB64 <$> info- , Just $ Git.fromRef $ Git.Ref.base b+ , toB64' . encodeBS <$> info+ , Just $ Git.fromRef' $ Git.Ref.base b ] fromTaggedBranch :: Git.Ref -> Maybe (UUID, Maybe String)
Annex/Transfer.hs view
@@ -30,9 +30,9 @@ import Annex.LockPool import Types.Key import qualified Types.Remote as Remote-import Annex.Concurrent import Types.Concurrency import Types.WorkerPool+import Annex.WorkerPool import Control.Concurrent import qualified Data.Map.Strict as M
Annex/UUID.hs view
@@ -111,7 +111,7 @@ setUUID :: Git.Repo -> UUID -> IO Git.Repo setUUID r u = do let s = encodeBS' $ show configkeyUUID ++ "=" ++ fromUUID u- Git.Config.store s r+ Git.Config.store s Git.Config.ConfigList r -- Dummy uuid for the whole web. Do not alter. webUUID :: UUID
Annex/Url.hs view
@@ -177,9 +177,5 @@ Right b -> return b Left err -> warning err >> return False -getUrlInfo :: U.URLString -> U.UrlOptions -> Annex U.UrlInfo-getUrlInfo url uo = liftIO (U.getUrlInfo url uo) >>= \case- Right i -> return i- Left err -> do- warning err- return $ U.UrlInfo False Nothing Nothing+getUrlInfo :: U.URLString -> U.UrlOptions -> Annex (Either String U.UrlInfo)+getUrlInfo url uo = liftIO (U.getUrlInfo url uo)
Annex/View.hs view
@@ -412,9 +412,7 @@ - Note that the file does not necessarily exist, or can contain - info staged for an old view. -} withViewIndex :: Annex a -> Annex a-withViewIndex a = do- f <- fromRepo gitAnnexViewIndex- withIndexFile f a+withViewIndex = withIndexFile ViewIndexFile . const {- Generates a branch for a view, using the view index file - to make a commit to the view branch. The view branch is not
+ Annex/WorkerPool.hs view
@@ -0,0 +1,126 @@+{- git-annex worker thread pool+ -+ - Copyright 2015-2019 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Annex.WorkerPool where++import Annex+import Annex.Common+import Types.WorkerPool++import Control.Concurrent+import Control.Concurrent.STM++{- Runs an action and makes the current thread have the specified stage+ - while doing so. If too many other threads are running in the specified+ - stage, waits for one of them to become idle.+ -+ - Noop if the current thread already has the requested stage, or if the+ - current thread is not in the worker pool, or if concurrency is not+ - enabled.+ -+ - Also a noop if the stage is not one of the stages that the worker pool+ - uses.+ -}+enteringStage :: WorkerStage -> Annex a -> Annex a+enteringStage newstage a = Annex.getState Annex.workers >>= \case+ Nothing -> a+ Just tv -> do+ mytid <- liftIO myThreadId+ let set = changeStageTo mytid tv (const newstage)+ let restore = maybe noop (void . changeStageTo mytid tv . const)+ bracket set restore (const a)++{- Transition the current thread to the initial stage.+ - This is done once the thread is ready to begin work.+ -}+enteringInitialStage :: Annex ()+enteringInitialStage = Annex.getState Annex.workers >>= \case+ Nothing -> noop+ Just tv -> do+ mytid <- liftIO myThreadId+ void $ changeStageTo mytid tv initialStage++{- This needs to leave the WorkerPool with the same number of+ - idle and active threads, and with the same number of threads for each+ - WorkerStage. So, all it can do is swap the WorkerStage of our thread's+ - ActiveWorker with an IdleWorker.+ -+ - Must avoid a deadlock if all worker threads end up here at the same+ - time, or if there are no suitable IdleWorkers left. So if necessary+ - we first replace our ActiveWorker with an IdleWorker in the pool, to allow+ - some other thread to use it, before waiting for a suitable IdleWorker+ - for us to use.+ -+ - Note that the spareVals in the WorkerPool does not get anything added to+ - it when adding the IdleWorker, so there will for a while be more IdleWorkers+ - in the pool than spareVals. That does not prevent other threads that call+ - this from using them though, so it's fine.+ -}+changeStageTo :: ThreadId -> TMVar (WorkerPool AnnexState) -> (UsedStages -> WorkerStage) -> Annex (Maybe WorkerStage)+changeStageTo mytid tv getnewstage = liftIO $+ replaceidle >>= maybe+ (return Nothing)+ (either waitidle (return . Just))+ where+ replaceidle = atomically $ do+ pool <- takeTMVar tv+ let newstage = getnewstage (usedStages pool)+ let notchanging = do+ putTMVar tv pool+ return Nothing+ if memberStage newstage (usedStages pool)+ then case removeThreadIdWorkerPool mytid pool of+ Just ((myaid, oldstage), pool')+ | oldstage /= newstage -> case getIdleWorkerSlot newstage pool' of+ Nothing -> do+ putTMVar tv $+ addWorkerPool (IdleWorker oldstage) pool'+ return $ Just $ Left (myaid, newstage, oldstage)+ Just pool'' -> do+ -- optimisation+ putTMVar tv $+ addWorkerPool (IdleWorker oldstage) $+ addWorkerPool (ActiveWorker myaid newstage) pool''+ return $ Just $ Right oldstage+ | otherwise -> notchanging+ _ -> notchanging+ else notchanging+ + waitidle (myaid, newstage, oldstage) = atomically $ do+ pool <- waitIdleWorkerSlot newstage =<< takeTMVar tv+ putTMVar tv $ addWorkerPool (ActiveWorker myaid newstage) pool+ return (Just oldstage)++-- | Waits until there's an idle StartStage worker in the worker pool,+-- removes it from the pool, and returns its state.+--+-- If the worker pool is not already allocated, returns Nothing.+waitStartWorkerSlot :: TMVar (WorkerPool Annex.AnnexState) -> STM (Maybe (Annex.AnnexState, WorkerStage))+waitStartWorkerSlot tv = do+ pool <- takeTMVar tv+ st <- go pool+ return $ Just (st, StartStage)+ where+ go pool = case spareVals pool of+ [] -> retry+ (v:vs) -> do+ let pool' = pool { spareVals = vs }+ putTMVar tv =<< waitIdleWorkerSlot StartStage pool'+ return v++waitIdleWorkerSlot :: WorkerStage -> WorkerPool Annex.AnnexState -> STM (WorkerPool Annex.AnnexState)+waitIdleWorkerSlot wantstage = maybe retry return . getIdleWorkerSlot wantstage++getIdleWorkerSlot :: WorkerStage -> WorkerPool Annex.AnnexState -> Maybe (WorkerPool Annex.AnnexState)+getIdleWorkerSlot wantstage pool = do+ l <- findidle [] (workerList pool)+ return $ pool { workerList = l }+ where+ findidle _ [] = Nothing+ findidle c ((IdleWorker stage):rest)+ | stage == wantstage = Just (c ++ rest)+ findidle c (w:rest) = findidle (w:c) rest
Assistant.hs view
@@ -50,6 +50,7 @@ import Utility.HumanTime import qualified BuildInfo import Annex.Perms+import Annex.BranchState import Utility.LogFile #ifdef mingw32_HOST_OS import Utility.Env@@ -70,8 +71,8 @@ - stdout and stderr descriptors. -} startDaemon :: Bool -> Bool -> Maybe Duration -> Maybe String -> Maybe HostName -> Maybe (Maybe Handle -> Maybe Handle -> String -> FilePath -> IO ()) -> Annex () startDaemon assistant foreground startdelay cannotrun listenhost startbrowser = do- Annex.changeState $ \s -> s { Annex.daemon = True }+ enableInteractiveJournalAccess pidfile <- fromRepo gitAnnexPidFile logfile <- fromRepo gitAnnexLogFile liftIO $ debugM desc $ "logging to " ++ logfile
Assistant/Sync.hs view
@@ -216,7 +216,8 @@ , return $ Just r ) else return Nothing- haddiverged <- liftAnnex Annex.Branch.forceUpdate+ haddiverged <- Annex.Branch.refsWereMerged+ <$> liftAnnex Annex.Branch.forceUpdate forM_ remotes $ \r -> liftAnnex $ Command.Sync.mergeRemote r currentbranch Command.Sync.mergeConfig def
Assistant/Threads/Merger.hs view
@@ -65,7 +65,8 @@ | ".lock" `isSuffixOf` file = noop | isAnnexBranch file = do branchChanged- diverged <- liftAnnex Annex.Branch.forceUpdate+ diverged <- Annex.Branch.refsWereMerged+ <$> liftAnnex Annex.Branch.forceUpdate when diverged $ do updateExportTreeFromLogAll queueDeferredDownloads "retrying deferred download" Later@@ -109,6 +110,6 @@ n = '/' : Git.fromRef Annex.Branch.name fileToBranch :: FilePath -> Git.Ref-fileToBranch f = Git.Ref $ "refs" </> base+fileToBranch f = Git.Ref $ encodeBS' $ "refs" </> base where base = Prelude.last $ split "/refs/" f
Assistant/Threads/Watcher.hs view
@@ -330,7 +330,7 @@ addLink file link mk = do debug ["add symlink", file] liftAnnex $ do- v <- catObjectDetails $ Ref $ ':':file+ v <- catObjectDetails $ Ref $ encodeBS' $ ':':file case v of Just (currlink, sha, _type) | s2w8 link == L.unpack currlink ->
Assistant/Upgrade.hs view
@@ -225,20 +225,22 @@ {- Finds where the old version was installed. -} oldVersionLocation :: IO FilePath-oldVersionLocation = do- pdir <- parentDir <$> readProgramFile+oldVersionLocation = readProgramFile >>= \case+ Nothing -> error "Cannot find old distribution bundle; not upgrading."+ Just pf -> do+ let pdir = parentDir pf #ifdef darwin_HOST_OS- let dirs = splitDirectories pdir- {- It will probably be deep inside a git-annex.app directory. -}- let olddir = case findIndex ("git-annex.app" `isPrefixOf`) dirs of- Nothing -> pdir- Just i -> joinPath (take (i + 1) dirs)+ let dirs = splitDirectories pdir+ {- It will probably be deep inside a git-annex.app directory. -}+ let olddir = case findIndex ("git-annex.app" `isPrefixOf`) dirs of+ Nothing -> pdir+ Just i -> joinPath (take (i + 1) dirs) #else- let olddir = pdir+ let olddir = pdir #endif- when (null olddir) $- error $ "Cannot find old distribution bundle; not upgrading. (Looked in " ++ pdir ++ ")"- return olddir+ when (null olddir) $+ error $ "Cannot find old distribution bundle; not upgrading. (Looked in " ++ pdir ++ ")"+ return olddir {- Finds a place to install the new version. - Generally, put it in the parent directory of where the old version was@@ -344,10 +346,9 @@ - trustedkeys.gpg, next to the git-annex program. -} verifyDistributionSig :: GpgCmd -> FilePath -> IO Bool-verifyDistributionSig gpgcmd sig = do- p <- readProgramFile- if isAbsolute p- then withUmask 0o0077 $ withTmpDir "git-annex-gpg.tmp" $ \gpgtmp -> do+verifyDistributionSig gpgcmd sig = readProgramFile >>= \case+ Just p | isAbsolute p ->+ withUmask 0o0077 $ withTmpDir "git-annex-gpg.tmp" $ \gpgtmp -> do let trustedkeys = takeDirectory p </> "trustedkeys.gpg" boolGpgCmd gpgcmd [ Param "--no-default-keyring"@@ -360,4 +361,4 @@ , Param "--verify" , File sig ]- else return False+ _ -> return False
Assistant/WebApp/Configurators/Pairing.hs view
@@ -33,7 +33,7 @@ import Command.P2P (unusedPeerRemoteName, PairingResult(..)) import P2P.Address import Git-import Config.Files+import Annex.Path import Utility.Process.Transcript import qualified Data.Map as M@@ -72,7 +72,7 @@ enableTor :: Handler () enableTor = do- gitannex <- liftIO readProgramFile+ gitannex <- liftIO programPath (transcript, ok) <- liftIO $ processTranscript gitannex ["enable-tor"] Nothing if ok -- Reload remotedameon so it's serving the tor hidden
Benchmark.hs view
@@ -28,7 +28,7 @@ -- so skewing the runtime of the first action that will be -- benchmarked. Annex.Branch.commit "benchmarking"- Annex.Branch.update+ _ <- Annex.Branch.update l <- mapM parsesubcommand $ split [";"] userinput return $ do forM_ l $ \(cmd, seek, st) ->
CHANGELOG view
@@ -1,3 +1,52 @@+git-annex (8.20200501) upstream; urgency=medium++ * Improve git-annex's ability to find the path to its program,+ especially when it needs to run itself in another repo to upgrade it.+ * adb: Better messages when the adb command is not installed.+ * Sped up query commands that read the git-annex branch by around 9%.+ * Various speed improvements gained by using ByteStrings for git refs and+ shas.+ * Fix a potential failure to parse git config.+ * Support boolean git configs that are represented by the name of the+ setting with no value, eg "core.bare" is the same as "core.bare = true".+ * When parsing git configs, support all the documented ways to write+ true and false, including "yes", "on", "1", etc.+ * Fix --batch commands (and git-annex info) to accept absolute filenames + for unlocked files, which already worked for locked files.+ * Avoid repeatedly opening keys db when accessing a local git remote+ and -J is used.+ * Avoid running a large number of git cat-file child processes when run+ with a large -J value.+ * Avoid running with more git check-attr and check-ignore processes than+ there are CPU cores when run with a large -J value.+ * get --from, move --from: When used with a local git remote, these used+ to silently skip files that the location log thought were present on the+ remote, when the remote actually no longer contained them. Since that+ behavior could be surprising, now instead display a warning.+ * external special remotes: remote.name.annex-readonly=true no longer+ disables running the external special remote program. Instead, it just+ makes the remote operate in a readonly mode, same as any remote.+ To disable running the external special remote program, now need to set+ remote.name.annex-externaltype=readonly. That is done when+ git-annex enableremote is passed readonly=true.+ * Stop storing readonly=true in remote.log of external special remotes;+ it is a local setting only.+ * sync: When some remotes to sync with are specified, and --fast is too,+ pick the lowest cost of the specified remotes, do not sync with a+ faster remote that was not specified.+ * addurl: When run with --fast on an url that + annex.security.allowed-ip-addresses prevents accessing, display+ a more useful message.+ * When the required content is set to "groupwanted", use whatever+ expression has been set in groupwanted as the required content of the+ repo, similar to how setting required content to "standard" already+ worked.+ * Avoid a test suite failure when the environment does not let gpg be+ tested due to eg, too long a path to the agent socket.+ * test: Include testremote tests, run on a directory special remote.++ -- Joey Hess <id@joeyh.name> Fri, 01 May 2020 13:09:24 -0400+ git-annex (8.20200330) upstream; urgency=medium * fsck: Fix reversion in 8.20200226 that made it incorrectly warn
CmdLine/Action.hs view
@@ -17,7 +17,6 @@ import Messages.Concurrent import Types.Messages import Types.WorkerPool-import Remote.List import Control.Concurrent import Control.Concurrent.Async@@ -200,8 +199,7 @@ startConcurrency usedstages a = do fromcmdline <- Annex.getState Annex.concurrency fromgitcfg <- annexJobs <$> Annex.getGitConfig- let usegitcfg = Annex.changeState $ - \c -> c { Annex.concurrency = fromgitcfg }+ let usegitcfg = setConcurrency fromgitcfg case (fromcmdline, fromgitcfg) of (NonConcurrent, NonConcurrent) -> a (Concurrent n, _) ->@@ -245,11 +243,6 @@ liftIO $ setNumCapabilities n initworkerpool n = do- -- Generate the remote list now, to avoid each thread- -- generating it, which would be more expensive and- -- could cause threads to contend over eg, calls to- -- setConfig.- _ <- remoteList tv <- liftIO newEmptyTMVarIO Annex.changeState $ \s -> s { Annex.workers = Just tv } st <- dupState
CmdLine/Batch.hs view
@@ -14,6 +14,7 @@ import Options.Applicative import Limit import Types.FileMatcher+import Annex.BranchState data BatchMode = Batch BatchFormat | NoBatch @@ -49,7 +50,7 @@ batchseeker (opts, NoBatch, params) = mapM_ (go NoBatch opts) params batchseeker (opts, batchmode@(Batch fmt), _) = - batchInput fmt Right (go batchmode opts)+ batchInput fmt (pure . Right) (go batchmode opts) go batchmode opts p = unlessM (handler opts p) $@@ -61,18 +62,26 @@ batchBadInput NoBatch = liftIO exitFailure batchBadInput (Batch _) = liftIO $ putStrLn "" --- Reads lines of batch mode input and passes to the action to handle.-batchInput :: BatchFormat -> (String -> Either String a) -> (a -> Annex ()) -> Annex ()+-- Reads lines of batch mode input, runs a parser, and passes the result+-- to the action.+--+-- Note that if the batch input includes a worktree filename, it should+-- be converted to relative. Normally, filename parameters are passed+-- through git ls-files, which makes them relative, but batch mode does+-- not use that, and absolute worktree files are likely to cause breakage.+batchInput :: BatchFormat -> (String -> Annex (Either String a)) -> (a -> Annex ()) -> Annex () batchInput fmt parser a = go =<< batchLines fmt where go [] = return () go (l:rest) = do- either parseerr a (parser l)+ either parseerr a =<< parser l go rest parseerr s = giveup $ "Batch input parse failure: " ++ s batchLines :: BatchFormat -> Annex [String]-batchLines fmt = liftIO $ splitter <$> getContents+batchLines fmt = do+ enableInteractiveJournalAccess+ liftIO $ splitter <$> getContents where splitter = case fmt of BatchLine -> lines@@ -92,9 +101,12 @@ -- Reads lines of batch input and passes the filepaths to a CommandStart -- to handle them. --+-- Absolute filepaths are converted to relative.+-- -- File matching options are not checked.-batchStart :: BatchFormat -> (String -> CommandStart) -> Annex ()-batchStart fmt a = batchInput fmt Right $ batchCommandAction . a+batchStart :: BatchFormat -> (FilePath -> CommandStart) -> Annex ()+batchStart fmt a = batchInput fmt (Right <$$> liftIO . relPathCwdToFile) $+ batchCommandAction . a -- Like batchStart, but checks the file matching options -- and skips non-matching files.
CmdLine/GitAnnex/Options.hs view
@@ -36,6 +36,7 @@ import qualified Backend import qualified Types.Backend as Backend import Utility.HumanTime+import Annex.Concurrent -- Global options that are accepted by all git-annex sub-commands, -- although not always used.@@ -96,7 +97,7 @@ setgitconfig v = Annex.adjustGitRepo $ \r -> if Param v `elem` gitGlobalOpts r then return r- else Git.Config.store (encodeBS' v) $ + else Git.Config.store (encodeBS' v) Git.Config.ConfigList $ r { gitGlobalOpts = gitGlobalOpts r ++ [Param "-c", Param v] } setdesktopnotify v = Annex.changeState $ \s -> s { Annex.desktopnotify = Annex.desktopnotify s <> v } @@ -395,7 +396,7 @@ -- action in `allowConcurrentOutput`. jobsOption :: [GlobalOption] jobsOption = - [ globalSetter set $ + [ globalSetter setConcurrency $ option (maybeReader parseConcurrency) ( long "jobs" <> short 'J' <> metavar (paramNumber `paramOr` "cpus")@@ -403,8 +404,6 @@ <> hidden ) ]- where- set v = Annex.changeState $ \s -> s { Annex.concurrency = v } timeLimitOption :: [GlobalOption] timeLimitOption =
Command/AddUrl.hs view
@@ -103,7 +103,7 @@ else checkUrl addunlockedmatcher r o' u forM_ (addUrls o) (\u -> go (o, u)) case batchOption o of- Batch fmt -> batchInput fmt (parseBatchInput o) go+ Batch fmt -> batchInput fmt (pure . parseBatchInput o) go NoBatch -> noop parseBatchInput :: AddUrlOptions -> String -> Either String (AddUrlOptions, URLString)@@ -194,11 +194,16 @@ where bad = fromMaybe (giveup $ "bad url " ++ urlstring) $ Url.parseURIRelaxed $ urlstring- go url = startingAddUrl urlstring o $ do+ go url = startingAddUrl urlstring o $+ if relaxedOption (downloadOptions o)+ then go' url Url.assumeUrlExists+ else Url.withUrlOptions (Url.getUrlInfo urlstring) >>= \case+ Right urlinfo -> go' url urlinfo+ Left err -> do+ warning err+ next $ return False+ go' url urlinfo = do pathmax <- liftIO $ fileNameLengthLimit "."- urlinfo <- if relaxedOption (downloadOptions o)- then pure Url.assumeUrlExists- else Url.withUrlOptions $ Url.getUrlInfo urlstring file <- adjustFile o <$> case fileOption (downloadOptions o) of Just f -> pure f Nothing -> case Url.urlSuggestedFile urlinfo of
Command/CheckPresentKey.hs view
@@ -38,7 +38,7 @@ (rn:[]) -> toRemote rn >>= \r -> return (flip check (Just r)) [] -> return (flip check Nothing) _ -> wrongnumparams- batchInput fmt Right $ checker >=> batchResult+ batchInput fmt (pure . Right) $ checker >=> batchResult where wrongnumparams = giveup "Wrong number of parameters"
Command/Config.hs view
@@ -69,8 +69,9 @@ seek (GetConfig ck) = checkIsGlobalConfig ck $ commandAction $ startingCustomOutput (ActionItemOther Nothing) $ do getGlobalConfig ck >>= \case- Nothing -> return () Just (ConfigValue v) -> liftIO $ S8.putStrLn v+ Just NoConfigValue -> return ()+ Nothing -> return () next $ return True checkIsGlobalConfig :: ConfigKey -> Annex a -> Annex a
Command/DropKey.hs view
@@ -35,7 +35,8 @@ giveup "dropkey can cause data loss; use --force if you're sure you want to do this" withKeys (commandAction . start) (toDrop o) case batchOption o of- Batch fmt -> batchInput fmt parsekey $ batchCommandAction . start+ Batch fmt -> batchInput fmt (pure . parsekey) $+ batchCommandAction . start NoBatch -> noop where parsekey = maybe (Left "bad key") Right . deserializeKey
Command/EnableTor.hs view
@@ -16,7 +16,7 @@ import Utility.Tor import Annex.UUID #ifndef mingw32_HOST_OS-import Config.Files+import Annex.Path #endif import P2P.IO import qualified P2P.Protocol as P2P@@ -53,7 +53,7 @@ Nothing -> giveup "Need user-id parameter." Just userid -> go userid else starting "enable-tor" (ActionItemOther Nothing) $ do- gitannex <- liftIO readProgramFile+ gitannex <- liftIO programPath let ps = [Param (cmdname cmd), Param (show curruserid)] sucommand <- liftIO $ mkSuCommand gitannex ps maybe noop showLongNote
Command/Export.hs view
@@ -37,6 +37,7 @@ import Utility.Metered import Utility.Matcher +import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L import qualified Data.Map as M import Control.Concurrent@@ -112,7 +113,7 @@ return (fmap (tb, ) commitsha) | otherwise = return Nothing where- baseref = Ref $ takeWhile (/= ':') $ fromRef $ + baseref = Ref $ S8.takeWhile (/= ':') $ fromRef' $ Git.Ref.removeBase refsheads treeish refsheads = "refs/heads"
Command/FindRef.hs view
@@ -22,6 +22,6 @@ where o' = o { Find.keyOptions = Just $ WantBranchKeys $- map Git.Ref (Find.findThese o)+ map (Git.Ref . encodeBS') (Find.findThese o) , Find.findThese = [] }
Command/Forget.hs view
@@ -45,7 +45,7 @@ perform ts True = do recordTransitions Branch.change ts -- get branch committed before contining with the transition- Branch.update+ _ <- Branch.update void $ Branch.performTransitions ts True [] next $ return True perform _ False = do
Command/FromKey.hs view
@@ -47,11 +47,14 @@ seekBatch :: BatchFormat -> CommandSeek seekBatch fmt = batchInput fmt parse commandAction where- parse s = + parse s = do let (keyname, file) = separate (== ' ') s- in if not (null keyname) && not (null file)- then Right $ go file (keyOpt keyname)- else Left "Expected pairs of key and filename"+ if not (null keyname) && not (null file)+ then do+ file' <- liftIO $ relPathCwdToFile file+ return $ Right $ go file' (keyOpt keyname)+ else return $+ Left "Expected pairs of key and filename" go file key = starting "fromkey" (mkActionItem (key, toRawFilePath file)) $ perform key file
Command/Import.hs view
@@ -66,7 +66,7 @@ [bs] -> let (branch, subdir) = separate (== ':') bs in RemoteImportOptions r- (Ref branch)+ (Ref (encodeBS' branch)) (if null subdir then Nothing else Just subdir) _ -> giveup "expected BRANCH[:SUBDIR]"
Command/ImportFeed.hs view
@@ -162,10 +162,6 @@ r <- Remote.claimingUrl url if Remote.uuid r == webUUID || rawOption (downloadOptions opts) then do- urlinfo <- if relaxedOption (downloadOptions opts)- then pure Url.assumeUrlExists- else Url.withUrlOptions $- Url.getUrlInfo url let dlopts = (downloadOptions opts) -- force using the filename -- chosen here@@ -173,7 +169,14 @@ -- don't use youtube-dl , rawOption = True }- maybeToList <$> addUrlFile addunlockedmatcher dlopts url urlinfo f+ let go urlinfo = maybeToList <$> addUrlFile addunlockedmatcher dlopts url urlinfo f+ if relaxedOption (downloadOptions opts)+ then go Url.assumeUrlExists+ else Url.withUrlOptions (Url.getUrlInfo url) >>= \case+ Right urlinfo -> go urlinfo+ Left err -> do+ warning err+ return [] else do res <- tryNonAsync $ maybe (error $ "unable to checkUrl of " ++ Remote.name r)
Command/Info.hs view
@@ -119,7 +119,7 @@ seek :: InfoOptions -> CommandSeek seek o = case batchOption o of NoBatch -> withWords (commandAction . start o) (infoFor o)- Batch fmt -> batchInput fmt Right (itemInfo o)+ Batch fmt -> batchInput fmt (pure . Right) (itemInfo o) start :: InfoOptions -> [String] -> CommandStart start o [] = do@@ -152,9 +152,11 @@ v' <- Remote.nameToUUID' p case v' of Right u -> uuidInfo o u- Left _ -> ifAnnexed (toRawFilePath p)- (fileInfo o p)- (treeishInfo o p)+ Left _ -> do+ relp <- liftIO $ relPathCwdToFile p+ ifAnnexed (toRawFilePath relp)+ (fileInfo o relp)+ (treeishInfo o p) ) where isdir = liftIO . catchBoolIO . (isDirectory <$$> getFileStatus)@@ -181,7 +183,7 @@ treeishInfo :: InfoOptions -> String -> Annex () treeishInfo o t = do- mi <- getTreeStatInfo o (Git.Ref t)+ mi <- getTreeStatInfo o (Git.Ref (encodeBS' t)) case mi of Nothing -> noInfo t Just i -> showCustom (unwords ["info", t]) $ do
Command/Log.hs view
@@ -264,7 +264,8 @@ parseRawChangeLine :: String -> Maybe (Git.Ref, Git.Ref) parseRawChangeLine = go . words where- go (_:_:oldsha:newsha:_) = Just (Git.Ref oldsha, Git.Ref newsha)+ go (_:_:oldsha:newsha:_) = + Just (Git.Ref (encodeBS oldsha), Git.Ref (encodeBS newsha)) go _ = Nothing parseTimeStamp :: String -> POSIXTime
Command/Map.hs view
@@ -222,18 +222,18 @@ Nothing -> return $ Just r | otherwise = liftIO $ safely $ Git.Config.read r where- pipedconfig pcmd params = liftIO $ safely $+ pipedconfig st pcmd params = liftIO $ safely $ withHandle StdoutHandle createProcessSuccess p $- Git.Config.hRead r+ Git.Config.hRead r st where p = proc pcmd $ toCommand params configlist = Ssh.onRemote NoConsumeStdin r- (pipedconfig, return Nothing) "configlist" [] []+ (pipedconfig Git.Config.ConfigList, return Nothing) "configlist" [] [] manualconfiglist = do gc <- Annex.getRemoteGitConfig r (sshcmd, sshparams) <- Ssh.toRepo NoConsumeStdin r gc remotecmd- liftIO $ pipedconfig sshcmd sshparams+ liftIO $ pipedconfig Git.Config.ConfigNullList sshcmd sshparams where remotecmd = "sh -c " ++ shellEscape (cddir ++ " && " ++ "git config --null --list")
Command/Merge.hs view
@@ -26,11 +26,11 @@ commandAction mergeSyncedBranch seek bs = do prepMerge- forM_ bs (commandAction . mergeBranch . Git.Ref)+ forM_ bs (commandAction . mergeBranch . Git.Ref . encodeBS') mergeAnnexBranch :: CommandStart mergeAnnexBranch = starting "merge" (ActionItemOther (Just "git-annex")) $ do- Annex.Branch.update+ _ <- Annex.Branch.update -- commit explicitly, in case no remote branches were merged Annex.Branch.commit =<< Annex.Branch.commitMessage next $ return True
Command/MetaData.hs view
@@ -119,8 +119,9 @@ cleanup :: Key -> CommandCleanup cleanup k = do m <- getCurrentMetaData k- let Object o = toJSON' (MetaDataFields m)- maybeShowJSON $ AesonObject o+ case toJSON' (MetaDataFields m) of+ Object o -> maybeShowJSON $ AesonObject o+ _ -> noop showLongNote $ unlines $ concatMap showmeta $ map unwrapmeta (fromMetaData m) return True@@ -147,16 +148,21 @@ fieldsField :: T.Text fieldsField = T.pack "fields" -parseJSONInput :: String -> Either String (Either RawFilePath Key, MetaData)-parseJSONInput i = do- v <- eitherDecode (BU.fromString i)- let m = case itemAdded v of- Nothing -> emptyMetaData- Just (MetaDataFields m') -> m'- case (itemKey v, itemFile v) of- (Just k, _) -> Right (Right k, m)- (Nothing, Just f) -> Right (Left (toRawFilePath f), m)- (Nothing, Nothing) -> Left "JSON input is missing either file or key"+parseJSONInput :: String -> Annex (Either String (Either RawFilePath Key, MetaData))+parseJSONInput i = case eitherDecode (BU.fromString i) of+ Left e -> return (Left e)+ Right v -> do+ let m = case itemAdded v of+ Nothing -> emptyMetaData+ Just (MetaDataFields m') -> m'+ case (itemKey v, itemFile v) of+ (Just k, _) -> return $+ Right (Right k, m)+ (Nothing, Just f) -> do+ f' <- liftIO $ relPathCwdToFile f+ return $ Right (Left (toRawFilePath f'), m)+ (Nothing, Nothing) -> return $ + Left "JSON input is missing either file or key" startBatch :: (Either RawFilePath Key, MetaData) -> CommandStart startBatch (i, (MetaData m)) = case i of
Command/Move.hs view
@@ -187,16 +187,10 @@ fromPerform src removewhen key afile fromOk :: Remote -> Key -> Annex Bool-fromOk src key - | Remote.hasKeyCheap src =- either (const checklog) return =<< haskey- | otherwise = checklog- where- haskey = Remote.hasKey src key- checklog = do- u <- getUUID- remotes <- Remote.keyPossibilities key- return $ u /= Remote.uuid src && elem src remotes+fromOk src key = do+ u <- getUUID+ remotes <- Remote.keyPossibilities key+ return $ u /= Remote.uuid src && elem src remotes fromPerform :: Remote -> RemoveWhen -> Key -> AssociatedFile -> CommandPerform fromPerform src removewhen key afile = do
Command/PreCommit.hs view
@@ -16,7 +16,6 @@ import Annex.Link import Annex.View import Annex.View.ViewedFile-import Annex.LockFile import Logs.View import Logs.MetaData import Types.View
Command/ReKey.hs view
@@ -39,17 +39,21 @@ -- Split on the last space, since a FilePath can contain whitespace, -- but a Key very rarely does.-batchParser :: String -> Either String (RawFilePath, Key)+batchParser :: String -> Annex (Either String (RawFilePath, Key)) batchParser s = case separate (== ' ') (reverse s) of (rk, rf)- | null rk || null rf -> Left "Expected: \"file key\""+ | null rk || null rf -> return $ Left "Expected: \"file key\"" | otherwise -> case deserializeKey (reverse rk) of- Nothing -> Left "bad key"- Just k -> Right (toRawFilePath (reverse rf), k)+ Nothing -> return $ Left "bad key"+ Just k -> do+ let f = reverse rf+ f' <- liftIO $ relPathCwdToFile f+ return $ Right (toRawFilePath f', k) seek :: ReKeyOptions -> CommandSeek seek o = case batchOption o of- Batch fmt -> batchInput fmt batchParser (batchCommandAction . start)+ Batch fmt -> batchInput fmt batchParser $+ batchCommandAction . start NoBatch -> withPairs (commandAction . start . parsekey) (reKeyThese o) where parsekey (file, skey) =
Command/ResolveMerge.hs view
@@ -13,6 +13,8 @@ import qualified Git.Branch import Annex.AutoMerge +import qualified Data.ByteString as S+ cmd :: Command cmd = command "resolvemerge" SectionPlumbing "resolve merge conflicts"@@ -27,7 +29,7 @@ d <- fromRawFilePath <$> fromRepo Git.localGitDir let merge_head = d </> "MERGE_HEAD" them <- fromMaybe (error nomergehead) . extractSha- <$> liftIO (readFile merge_head)+ <$> liftIO (S.readFile merge_head) ifM (resolveMerge (Just us) them False) ( do void $ commitResolvedMerge Git.Branch.ManualCommit
Command/RmUrl.hs view
@@ -30,16 +30,20 @@ seek :: RmUrlOptions -> CommandSeek seek o = case batchOption o of- Batch fmt -> batchInput fmt batchParser (batchCommandAction . start)+ Batch fmt -> batchInput fmt batchParser+ (batchCommandAction . start) NoBatch -> withPairs (commandAction . start) (rmThese o) -- Split on the last space, since a FilePath can contain whitespace, -- but a url should not.-batchParser :: String -> Either String (FilePath, URLString)+batchParser :: String -> Annex (Either String (FilePath, URLString)) batchParser s = case separate (== ' ') (reverse s) of (ru, rf)- | null ru || null rf -> Left "Expected: \"file url\""- | otherwise -> Right (reverse rf, reverse ru)+ | null ru || null rf -> return $ Left "Expected: \"file url\""+ | otherwise -> do+ let f = reverse rf+ f' <- liftIO $ relPathCwdToFile f+ return $ Right (f', reverse ru) start :: (FilePath, URLString) -> CommandStart start (file, url) = flip whenAnnexed file' $ \_ key ->
Command/SetPresentKey.hs view
@@ -31,7 +31,7 @@ seek :: SetPresentKeyOptions -> CommandSeek seek o = case batchOption o of Batch fmt -> batchInput fmt- (parseKeyStatus . words)+ (pure . parseKeyStatus . words) (batchCommandAction . start) NoBatch -> either giveup (commandAction . start) (parseKeyStatus $ params o)
Command/Sync.hs view
@@ -46,7 +46,7 @@ import Config.GitConfig import Annex.SpecialRemote.Config import Config.DynamicConfig-import Config.Files+import Annex.Path import Annex.Wanted import Annex.Content import Command.Get (getKey')@@ -72,6 +72,8 @@ import Control.Concurrent.MVar import qualified Data.Map as M+import qualified Data.ByteString as S+import Data.Char cmd :: Command cmd = withGlobalOptions [jobsOption] $@@ -289,10 +291,8 @@ syncRemotes' :: [String] -> [Remote] -> Annex [Remote] syncRemotes' ps available = - ifM (Annex.getState Annex.fast) ( nub <$> pickfast , wanted )+ ifM (Annex.getState Annex.fast) ( fastest <$> wanted , wanted ) where- pickfast = (++) <$> listed <*> (filterM good (fastest available))- wanted | null ps = filterM good (concat $ Remote.byCost available) | otherwise = listed@@ -444,11 +444,11 @@ | otherwise = case remoteAnnexTrackingBranch (Remote.gitconfig remote) of Nothing -> noop Just tb -> do- let (b, s) = separate (== ':') (Git.fromRef tb)+ let (b, p) = separate' (== (fromIntegral (ord ':'))) (Git.fromRef' tb) let branch = Git.Ref b- let subdir = if null s+ let subdir = if S.null p then Nothing- else Just (asTopFilePath (toRawFilePath s))+ else Just (asTopFilePath p) Command.Import.seekRemote remote branch subdir void $ mergeRemote remote currbranch mergeconfig o where@@ -509,13 +509,13 @@ Nothing -> return True Just wt -> ifM needemulation ( liftIO $ do- p <- readProgramFile+ p <- programPath boolSystem' p [Param "post-receive"] (\cp -> cp { cwd = Just (fromRawFilePath wt) }) , return True ) where- needemulation = Remote.Git.onLocal repo remote $+ needemulation = Remote.Git.onLocalRepo repo $ (annexCrippledFileSystem <$> Annex.getGitConfig) <&&> needUpdateInsteadEmulation
Command/TestRemote.hs view
@@ -5,6 +5,8 @@ - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE RankNTypes #-}+ module Command.TestRemote where import Command@@ -27,9 +29,8 @@ import Types.RemoteConfig import Types.ProposedAccepted import Annex.SpecialRemote.Config (exportTreeField)-import Remote.Helper.ExportImport import Remote.Helper.Chunked-import Remote.Helper.Encryptable (describeEncryption, encryptionField, highRandomQualityField)+import Remote.Helper.Encryptable (encryptionField, highRandomQualityField) import Git.Types import Test.Tasty@@ -39,6 +40,7 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as L import qualified Data.Map as M+import Control.Concurrent.STM hiding (check) cmd :: Command cmd = command "testremote" SectionTesting@@ -83,58 +85,63 @@ let r' = if null (testReadonlyFile o) then r else r { Remote.readonly = True }- rs <- if Remote.readonly r'- then return [r']- else do- rs <- catMaybes <$> mapM (adjustChunkSize r') (chunkSizes basesz fast)- concat <$> mapM encryptionVariants rs- unavailrs <- catMaybes <$> mapM Remote.mkUnavailable [r']- exportr <- if Remote.readonly r'+ let drs = if Remote.readonly r'+ then [Described "remote" (pure (Just r'))]+ else remoteVariants (Described "remote" (pure r')) basesz fast+ unavailr <- Remote.mkUnavailable r'+ let exportr = if Remote.readonly r' then return Nothing else exportTreeVariant r'- perform rs unavailrs exportr ks+ perform drs unavailr exportr ks where basesz = fromInteger $ sizeOption o -perform :: [Remote] -> [Remote] -> Maybe Remote -> [Key] -> CommandPerform-perform rs unavailrs exportr ks = do- let ea = maybe exportUnsupported Remote.exportActions exportr- st <- Annex.getState id- let tests = testGroup "Remote Tests" $ concat- [ [ testGroup "unavailable remote" (testUnavailable st r (Prelude.head ks)) | r <- unavailrs ]- , [ testGroup (desc r k) (test st r k) | k <- ks, r <- rs ]- , [ testGroup (descexport k1 k2) (testExportTree st exportr ea k1 k2) | k1 <- take 2 ks, k2 <- take 2 (reverse ks) ]- ]+perform :: [Described (Annex (Maybe Remote))] -> Maybe Remote -> Annex (Maybe Remote) -> [Key] -> CommandPerform+perform drs unavailr exportr ks = do+ st <- liftIO . newTVarIO =<< Annex.getState id+ let tests = testGroup "Remote Tests" $ mkTestTrees+ (runTestCase st) + drs+ (pure unavailr)+ exportr+ (map (\k -> Described (desck k) (pure k)) ks) ok <- case tryIngredients [consoleTestReporter] mempty tests of Nothing -> error "No tests found!?" Just act -> liftIO act+ rs <- catMaybes <$> mapM getVal drs next $ cleanup rs ks ok where- desc r' k = intercalate "; " $ map unwords- [ [ "key size", show (fromKey keySize k) ]- , [ show (getChunkConfig (Remote.config r')) ]- , ["encryption", describeEncryption (Remote.config r')]- ]- descexport k1 k2 = intercalate "; " $ map unwords- [ [ "exporttree=yes" ]- , [ "key1 size", show (fromKey keySize k1) ]- , [ "key2 size", show (fromKey keySize k2) ]- ]+ desck k = unwords [ "key size", show (fromKey keySize k) ] +remoteVariants :: Described (Annex Remote) -> Int -> Bool -> [Described (Annex (Maybe Remote))]+remoteVariants dr basesz fast = + concatMap encryptionVariants $+ map chunkvariant (chunkSizes basesz fast)+ where+ chunkvariant sz = Described (getDesc dr ++ " chunksize=" ++ show sz) $ do+ r <- getVal dr+ adjustChunkSize r sz+ adjustChunkSize :: Remote -> Int -> Annex (Maybe Remote) adjustChunkSize r chunksize = adjustRemoteConfig r $ M.insert chunkField (Proposed (show chunksize)) -- Variants of a remote with no encryption, and with simple shared -- encryption. Gpg key based encryption is not tested.-encryptionVariants :: Remote -> Annex [Remote]-encryptionVariants r = do- noenc <- adjustRemoteConfig r $- M.insert encryptionField (Proposed "none")- sharedenc <- adjustRemoteConfig r $- M.insert encryptionField (Proposed "shared") .- M.insert highRandomQualityField (Proposed "false")- return $ catMaybes [noenc, sharedenc]+encryptionVariants :: Described (Annex (Maybe Remote)) -> [Described (Annex (Maybe Remote))]+encryptionVariants dr = [noenc, sharedenc]+ where+ noenc = Described (getDesc dr ++ " encryption=none") $+ getVal dr >>= \case+ Nothing -> return Nothing+ Just r -> adjustRemoteConfig r $+ M.insert encryptionField (Proposed "none")+ sharedenc = Described (getDesc dr ++ " encryption=shared") $+ getVal dr >>= \case+ Nothing -> return Nothing+ Just r -> adjustRemoteConfig r $+ M.insert encryptionField (Proposed "shared") .+ M.insert highRandomQualityField (Proposed "false") -- Variant of a remote with exporttree disabled. disableExportTree :: Remote -> Annex Remote@@ -162,19 +169,65 @@ (Remote.gitconfig r) (Remote.remoteStateHandle r) -test :: Annex.AnnexState -> Remote -> Key -> [TestTree]-test st r k = catMaybes- [ whenwritable $ check "removeKey when not present" remove- , whenwritable $ present False- , whenwritable $ check "storeKey" store- , whenwritable $ present True- , whenwritable $ check "storeKey when already present" store- , Just $ present True- , Just $ check "retrieveKeyFile" $ do+data Described t = Described+ { getDesc :: String+ , getVal :: t+ }++type RunAnnex = forall a. Annex a -> IO a++runTestCase :: TVar Annex.AnnexState -> RunAnnex+runTestCase stv a = do+ st <- atomically $ readTVar stv+ (r, st') <- Annex.run st $ do+ Annex.setOutput QuietOutput + a+ atomically $ writeTVar stv st'+ return r++-- Note that the same remotes and keys should be produced each time+-- the provided actions are called.+mkTestTrees+ :: RunAnnex+ -> [Described (Annex (Maybe Remote))]+ -> Annex (Maybe Remote)+ -> Annex (Maybe Remote)+ -> [Described (Annex Key)]+ -> [TestTree]+mkTestTrees runannex mkrs mkunavailr mkexportr mkks = concat $+ [ [ testGroup "unavailable remote" (testUnavailable runannex mkunavailr (getVal (Prelude.head mkks))) ]+ , [ testGroup (desc mkr mkk) (test runannex (getVal mkr) (getVal mkk)) | mkk <- mkks, mkr <- mkrs ]+ , [ testGroup (descexport mkk1 mkk2) (testExportTree runannex mkexportr (getVal mkk1) (getVal mkk2)) | mkk1 <- take 2 mkks, mkk2 <- take 2 (reverse mkks) ]+ ]+ where+ desc r k = intercalate "; " $ map unwords+ [ [ getDesc k ]+ , [ getDesc r ]+ ]+ descexport k1 k2 = intercalate "; " $ map unwords+ [ [ "exporttree=yes" ]+ , [ getDesc k1 ]+ , [ getDesc k2 ]+ ]++test :: RunAnnex -> Annex (Maybe Remote) -> Annex Key -> [TestTree]+test runannex mkr mkk =+ [ check "removeKey when not present" $ \r k ->+ whenwritable r $ remove r k+ , check ("present " ++ show False) $ \r k ->+ whenwritable r $ present r k False+ , check "storeKey" $ \r k ->+ whenwritable r $ store r k+ , check ("present " ++ show True) $ \r k ->+ whenwritable r $ present r k True+ , check "storeKey when already present" $ \r k ->+ whenwritable r $ store r k+ , check ("present " ++ show True) $ \r k -> present r k True+ , check "retrieveKeyFile" $ \r k -> do lockContentForRemoval k removeAnnex- get- , Just $ check "fsck downloaded object" fsck- , Just $ check "retrieveKeyFile resume from 33%" $ do+ get r k+ , check "fsck downloaded object" fsck+ , check "retrieveKeyFile resume from 33%" $ \r k -> do loc <- fromRawFilePath <$> Annex.calcRepo (gitAnnexLocation k) tmp <- prepTmp k partial <- liftIO $ bracket (openBinaryFile loc ReadMode) hClose $ \h -> do@@ -182,106 +235,133 @@ L.hGet h $ fromInteger $ sz `div` 3 liftIO $ L.writeFile tmp partial lockContentForRemoval k removeAnnex- get- , Just $ check "fsck downloaded object" fsck- , Just $ check "retrieveKeyFile resume from 0" $ do+ get r k+ , check "fsck downloaded object" fsck+ , check "retrieveKeyFile resume from 0" $ \r k -> do tmp <- prepTmp k liftIO $ writeFile tmp "" lockContentForRemoval k removeAnnex- get- , Just $ check "fsck downloaded object" fsck- , Just $ check "retrieveKeyFile resume from end" $ do+ get r k+ , check "fsck downloaded object" fsck+ , check "retrieveKeyFile resume from end" $ \r k -> do loc <- fromRawFilePath <$> Annex.calcRepo (gitAnnexLocation k) tmp <- prepTmp k void $ liftIO $ copyFileExternal CopyAllMetaData loc tmp lockContentForRemoval k removeAnnex- get- , Just $ check "fsck downloaded object" fsck- , whenwritable $ check "removeKey when present" remove- , whenwritable $ present False+ get r k+ , check "fsck downloaded object" fsck+ , check "removeKey when present" $ \r k -> + whenwritable r $ remove r k+ , check ("present " ++ show False) $ \r k -> + whenwritable r $ present r k False ] where- whenwritable a = if Remote.readonly r then Nothing else Just a- check desc a = testCase desc $- Annex.eval st (Annex.setOutput QuietOutput >> a) @? "failed"- present b = check ("present " ++ show b) $- (== Right b) <$> Remote.hasKey r k- fsck = case maybeLookupBackendVariety (fromKey keyVariety k) of+ whenwritable r a+ | Remote.readonly r = return True+ | otherwise = a+ check desc a = testCase desc $ do+ let a' = mkr >>= \case+ Just r -> do+ k <- mkk+ a r k+ Nothing -> return True+ runannex a' @? "failed"+ present r k b = (== Right b) <$> Remote.hasKey r k+ fsck _ k = case maybeLookupBackendVariety (fromKey keyVariety k) of Nothing -> return True Just b -> case Backend.verifyKeyContent b of Nothing -> return True Just verifier -> verifier k (serializeKey k)- get = getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest ->+ get r k = getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest -> Remote.retrieveKeyFile r k (AssociatedFile Nothing) dest nullMeterUpdate- store = Remote.storeKey r k (AssociatedFile Nothing) nullMeterUpdate- remove = Remote.removeKey r k+ store r k = Remote.storeKey r k (AssociatedFile Nothing) nullMeterUpdate+ remove r k = Remote.removeKey r k -testExportTree :: Annex.AnnexState -> Maybe Remote -> Remote.ExportActions Annex -> Key -> Key -> [TestTree]-testExportTree _ Nothing _ _ _ = []-testExportTree st (Just _) ea k1 k2 =- [ check "check present export when not present" $- not <$> checkpresentexport k1- , check "remove export when not present" (removeexport k1)- , check "store export" (storeexport k1)- , check "check present export after store" $- checkpresentexport k1- , check "store export when already present" (storeexport k1)- , check "retrieve export" (retrieveexport k1)- , check "store new content to export" (storeexport k2)- , check "check present export after store of new content" $- checkpresentexport k2- , check "retrieve export new content" (retrieveexport k2)- , check "remove export" (removeexport k2)- , check "check present export after remove" $- not <$> checkpresentexport k2- , check "retrieve export fails after removal" $- not <$> retrieveexport k2- , check "remove export directory" removeexportdirectory- , check "remove export directory that is already removed" removeexportdirectory+testExportTree :: RunAnnex -> Annex (Maybe Remote) -> Annex Key -> Annex Key -> [TestTree]+testExportTree runannex mkr mkk1 mkk2 =+ [ check "check present export when not present" $ \ea k1 _k2 ->+ not <$> checkpresentexport ea k1+ , check "remove export when not present" $ \ea k1 _k2 -> + removeexport ea k1+ , check "store export" $ \ea k1 _k2 ->+ storeexport ea k1+ , check "check present export after store" $ \ea k1 _k2 ->+ checkpresentexport ea k1+ , check "store export when already present" $ \ea k1 _k2 ->+ storeexport ea k1+ , check "retrieve export" $ \ea k1 _k2 -> + retrieveexport ea k1+ , check "store new content to export" $ \ea _k1 k2 ->+ storeexport ea k2+ , check "check present export after store of new content" $ \ea _k1 k2 ->+ checkpresentexport ea k2+ , check "retrieve export new content" $ \ea _k1 k2 ->+ retrieveexport ea k2+ , check "remove export" $ \ea _k1 k2 -> + removeexport ea k2+ , check "check present export after remove" $ \ea _k1 k2 ->+ not <$> checkpresentexport ea k2+ , check "retrieve export fails after removal" $ \ea _k1 k2 ->+ not <$> retrieveexport ea k2+ , check "remove export directory" $ \ea _k1 _k2 ->+ removeexportdirectory ea+ , check "remove export directory that is already removed" $ \ea _k1 _k2 ->+ removeexportdirectory ea -- renames are not tested because remotes do not need to support them ] where testexportdirectory = "testremote-export" testexportlocation = mkExportLocation (toRawFilePath (testexportdirectory </> "location"))- check desc a = testCase desc $- Annex.eval st (Annex.setOutput QuietOutput >> a) @? "failed"- storeexport k = do+ check desc a = testCase desc $ do+ let a' = mkr >>= \case+ Just r -> do+ let ea = Remote.exportActions r+ k1 <- mkk1+ k2 <- mkk2+ a ea k1 k2+ Nothing -> return True+ runannex a' @? "failed"+ storeexport ea k = do loc <- fromRawFilePath <$> Annex.calcRepo (gitAnnexLocation k) Remote.storeExport ea loc k testexportlocation nullMeterUpdate- retrieveexport k = withTmpFile "exported" $ \tmp h -> do+ retrieveexport ea k = withTmpFile "exported" $ \tmp h -> do liftIO $ hClose h ifM (Remote.retrieveExport ea k testexportlocation tmp nullMeterUpdate) ( verifyKeyContent RetrievalAllKeysSecure AlwaysVerify UnVerified k tmp , return False )- checkpresentexport k = Remote.checkPresentExport ea k testexportlocation- removeexport k = Remote.removeExport ea k testexportlocation- removeexportdirectory = case Remote.removeExportDirectory ea of+ checkpresentexport ea k = Remote.checkPresentExport ea k testexportlocation+ removeexport ea k = Remote.removeExport ea k testexportlocation+ removeexportdirectory ea = case Remote.removeExportDirectory ea of Nothing -> return True Just a -> a (mkExportDirectory (toRawFilePath testexportdirectory)) -testUnavailable :: Annex.AnnexState -> Remote -> Key -> [TestTree]-testUnavailable st r k =- [ check (== Right False) "removeKey" $+testUnavailable :: RunAnnex -> Annex (Maybe Remote) -> Annex Key -> [TestTree]+testUnavailable runannex mkr mkk =+ [ check (== Right False) "removeKey" $ \r k -> Remote.removeKey r k- , check (== Right False) "storeKey" $+ , check (== Right False) "storeKey" $ \r k -> Remote.storeKey r k (AssociatedFile Nothing) nullMeterUpdate- , check (`notElem` [Right True, Right False]) "checkPresent" $+ , check (`notElem` [Right True, Right False]) "checkPresent" $ \r k -> Remote.checkPresent r k- , check (== Right False) "retrieveKeyFile" $+ , check (== Right False) "retrieveKeyFile" $ \r k -> getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest -> Remote.retrieveKeyFile r k (AssociatedFile Nothing) dest nullMeterUpdate- , check (== Right False) "retrieveKeyFileCheap" $+ , check (== Right False) "retrieveKeyFileCheap" $ \r k -> getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest -> unVerified $ Remote.retrieveKeyFileCheap r k (AssociatedFile Nothing) dest ] where- check checkval desc a = testCase desc $ do- v <- Annex.eval st $ do- Annex.setOutput QuietOutput- either (Left . show) Right <$> tryNonAsync a- checkval v @? ("(got: " ++ show v ++ ")")+ check checkval desc a = testCase desc $ + join $ runannex $ mkr >>= \case+ Just r -> do+ k <- mkk+ v <- either (Left . show) Right+ <$> tryNonAsync (a r k)+ return $ checkval v+ @? ("(got: " ++ show v ++ ")")+ Nothing -> return noop cleanup :: [Remote] -> [Key] -> Bool -> CommandCleanup cleanup rs ks ok
Command/TransferKeys.hs view
@@ -17,6 +17,7 @@ import Utility.SimpleProtocol (dupIoHandles) import Git.Types (RemoteName) import qualified Database.Keys+import Annex.BranchState data TransferRequest = TransferRequest Direction Remote Key AssociatedFile @@ -29,6 +30,7 @@ start :: CommandStart start = do+ enableInteractiveJournalAccess (readh, writeh) <- liftIO dupIoHandles runRequests readh writeh runner stop
Command/Uninit.hs view
@@ -35,7 +35,7 @@ whenM ((/=) <$> liftIO (absPath top) <*> liftIO (absPath currdir)) $ giveup "can only run uninit from the top of the git repository" where- current_branch = Git.Ref . Prelude.head . lines . decodeBS' <$> revhead+ current_branch = Git.Ref . encodeBS' . Prelude.head . lines . decodeBS' <$> revhead revhead = inRepo $ Git.Command.pipeReadStrict [Param "rev-parse", Param "--abbrev-ref", Param "HEAD"]
Command/Unused.hs view
@@ -5,12 +5,10 @@ - Licensed under the GNU AGPL version 3 or higher. -} -{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE BangPatterns, OverloadedStrings #-} module Command.Unused where -import qualified Data.Map as M- import Command import Logs.Unused import Annex.Content@@ -37,6 +35,11 @@ import qualified Database.Keys import Annex.InodeSentinal +import qualified Data.Map as M+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8+import Data.Char+ cmd :: Command cmd = command "unused" SectionMaintenance "look for unused file content" paramNothing (seek <$$> optParser)@@ -221,8 +224,7 @@ withKeysReferencedDiffGitRefs :: RefSpec -> (Key -> Annex ()) -> Annex () withKeysReferencedDiffGitRefs refspec a = do- rs <- relevantrefs . decodeBS'- <$> inRepo (Git.Command.pipeReadStrict [Param "show-ref"])+ rs <- relevantrefs <$> inRepo (Git.Command.pipeReadStrict [Param "show-ref"]) shaHead <- maybe (return Nothing) (inRepo . Git.Ref.sha) =<< inRepo Git.Branch.currentUnsafe let haveHead = any (\(shaRef, _) -> Just shaRef == shaHead) rs@@ -233,12 +235,12 @@ where relevantrefs = map (\(r, h) -> (Git.Ref r, Git.Ref h)) . filter ourbranches .- map (separate (== ' ')) .- lines+ map (separate' (== (fromIntegral (ord ' ')))) .+ S8.lines nubRefs = nubBy (\(x, _) (y, _) -> x == y)- ourbranchend = '/' : Git.fromRef Annex.Branch.name- ourbranches (_, b) = not (ourbranchend `isSuffixOf` b)- && not ("refs/synced/" `isPrefixOf` b)+ ourbranchend = S.cons (fromIntegral (ord '/')) (Git.fromRef' Annex.Branch.name)+ ourbranches (_, b) = not (ourbranchend `S.isSuffixOf` b)+ && not ("refs/synced/" `S.isPrefixOf` b) && not (is_branchView (Git.Ref b)) getreflog rs = inRepo $ Git.RefLog.getMulti rs
Config/Cost.hs view
@@ -46,9 +46,9 @@ insertCostAfter l pos | pos < 0 = costBetween 0 (l !! 0) : l | nextpos > maxpos = l ++ [1 + l !! maxpos]- | item == nextitem =- let (_dup:new:l') = insertCostAfter lastsegment 0- in firstsegment ++ [costBetween item new, new] ++ l'+ | item == nextitem = case insertCostAfter lastsegment 0 of+ (_dup:new:l') -> firstsegment ++ [costBetween item new, new] ++ l'+ _ -> error "insertCostAfter internal error" | otherwise = firstsegment ++ [costBetween item nextitem ] ++ lastsegment where
Config/Files.hs view
@@ -61,27 +61,6 @@ programFile :: IO FilePath programFile = userConfigFile "program" -{- Returns a command to run for git-annex. -}-readProgramFile :: IO FilePath-readProgramFile = do- programfile <- programFile- p <- catchDefaultIO cmd $ - fromMaybe cmd . headMaybe . lines <$> readFile programfile- ifM (inPath p)- ( return p- , ifM (inPath cmd)- ( return cmd- , cannotFindProgram- )- )- where- cmd = "git-annex"--cannotFindProgram :: IO a-cannotFindProgram = do- f <- programFile- giveup $ "cannot find git-annex program in PATH or in the location listed in " ++ f- {- A .noannex file in a git repository prevents git-annex from - initializing that repository.. The content of the file is returned. -} noAnnexFileContent :: Maybe FilePath -> IO (Maybe String)
Database/Keys.hs view
@@ -6,6 +6,7 @@ -} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE OverloadedStrings #-} module Database.Keys ( DbHandle,@@ -44,6 +45,7 @@ import Git.Index import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8 import qualified System.FilePath.ByteString as P {- Runs an action that reads from the database.@@ -59,7 +61,7 @@ -} runReader :: Monoid v => (SQL.ReadHandle -> Annex v) -> Annex v runReader a = do- h <- getDbHandle+ h <- Annex.getState Annex.keysdbhandle withDbState h go where go DbUnavailable = return (mempty, DbUnavailable)@@ -83,7 +85,7 @@ - The database is created if it doesn't exist yet. -} runWriter :: (SQL.WriteHandle -> Annex ()) -> Annex () runWriter a = do- h <- getDbHandle+ h <- Annex.getState Annex.keysdbhandle withDbState h go where go st@(DbOpen qh) = do@@ -99,17 +101,6 @@ runWriterIO :: (SQL.WriteHandle -> IO ()) -> Annex () runWriterIO a = runWriter (liftIO . a) -{- Gets the handle cached in Annex state; creates a new one if it's not yet- - available, but doesn't open the database. -}-getDbHandle :: Annex DbHandle-getDbHandle = go =<< Annex.getState Annex.keysdbhandle- where- go (Just h) = pure h- go Nothing = do- h <- liftIO newDbHandle- Annex.changeState $ \s -> s { Annex.keysdbhandle = Just h }- return h- {- Opens the database, perhaps creating it if it doesn't exist yet. - - Multiple readers and writers can have the database open at the same@@ -151,9 +142,7 @@ - data to it. -} closeDb :: Annex ()-closeDb = Annex.getState Annex.keysdbhandle >>= \case- Nothing -> return ()- Just h -> liftIO (closeDbHandle h)+closeDb = liftIO . closeDbHandle =<< Annex.getState Annex.keysdbhandle addAssociatedFile :: Key -> TopFilePath -> Annex () addAssociatedFile k f = runWriterIO $ SQL.addAssociatedFile k f@@ -237,8 +226,8 @@ Nothing -> noop where go cur indexcache = do- (l, cleanup) <- inRepo $ pipeNullSplit diff- changed <- procdiff (map decodeBL' l) False+ (l, cleanup) <- inRepo $ pipeNullSplit' diff+ changed <- procdiff l False void $ liftIO cleanup -- Flush database changes immediately -- so other processes can see them.@@ -278,15 +267,16 @@ , Param "--no-ext-diff" ] - procdiff (info:file:rest) changed = case words info of- ((':':_srcmode):dstmode:_srcsha:dstsha:_change:[])- -- Only want files, not symlinks- | dstmode /= decodeBS' (fmtTreeItemType TreeSymlink) -> do- maybe noop (reconcile (asTopFilePath (toRawFilePath file)))- =<< catKey (Ref dstsha)- procdiff rest True- | otherwise -> procdiff rest changed- _ -> return changed -- parse failed+ procdiff (info:file:rest) changed+ | ":" `S.isPrefixOf` info = case S8.words info of+ (_colonsrcmode:dstmode:_srcsha:dstsha:_change:[])+ -- Only want files, not symlinks+ | dstmode /= fmtTreeItemType TreeSymlink -> do+ maybe noop (reconcile (asTopFilePath file))+ =<< catKey (Ref dstsha)+ procdiff rest True+ | otherwise -> procdiff rest changed+ _ -> return changed -- parse failed procdiff _ changed = return changed -- Note that database writes done in here will not necessarily
Database/Types.hs view
@@ -28,6 +28,7 @@ import Key import Utility.InodeCache import Utility.FileSize+import Utility.FileSystemEncoding import Git.Types import Types.UUID import Types.Import@@ -94,10 +95,10 @@ deriving (Eq, Show) toSSha :: Sha -> SSha-toSSha (Ref s) = SSha s+toSSha (Ref s) = SSha (decodeBS' s) fromSSha :: SSha -> Ref-fromSSha (SSha s) = Ref s+fromSSha (SSha s) = Ref (encodeBS' s) instance PersistField SSha where toPersistValue (SSha b) = toPersistValue b
Git.hs view
@@ -14,6 +14,7 @@ Repo(..), Ref(..), fromRef,+ fromRef', Branch, Sha, Tag,
Git/Branch.hs view
@@ -18,6 +18,7 @@ import qualified Git.Ref import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as B8 {- The currently checked out branch. -@@ -39,25 +40,27 @@ {- The current branch, which may not really exist yet. -} currentUnsafe :: Repo -> IO (Maybe Branch)-currentUnsafe r = parse . firstLine'- <$> pipeReadStrict [Param "symbolic-ref", Param "-q", Param $ fromRef Git.Ref.headRef] r+currentUnsafe r = parse . firstLine' <$> pipeReadStrict+ [ Param "symbolic-ref"+ , Param "-q"+ , Param $ fromRef Git.Ref.headRef+ ] r where parse b | B.null b = Nothing- | otherwise = Just $ Git.Ref $ decodeBS b+ | otherwise = Just $ Git.Ref b {- Checks if the second branch has any commits not present on the first - branch. -} changed :: Branch -> Branch -> Repo -> IO Bool changed origbranch newbranch repo | origbranch == newbranch = return False- | otherwise = not . null+ | otherwise = not . B.null <$> changed' origbranch newbranch [Param "-n1"] repo where -changed' :: Branch -> Branch -> [CommandParam] -> Repo -> IO String-changed' origbranch newbranch extraps repo =- decodeBS <$> pipeReadStrict ps repo+changed' :: Branch -> Branch -> [CommandParam] -> Repo -> IO B.ByteString+changed' origbranch newbranch extraps repo = pipeReadStrict ps repo where ps = [ Param "log"@@ -68,7 +71,7 @@ {- Lists commits that are in the second branch and not in the first branch. -} changedCommits :: Branch -> Branch -> [CommandParam] -> Repo -> IO [Sha] changedCommits origbranch newbranch extraps repo = - catMaybes . map extractSha . lines+ catMaybes . map extractSha . B8.lines <$> changed' origbranch newbranch extraps repo {- Check if it's possible to fast-forward from the old@@ -163,8 +166,7 @@ -} commit :: CommitMode -> Bool -> String -> Branch -> [Ref] -> Repo -> IO (Maybe Sha) commit commitmode allowempty message branch parentrefs repo = do- tree <- getSha "write-tree" $- decodeBS' <$> pipeReadStrict [Param "write-tree"] repo+ tree <- getSha "write-tree" $ pipeReadStrict [Param "write-tree"] repo ifM (cancommit tree) ( do sha <- commitTree commitmode message parentrefs tree repo
Git/CatFile.hs view
@@ -1,10 +1,12 @@ {- git cat-file interface -- - Copyright 2011-2019 Joey Hess <id@joeyh.name>+ - Copyright 2011-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE OverloadedStrings #-}+ module Git.CatFile ( CatFileHandle, catFileStart,@@ -22,7 +24,9 @@ import System.IO import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString.Lazy.Char8 as L8+import qualified Data.ByteString.Char8 as S8+import qualified Data.Attoparsec.ByteString as A+import qualified Data.Attoparsec.ByteString.Char8 as A8 import qualified Data.Map as M import Data.String import Data.Char@@ -69,11 +73,11 @@ {- Reads a file from a specified branch. -} catFile :: CatFileHandle -> Branch -> RawFilePath -> IO L.ByteString catFile h branch file = catObject h $ Ref $- fromRef branch ++ ":" ++ fromRawFilePath (toInternalGitPath file)+ fromRef' branch <> ":" <> toInternalGitPath file catFileDetails :: CatFileHandle -> Branch -> RawFilePath -> IO (Maybe (L.ByteString, Sha, ObjectType)) catFileDetails h branch file = catObjectDetails h $ Ref $- fromRef branch ++ ":" ++ fromRawFilePath (toInternalGitPath file)+ fromRef' branch <> ":" <> toInternalGitPath file {- Uses a running git cat-file read the content of an object. - Objects that do not exist will have "" returned. -}@@ -82,9 +86,9 @@ catObjectDetails :: CatFileHandle -> Ref -> IO (Maybe (L.ByteString, Sha, ObjectType)) catObjectDetails h object = query (catFileProcess h) object newlinefallback $ \from -> do- header <- hGetLine from+ header <- S8.hGetLine from case parseResp object header of- Just (ParsedResp sha size objtype) -> do+ Just (ParsedResp sha objtype size) -> do content <- S.hGet from (fromIntegral size) eatchar '\n' from return $ Just (L.fromChunks [content], sha, objtype)@@ -112,9 +116,9 @@ {- Gets the size and type of an object, without reading its content. -} catObjectMetaData :: CatFileHandle -> Ref -> IO (Maybe (Sha, FileSize, ObjectType)) catObjectMetaData h object = query (checkFileProcess h) object newlinefallback $ \from -> do- resp <- hGetLine from+ resp <- S8.hGetLine from case parseResp object resp of- Just (ParsedResp sha size objtype) ->+ Just (ParsedResp sha objtype size) -> return $ Just (sha, size, objtype) Just DNE -> return Nothing Nothing -> error $ "unknown response from git cat-file " ++ show (resp, object)@@ -126,36 +130,40 @@ objtype <- queryObjectType object (gitRepo h) return $ (,,) <$> sha <*> sz <*> objtype -data ParsedResp = ParsedResp Sha FileSize ObjectType | DNE+data ParsedResp = ParsedResp Sha ObjectType FileSize | DNE+ deriving (Show) query :: CoProcess.CoProcessHandle -> Ref -> IO a -> (Handle -> IO a) -> IO a query hdl object newlinefallback receive -- git cat-file --batch uses a line based protocol, so when the -- filename itself contains a newline, have to fall back to another -- method of getting the information.- | '\n' `elem` s = newlinefallback+ | '\n' `S8.elem` s = newlinefallback -- git strips carriage return from the end of a line, out of some -- misplaced desire to support windows, so also use the newline -- fallback for those.- | "\r" `isSuffixOf` s = newlinefallback+ | "\r" `S8.isSuffixOf` s = newlinefallback | otherwise = CoProcess.query hdl send receive where- send to = hPutStrLn to s- s = fromRef object+ send to = S8.hPutStrLn to s+ s = fromRef' object -parseResp :: Ref -> String -> Maybe ParsedResp-parseResp object l - | " missing" `isSuffixOf` l -- less expensive than full check- && l == fromRef object ++ " missing" = Just DNE- | otherwise = case words l of- [sha, objtype, size] -> case extractSha sha of- Just sha' -> case (readObjectType (encodeBS objtype), reads size) of- (Just t, [(bytes, "")]) -> - Just $ ParsedResp sha' bytes t- _ -> Nothing- Nothing -> Nothing- _ -> Nothing+parseResp :: Ref -> S.ByteString -> Maybe ParsedResp+parseResp object s+ | " missing" `S.isSuffixOf` s -- less expensive than full check+ && s == fromRef' object <> " missing" = Just DNE+ | otherwise = eitherToMaybe $ A.parseOnly respParser s +respParser :: A.Parser ParsedResp+respParser = ParsedResp+ <$> (maybe (fail "bad sha") return . extractSha =<< nextword)+ <* A8.char ' '+ <*> (maybe (fail "bad object type") return . readObjectType =<< nextword)+ <* A8.char ' '+ <*> A8.decimal+ where+ nextword = A8.takeTill (== ' ')+ querySingle :: CommandParam -> Ref -> Repo -> (Handle -> IO a) -> IO (Maybe a) querySingle o r repo reader = assertLocal repo $ -- In non-batch mode, git cat-file warns on stderr when@@ -218,39 +226,39 @@ catCommit :: CatFileHandle -> Ref -> IO (Maybe Commit) catCommit h commitref = go <$> catObjectDetails h commitref where- go (Just (b, _, CommitObject)) = parseCommit b+ go (Just (b, _, CommitObject)) = parseCommit (L.toStrict b) go _ = Nothing -parseCommit :: L.ByteString -> Maybe Commit+parseCommit :: S.ByteString -> Maybe Commit parseCommit b = Commit- <$> (extractSha . L8.unpack =<< field "tree")- <*> Just (maybe [] (mapMaybe (extractSha . L8.unpack)) (fields "parent"))+ <$> (extractSha =<< field "tree")+ <*> Just (maybe [] (mapMaybe extractSha) (fields "parent")) <*> (parsemetadata <$> field "author") <*> (parsemetadata <$> field "committer")- <*> Just (L8.unpack $ L.intercalate (L.singleton nl) message)+ <*> Just (decodeBS $ S.intercalate (S.singleton nl) message) where field n = headMaybe =<< fields n fields n = M.lookup (fromString n) fieldmap fieldmap = M.fromListWith (++) ((map breakfield) header) breakfield l =- let (k, sp_v) = L.break (== sp) l- in (k, [L.drop 1 sp_v])- (header, message) = separate L.null ls- ls = L.split nl b+ let (k, sp_v) = S.break (== sp) l+ in (k, [S.drop 1 sp_v])+ (header, message) = separate S.null ls+ ls = S.split nl b -- author and committer lines have the form: "name <email> date" -- The email is always present, even if empty "<>" parsemetadata l = CommitMetaData- { commitName = whenset $ L.init name_sp+ { commitName = whenset $ S.init name_sp , commitEmail = whenset email- , commitDate = whenset $ L.drop 2 gt_sp_date+ , commitDate = whenset $ S.drop 2 gt_sp_date } where- (name_sp, rest) = L.break (== lt) l- (email, gt_sp_date) = L.break (== gt) (L.drop 1 rest)+ (name_sp, rest) = S.break (== lt) l+ (email, gt_sp_date) = S.break (== gt) (S.drop 1 rest) whenset v- | L.null v = Nothing- | otherwise = Just (L8.unpack v)+ | S.null v = Nothing+ | otherwise = Just (decodeBS v) nl = fromIntegral (ord '\n') sp = fromIntegral (ord ' ')
Git/Command.hs view
@@ -81,11 +81,16 @@ {- Runs a git command, feeding it an input, and returning its output, - which is expected to be fairly small, since it's all read into memory - strictly. -}-pipeWriteRead :: [CommandParam] -> Maybe (Handle -> IO ()) -> Repo -> IO String+pipeWriteRead :: [CommandParam] -> Maybe (Handle -> IO ()) -> Repo -> IO S.ByteString pipeWriteRead params writer repo = assertLocal repo $ writeReadProcessEnv "git" (toCommand $ gitCommandLine params repo) - (gitEnv repo) writer (Just adjusthandle)+ (gitEnv repo) writer' where+ writer' = case writer of+ Nothing -> Nothing+ Just a -> Just $ \h -> do+ adjusthandle h+ a h adjusthandle h = hSetNewlineMode h noNewlineTranslation {- Runs a git command, feeding it input on a handle with an action. -}
Git/Config.hs view
@@ -59,7 +59,7 @@ go Repo { location = LocalUnknown d } = git_config d go _ = assertLocal repo $ error "internal" git_config d = withHandle StdoutHandle createProcessSuccess p $- hRead repo+ hRead repo ConfigNullList where params = ["config", "--null", "--list"] p = (proc "git" params)@@ -74,7 +74,7 @@ ifM (doesFileExist $ home </> ".gitconfig") ( do repo <- withHandle StdoutHandle createProcessSuccess p $- hRead (Git.Construct.fromUnknown)+ hRead (Git.Construct.fromUnknown) ConfigNullList return $ Just repo , return Nothing )@@ -83,18 +83,18 @@ p = (proc "git" params) {- Reads git config from a handle and populates a repo with it. -}-hRead :: Repo -> Handle -> IO Repo-hRead repo h = do+hRead :: Repo -> ConfigStyle -> Handle -> IO Repo+hRead repo st h = do val <- S.hGetContents h- store val repo+ store val st 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 can be updated incrementally. -}-store :: S.ByteString -> Repo -> IO Repo-store s repo = do- let c = parse s+store :: S.ByteString -> ConfigStyle -> Repo -> IO Repo+store s st repo = do+ let c = parse s st updateLocation $ repo { config = (M.map Prelude.head c) `M.union` config repo , fullconfig = M.unionWith (++) c (fullconfig repo)@@ -135,27 +135,30 @@ top <- absPath $ fromRawFilePath (gitdir l) let p = absPathFrom top (fromRawFilePath d) return $ l { worktree = Just (toRawFilePath p) }+ Just NoConfigValue -> return l return $ r { location = l' } +data ConfigStyle = ConfigList | ConfigNullList+ {- Parses git config --list or git config --null --list output into a - config map. -}-parse :: S.ByteString -> M.Map ConfigKey [ConfigValue]-parse s+parse :: S.ByteString -> ConfigStyle -> M.Map ConfigKey [ConfigValue]+parse s st | S.null s = M.empty- -- --list output will have a '=' in the first line- -- (The first line of --null --list output is the name of a key,- -- which is assumed to never contain '='.)- | S.elem eq firstline = sep eq $ S.split nl s- -- --null --list output separates keys from values with newlines- | otherwise = sep nl $ S.split 0 s+ | otherwise = case st of+ ConfigList -> sep eq $ S.split nl s+ ConfigNullList -> sep nl $ S.split 0 s where nl = fromIntegral (ord '\n') eq = fromIntegral (ord '=')- firstline = S.takeWhile (/= nl) s sep c = M.fromListWith (++)- . map (\(k,v) -> (ConfigKey k, [ConfigValue (S.drop 1 v)])) + . map (\(k,v) -> (ConfigKey k, [mkval v])) . map (S.break (== c))+ + mkval v + | S.null v = NoConfigValue+ | otherwise = ConfigValue (S.drop 1 v) {- Checks if a string from git config is a true/false value. -} isTrueFalse :: String -> Maybe Bool@@ -163,11 +166,21 @@ isTrueFalse' :: ConfigValue -> Maybe Bool isTrueFalse' (ConfigValue s)+ | s' == "yes" = Just True+ | s' == "on" = Just True | s' == "true" = Just True+ | s' == "1" = Just True++ | s' == "no" = Just False+ | s' == "off" = Just False | s' == "false" = Just False+ | s' == "0" = Just False+ | s' == "" = Just False+ | otherwise = Nothing where s' = S8.map toLower s+isTrueFalse' NoConfigValue = Just True boolConfig :: Bool -> String boolConfig True = "true"@@ -186,14 +199,14 @@ {- Runs a command to get the configuration of a repo, - and returns a repo populated with the configuration, as well as the raw - output and any standard output of the command. -}-fromPipe :: Repo -> String -> [CommandParam] -> IO (Either SomeException (Repo, S.ByteString, S.ByteString))-fromPipe r cmd params = try $+fromPipe :: Repo -> String -> [CommandParam] -> ConfigStyle -> IO (Either SomeException (Repo, S.ByteString, S.ByteString))+fromPipe r cmd params st = try $ withOEHandles createProcessSuccess p $ \(hout, herr) -> do geterr <- async $ S.hGetContents herr getval <- async $ S.hGetContents hout val <- wait getval err <- wait geterr- r' <- store val r+ r' <- store val st r return (r', val, err) where p = proc cmd $ toCommand params@@ -206,7 +219,7 @@ , Param "--file" , File f , Param "--list"- ]+ ] ConfigList {- Changes a git config setting in the specified config file. - (Creates the file if it does not already exist.) -}
Git/ConfigTypes.hs view
@@ -23,7 +23,6 @@ getSharedRepository :: Repo -> SharedRepository getSharedRepository r = case Git.Config.getMaybe "core.sharedrepository" r of- Nothing -> UnShared Just (ConfigValue v) -> case S8.map toLower v of "1" -> GroupShared "2" -> AllShared@@ -33,6 +32,8 @@ "world" -> AllShared "everybody" -> AllShared _ -> maybe UnShared UmaskShared (readish (decodeBS' v))+ Just NoConfigValue -> UnShared+ Nothing -> UnShared data DenyCurrentBranch = UpdateInstead | RefusePush | WarnPush | IgnorePush deriving (Eq)@@ -45,4 +46,5 @@ "warn" -> WarnPush "ignore" -> IgnorePush _ -> RefusePush+ Just NoConfigValue -> RefusePush Nothing -> RefusePush
Git/Credential.hs view
@@ -58,7 +58,7 @@ runCredential :: String -> Credential -> Repo -> IO Credential runCredential action input r =- parseCredential <$> pipeWriteRead + parseCredential . decodeBS <$> pipeWriteRead [ Param "credential" , Param action ]
Git/DiffTree.hs view
@@ -1,6 +1,6 @@ {- git diff-tree interface -- - Copyright 2012 Joey Hess <id@joeyh.name>+ - Copyright 2012-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -17,7 +17,9 @@ commitDiff, ) where -import Numeric+import qualified Data.ByteString.Lazy as L+import qualified Data.Attoparsec.ByteString.Lazy as A+import qualified Data.Attoparsec.ByteString.Char8 as A8 import Common import Git@@ -27,6 +29,7 @@ import Git.DiffTreeItem import qualified Git.Filename import qualified Git.Ref+import Utility.Attoparsec {- Checks if the DiffTreeItem modifies a file with a given name - or under a directory by that name. -}@@ -89,7 +92,7 @@ getdiff :: CommandParam -> [CommandParam] -> Repo -> IO ([DiffTreeItem], IO Bool) getdiff command params repo = do (diff, cleanup) <- pipeNullSplit ps repo- return (parseDiffRaw (map decodeBL diff), cleanup)+ return (parseDiffRaw diff, cleanup) where ps = command :@@ -100,26 +103,28 @@ params {- Parses --raw output used by diff-tree and git-log. -}-parseDiffRaw :: [String] -> [DiffTreeItem]+parseDiffRaw :: [L.ByteString] -> [DiffTreeItem] parseDiffRaw l = go l where go [] = []- go (info:f:rest) = mk info f : go rest- go (s:[]) = error $ "diff-tree parse error near \"" ++ s ++ "\""-- mk info f = DiffTreeItem- { srcmode = readmode srcm- , dstmode = readmode dstm- , srcsha = fromMaybe (error "bad srcsha") $ extractSha ssha- , dstsha = fromMaybe (error "bad dstsha") $ extractSha dsha- , status = s- , file = asTopFilePath $ fromInternalGitPath $ Git.Filename.decode $ toRawFilePath f- }- where- readmode = fst . Prelude.head . readOct+ go (info:f:rest) = case A.parse (parserDiffRaw (L.toStrict f)) info of+ A.Done _ r -> r : go rest+ A.Fail _ _ err -> error $ "diff-tree parse error: " ++ err+ go (s:[]) = error $ "diff-tree parse error near \"" ++ decodeBL' s ++ "\"" - -- info = :<srcmode> SP <dstmode> SP <srcsha> SP <dstsha> SP <status>- (srcm, past_srcm) = splitAt 7 $ drop 1 info- (dstm, past_dstm) = splitAt 7 past_srcm- (ssha, past_ssha) = separate (== ' ') past_dstm- (dsha, s) = separate (== ' ') past_ssha+-- :<srcmode> SP <dstmode> SP <srcsha> SP <dstsha> SP <status>+parserDiffRaw :: RawFilePath -> A.Parser DiffTreeItem+parserDiffRaw f = DiffTreeItem+ <$ A8.char ':'+ <*> octal+ <* A8.char ' '+ <*> octal+ <* A8.char ' '+ <*> (maybe (fail "bad srcsha") return . extractSha =<< nextword)+ <* A8.char ' '+ <*> (maybe (fail "bad dstsha") return . extractSha =<< nextword)+ <* A8.char ' '+ <*> A.takeByteString+ <*> pure (asTopFilePath $ fromInternalGitPath $ Git.Filename.decode f)+ where+ nextword = A8.takeTill (== ' ')
Git/DiffTreeItem.hs view
@@ -10,6 +10,7 @@ ) where import System.Posix.Types+import qualified Data.ByteString as S import Git.FilePath import Git.Types@@ -19,6 +20,6 @@ , dstmode :: FileMode , srcsha :: Sha -- null sha if file was added , dstsha :: Sha -- null sha if file was deleted- , status :: String+ , status :: S.ByteString , file :: TopFilePath } deriving Show
Git/FilePath.hs view
@@ -50,7 +50,7 @@ {- Git uses the branch:file form to refer to a BranchFilePath -} descBranchFilePath :: BranchFilePath -> S.ByteString descBranchFilePath (BranchFilePath b f) =- encodeBS' (fromRef b) <> ":" <> getTopFilePath f+ fromRef' b <> ":" <> getTopFilePath f {- Path to a TopFilePath, within the provided git repo. -} fromTopFilePath :: TopFilePath -> Git.Repo -> RawFilePath
Git/Fsck.hs view
@@ -139,7 +139,8 @@ ] r findShas :: [String] -> [Sha]-findShas = catMaybes . map extractSha . concat . map words . filter wanted+findShas = catMaybes . map (extractSha . encodeBS') + . concat . map words . filter wanted where wanted l = not ("dangling " `isPrefixOf` l)
Git/GCrypt.hs view
@@ -98,6 +98,7 @@ defaultkey = "gcrypt.participants" parse (Just (ConfigValue "simple")) = [] parse (Just (ConfigValue b)) = words (decodeBS' b)+ parse (Just NoConfigValue) = [] parse Nothing = [] remoteParticipantConfigKey :: RemoteName -> ConfigKey
Git/HashObject.hs view
@@ -18,6 +18,7 @@ import Utility.Tmp import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L import Data.ByteString.Builder @@ -39,7 +40,7 @@ hashFile h file = CoProcess.query h send receive where send to = hPutStrLn to =<< absPath file- receive from = getSha "hash-object" $ hGetLine from+ receive from = getSha "hash-object" $ S8.hGetLine from class HashableBlob t where hashableBlobToHandle :: Handle -> t -> IO ()
Git/History.hs view
@@ -15,6 +15,9 @@ import Git.Sha import qualified Data.Set as S+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Char8 as B8+import qualified Data.ByteString.Lazy.Char8 as L8 data History t = History t (S.Set (History t)) deriving (Show, Eq, Ord)@@ -53,8 +56,9 @@ !h <- fmap (truncateHistoryToDepth n) . build Nothing . map parsehistorycommit- . lines- <$> hGetContents inh+ . map L.toStrict+ . L8.lines+ <$> L.hGetContents inh hClose inh void $ waitForProcess pid return h@@ -93,7 +97,7 @@ , Param "--format=%T %H %P" ] - parsehistorycommit l = case map extractSha (splitc ' ' l) of+ parsehistorycommit l = case map extractSha (B8.split ' ' l) of (Just t:Just c:ps) -> Just $ ( HistoryCommit { historyCommit = c
Git/LsFiles.hs view
@@ -36,9 +36,10 @@ import Utility.TimeStamp import Numeric+import Data.Char import System.Posix.Types import qualified Data.Map as M-import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString as S {- It's only safe to use git ls-files on the current repo, not on a remote. -@@ -81,7 +82,7 @@ {- Files that are checked into the index or have been committed to a - branch. -} inRepoOrBranch :: Branch -> [RawFilePath] -> Repo -> IO ([RawFilePath], IO Bool)-inRepoOrBranch (Ref b) = inRepo' [Param $ "--with-tree=" ++ b]+inRepoOrBranch b = inRepo' [Param $ "--with-tree=" ++ fromRef b] {- Scans for files at the specified locations that are not checked into git. -} notInRepo :: Bool -> [RawFilePath] -> Repo -> IO ([RawFilePath], IO Bool)@@ -189,21 +190,21 @@ - contents. -} stagedDetails' :: [CommandParam] -> [RawFilePath] -> Repo -> IO ([StagedDetails], IO Bool) stagedDetails' ps l repo = guardSafeForLsFiles repo $ do- (ls, cleanup) <- pipeNullSplit params repo+ (ls, cleanup) <- pipeNullSplit' params repo return (map parseStagedDetails ls, cleanup) where params = Param "ls-files" : Param "--stage" : Param "-z" : ps ++ Param "--" : map (File . fromRawFilePath) l -parseStagedDetails :: L.ByteString -> StagedDetails+parseStagedDetails :: S.ByteString -> StagedDetails parseStagedDetails s- | null file = (L.toStrict s, Nothing, Nothing)- | otherwise = (toRawFilePath file, extractSha sha, readmode mode)+ | S.null file = (s, Nothing, Nothing)+ | otherwise = (file, extractSha sha, readmode mode) where- (metadata, file) = separate (== '\t') (decodeBL' s)- (mode, metadata') = separate (== ' ') metadata- (sha, _) = separate (== ' ') metadata'- readmode = fst <$$> headMaybe . readOct+ (metadata, file) = separate' (== fromIntegral (ord '\t')) s+ (mode, metadata') = separate' (== fromIntegral (ord ' ')) metadata+ (sha, _) = separate' (== fromIntegral (ord ' ')) metadata'+ readmode = fst <$$> headMaybe . readOct . decodeBS' {- Returns a list of the files in the specified locations that are staged - for commit, and whose type has changed. -}@@ -284,7 +285,7 @@ then Nothing else do treeitemtype <- readTreeItemType (encodeBS rawtreeitemtype)- sha <- extractSha rawsha+ sha <- extractSha (encodeBS' rawsha) return $ InternalUnmerged (stage == 2) (toRawFilePath file) (Just treeitemtype) (Just sha) _ -> Nothing
Git/LsTree.hs view
@@ -96,7 +96,7 @@ <*> A8.takeTill (== ' ') <* A8.char ' ' -- sha- <*> (Ref . decodeBS' <$> A8.takeTill (== '\t'))+ <*> (Ref <$> A8.takeTill (== '\t')) <* A8.char '\t' -- file <*> (asTopFilePath . Git.Filename.decode <$> A.takeByteString)
Git/Objects.hs view
@@ -26,7 +26,7 @@ listLooseObjectShas :: Repo -> IO [Sha] listLooseObjectShas r = catchDefaultIO [] $- mapMaybe (extractSha . concat . reverse . take 2 . reverse . splitDirectories)+ mapMaybe (extractSha . encodeBS . concat . reverse . take 2 . reverse . splitDirectories) <$> dirContentsRecursiveSkipping (== "pack") True (objectsDir r) looseObjectFile :: Repo -> Sha -> FilePath
Git/Ref.hs view
@@ -17,6 +17,7 @@ import Data.Char (chr, ord) import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8 headRef :: Ref headRef = Ref "HEAD"@@ -25,7 +26,7 @@ headFile r = fromRawFilePath (localGitDir r) </> "HEAD" setHeadRef :: Ref -> Repo -> IO ()-setHeadRef ref r = writeFile (headFile r) ("ref: " ++ fromRef ref)+setHeadRef ref r = S.writeFile (headFile r) ("ref: " <> fromRef' ref) {- Converts a fully qualified git ref into a user-visible string. -} describe :: Ref -> String@@ -41,10 +42,11 @@ {- Removes a directory such as "refs/heads/master" from a - fully qualified ref. Any ref not starting with it is left as-is. -} removeBase :: String -> Ref -> Ref-removeBase dir (Ref r)- | prefix `isPrefixOf` r = Ref (drop (length prefix) r)- | otherwise = Ref r+removeBase dir r+ | prefix `isPrefixOf` rs = Ref $ encodeBS $ drop (length prefix) rs+ | otherwise = r where+ rs = fromRef r prefix = case end dir of ['/'] -> dir _ -> dir ++ "/"@@ -53,7 +55,7 @@ - refs/heads/master, yields a version of that ref under the directory, - such as refs/remotes/origin/master. -} underBase :: String -> Ref -> Ref-underBase dir r = Ref $ dir ++ "/" ++ fromRef (base r)+underBase dir r = Ref $ encodeBS dir <> "/" <> fromRef' (base r) {- Convert a branch such as "master" into a fully qualified ref. -} branchRef :: Branch -> Ref@@ -66,21 +68,25 @@ - of a repo. -} fileRef :: RawFilePath -> Ref-fileRef f = Ref $ ":./" ++ fromRawFilePath f+fileRef f = Ref $ ":./" <> f {- Converts a Ref to refer to the content of the Ref on a given date. -} dateRef :: Ref -> RefDate -> Ref-dateRef (Ref r) (RefDate d) = Ref $ r ++ "@" ++ d+dateRef r (RefDate d) = Ref $ fromRef' r <> "@" <> encodeBS' d {- A Ref that can be used to refer to a file in the repository as it - appears in a given Ref. -} fileFromRef :: Ref -> RawFilePath -> Ref-fileFromRef (Ref r) f = let (Ref fr) = fileRef f in Ref (r ++ fr)+fileFromRef r f = let (Ref fr) = fileRef f in Ref (fromRef' r <> fr) {- Checks if a ref exists. -} exists :: Ref -> Repo -> IO Bool exists ref = runBool- [Param "show-ref", Param "--verify", Param "-q", Param $ fromRef ref]+ [ Param "show-ref"+ , Param "--verify"+ , Param "-q"+ , Param $ fromRef ref+ ] {- The file used to record a ref. (Git also stores some refs in a - packed-refs file.) -}@@ -107,26 +113,26 @@ ] process s | S.null s = Nothing- | otherwise = Just $ Ref $ decodeBS' $ firstLine' s+ | otherwise = Just $ Ref $ firstLine' s headSha :: Repo -> IO (Maybe Sha) headSha = sha headRef {- List of (shas, branches) matching a given ref or refs. -} matching :: [Ref] -> Repo -> IO [(Sha, Branch)]-matching refs repo = matching' (map fromRef refs) repo+matching = matching' [] {- Includes HEAD in the output, if asked for it. -} matchingWithHEAD :: [Ref] -> Repo -> IO [(Sha, Branch)]-matchingWithHEAD refs repo = matching' ("--head" : map fromRef refs) repo+matchingWithHEAD = matching' [Param "--head"] -{- List of (shas, branches) matching a given ref spec. -}-matching' :: [String] -> Repo -> IO [(Sha, Branch)]-matching' ps repo = map gen . lines . decodeBS' <$> - pipeReadStrict (Param "show-ref" : map Param ps) repo+matching' :: [CommandParam] -> [Ref] -> Repo -> IO [(Sha, Branch)]+matching' ps rs repo = map gen . S8.lines <$> + pipeReadStrict (Param "show-ref" : ps ++ rps) repo where- gen l = let (r, b) = separate (== ' ') l+ gen l = let (r, b) = separate' (== fromIntegral (ord ' ')) l in (Ref r, Ref b)+ rps = map (Param . fromRef) rs {- List of (shas, branches) matching a given ref. - Duplicate shas are filtered out. -}@@ -137,7 +143,7 @@ {- List of all refs. -} list :: Repo -> IO [(Sha, Ref)]-list = matching' []+list = matching' [] [] {- Deletes a ref. This can delete refs that are not branches, - which git branch --delete refuses to delete. -}@@ -154,13 +160,17 @@ - The ref may be something like a branch name, and it could contain - ":subdir" if a subtree is wanted. -} tree :: Ref -> Repo -> IO (Maybe Sha)-tree (Ref ref) = extractSha . decodeBS <$$> pipeReadStrict- [ Param "rev-parse", Param "--verify", Param "--quiet", Param ref' ]+tree (Ref ref) = extractSha <$$> pipeReadStrict+ [ Param "rev-parse"+ , Param "--verify"+ , Param "--quiet"+ , Param (decodeBS' ref')+ ] where- ref' = if ":" `isInfixOf` ref+ ref' = if ":" `S.isInfixOf` ref then ref -- de-reference commit objects to the tree- else ref ++ ":"+ else ref <> ":" {- Checks if a String is a legal git ref name. -
Git/RefLog.hs view
@@ -12,6 +12,9 @@ import Git.Command import Git.Sha +import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8+ {- Gets the reflog for a given branch. -} get :: Branch -> Repo -> IO [Sha] get b = getMulti [b]@@ -21,7 +24,7 @@ getMulti bs = get' (map (Param . fromRef) bs) get' :: [CommandParam] -> Repo -> IO [Sha]-get' ps = mapMaybe extractSha . lines . decodeBS <$$> pipeReadStrict ps'+get' ps = mapMaybe (extractSha . S.copy) . S8.lines <$$> pipeReadStrict ps' where ps' = catMaybes [ Just $ Param "log"
Git/Remote.hs view
@@ -84,12 +84,17 @@ where replacement = decodeBS' $ S.drop (S.length prefix) $ S.take (S.length bestkey - S.length suffix) bestkey- (ConfigKey bestkey, ConfigValue bestvalue) = maximumBy longestvalue insteadofs+ (bestkey, bestvalue) = + case maximumBy longestvalue insteadofs of+ (ConfigKey k, ConfigValue v) -> (k, v)+ (ConfigKey k, NoConfigValue) -> (k, mempty) longestvalue (_, a) (_, b) = compare b a- insteadofs = filterconfig $ \(ConfigKey k, ConfigValue v) -> - prefix `S.isPrefixOf` k &&- suffix `S.isSuffixOf` k &&- v `S.isPrefixOf` encodeBS l+ insteadofs = filterconfig $ \case+ (ConfigKey k, ConfigValue v) -> + prefix `S.isPrefixOf` k &&+ suffix `S.isSuffixOf` k &&+ v `S.isPrefixOf` encodeBS l+ (_, NoConfigValue) -> False filterconfig f = filter f $ concatMap splitconfigs $ M.toList $ fullconfig repo splitconfigs (k, vs) = map (\v -> (k, v)) vs
Git/Repair.hs view
@@ -232,7 +232,7 @@ getAllRefs' :: FilePath -> IO [Ref] getAllRefs' refdir = do let topsegs = length (splitPath refdir) - 1- let toref = Ref . joinPath . drop topsegs . splitPath+ let toref = Ref . encodeBS' . joinPath . drop topsegs . splitPath map toref <$> dirContentsRecursive refdir explodePackedRefsFile :: Repo -> IO ()@@ -257,8 +257,8 @@ parsePacked :: String -> Maybe (Sha, Ref) parsePacked l = case words l of (sha:ref:[])- | isJust (extractSha sha) && Ref.legal True ref ->- Just (Ref sha, Ref ref)+ | isJust (extractSha (encodeBS' sha)) && Ref.legal True ref ->+ Just (Ref (encodeBS' sha), Ref (encodeBS' ref)) _ -> Nothing {- git-branch -d cannot be used to remove a branch that is directly@@ -279,13 +279,13 @@ if ok then return (Just branch, goodcommits') else do- (ls, cleanup) <- pipeNullSplit+ (ls, cleanup) <- pipeNullSplit' [ Param "log" , Param "-z" , Param "--format=%H" , Param (fromRef branch) ] r- let branchshas = catMaybes $ map (extractSha . decodeBL) ls+ let branchshas = catMaybes $ map extractSha ls reflogshas <- RefLog.get branch r -- XXX Could try a bit harder here, and look -- for uncorrupted old commits in branches in the@@ -328,8 +328,8 @@ where parse l = case words l of (commitsha:treesha:[]) -> (,)- <$> extractSha commitsha- <*> extractSha treesha+ <$> extractSha (encodeBS' commitsha)+ <*> extractSha (encodeBS' treesha) _ -> Nothing check [] = return True check ((c, t):rest)@@ -448,7 +448,8 @@ void $ tryIO $ allowWrite f where headfile = fromRawFilePath (localGitDir g) </> "HEAD"- validhead s = "ref: refs/" `isPrefixOf` s || isJust (extractSha s)+ validhead s = "ref: refs/" `isPrefixOf` s+ || isJust (extractSha (encodeBS' s)) {- Put it all together. -} runRepair :: (Ref -> Bool) -> Bool -> Repo -> IO (Bool, [Branch])
Git/Sha.hs view
@@ -5,31 +5,43 @@ - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE OverloadedStrings #-}+ module Git.Sha where import Common import Git.Types +import qualified Data.ByteString as S+import Data.Char+ {- Runs an action that causes a git subcommand to emit a Sha, and strips - any trailing newline, returning the sha. -}-getSha :: String -> IO String -> IO Sha+getSha :: String -> IO S.ByteString -> IO Sha getSha subcommand a = maybe bad return =<< extractSha <$> a where bad = error $ "failed to read sha from git " ++ subcommand -{- Extracts the Sha from a string. There can be a trailing newline after- - it, but nothing else. -}-extractSha :: String -> Maybe Sha+{- Extracts the Sha from a ByteString. + -+ - There can be a trailing newline after it, but nothing else.+ -}+extractSha :: S.ByteString -> Maybe Sha extractSha s | len `elem` shaSizes = val s- | len - 1 `elem` shaSizes && length s' == len - 1 = val s'+ | len - 1 `elem` shaSizes && S.length s' == len - 1 = val s' | otherwise = Nothing where- len = length s- s' = firstLine s+ len = S.length s+ s' = firstLine' s val v- | all (`elem` "1234567890ABCDEFabcdef") v = Just $ Ref v+ | S.all validinsha v = Just $ Ref v | otherwise = Nothing+ validinsha w = or+ [ w >= 48 && w <= 57 -- 0-9+ , w >= 97 && w <= 102 -- a-f+ , w >= 65 && w <= 70 -- A-F+ ] {- Sizes of git shas. -} shaSizes :: [Int]@@ -41,7 +53,9 @@ {- Git plumbing often uses a all 0 sha to represent things like a - deleted file. -} nullShas :: [Sha]-nullShas = map (\n -> Ref (replicate n '0')) shaSizes+nullShas = map (\n -> Ref (S.replicate n zero)) shaSizes+ where+ zero = fromIntegral (ord '0') {- Sha to provide to git plumbing when deleting a file. -
Git/Tree.hs view
@@ -38,6 +38,7 @@ import Control.Monad.IO.Class import qualified Data.Set as S import qualified Data.Map as M+import qualified Data.ByteString.Char8 as S8 newtype Tree = Tree [TreeContent] deriving (Show)@@ -106,7 +107,7 @@ NewSubTree _ _ -> error "recordSubTree internal error; unexpected NewSubTree" TreeCommit f fm s -> mkTreeOutput fm CommitObject s f hPutStr h "\NUL" -- signal end of tree to --batch- receive h = getSha "mktree" (hGetLine h)+ receive h = getSha "mktree" (S8.hGetLine h) treeMode :: FileMode treeMode = 0o040000
Git/Types.hs view
@@ -1,12 +1,11 @@ {- git data types -- - Copyright 2010-2019 Joey Hess <id@joeyh.name>+ - Copyright 2010-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Git.Types where @@ -18,6 +17,8 @@ import System.Posix.Types import Utility.SafeCommand import Utility.FileSystemEncoding+import qualified Data.Semigroup as Sem+import Prelude {- Support repositories on local disk, and repositories accessed via an URL. -@@ -54,9 +55,21 @@ newtype ConfigKey = ConfigKey S.ByteString deriving (Ord, Eq) -newtype ConfigValue = ConfigValue S.ByteString- deriving (Ord, Eq, Semigroup, Monoid)+data ConfigValue+ = ConfigValue S.ByteString+ | NoConfigValue+ -- ^ git treats a setting with no value as different than a setting+ -- with an empty value+ deriving (Ord, Eq) +instance Sem.Semigroup ConfigValue where+ ConfigValue a <> ConfigValue b = ConfigValue (a <> b)+ a <> NoConfigValue = a+ NoConfigValue <> b = b++instance Monoid ConfigValue where+ mempty = ConfigValue mempty+ instance Default ConfigValue where def = ConfigValue mempty @@ -68,6 +81,7 @@ fromConfigValue :: ConfigValue -> String fromConfigValue (ConfigValue s) = decodeBS' s+fromConfigValue NoConfigValue = mempty instance Show ConfigValue where show = fromConfigValue@@ -81,12 +95,15 @@ type RemoteName = String {- A git ref. Can be a sha1, or a branch or tag name. -}-newtype Ref = Ref String+newtype Ref = Ref S.ByteString deriving (Eq, Ord, Read, Show) fromRef :: Ref -> String-fromRef (Ref s) = s+fromRef = decodeBS' . fromRef' +fromRef' :: Ref -> S.ByteString+fromRef' (Ref s) = s+ {- Aliases for Ref. -} type Branch = Ref type Sha = Ref@@ -98,6 +115,7 @@ {- Types of objects that can be stored in git. -} data ObjectType = BlobObject | CommitObject | TreeObject+ deriving (Show) readObjectType :: S.ByteString -> Maybe ObjectType readObjectType "blob" = Just BlobObject
Git/UnionMerge.hs view
@@ -10,8 +10,10 @@ mergeIndex ) where +import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy.Char8 as L8+import qualified Data.ByteString.Char8 as S8 import qualified Data.Set as S import Common@@ -54,13 +56,13 @@ {- For merging two trees. -} mergeTrees :: Ref -> Ref -> HashObjectHandle -> CatFileHandle -> Repo -> Streamer-mergeTrees (Ref x) (Ref y) hashhandle ch = doMerge hashhandle ch- ("diff-tree":diffOpts ++ [x, y, "--"])+mergeTrees x y hashhandle ch = doMerge hashhandle ch+ ("diff-tree":diffOpts ++ [fromRef x, fromRef y, "--"]) {- For merging a single tree into the index. -} mergeTreeIndex :: Ref -> HashObjectHandle -> CatFileHandle -> Repo -> Streamer-mergeTreeIndex (Ref r) hashhandle ch = doMerge hashhandle ch $- "diff-index" : diffOpts ++ ["--cached", r, "--"]+mergeTreeIndex r hashhandle ch = doMerge hashhandle ch $+ "diff-index" : diffOpts ++ ["--cached", fromRef r, "--"] diffOpts :: [String] diffOpts = ["--raw", "-z", "-r", "--no-renames", "-l0"]@@ -69,27 +71,29 @@ - using git to get a raw diff. -} doMerge :: HashObjectHandle -> CatFileHandle -> [String] -> Repo -> Streamer doMerge hashhandle ch differ repo streamer = do- (diff, cleanup) <- pipeNullSplit (map Param differ) repo+ (diff, cleanup) <- pipeNullSplit' (map Param differ) repo go diff void $ cleanup where go [] = noop- go (info:file:rest) = mergeFile (decodeBL' info) (L.toStrict file) hashhandle ch >>=+ go (info:file:rest) = mergeFile info file hashhandle ch >>= maybe (go rest) (\l -> streamer l >> go rest) go (_:[]) = error $ "parse error " ++ show differ {- Given an info line from a git raw diff, and the filename, generates - a line suitable for update-index that union merges the two sides of the - diff. -}-mergeFile :: String -> RawFilePath -> HashObjectHandle -> CatFileHandle -> IO (Maybe L.ByteString)-mergeFile info file hashhandle h = case filter (`notElem` nullShas) [Ref asha, Ref bsha] of- [] -> return Nothing- (sha:[]) -> use sha- shas -> use- =<< either return (hashBlob hashhandle . L8.unlines)- =<< calcMerge . zip shas <$> mapM getcontents shas+mergeFile :: S.ByteString -> RawFilePath -> HashObjectHandle -> CatFileHandle -> IO (Maybe L.ByteString)+mergeFile info file hashhandle h = case S8.words info of+ [_colonmode, _bmode, asha, bsha, _status] -> + case filter (`notElem` nullShas) [Ref asha, Ref bsha] of+ [] -> return Nothing+ (sha:[]) -> use sha+ shas -> use+ =<< either return (hashBlob hashhandle . L8.unlines)+ =<< calcMerge . zip shas <$> mapM getcontents shas+ _ -> return Nothing where- [_colonmode, _bmode, asha, bsha, _status] = words info use sha = return $ Just $ updateIndexLine sha TreeFile $ asTopFilePath file -- Get file and split into lines to union merge.
Git/UpdateIndex.hs view
@@ -75,14 +75,14 @@ mapM_ streamer s void $ cleanup where- params = map Param ["ls-tree", "-z", "-r", "--full-tree", x]+ params = map Param ["ls-tree", "-z", "-r", "--full-tree", decodeBS' x] lsSubTree :: Ref -> FilePath -> Repo -> Streamer lsSubTree (Ref x) p repo streamer = do (s, cleanup) <- pipeNullSplit params repo mapM_ streamer s void $ cleanup where- params = map Param ["ls-tree", "-z", "-r", "--full-tree", x, p]+ params = map Param ["ls-tree", "-z", "-r", "--full-tree", decodeBS' x, p] {- Generates a line suitable to be fed into update-index, to add - a given file with a given sha. -}@@ -90,7 +90,7 @@ updateIndexLine sha treeitemtype file = L.fromStrict $ fmtTreeItemType treeitemtype <> " blob "- <> encodeBS (fromRef sha)+ <> fromRef' sha <> "\t" <> indexPath file @@ -108,7 +108,7 @@ unstageFile' :: TopFilePath -> Streamer unstageFile' p = pureStreamer $ L.fromStrict $ "0 "- <> encodeBS' (fromRef deleteSha)+ <> fromRef' deleteSha <> "\t" <> indexPath p
Logs/Config.hs view
@@ -55,6 +55,7 @@ where configkeybuilder (ConfigKey k) = byteString k valuebuilder (ConfigValue v) = byteString v+ valuebuilder NoConfigValue = mempty parseGlobalConfig :: L.ByteString -> MapLog ConfigKey ConfigValue parseGlobalConfig = parseMapLog configkeyparser valueparser@@ -63,7 +64,10 @@ valueparser = ConfigValue <$> A.takeByteString loadGlobalConfig :: Annex (M.Map ConfigKey ConfigValue)-loadGlobalConfig = M.filter (\(ConfigValue v) -> not (S.null v)) +loadGlobalConfig = M.filter nonempty . simpleMap . parseGlobalConfig <$> Annex.Branch.get configLog+ where+ nonempty (ConfigValue v) = not (S.null v)+ nonempty NoConfigValue = True
Logs/Export.hs view
@@ -158,12 +158,12 @@ where go [] = mempty go (r:rs) = rref r <> mconcat [ charUtf8 ' ' <> rref r' | r' <- rs ]- rref r = byteString (encodeBS' (Git.fromRef r))+ rref = byteString . Git.fromRef' exportedParser :: A.Parser Exported exportedParser = Exported <$> refparser <*> many refparser where- refparser = (Git.Ref . decodeBS <$> A8.takeWhile1 (/= ' ') )+ refparser = (Git.Ref <$> A8.takeWhile1 (/= ' ') ) <* ((const () <$> A8.char ' ') <|> A.endOfInput) logExportExcluded :: UUID -> ((Git.Tree.TreeItem -> IO ()) -> Annex a) -> Annex a
Logs/FsckResults.hs view
@@ -43,7 +43,7 @@ deserialize ("truncated":ls) = deserialize' ls True deserialize ls = deserialize' ls False deserialize' ls t =- let s = S.fromList $ map Ref ls+ let s = S.fromList $ map (Ref . encodeBS') ls in if S.null s then FsckFailed else FsckFoundMissing s t clearFsckResults :: UUID -> Annex ()
Logs/PreferredContent.hs view
@@ -92,8 +92,9 @@ in simpleMap . parseLogOldWithUUID (\u -> mk u . decodeBS <$> A.takeByteString) <$> Annex.Branch.get l- pc <- genmap preferredContentLog =<< groupPreferredContentMapRaw- rc <- genmap requiredContentLog M.empty+ gm <- groupPreferredContentMapRaw+ pc <- genmap preferredContentLog gm+ rc <- genmap requiredContentLog gm -- Required content is implicitly also preferred content, so combine. let pc' = M.unionWith combiner pc rc return (pc', rc)
Logs/Transfer.hs view
@@ -143,12 +143,12 @@ transfers <- filter (wanted . transferKey) <$> mapMaybe parseTransferFile . concat <$> findfiles infos <- mapM checkTransfer transfers- return $ map (\(t, Just i) -> (t, i)) $- filter running $ zip transfers infos+ return $ mapMaybe running $ zip transfers infos where findfiles = liftIO . mapM dirContentsRecursive =<< mapM (fromRepo . transferDir) dirs- running (_, i) = isJust i+ running (t, Just i) = Just (t, i)+ running (_, Nothing) = Nothing {- Number of bytes remaining to download from matching downloads that are in - progress. -}
Logs/View.hs view
@@ -9,6 +9,8 @@ - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE OverloadedStrings #-}+ module Logs.View ( currentView, setView,@@ -31,6 +33,7 @@ import qualified Data.Text as T import qualified Data.Set as S import Data.Char+import qualified Data.ByteString as B setView :: View -> Annex () setView v = do@@ -54,11 +57,11 @@ currentView :: Annex (Maybe View) currentView = go =<< inRepo Git.Branch.current where- go (Just b) | branchViewPrefix `isPrefixOf` fromRef b =+ go (Just b) | branchViewPrefix `B.isPrefixOf` fromRef' b = headMaybe . filter (\v -> branchView v == b) <$> recentViews go _ = return Nothing -branchViewPrefix :: String+branchViewPrefix :: B.ByteString branchViewPrefix = "refs/heads/views" {- Generates a git branch name for a View.@@ -68,10 +71,11 @@ -} branchView :: View -> Git.Branch branchView view- | null name = Git.Ref branchViewPrefix- | otherwise = Git.Ref $ branchViewPrefix ++ "/" ++ name+ | B.null name = Git.Ref branchViewPrefix+ | otherwise = Git.Ref $ branchViewPrefix <> "/" <> name where- name = intercalate ";" $ map branchcomp (viewComponents view)+ name = encodeBS' $+ intercalate ";" $ map branchcomp (viewComponents view) branchcomp c | viewVisible c = branchcomp' c | otherwise = "(" ++ branchcomp' c ++ ")"@@ -92,7 +96,7 @@ is_branchView :: Git.Branch -> Bool is_branchView (Ref b) | b == branchViewPrefix = True- | otherwise = (branchViewPrefix ++ "/") `isPrefixOf` b+ | otherwise = (branchViewPrefix <> "/") `B.isPrefixOf` b prop_branchView_legal :: View -> Bool prop_branchView_legal = Git.Ref.legal False . fromRef . branchView
Logs/Web.hs view
@@ -90,7 +90,7 @@ {- Ensure the git-annex branch's index file is up-to-date and - any journaled changes are reflected in it, since we're going - to query its index directly. -}- Annex.Branch.update+ _ <- Annex.Branch.update Annex.Branch.commit =<< Annex.Branch.commitMessage Annex.Branch.withIndex $ do top <- fromRepo Git.repoPath
Messages/JSON.hs view
@@ -65,9 +65,11 @@ none = id start :: String -> Maybe RawFilePath -> Maybe Key -> JSONBuilder-start command file key _ = Just (o, False)+start command file key _ = case j of+ Object o -> Just (o, False)+ _ -> Nothing where- Object o = toJSON' $ JSONActionItem+ j = toJSON' $ JSONActionItem { itemCommand = Just command , itemKey = key , itemFile = fromRawFilePath <$> file@@ -102,18 +104,22 @@ combinelines new _old = new info :: String -> JSONBuilder-info s _ = Just (o, True)+info s _ = case j of+ Object o -> Just (o, True)+ _ -> Nothing where- Object o = object ["info" .= toJSON' s]+ j = object ["info" .= toJSON' s] data JSONChunk v where AesonObject :: Object -> JSONChunk Object JSONChunk :: ToJSON' v => [(String, v)] -> JSONChunk [(String, v)] add :: JSONChunk v -> JSONBuilder-add v (Just (o, e)) = Just (HM.union o' o, e)+add v (Just (o, e)) = case j of+ Object o' -> Just (HM.union o' o, e)+ _ -> Nothing where- Object o' = case v of+ j = case v of AesonObject ao -> Object ao JSONChunk l -> object $ map mkPair l mkPair (s, d) = (packString s, toJSON' d)@@ -125,12 +131,15 @@ -- Show JSON formatted progress, including the current state of the JSON -- object for the action being performed. progress :: Maybe Object -> Maybe Integer -> BytesProcessed -> IO ()-progress maction msize bytesprocessed = emit $ case maction of- Just action -> HM.insert "action" (Object action) o- Nothing -> o+progress maction msize bytesprocessed = + case j of+ Object o -> emit $ case maction of+ Just action -> HM.insert "action" (Object action) o+ Nothing -> o+ _ -> return () where n = fromBytesProcessed bytesprocessed :: Integer- Object o = case msize of+ j = case msize of Just size -> object [ "byte-progress" .= n , "percent-progress" .= showPercentage 2 (percentage size n)
Remote/Adb.hs view
@@ -128,12 +128,12 @@ (giveup "Specify androiddirectory=") (pure . AndroidPath . fromProposedAccepted) (M.lookup androiddirectoryField c)- serial <- getserial =<< liftIO enumerateAdbConnected+ serial <- getserial =<< enumerateAdbConnected let c' = M.insert androidserialField (Proposed (fromAndroidSerial serial)) c (c'', _encsetup) <- encryptionSetup c' gc - ok <- liftIO $ adbShellBool serial+ ok <- adbShellBool serial [Param "mkdir", Param "-p", File (fromAndroidPath adir)] unless ok $ giveup "Creating directory on Android device failed."@@ -166,15 +166,15 @@ store' serial dest src = store'' serial dest src (return True) store'' :: AndroidSerial -> AndroidPath -> FilePath -> Annex Bool -> Annex Bool-store'' serial dest src canoverwrite = do+store'' serial dest src canoverwrite = checkAdbInPath False $ do let destdir = takeDirectory $ fromAndroidPath dest- liftIO $ void $ adbShell serial [Param "mkdir", Param "-p", File destdir]+ void $ adbShell serial [Param "mkdir", Param "-p", File destdir] showOutput -- make way for adb push output let tmpdest = fromAndroidPath dest ++ ".annextmp" ifM (liftIO $ boolSystem "adb" (mkAdbCommand serial [Param "push", File src, File tmpdest])) ( ifM canoverwrite -- move into place atomically- ( liftIO $ adbShellBool serial [Param "mv", File tmpdest, File (fromAndroidPath dest)]+ ( adbShellBool serial [Param "mv", File tmpdest, File (fromAndroidPath dest)] , do void $ remove' serial (AndroidPath tmpdest) return False@@ -189,7 +189,7 @@ giveup "adb pull failed" retrieve' :: AndroidSerial -> AndroidPath -> FilePath -> Annex Bool-retrieve' serial src dest = do+retrieve' serial src dest = checkAdbInPath False $ do showOutput -- make way for adb pull output liftIO $ boolSystem "adb" $ mkAdbCommand serial [ Param "pull"@@ -201,7 +201,7 @@ remove serial adir k = remove' serial (androidLocation adir k) remove' :: AndroidSerial -> AndroidPath -> Annex Bool-remove' serial aloc = liftIO $ adbShellBool serial+remove' serial aloc = adbShellBool serial [Param "rm", Param "-f", File (fromAndroidPath aloc)] checkKey :: Remote -> AndroidSerial -> AndroidPath -> CheckPresent@@ -210,7 +210,7 @@ checkKey' :: Remote -> AndroidSerial -> AndroidPath -> Annex Bool checkKey' r serial aloc = do showChecking r- out <- liftIO $ adbShellRaw serial $ unwords+ out <- adbShellRaw serial $ unwords [ "if test -e ", shellEscape (fromAndroidPath aloc) , "; then echo y" , "; else echo n"@@ -247,7 +247,7 @@ aloc = androidExportLocation adir loc removeExportDirectoryM :: AndroidSerial -> AndroidPath -> ExportDirectory -> Annex Bool-removeExportDirectoryM serial abase dir = liftIO $ adbShellBool serial+removeExportDirectoryM serial abase dir = adbShellBool serial [Param "rm", Param "-rf", File (fromAndroidPath adir)] where adir = androidExportLocation abase (mkExportLocation (fromExportDirectory dir))@@ -258,14 +258,19 @@ aloc = androidExportLocation adir loc renameExportM :: AndroidSerial -> AndroidPath -> Key -> ExportLocation -> ExportLocation -> Annex (Maybe Bool)-renameExportM serial adir _k old new = liftIO $ Just <$> - adbShellBool serial [Param "mv", Param "-f", File oldloc, File newloc]+renameExportM serial adir _k old new = Just <$> adbShellBool serial ps where oldloc = fromAndroidPath $ androidExportLocation adir old newloc = fromAndroidPath $ androidExportLocation adir new+ ps =+ [ Param "mv"+ , Param "-f"+ , File oldloc+ , File newloc+ ] listImportableContentsM :: AndroidSerial -> AndroidPath -> Annex (Maybe (ImportableContents (ContentIdentifier, ByteSize)))-listImportableContentsM serial adir = liftIO $+listImportableContentsM serial adir = process <$> adbShell serial [ Param "find" -- trailing slash is needed, or android's find command@@ -300,7 +305,7 @@ ifM (retrieve' serial src dest) ( do k <- mkkey- currcid <- liftIO $ getExportContentIdentifier serial adir loc+ currcid <- getExportContentIdentifier serial adir loc return $ if currcid == Right (Just cid) then k else Nothing@@ -315,7 +320,7 @@ -- file is expensive and don't want to do it unncessarily. ifM checkcanoverwrite ( ifM (store'' serial dest src checkcanoverwrite)- ( liftIO $ getExportContentIdentifier serial adir loc >>= return . \case+ ( getExportContentIdentifier serial adir loc >>= return . \case Right (Just cid) -> Right cid Right Nothing -> Left "adb failed to store file" Left _ -> Left "unable to get content identifier for file stored on adtb"@@ -325,7 +330,7 @@ ) where dest = androidExportLocation adir loc- checkcanoverwrite = liftIO $+ checkcanoverwrite = getExportContentIdentifier serial adir loc >>= return . \case Right (Just cid) | cid `elem` overwritablecids -> True Right Nothing -> True@@ -333,7 +338,7 @@ removeExportWithContentIdentifierM :: AndroidSerial -> AndroidPath -> Key -> ExportLocation -> [ContentIdentifier] -> Annex Bool removeExportWithContentIdentifierM serial adir k loc removeablecids = catchBoolIO $- liftIO (getExportContentIdentifier serial adir loc) >>= \case+ getExportContentIdentifier serial adir loc >>= \case Right Nothing -> return True Right (Just cid) | cid `elem` removeablecids -> removeExportM serial adir k loc@@ -341,7 +346,7 @@ checkPresentExportWithContentIdentifierM :: AndroidSerial -> AndroidPath -> Key -> ExportLocation -> [ContentIdentifier] -> Annex Bool checkPresentExportWithContentIdentifierM serial adir _k loc knowncids = - liftIO $ getExportContentIdentifier serial adir loc >>= \case+ getExportContentIdentifier serial adir loc >>= \case Right (Just cid) | cid `elem` knowncids -> return True Right _ -> return False Left _ -> giveup "unable to access Android device"@@ -351,8 +356,8 @@ fromAndroidPath adir ++ "/" ++ fromRawFilePath (fromExportLocation loc) -- | List all connected Android devices.-enumerateAdbConnected :: IO [AndroidSerial]-enumerateAdbConnected = +enumerateAdbConnected :: Annex [AndroidSerial]+enumerateAdbConnected = checkAdbInPath [] $ liftIO $ mapMaybe parse . lines <$> readProcess "adb" ["devices"] where parse l = @@ -364,11 +369,11 @@ -- | Runs a command on the android device with the given serial number. -- -- Any stdout from the command is returned, separated into lines.-adbShell :: AndroidSerial -> [CommandParam] -> IO (Maybe [String])+adbShell :: AndroidSerial -> [CommandParam] -> Annex (Maybe [String]) adbShell serial cmd = adbShellRaw serial $ unwords $ map shellEscape (toCommand cmd) -adbShellBool :: AndroidSerial -> [CommandParam] -> IO Bool+adbShellBool :: AndroidSerial -> [CommandParam] -> Annex Bool adbShellBool serial cmd = adbShellRaw serial cmd' >>= return . \case Just l -> end l == ["y"]@@ -379,8 +384,8 @@ -- | Runs a raw shell command on the android device. -- Any necessary shellEscaping must be done by caller.-adbShellRaw :: AndroidSerial -> String -> IO (Maybe [String])-adbShellRaw serial cmd = catchMaybeIO $ +adbShellRaw :: AndroidSerial -> String -> Annex (Maybe [String])+adbShellRaw serial cmd = checkAdbInPath Nothing $ liftIO $ catchMaybeIO $ processoutput <$> readProcess "adb" [ "-s" , fromAndroidSerial serial@@ -393,13 +398,21 @@ -- despite both linux and android being unix systems. trimcr = takeWhile (/= '\r') +checkAdbInPath :: a -> Annex a -> Annex a+checkAdbInPath d a = ifM (isJust <$> liftIO (searchPath "adb"))+ ( a+ , do+ warning "adb command not found in PATH. Install it to use this remote."+ return d+ )+ mkAdbCommand :: AndroidSerial -> [CommandParam] -> [CommandParam] mkAdbCommand serial cmd = [Param "-s", Param (fromAndroidSerial serial)] ++ cmd -- Gets the current content identifier for a file on the android device. -- If the file is not present, returns Right Nothing-getExportContentIdentifier :: AndroidSerial -> AndroidPath -> ExportLocation -> IO (Either ExitCode (Maybe ContentIdentifier))-getExportContentIdentifier serial adir loc = liftIO $ do+getExportContentIdentifier :: AndroidSerial -> AndroidPath -> ExportLocation -> Annex (Either ExitCode (Maybe ContentIdentifier))+getExportContentIdentifier serial adir loc = do ls <- adbShellRaw serial $ unwords [ "if test -e ", shellEscape aloc , "; then stat -c"
Remote/Bup.hs view
@@ -223,8 +223,10 @@ giveup "ssh failed" else liftIO $ do r' <- Git.Config.read r- let ConfigValue olduuid = Git.Config.get configkeyUUID mempty r'- when (S.null olduuid) $+ let noolduuid = case Git.Config.get configkeyUUID mempty r' of+ ConfigValue olduuid -> S.null olduuid+ NoConfigValue -> True+ when noolduuid $ Git.Command.run [ Param "config" , Param "annex.uuid"
Remote/External.hs view
@@ -65,7 +65,7 @@ gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> RemoteStateHandle -> Annex (Maybe Remote) gen r u rc gc rs -- readonly mode only downloads urls; does not use external program- | remoteAnnexReadOnly gc = do+ | externaltype == "readonly" = do c <- parsedRemoteConfig remote rc cst <- remoteCost gc expensiveRemoteCost mk c cst GloballyAvailable@@ -165,15 +165,22 @@ externalSetup _ mu _ c gc = do u <- maybe (liftIO genUUID) return mu pc <- either giveup return $ parseRemoteConfig c lenientRemoteConfigParser- let externaltype = fromMaybe (giveup "Specify externaltype=") $- getRemoteConfigValue externaltypeField pc+ let readonlyconfig = getRemoteConfigValue readonlyField pc == Just True+ let externaltype = if readonlyconfig+ then "readonly"+ else fromMaybe (giveup "Specify externaltype=") $+ getRemoteConfigValue externaltypeField pc (c', _encsetup) <- encryptionSetup c gc - c'' <- case getRemoteConfigValue readonlyField pc of- Just True -> do+ c'' <- if readonlyconfig+ then do+ -- Setting annex-readonly is not really necessary+ -- anymore, but older versions of git-annex used+ -- this, not externaltype=readonly, so still set+ -- it. setConfig (remoteAnnexConfig (fromJust (lookupName c)) "readonly") (boolConfig True) return c'- _ -> do+ else do pc' <- either giveup return $ parseRemoteConfig c' lenientRemoteConfigParser external <- newExternal externaltype (Just u) pc' (Just gc) Nothing -- Now that we have an external, ask it to LISTCONFIGS, @@ -194,14 +201,16 @@ return (changes c') gitConfigSpecialRemote u c'' [("externaltype", externaltype)]- return (c'', u)+ return (M.delete readonlyField c'', u) checkExportSupported :: ParsedRemoteConfig -> RemoteGitConfig -> Annex Bool checkExportSupported c gc = do let externaltype = fromMaybe (giveup "Specify externaltype=") $ remoteAnnexExternalType gc <|> getRemoteConfigValue externaltypeField c- checkExportSupported' - =<< newExternal externaltype Nothing c (Just gc) Nothing+ if externaltype == "readonly"+ then return False+ else checkExportSupported' + =<< newExternal externaltype Nothing c (Just gc) Nothing checkExportSupported' :: External -> Annex Bool checkExportSupported' external = go `catchNonAsync` (const (return False))@@ -649,7 +658,16 @@ giveup $ "Cannot run " ++ cmd ++ " -- Make sure it's executable and that its dependencies are installed." runerr Nothing _ = do path <- intercalate ":" <$> getSearchPath- giveup $ "Cannot run " ++ basecmd ++ " -- It is not installed in PATH (" ++ path ++ ")"+ let err = "Cannot run " ++ basecmd ++ " -- It is not installed in PATH (" ++ path ++ ")"+ case (lookupName (unparsedRemoteConfig (externalDefaultConfig external)), remoteAnnexReadOnly <$> externalGitConfig external) of+ (Just rname, Just True) -> giveup $ unlines+ [ err+ , "This remote has annex-readonly=true, and previous versions of"+ , "git-annex would tried to download from it without"+ , "installing " ++ basecmd ++ ". If you want that, you need to set:"+ , "git config remote." ++ rname ++ ".annex-externaltype readonly"+ ]+ _ -> giveup err stopExternal :: External -> Annex () stopExternal external = liftIO $ do
Remote/GCrypt.hs view
@@ -474,7 +474,7 @@ | Git.repoIsLocal r || Git.repoIsLocalUnknown r = extract <$> liftIO (catchMaybeIO $ Git.Config.read r) | not fast = extract . liftM fst3 <$> getM (eitherToMaybe <$>)- [ Ssh.onRemote NoConsumeStdin r (\f p -> liftIO (Git.Config.fromPipe r f p), return (Left $ error "configlist failed")) "configlist" [] []+ [ Ssh.onRemote NoConsumeStdin r (\f p -> liftIO (Git.Config.fromPipe r f p Git.Config.ConfigList), return (Left $ error "configlist failed")) "configlist" [] [] , getConfigViaRsync r gc ] | otherwise = return (Nothing, r)
Remote/Git.hs view
@@ -1,6 +1,6 @@ {- Standard git remotes. -- - Copyright 2011-2019 Joey Hess <id@joeyh.name>+ - Copyright 2011-2020 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -12,7 +12,7 @@ remote, configRead, repoAvail,- onLocal,+ onLocalRepo, ) where import Annex.Common@@ -249,7 +249,7 @@ | haveconfig r = return r -- already read | Git.repoIsSsh r = storeUpdatedRemote $ do v <- Ssh.onRemote NoConsumeStdin r- (pipedconfig autoinit (Git.repoDescribe r), return (Left $ giveup "configlist failed"))+ (pipedconfig Git.Config.ConfigList autoinit (Git.repoDescribe r), return (Left $ giveup "configlist failed")) "configlist" [] configlistfields case v of Right r'@@ -264,8 +264,8 @@ where haveconfig = not . M.null . Git.config - pipedconfig mustincludeuuuid configloc cmd params = do- v <- liftIO $ Git.Config.fromPipe r cmd params+ pipedconfig st mustincludeuuuid configloc cmd params = do+ v <- liftIO $ Git.Config.fromPipe r cmd params st case v of Right (r', val, _err) -> do unless (isUUIDConfigured r' || S.null val || not mustincludeuuuid) $ do@@ -282,7 +282,7 @@ liftIO $ hClose h let url = Git.repoLocation r ++ "/config" ifM (liftIO $ Url.downloadQuiet nullMeterUpdate url tmpfile uo)- ( Just <$> pipedconfig False url "git" [Param "config", Param "--null", Param "--list", Param "--file", File tmpfile]+ ( Just <$> pipedconfig Git.Config.ConfigNullList False url "git" [Param "config", Param "--null", Param "--list", Param "--file", File tmpfile] , return Nothing ) case v of@@ -375,7 +375,7 @@ inAnnex' repo rmt st key inAnnex' :: Git.Repo -> Remote -> State -> Key -> Annex Bool-inAnnex' repo rmt (State connpool duc _ _) key+inAnnex' repo rmt st@(State connpool duc _ _ _) key | Git.repoIsHttp repo = checkhttp | Git.repoIsUrl repo = checkremote | otherwise = checklocal@@ -393,7 +393,7 @@ checklocal = ifM duc ( guardUsable repo (cantCheck repo) $ maybe (cantCheck repo) return- =<< onLocalFast repo rmt (Annex.Content.inAnnexSafe key)+ =<< onLocalFast st (Annex.Content.inAnnexSafe key) , cantCheck repo ) @@ -421,10 +421,10 @@ (\e -> warning (show e) >> return False) dropKey' :: Git.Repo -> Remote -> State -> Key -> Annex Bool-dropKey' repo r (State connpool duc _ _) key+dropKey' repo r st@(State connpool duc _ _ _) key | not $ Git.repoIsUrl repo = ifM duc ( guardUsable repo (return False) $- commitOnCleanup repo r $ onLocalFast repo r $ do+ commitOnCleanup repo r st $ onLocalFast st $ do whenM (Annex.Content.inAnnex key) $ do Annex.Content.lockContentForRemoval key $ \lock -> do Annex.Content.removeAnnex lock@@ -436,7 +436,7 @@ | Git.repoIsHttp repo = do warning "dropping from http remote not supported" return False- | otherwise = commitOnCleanup repo r $ do+ | otherwise = commitOnCleanup repo r st $ do let fallback = Ssh.dropKey repo key P2PHelper.remove (Ssh.runProto r connpool (return False) fallback) key @@ -446,14 +446,14 @@ lockKey' repo r st key callback lockKey' :: Git.Repo -> Remote -> State -> Key -> (VerifiedCopy -> Annex r) -> Annex r-lockKey' repo r (State connpool duc _ _) key callback+lockKey' repo r st@(State connpool duc _ _ _) key callback | not $ Git.repoIsUrl repo = ifM duc ( guardUsable repo failedlock $ do inorigrepo <- Annex.makeRunner -- Lock content from perspective of remote, -- and then run the callback in the original -- annex monad, not the remote's.- onLocalFast repo r $ + onLocalFast st $ Annex.Content.lockContentShared key $ liftIO . inorigrepo . callback , failedlock@@ -514,7 +514,7 @@ copyFromRemote'' repo forcersync r st key file dest meterupdate copyFromRemote'' :: Git.Repo -> Bool -> Remote -> State -> Key -> AssociatedFile -> FilePath -> MeterUpdate -> Annex (Bool, Verification)-copyFromRemote'' repo forcersync r st@(State connpool _ _ _) key file dest meterupdate+copyFromRemote'' repo forcersync r st@(State connpool _ _ _ _) key file dest meterupdate | Git.repoIsHttp repo = unVerified $ do gc <- Annex.getGitConfig Url.withUrlOptionsPromptingCreds $@@ -524,10 +524,12 @@ u <- getUUID hardlink <- wantHardLink -- run copy from perspective of remote- onLocalFast repo r $ do+ onLocalFast st $ do v <- Annex.Content.prepSendAnnex key case v of- Nothing -> return (False, UnVerified)+ Nothing -> do+ warning "content is not present in remote"+ return (False, UnVerified) Just (object, checksuccess) -> do copier <- mkCopier hardlink st params runTransfer (Transfer Download u (fromKey id key))@@ -643,13 +645,13 @@ copyToRemote' repo r st key file meterupdate copyToRemote' :: Git.Repo -> Remote -> State -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool-copyToRemote' repo r st@(State connpool duc _ _) key file meterupdate+copyToRemote' repo r st@(State connpool duc _ _ _) key file meterupdate | not $ Git.repoIsUrl repo = ifM duc- ( guardUsable repo (return False) $ commitOnCleanup repo r $+ ( guardUsable repo (return False) $ commitOnCleanup repo r st $ copylocal =<< Annex.Content.prepSendAnnex key , return False )- | Git.repoIsSsh repo = commitOnCleanup repo r $+ | Git.repoIsSsh repo = commitOnCleanup repo r st $ P2PHelper.store (\p -> Ssh.runProto r connpool (return False) (copyremotefallback p)) key file meterupdate@@ -668,7 +670,7 @@ u <- getUUID hardlink <- wantHardLink -- run copy from perspective of remote- onLocalFast repo r $ ifM (Annex.Content.inAnnex key)+ onLocalFast st $ ifM (Annex.Content.inAnnex key) ( return True , runTransfer (Transfer Download u (fromKey id key)) file stdRetry $ \p -> do copier <- mkCopier hardlink st params@@ -715,6 +717,13 @@ ensureInitialized a `finally` stopCoProcesses +data LocalRemoteAnnex = LocalRemoteAnnex Git.Repo (MVar (Maybe Annex.AnnexState))++{- This can safely be called on a Repo that is not local, but of course+ - onLocal will not work if used with the result. -}+mkLocalRemoteAnnex :: Git.Repo -> Annex (LocalRemoteAnnex)+mkLocalRemoteAnnex repo = LocalRemoteAnnex repo <$> liftIO (newMVar Nothing)+ {- Runs an action from the perspective of a local remote. - - The AnnexState is cached for speed and to avoid resource leaks.@@ -724,23 +733,29 @@ - The remote will be automatically initialized/upgraded first, - when possible. -}-onLocal :: Git.Repo -> Remote -> Annex a -> Annex a-onLocal repo r a = do- m <- Annex.getState Annex.remoteannexstate- case M.lookup (uuid r) m of- Nothing -> do- st <- liftIO $ Annex.new repo- go (st, ensureInitialized >> a)- Just st -> go (st, a)+onLocal :: State -> Annex a -> Annex a+onLocal (State _ _ _ _ lra) = onLocal' lra++onLocalRepo :: Git.Repo -> Annex a -> Annex a+onLocalRepo repo a = do+ lra <- mkLocalRemoteAnnex repo+ onLocal' lra a++onLocal' :: LocalRemoteAnnex -> Annex a -> Annex a+onLocal' (LocalRemoteAnnex repo v) a = liftIO (takeMVar v) >>= \case+ Nothing -> do+ st <- liftIO $ Annex.new repo+ go (st, ensureInitialized >> a)+ Just st -> go (st, a) where- cache st = Annex.changeState $ \s -> s- { Annex.remoteannexstate = M.insert (uuid r) st (Annex.remoteannexstate s) } go (st, a') = do curro <- Annex.getState Annex.output- (ret, st') <- liftIO $ Annex.run (st { Annex.output = curro }) $+ let act = Annex.run (st { Annex.output = curro }) $ a' `finally` stopCoProcesses- cache st'+ (ret, st') <- liftIO $ act `onException` cache st+ liftIO $ cache st' return ret+ cache st = putMVar v (Just st) {- Faster variant of onLocal. -@@ -749,8 +764,8 @@ - it gets the most current value. Caller of onLocalFast can make changes - to the branch, however. -}-onLocalFast :: Git.Repo -> Remote -> Annex a -> Annex a-onLocalFast repo r a = onLocal repo r $ Annex.BranchState.disableUpdate >> a+onLocalFast :: State -> Annex a -> Annex a+onLocalFast st a = onLocal st $ Annex.BranchState.disableUpdate >> a -- To avoid the overhead of trying copy-on-write every time, it's tried -- once and if it fails, is not tried again.@@ -784,7 +799,7 @@ ) where copycowtried = case st of- State _ _ (CopyCoWTried v) _ -> v+ State _ _ (CopyCoWTried v) _ _ -> v dorsync = do -- dest may already exist, so make sure rsync can write to it void $ liftIO $ tryIO $ allowWrite dest@@ -796,12 +811,12 @@ docopywith a = liftIO $ watchFileSize dest p $ a CopyTimeStamps src dest -commitOnCleanup :: Git.Repo -> Remote -> Annex a -> Annex a-commitOnCleanup repo r a = go `after` a+commitOnCleanup :: Git.Repo -> Remote -> State -> Annex a -> Annex a+commitOnCleanup repo r st a = go `after` a where go = Annex.addCleanup (RemoteCleanup $ uuid r) cleanup cleanup- | not $ Git.repoIsUrl repo = onLocalFast repo r $+ | not $ Git.repoIsUrl repo = onLocalFast st $ doQuietSideAction $ Annex.Branch.commit =<< Annex.Branch.commitMessage | otherwise = void $ do@@ -857,23 +872,24 @@ - This returns False when the repository UUID is not as expected. -} type DeferredUUIDCheck = Annex Bool -data State = State Ssh.P2PSshConnectionPool DeferredUUIDCheck CopyCoWTried (Annex (Git.Repo, GitConfig))+data State = State Ssh.P2PSshConnectionPool DeferredUUIDCheck CopyCoWTried (Annex (Git.Repo, GitConfig)) LocalRemoteAnnex getRepoFromState :: State -> Annex Git.Repo-getRepoFromState (State _ _ _ a) = fst <$> a+getRepoFromState (State _ _ _ a _) = fst <$> a #ifndef mingw32_HOST_OS {- The config of the remote git repository, cached for speed. -} getGitConfigFromState :: State -> Annex GitConfig-getGitConfigFromState (State _ _ _ a) = snd <$> a+getGitConfigFromState (State _ _ _ a _) = snd <$> a #endif mkState :: Git.Repo -> UUID -> RemoteGitConfig -> Annex State mkState r u gc = do pool <- Ssh.mkP2PSshConnectionPool copycowtried <- liftIO newCopyCoWTried+ lra <- mkLocalRemoteAnnex r (duc, getrepo) <- go- return $ State pool duc copycowtried getrepo+ return $ State pool duc copycowtried getrepo lra where go | remoteAnnexCheckUUID gc = return
RemoteDaemon/Core.hs view
@@ -22,6 +22,7 @@ import Utility.ThreadScheduler import Config import Annex.Ssh+import Annex.BranchState import Types.Messages import Control.Concurrent@@ -163,7 +164,9 @@ annexstate <- newMVar =<< Annex.new =<< Git.CurrentRepo.get g <- Annex.repo <$> readMVar annexstate let h = TransportHandle (LocalRepo g) annexstate- liftAnnex h $ Annex.setOutput QuietOutput+ liftAnnex h $ do+ Annex.setOutput QuietOutput+ enableInteractiveJournalAccess return h updateTransportHandle :: TransportHandle -> IO TransportHandle
Test.hs view
@@ -24,6 +24,7 @@ import qualified Data.Map as M import qualified Data.ByteString.Lazy.UTF8 as BU8 import System.Environment+import Control.Concurrent.STM hiding (check) import Common import CmdLine.GitAnnex.Options@@ -65,6 +66,7 @@ import qualified Annex.View import qualified Annex.View.ViewedFile import qualified Logs.View+import qualified Command.TestRemote import qualified Utility.Path import qualified Utility.FileMode import qualified BuildInfo@@ -145,14 +147,16 @@ tests :: Bool -> Bool -> TestOptions -> TestTree tests crippledfilesystem adjustedbranchok opts = - testGroup "Tests" $ properties :- map (\(d, te) -> withTestMode te initTests (unitTests d)) testmodes+ testGroup "Tests" $ properties + : withTestMode remotetestmode Nothing testRemotes+ : map (\(d, te) -> withTestMode te (Just initTests) (unitTests d)) testmodes where testmodes = catMaybes [ canadjust ("v8 adjusted unlocked branch", (testMode opts (RepoVersion 8)) { adjustedUnlockedBranch = True }) , unlesscrippled ("v8 unlocked", (testMode opts (RepoVersion 8)) { unlockedFiles = True }) , unlesscrippled ("v8 locked", testMode opts (RepoVersion 8)) ]+ remotetestmode = testMode opts (RepoVersion 8) unlesscrippled v | crippledfilesystem = Nothing | otherwise = Just v@@ -203,6 +207,62 @@ , Utility.Hash.props_macs_stable ] +testRemotes :: TestTree+testRemotes = testGroup "Remote Tests"+ [ testRemote "directory"+ [ "directory=remotedir"+ , "encryption=none"+ ]+ (createDirectory "remotedir")+ ]++testRemote :: String -> [String] -> IO () -> TestTree+testRemote remotetype config preinitremote = + withResource newEmptyTMVarIO (const noop) $ \getv -> + testGroup ("remote type " ++ remotetype) $ concat+ [ [testCase "init" (prep getv)]+ , go getv+ ]+ where+ reponame = "test repo"+ remotename = "testremote"+ basesz = 1024 * 1024+ keysizes = Command.TestRemote.keySizes basesz False+ prep getv = do+ d <- newmainrepodir+ setmainrepodir d+ innewrepo $ do+ git_annex "init" [reponame, "--quiet"]+ @? "init failed"+ preinitremote+ git_annex "initremote"+ ([ remotename+ , "type=" ++ remotetype+ , "--quiet"+ ] ++ config)+ @? "init failed"+ r <- annexeval $ either error return + =<< Remote.byName' remotename+ unavailr <- annexeval $ Types.Remote.mkUnavailable r+ exportr <- annexeval $ Command.TestRemote.exportTreeVariant r+ ks <- annexeval $ mapM Command.TestRemote.randKey keysizes+ v <- getv+ liftIO $ atomically $ putTMVar v+ (r, (unavailr, (exportr, ks)))+ go getv = Command.TestRemote.mkTestTrees runannex mkrs mkunavailr mkexportr mkks+ where+ runannex = inmainrepo . annexeval+ mkrs = Command.TestRemote.remoteVariants mkr basesz False+ mkr = descas (remotetype ++ " remote") (fst <$> v)+ mkunavailr = fst . snd <$> v+ mkexportr = fst . snd . snd <$> v+ mkks = map (\(sz, n) -> desckeysize sz (getk n))+ (zip keysizes [0..])+ getk n = fmap (!! n) (snd . snd . snd <$> v)+ v = liftIO $ atomically . readTMVar =<< getv+ descas = Command.TestRemote.Described+ desckeysize sz = descas ("key size " ++ show sz)+ {- These tests set up the test environment, but also test some basic parts - of git-annex. They are always run before the unitTests. -} initTests :: TestTree@@ -281,7 +341,7 @@ , testCase "addurl" test_addurl ] --- this test case create the main repo+-- this test case creates the main repo test_init :: Assertion test_init = innewrepo $ do ver <- annexVersion <$> getTestMode@@ -1576,14 +1636,12 @@ testscheme scheme = do abstmp <- absPath tmpdir testscheme' scheme abstmp- testscheme' scheme abstmp = intmpclonerepo $ whenM (Utility.Path.inPath (Utility.Gpg.unGpgCmd gpgcmd)) $ do- -- Use a relative path to avoid too long path to gpg's- -- agent socket.+ testscheme' scheme abstmp = intmpclonerepo $ do gpgtmp <- (</> "gpgtmp") <$> relPathCwdToFile abstmp createDirectoryIfMissing False gpgtmp- Utility.Gpg.testTestHarness gpgtmp gpgcmd + Utility.Gpg.testTestHarness gpgtmp gpgcmd @? "test harness self-test failed"- Utility.Gpg.testHarness gpgtmp gpgcmd $ do+ void $ Utility.Gpg.testHarness gpgtmp gpgcmd $ do createDirectory "dir" let a cmd = git_annex cmd $ [ "foo"@@ -1664,7 +1722,7 @@ unlessM (hasUnlockedFiles <$> getTestMode) $ do git_annex "sync" [] @? "sync failed" l <- annexeval $ Utility.FileSystemEncoding.decodeBL- <$> Annex.CatFile.catObject (Git.Types.Ref "HEAD:dir/foo")+ <$> Annex.CatFile.catObject (Git.Types.Ref (encodeBS "HEAD:dir/foo")) "../.git/annex/" `isPrefixOf` l @? ("symlink from subdir to .git/annex is wrong: " ++ l) createDirectory "dir2"
Test/Framework.hs view
@@ -79,10 +79,10 @@ Annex.setOutput Types.Messages.QuietOutput a `finally` Annex.Action.stopCoProcesses -innewrepo :: Assertion -> Assertion+innewrepo :: IO () -> IO () innewrepo a = withgitrepo $ \r -> indir r a -inmainrepo :: Assertion -> Assertion+inmainrepo :: IO a -> IO a inmainrepo a = do d <- mainrepodir indir d a@@ -417,17 +417,19 @@ hasUnlockedFiles :: TestMode -> Bool hasUnlockedFiles m = unlockedFiles m || adjustedUnlockedBranch m -withTestMode :: TestMode -> TestTree -> TestTree -> TestTree-withTestMode testmode inittests = withResource prepare release . const+withTestMode :: TestMode -> Maybe TestTree -> TestTree -> TestTree+withTestMode testmode minittests = withResource prepare release . const where prepare = do setTestMode testmode setmainrepodir =<< newmainrepodir- case tryIngredients [consoleTestReporter] mempty inittests of- Nothing -> error "No tests found!?"- Just act -> unlessM act $- error "init tests failed! cannot continue"- return ()+ case minittests of+ Just inittests ->+ case tryIngredients [consoleTestReporter] mempty inittests of+ Nothing -> error "No tests found!?"+ Just act -> unlessM act $+ error "init tests failed! cannot continue"+ Nothing -> return () release _ = noop setTestMode :: TestMode -> IO ()
Types/BranchState.hs view
@@ -8,9 +8,17 @@ module Types.BranchState where data BranchState = BranchState- { branchUpdated :: Bool -- has the branch been updated this run?- , indexChecked :: Bool -- has the index file been checked to exist?+ { branchUpdated :: Bool+ -- ^ has the branch been updated this run?+ , indexChecked :: Bool+ -- ^ has the index file been checked to exist?+ , journalIgnorable :: Bool+ -- ^ can reading the journal be skipped, while still getting+ -- sufficiently up-to-date information from the branch?+ , journalNeverIgnorable :: Bool+ -- ^ should the journal always be read even if it would normally+ -- be safe to skip it? } startBranchState :: BranchState-startBranchState = BranchState False False+startBranchState = BranchState False False False False
+ Types/CatFileHandles.hs view
@@ -0,0 +1,30 @@+{- git-cat file handles pools+ -+ - Copyright 2020 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Types.CatFileHandles (+ CatFileHandles(..),+ catFileHandlesNonConcurrent,+ catFileHandlesPool,+) where++import Control.Concurrent.STM+import qualified Data.Map as M++import Utility.ResourcePool+import Git.CatFile (CatFileHandle)++data CatFileHandles+ = CatFileHandlesNonConcurrent CatMap+ | CatFileHandlesPool (TMVar CatMap)++type CatMap = M.Map FilePath (ResourcePool CatFileHandle)++catFileHandlesNonConcurrent :: CatFileHandles+catFileHandlesNonConcurrent = CatFileHandlesNonConcurrent M.empty++catFileHandlesPool :: IO CatFileHandles+catFileHandlesPool = CatFileHandlesPool <$> newTMVarIO M.empty
Types/Concurrency.hs view
@@ -11,6 +11,7 @@ -- the former specifies 1 job of each particular kind, but there can be -- more than one kind of job running concurrently. data Concurrency = NonConcurrent | Concurrent Int | ConcurrentPerCpu+ deriving (Eq) parseConcurrency :: String -> Maybe Concurrency parseConcurrency "cpus" = Just ConcurrentPerCpu
Types/GitConfig.hs view
@@ -343,7 +343,7 @@ , remoteAnnexReadOnly = getbool "readonly" False , remoteAnnexCheckUUID = getbool "checkuuid" True , remoteAnnexVerify = getbool "verify" True- , remoteAnnexTrackingBranch = Git.Ref <$>+ , remoteAnnexTrackingBranch = Git.Ref . encodeBS <$> ( notempty (getmaybe "tracking-branch") <|> notempty (getmaybe "export-tracking") -- old name )
+ Types/IndexFiles.hs view
@@ -0,0 +1,11 @@+{- Alternative git index files+ - + - Copyright 2020 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Types.IndexFiles where++data AltIndexFile = AnnexIndexFile | ViewIndexFile+ deriving (Eq, Show)
Types/RefSpec.hs view
@@ -32,7 +32,7 @@ mk ('+':s) | any (`elem` s) "*?" = Right $ AddMatching $ compileGlob s CaseSensative- | otherwise = Right $ AddRef $ Ref s+ | otherwise = Right $ AddRef $ Ref $ encodeBS s mk ('-':s) = Right $ RemoveMatching $ compileGlob s CaseSensative mk "reflog" = Right AddRefLog mk s = Left $ "bad refspec item \"" ++ s ++ "\" (expected + or - prefix)"
Types/UUID.hs view
@@ -58,6 +58,7 @@ instance ToUUID ConfigValue where toUUID (ConfigValue v) = toUUID v+ toUUID NoConfigValue = NoUUID -- There is no matching FromUUID U.UUID because a git-annex UUID may -- be NoUUID or perhaps contain something not allowed in a canonical UUID.
Types/View.hs view
@@ -5,6 +5,8 @@ - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE OverloadedStrings #-}+ module Types.View where import Annex.Common
Upgrade.hs view
@@ -13,7 +13,7 @@ import qualified Annex import qualified Git import Config-import Config.Files+import Annex.Path import Annex.Version import Types.RepoVersion #ifndef mingw32_HOST_OS@@ -103,7 +103,7 @@ -- upgrading a git repo other than the current repo. upgraderemote = do rp <- fromRawFilePath <$> fromRepo Git.repoPath- cmd <- liftIO readProgramFile+ cmd <- liftIO programPath liftIO $ boolSystem' cmd [ Param "upgrade" , Param "--quiet"
Upgrade/V2.hs view
@@ -92,7 +92,8 @@ push :: Annex () push = do- origin_master <- inRepo $ Git.Ref.exists $ Git.Ref "origin/master"+ origin_master <- inRepo $ Git.Ref.exists $ + Git.Ref $ encodeBS' "origin/master" origin_gitannex <- Annex.Branch.hasOrigin case (origin_master, origin_gitannex) of (_, True) -> do@@ -101,12 +102,12 @@ -- will immediately work. Not pushed here, -- because it's less obnoxious to let the user -- push.- Annex.Branch.update+ void Annex.Branch.update (True, False) -> do -- push git-annex to origin, so that -- "git push" will from then on -- automatically push it- Annex.Branch.update -- just in case+ void Annex.Branch.update -- just in case showAction "pushing new git-annex branch to origin" showOutput inRepo $ Git.Command.run@@ -117,7 +118,7 @@ _ -> do -- no origin exists, so just let the user -- know about the new branch- Annex.Branch.update+ void Annex.Branch.update showLongNote $ "git-annex branch created\n" ++ "Be sure to push this branch when pushing to remotes.\n"
Upgrade/V5/Direct.hs view
@@ -59,7 +59,7 @@ fromDirectBranch :: Ref -> Ref fromDirectBranch directhead = case splitc '/' $ fromRef directhead of ("refs":"heads":"annex":"direct":rest) -> - Ref $ "refs/heads/" ++ intercalate "/" rest+ Ref $ encodeBS' $ "refs/heads/" ++ intercalate "/" rest _ -> directhead switchHEADBack :: Annex ()
Utility/CoProcess.hs view
@@ -9,7 +9,8 @@ {-# LANGUAGE CPP #-} module Utility.CoProcess (- CoProcessHandle,+ CoProcessHandle(..),+ CoProcessState(..), start, stop, query,
Utility/Gpg.hs view
@@ -376,9 +376,18 @@ #ifndef mingw32_HOST_OS {- Runs an action using gpg in a test harness, in which gpg does - not use ~/.gpg/, but sets up the test key in a subdirectory of - - the passed directory and uses it. -}-testHarness :: FilePath -> GpgCmd -> IO a -> IO a-testHarness tmpdir cmd a = bracket setup cleanup (const a)+ - the passed directory and uses it.+ -+ - If the test harness is not able to be set up (eg, because gpg is not+ - installed or because there is some problem importing the test key,+ - perhaps related to the agent socket), the action is not run, and Nothing+ - is returned.+ -}+testHarness :: FilePath -> GpgCmd -> IO a -> IO (Maybe a)+testHarness tmpdir cmd a = ifM (inPath (unGpgCmd cmd))+ ( bracket (eitherToMaybe <$> tryNonAsync setup) cleanup go+ , return Nothing+ ) where var = "GNUPGHOME" @@ -395,9 +404,13 @@ [testSecretKey, testKey] return orig - cleanup (Just v) = setEnv var v True- cleanup Nothing = unsetEnv var+ cleanup (Just (Just v)) = setEnv var v True+ cleanup (Just Nothing) = unsetEnv var+ cleanup Nothing = return () + go (Just _) = Just <$> a+ go Nothing = return Nothing+ makenewdir n = do let subdir = tmpdir </> show n catchIOErrorType AlreadyExists (const $ makenewdir $ n + 1) $ do@@ -406,9 +419,12 @@ {- Tests the test harness. -} testTestHarness :: FilePath -> GpgCmd -> IO Bool-testTestHarness tmpdir cmd = do- keys <- testHarness tmpdir cmd $ findPubKeys cmd testKeyId- return $ KeyIds [testKeyId] == keys+testTestHarness tmpdir cmd =+ testHarness tmpdir cmd (findPubKeys cmd testKeyId) >>= \case+ Nothing -> do+ hPutStrLn stderr "unable to test gpg, setting up the test harness did not succeed"+ return True+ Just keys -> return $ KeyIds [testKeyId] == keys checkEncryptionFile :: GpgCmd -> FilePath -> Maybe KeyIds -> IO Bool checkEncryptionFile cmd filename keys =
Utility/Misc.hs view
@@ -11,6 +11,7 @@ hGetContentsStrict, readFileStrict, separate,+ separate', firstLine, firstLine', segment,@@ -53,6 +54,13 @@ unbreak r@(a, b) | null b = r | otherwise = (a, tail b)++separate' :: (Word8 -> Bool) -> S.ByteString -> (S.ByteString, S.ByteString)+separate' c l = unbreak $ S.break c l+ where+ unbreak r@(a, b)+ | S.null b = r+ | otherwise = (a, S.tail b) {- Breaks out the first line. -} firstLine :: String -> String
Utility/Process.hs view
@@ -1,7 +1,7 @@ {- System.Process enhancements, including additional ways of running - processes, and logging. -- - Copyright 2012-2015 Joey Hess <id@joeyh.name>+ - Copyright 2012-2020 Joey Hess <id@joeyh.name> - - License: BSD-2-clause -}@@ -53,6 +53,7 @@ import Control.Concurrent import qualified Control.Exception as E import Control.Monad+import qualified Data.ByteString as S type CreateProcessRunner = forall a. CreateProcess -> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO a) -> IO a @@ -85,25 +86,20 @@ -> [String] -> Maybe [(String, String)] -> (Maybe (Handle -> IO ()))- -> (Maybe (Handle -> IO ()))- -> IO String-writeReadProcessEnv cmd args environ writestdin adjusthandle = do+ -> IO S.ByteString+writeReadProcessEnv cmd args environ writestdin = do (Just inh, Just outh, _, pid) <- createProcess p - maybe (return ()) (\a -> a inh) adjusthandle- maybe (return ()) (\a -> a outh) adjusthandle- -- fork off a thread to start consuming the output- output <- hGetContents outh outMVar <- newEmptyMVar- _ <- forkIO $ E.evaluate (length output) >> putMVar outMVar ()+ _ <- forkIO $ putMVar outMVar =<< S.hGetContents outh -- now write and flush any input maybe (return ()) (\a -> a inh >> hFlush inh) writestdin hClose inh -- done with stdin -- wait on the output- takeMVar outMVar+ output <- takeMVar outMVar hClose outh -- wait on the process
+ Utility/ResourcePool.hs view
@@ -0,0 +1,94 @@+{- Resource pools.+ -+ - Copyright 2020 Joey Hess <id@joeyh.name>+ -+ - License: BSD-2-clause+ -}++{-# LANGUAGE BangPatterns #-}++module Utility.ResourcePool (+ ResourcePool(..),+ mkResourcePool,+ mkResourcePoolNonConcurrent,+ withResourcePool,+ freeResourcePool,+) where++import Common++import Control.Concurrent.STM+import Control.Monad.IO.Class+import Data.Either++data ResourcePool r+ = ResourcePool Int (TVar Int) (TVar [r])+ | ResourcePoolNonConcurrent r++{- Make a new resource pool, that can grow to contain the specified number+ - of resources. -}+mkResourcePool :: MonadIO m => Int -> m (ResourcePool r)+mkResourcePool maxsz = liftIO $+ ResourcePool maxsz+ <$> newTVarIO 0+ <*> newTVarIO []++{- When there will not be multiple threads that may + - may concurrently try to use it, using this is more+ - efficient than mkResourcePool.+ -}+mkResourcePoolNonConcurrent :: (MonadMask m, MonadIO m) => m r -> m (ResourcePool r)+mkResourcePoolNonConcurrent allocresource =+ ResourcePoolNonConcurrent <$> allocresource++{- Runs an action with a resource.+ -+ - If no free resource is available in the pool,+ - will run the action the allocate a new resource if the pool's size+ - allows. Or will block a resource becomes available to use.+ -+ - The resource is returned to the pool at the end.+ -}+withResourcePool :: (MonadMask m, MonadIO m) => ResourcePool r -> m r -> (r -> m a) -> m a+withResourcePool (ResourcePoolNonConcurrent r) _ a = a r+withResourcePool (ResourcePool maxsz currsz p) allocresource a =+ bracket setup cleanup a+ where+ setup = do+ mr <- liftIO $ atomically $ do+ l <- readTVar p+ case l of+ (r:rs) -> do+ writeTVar p rs+ return (Just r)+ [] -> do+ n <- readTVar currsz+ if n < maxsz+ then do+ let !n' = succ n+ writeTVar currsz n'+ return Nothing+ else retry+ case mr of+ Just r -> return r+ Nothing -> allocresource+ cleanup r = liftIO $ atomically $ modifyTVar' p (r:)++{- Frees all resources in use in the pool, running the supplied action on+ - each. (If any of the actions throw an exception, it will be rethrown+ - after all the actions have completed.)+ -+ - The pool should not have any resources in use when this is called,+ - and the pool should not be used again after calling this.+ -}+freeResourcePool :: (MonadMask m, MonadIO m) => ResourcePool r -> (r -> m ()) -> m ()+freeResourcePool (ResourcePoolNonConcurrent r) freeresource = freeresource r+freeResourcePool (ResourcePool _ currsz p) freeresource = do+ rs <- liftIO $ atomically $ do+ writeTVar currsz 0+ swapTVar p []+ res <- forM rs $ tryNonAsync . freeresource+ case lefts res of+ [] -> return ()+ (e:_) -> throwM e+
Utility/Url.hs view
@@ -162,8 +162,8 @@ {- Checks that an url exists and could be successfully downloaded, - also checking that its size, if available, matches a specified size. -- - The Left error is returned if policy does not allow accessing the url- - or the url scheme is not supported.+ - The Left error is returned if policy or the restricted http manager+ - does not allow accessing the url or the url scheme is not supported. -} checkBoth :: URLString -> Maybe Integer -> UrlOptions -> IO (Either String Bool) checkBoth url expected_size uo = fmap go <$> check url expected_size uo@@ -195,8 +195,8 @@ {- Checks that an url exists and could be successfully downloaded, - also returning its size and suggested filename if available. -- - The Left error is returned if policy does not allow accessing the url- - or the url scheme is not supported.+ - The Left error is returned if policy or the restricted http manages+ - does not allow accessing the url or the url scheme is not supported. -} getUrlInfo :: URLString -> UrlOptions -> IO (Either String UrlInfo) getUrlInfo url uo = case parseURIRelaxed url of@@ -205,15 +205,8 @@ where go :: URI -> IO (Either String UrlInfo) go u = case (urlDownloader uo, parseRequest (show u)) of- (DownloadWithConduit (DownloadWithCurlRestricted r), Just req) -> catchJust- -- When http redirects to a protocol which - -- conduit does not support, it will throw- -- a StatusCodeException with found302- -- and a Response with the redir Location.- (matchStatusCodeException (== found302))- (Right <$> existsconduit req uo)- (followredir r)- `catchNonAsync` (const $ return $ Right dne)+ (DownloadWithConduit (DownloadWithCurlRestricted r), Just req) ->+ existsconduit r req (DownloadWithConduit (DownloadWithCurlRestricted r), Nothing) | isfileurl u -> Right <$> existsfile u | isftpurl u -> (Right <$> existscurlrestricted r u url ftpport)@@ -250,7 +243,23 @@ extractfilename = contentDispositionFilename . B8.toString <=< lookup hContentDisposition . responseHeaders - existsconduit req uo' = do+ existsconduit r req =+ let go = catchcrossprotoredir r (existsconduit' req uo)+ in catchJust matchconnectionrestricted go retconnectionrestricted+ + matchconnectionrestricted he@(HttpExceptionRequest _ (InternalException ie)) =+ case fromException ie of+ Just (ConnectionRestricted why) -> Just he+ _ -> Nothing+ matchconnectionrestricted _ = Nothing++ retconnectionrestricted he@(HttpExceptionRequest _ (InternalException ie)) =+ case fromException ie of+ Just (ConnectionRestricted why) -> return (Left why)+ _ -> throwM he+ retconnectionrestricted he = throwM he++ existsconduit' req uo' = do let req' = headRequest (applyRequest uo req) debugM "url" (show req') join $ runResourceT $ do@@ -266,7 +275,7 @@ then return $ getBasicAuth uo' (show (getUri req)) >>= \case Nothing -> return dne Just (ba, signalsuccess) -> do- ui <- existsconduit+ ui <- existsconduit' (applyBasicAuth' ba req) (uo' { getBasicAuth = noBasicAuth }) signalsuccess (urlExists ui)@@ -301,6 +310,14 @@ sz <- getFileSize' f stat found (Just sz) Nothing Nothing -> return dne++ -- When http server redirects to a protocol which conduit does not+ -- support, it will throw a StatusCodeException with found302+ -- and a Response with the redir Location.+ catchcrossprotoredir r a = + catchJust (matchStatusCodeException (== found302))+ (Right <$> a)+ (followredir r) followredir r (HttpExceptionRequest _ (StatusCodeException resp _)) = case headMaybe $ map decodeBS $ getResponseHeader hLocation resp of
doc/git-annex-copy.mdwn view
@@ -10,6 +10,9 @@ Copies the content of files from or to another remote. +With no parameters, operates on all annexed files in the current directory.+Paths of files or directories to operate on can be specified.+ # OPTIONS * `--from=remote`@@ -38,7 +41,7 @@ * `--auto` - Rather than copying all files, only copy files that don't yet have+ Rather than copying all specified files, only copy those that don't yet have the desired number of copies, or that are preferred content of the destination repository. See [[git-annex-preferred-content]](1)
doc/git-annex-drop.mdwn view
@@ -18,6 +18,9 @@ Content that is required to be stored in the repository will not be dropped even if enough copies exist elsewhere. See [[git-annex-required]](1). +With no parameters, tries to drop all annexed files in the current directory.+Paths of files or directories to drop can be specified.+ # OPTIONS * `--from=remote`@@ -28,7 +31,7 @@ * `--auto` - Rather than trying to drop all specified files, drop only files that+ Rather than trying to drop all specified files, drop only those that are not preferred content of the repository. See [[git-annex-preferred-content]](1) @@ -44,7 +47,8 @@ Rather than specifying a filename or path to drop, this option can be used to drop all available versions of all files. - This is the default behavior when running git-annex drop in a bare repository.+ This is the default behavior when running git-annex drop in a bare+ repository. Note that this bypasses checking the .gitattributes annex.numcopies setting and required content settings.
doc/git-annex-fsck.mdwn view
@@ -8,11 +8,16 @@ # DESCRIPTION -With no parameters, this command checks the whole annex for consistency,-and warns about or fixes any problems found. This is a good complement to-`git fsck`.+This command checks annexed files for consistency, and warns about or+fixes any problems found. This is a good complement to `git fsck`. -With parameters, only the specified files are checked.+The default is to check all annexed files in the current directory and+subdirectories. With parameters, only the specified files are checked.++The problems fsck finds include files that have gotten corrupted,+files whose content has somehow become lost, files that do not have the+configured number of copies yet made, and keys that can be upgraded to a+better format. # OPTIONS
doc/git-annex-get.mdwn view
@@ -12,12 +12,16 @@ will involve copying them from a remote repository, or downloading them, or transferring them from some kind of key-value store. +With no parameters, gets all annexed files in the current directory whose+content was not already present. Paths of files or directories to get can+be specified.+ # OPTIONS * `--auto` - Rather than getting all files, get only files that don't yet have- the desired number of copies, or that are preferred content of the+ Rather than getting all the specified files, get only those that don't yet+ have the desired number of copies, or that are preferred content of the repository. See [[git-annex-preferred-content]](1) * `--from=remote`
doc/git-annex-move.mdwn view
@@ -10,6 +10,9 @@ Moves the content of files from or to another remote. +With no parameters, operates on all annexed files in the current directory.+Paths of files or directories to operate on can be specified.+ # OPTIONS * `--from=remote`
doc/git-annex-preferred-content.mdwn view
@@ -48,6 +48,10 @@ [[git-annex-export]](1)) and [[git-annex-import]](1), these match relative to the top of the subdirectory. + Note that, when a command is run with the `--all` option, or in a bare+ repository, there is no filename associated with an annexed object,+ and so "include=" and "exclude=" will not match.+ * `copies=number` Matches only files that git-annex believes to have the specified number@@ -159,6 +163,10 @@ `git annex enableremote $remote preferreddir=$dirname` (If no directory name is configured, it uses "public" by default.)++ Note that, when a command is run with the `--all` option, or in a bare+ repository, there is no filename associated with an annexed object,+ and so "inpreferreddir" will not match. * `standard`
doc/git-annex-sync.mdwn view
@@ -46,6 +46,9 @@ Only sync with the remotes with the lowest annex-cost value configured. + When a list of remotes (or remote groups) is provided, it picks from+ amoung those, otherwise it picks from amoung all remotes.+ * `--only-annex` `-a`, `--not-only-annex` Only sync the git-annex branch and annexed content with remotes,@@ -99,9 +102,9 @@ The `annex.synccontent` configuration can be set to true to make content be synced by default. - Normally this tries to get each annexed file that the local repository- does not yet have, and then copies each file to every remote that it- is syncing with.+ Normally this tries to get each annexed file that is in the working tree+ and whose content the local repository does not yet have, and then copies+ each file to every remote that it is syncing with. This behavior can be overridden by configuring the preferred content of a repository. See [[git-annex-preferred-content]](1).
doc/git-annex.mdwn view
@@ -1039,10 +1039,6 @@ commit the data by running `git annex merge` (or by automatic merges) or `git annex sync`. - You should beware running `git gc` when using this configuration,- since it could garbage collect objects that are staged in git-annex's- index but not yet committed.- * `annex.commitmessage` When git-annex updates the git-annex branch, it usually makes up@@ -1531,10 +1527,21 @@ It is set to "true" if this is a git-lfs remote. -* `remote.<name>.annex-hooktype`, `remote.<name>.annex-externaltype`+* `remote.<name>.annex-externaltype` - Used by hook special remotes and external special remotes to record- the type of the remote.+ Used external special remotes to record the type of the remote.++ Eg, if this is set to "foo", git-annex will run a "git-annex-remote-foo"+ program to communicate with the external special remote.++ If this is set to "readonly", then git-annex will not run any external+ special remote program, but will try to access things stored in the+ remote using http. That only works for some external special remotes,+ so consult the documentation of the one you are using.++* `remote.<name>.annex-hooktype`++ Used by hook special remotes to record the type of the remote. * `annex.web-options`
git-annex.cabal view
@@ -1,5 +1,5 @@ Name: git-annex-Version: 8.20200330+Version: 8.20200501 Cabal-Version: >= 1.8 License: AGPL-3 Maintainer: Joey Hess <id@joeyh.name>@@ -371,7 +371,7 @@ tasty-quickcheck, tasty-rerun CC-Options: -Wall- GHC-Options: -Wall -fno-warn-tabs+ GHC-Options: -Wall -fno-warn-tabs -Wincomplete-uni-patterns Extensions: PackageImports, LambdaCase -- Some things don't work with the non-threaded RTS. GHC-Options: -threaded@@ -619,6 +619,7 @@ Annex.CheckIgnore Annex.Common Annex.Concurrent+ Annex.Concurrent.Utility Annex.Content Annex.Content.LowLevel Annex.Content.PointerFile@@ -668,6 +669,7 @@ Annex.View Annex.View.ViewedFile Annex.Wanted+ Annex.WorkerPool Annex.WorkTree Annex.YoutubeDl Backend@@ -974,6 +976,7 @@ Types.Backend Types.Benchmark Types.BranchState+ Types.CatFileHandles Types.CleanupActions Types.Command Types.Concurrency@@ -988,6 +991,7 @@ Types.GitConfig Types.Group Types.Import+ Types.IndexFiles Types.Key Types.KeySource Types.LockCache@@ -1087,6 +1091,7 @@ Utility.Process.Transcript Utility.QuickCheck Utility.RawFilePath+ Utility.ResourcePool Utility.Rsync Utility.SafeCommand Utility.Scheduled