git-annex 10.20260717 → 10.20260901
raw patch · 35 files changed
+527/−149 lines, 35 filesdep ~magicdep ~warp
Dependency ranges changed: magic, warp
Files
- Annex/FileMatcher.hs +3/−3
- Annex/Import.hs +14/−13
- Annex/NumCopies.hs +1/−1
- Annex/Wanted.hs +4/−4
- CHANGELOG +39/−0
- Command/DiffDriver.hs +5/−1
- Command/Drop.hs +6/−3
- Command/Fsck.hs +1/−1
- Command/ImportFeed.hs +16/−8
- Command/MatchExpression.hs +1/−2
- Command/Sync.hs +9/−6
- Database/Benchmark.hs +21/−15
- Git/Config/Url.hs +167/−0
- Git/Credential.hs +0/−1
- Limit.hs +112/−42
- Remote/Adb.hs +1/−1
- Remote/Borg.hs +2/−1
- Remote/Directory.hs +4/−3
- Remote/External.hs +35/−7
- Remote/External/AsyncExtension.hs +1/−0
- Remote/External/Types.hs +21/−0
- Remote/Helper/ExportImport.hs +3/−1
- Remote/Rsync.hs +1/−0
- Remote/S3.hs +1/−1
- Remote/WebDAV.hs +9/−5
- Test.hs +2/−0
- Types/FileMatcher.hs +5/−2
- Types/Group.hs +9/−4
- Types/Remote.hs +12/−2
- Utility/Url.hs +3/−2
- cabal.project +4/−0
- git-annex.cabal +7/−11
- stack-NoLLMDependencies.yaml +4/−3
- stack-botan.yaml +2/−3
- stack.yaml +2/−3
Annex/FileMatcher.hs view
@@ -67,7 +67,7 @@ checkFileMatcher' :: LiveUpdate -> GetFileMatcher -> OsPath -> Annex Bool -> Annex Bool checkFileMatcher' lu getmatcher file notconfigured = do matcher <- getmatcher file- checkMatcher matcher Nothing afile lu S.empty notconfigured d+ checkMatcher matcher Nothing afile lu mempty notconfigured d where afile = AssociatedFile (Just file) -- checkMatcher will never use this, because afile is provided.@@ -287,7 +287,7 @@ checkAddUnlockedMatcher :: LiveUpdate -> AddUnlockedMatcher -> MatchInfo -> Annex Bool checkAddUnlockedMatcher lu (AddUnlockedMatcher matcher) mi = - checkMatcher' matcher mi lu S.empty+ checkMatcher' matcher mi lu mempty simply :: MatchFiles Annex -> ParseResult (MatchFiles Annex) simply = Right . Operation@@ -342,7 +342,7 @@ (groupwanted mygroups) | otherwise = unknownmatcher mygroups = fromMaybe S.empty (u `M.lookup` groupsByUUID groupmap)- groupwanted s = case M.elems $ M.filterWithKey (\k _ -> S.member k s) groupwantedmap of+ groupwanted s = case filter (not . null) $ M.elems $ M.filterWithKey (\k _ -> S.member k s) groupwantedmap of [pc] -> Just pc _ -> Nothing
Annex/Import.hs view
@@ -499,11 +499,11 @@ buildImportTreesHistory converttree basetree msubdir history hdl = S.fromList <$> mapM (\ic -> buildImportTreesGeneric' converttree basetree msubdir ic hdl) history -canImportKeys :: Remote -> Bool -> Bool+canImportKeys :: Remote -> Bool -> Annex Bool canImportKeys remote importcontent =- importcontent || isJust (Remote.importKey ia)+ pure (importcontent) <||> (isJust <$> Remote.importKey ia) where- ia = Remote.exportImportActions remote+ ia = Remote.importActions remote -- Result of an import. data ImportResult t@@ -671,7 +671,7 @@ -> ImportableContentsChunkable Annex (ContentIdentifier, ByteSize) -> Annex (ImportResult (ImportableContentsChunkable Annex (Either Sha Key))) importKeys remote importtreeconfig importcontent thirdpartypopulated importablecontents = do- unless (canImportKeys remote importcontent) $+ unlessM (canImportKeys remote importcontent) $ giveup "This remote does not support importing without downloading content." -- This map is used to remember content identifiers that -- were just imported, before they have necessarily been@@ -807,7 +807,7 @@ return (Right job) thirdpartypopulatedimport db (loc, (cid, sz)) = - case Remote.importKey (Remote.exportImportActions remote) of+ Remote.importKey (Remote.importActions remote) >>= \case Nothing -> return Nothing Just importkey -> tryNonAsync (importkey loc cid sz nullMeterUpdate) >>= \case@@ -823,20 +823,21 @@ importordownload cidmap (loc, (cid, sz)) largematcher = do f <- locworktreefile loc matcher <- largematcher f+ let usedownload = dodownload cidmap (loc, (cid, sz)) f matcher+ let useimport = doimport cidmap (loc, (cid, sz)) f matcher -- When importing a key is supported, always use it rather -- than downloading and retrieving a key, to avoid -- generating trees with different keys for the same content.- let act = if importcontent- then case Remote.importKey (Remote.exportImportActions remote) of- Nothing -> dodownload+ if importcontent+ then Remote.importKey (Remote.importActions remote) >>= \case+ Nothing -> usedownload Just _ -> if Utility.Matcher.introspect matchNeedsFileContent (fst matcher)- then dodownload- else doimport- else doimport- act cidmap (loc, (cid, sz)) f matcher+ then usedownload+ else useimport+ else useimport doimport cidmap (loc, (cid, sz)) f matcher =- case Remote.importKey (Remote.exportImportActions remote) of+ Remote.importKey (Remote.importActions remote) >>= \case Nothing -> error "internal" -- checked earlier Just importkey -> do when (Utility.Matcher.introspect matchNeedsFileContent (fst matcher)) $
Annex/NumCopies.hs view
@@ -213,7 +213,7 @@ let nhave = numCopiesCount have explain (ActionItemTreeFile file) $ Just $ UnquotedString $ "has " ++ show nhave ++ " " ++ pluralCopies nhave ++ - ", and the configured annex.numcopies is " ++ show needed+ ", and the configured annex.numcopies is " ++ show (fromNumCopies needed) return $ numCopiesCheck'' have vs needed numCopiesCheck'' :: [UUID] -> (Int -> Int -> v) -> NumCopies -> v
Annex/Wanted.hs view
@@ -19,11 +19,11 @@ {- Check if a file is preferred content for the local repository. -} wantGet :: LiveUpdate -> Bool -> Maybe Key -> AssociatedFile -> Annex Bool-wantGet lu d key file = isPreferredContent lu Nothing S.empty key file d+wantGet lu d key file = isPreferredContent lu Nothing mempty key file d {- Check if a file is preferred content for a repository. -} wantGetBy :: LiveUpdate -> Bool -> Maybe Key -> AssociatedFile -> UUID -> Annex Bool-wantGetBy lu d key file to = isPreferredContent lu (Just to) S.empty key file d+wantGetBy lu d key file to = isPreferredContent lu (Just to) mempty key file d {- Check if a file is not preferred or required content, and can be - dropped. When a UUID is provided, checks for that repository.@@ -46,8 +46,8 @@ checkDrop :: (LiveUpdate -> Maybe UUID -> AssumeNotPresent -> Maybe Key -> AssociatedFile -> Bool -> Annex Bool) -> LiveUpdate -> Bool -> Maybe UUID -> Maybe Key -> AssociatedFile -> (Maybe [AssociatedFile]) -> Annex (Maybe AssociatedFile) checkDrop checker lu d from key file others = do u <- maybe getUUID (pure . id) from- let s = S.singleton u- let checker' f = checker lu (Just u) s key f d+ let notpresent = AssumeNotPresent (S.singleton u)+ let checker' f = checker lu (Just u) notpresent key f d ifM (checker' file) ( return (Just file) , do
CHANGELOG view
@@ -1,3 +1,42 @@+git-annex (10.20260901) upstream; urgency=medium++ * Behavior change: drop --auto --from a remote does not any longer + try to drop content that is not known to be present on the remote.+ This avoids unncessary work and makes it consistent with the behavior+ of git-annex sync and push.+ * External special remote protocol extended to support IMPORTKEY.+ * git-annex-remote-internetarchive supports --no-content imports.+ * diffdriver: Avoid crashing when git passes an (undocumented) 8th+ parameter.+ * The preferred content groupwanted expression will no longer+ consider a groupwanted expression of "" to be set, which allows+ another group's groupwanted expression to be used instead.+ * Fixed buggy handling of preferred content+ "balanced=groupname:lackingcopies"+ * Expand preferred content "lackingcopies" and "approxlackingcopies"+ expression syntax to support "groupname:number"+ * Expand preferred content "copies", "lackingcopies", and+ "approxlackingcopies" expression syntax to support group limits which+ can include/exclude multiple groups. Eg+ "copies=archive+backup-offsite=3"+ * Also expanded --lackingcopies, --approxlackingcopies, and --copies+ with the same syntax.+ * Expand preferred content "balanced", "fullybalanced", + "sizebalanced" and "fullysizebalanced" expression syntax to support+ group limits as well. Eg+ "balanced=backup:lackingcopies=archive-offsite"+ * importfeed: Fix reporting and logging of problems with feeds.+ * importfeed: When adding an url, indicate which feed it is from.+ * Fix reversion in 8.20200226 that broke git-annex benchmark --databases+ * Remove the ParallelBuild cabal flag and add cabal.project that+ enables parallel build by default with ghc 9.8+ and cabal-install 3.12.+ * NoLLMDependencies: Update for warp and magic.+ * git-annex.cabal: Pin magic to 1.1 avoiding build failure on Windows+ with newer version.+ * stack.yaml: Update to lts-24.52++ -- Joey Hess <id@joeyh.name> Tue, 01 Sep 2026 11:25:45 -0400+ git-annex (10.20260717) upstream; urgency=medium * External special remote protocol extended to support import.
Command/DiffDriver.hs view
@@ -1,6 +1,6 @@ {- git-annex command -- - Copyright 2014-2023 Joey Hess <id@joeyh.name>+ - Copyright 2014-2026 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -88,6 +88,10 @@ , rNewHex = new_hex , rNewMode = new_mode }+ -- git documents 7 parameters, but there can be an additional parameter+ -- containing a similarity index description.+ mk (path:old_file:old_hex:old_mode:new_file:new_hex:new_mode:_:[]) =+ mk (path:old_file:old_hex:old_mode:new_file:new_hex:new_mode:[]) mk (unmergedpath:[]) = UnmergedReq { rPath = unmergedpath } mk _ = badopts
Command/Drop.hs view
@@ -90,7 +90,7 @@ stopUnless (wantdrop lu) $ case from of Nothing -> startLocal lu pcc afile ai si numcopies mincopies key [] ud- Just remote -> startRemote lu pcc afile ai si numcopies mincopies key ud remote+ Just remote -> startRemote' (autoMode o) lu pcc afile ai si numcopies mincopies key ud remote where remoteuuid = Remote.uuid <$> from wantdrop lu@@ -110,8 +110,11 @@ performLocal lu pcc key afile numcopies mincopies preverified ud startRemote :: LiveUpdate -> PreferredContentChecked -> AssociatedFile -> ActionItem -> SeekInput -> NumCopies -> MinCopies -> Key -> DroppingUnused -> Remote -> CommandStart-startRemote lu pcc afile ai si numcopies mincopies key ud remote = do- fast <- Annex.getRead Annex.fast+startRemote = startRemote' False++startRemote' :: Bool -> LiveUpdate -> PreferredContentChecked -> AssociatedFile -> ActionItem -> SeekInput -> NumCopies -> MinCopies -> Key -> DroppingUnused -> Remote -> CommandStart+startRemote' automode lu pcc afile ai si numcopies mincopies key ud remote = do+ fast <- if automode then pure True else Annex.getRead Annex.fast if fast then do remotes <- Remote.keyPossibilities (Remote.IncludeIgnored True) key
Command/Fsck.hs view
@@ -395,7 +395,7 @@ go requiredlocs = do presentlocs <- S.fromList <$> loggedLocations key missinglocs <- filterM- (\u -> isRequiredContent NoLiveUpdate (Just u) S.empty (Just key) afile False)+ (\u -> isRequiredContent NoLiveUpdate (Just u) mempty (Just key) afile False) (S.toList $ S.difference requiredlocs presentlocs) if null missinglocs then return True
Command/ImportFeed.hs view
@@ -172,10 +172,13 @@ parse tmpf = liftIO (parseFeedFromFile' tmpf) >>= \case Nothing -> debugfeedcontent tmpf "parsing the feed failed" Just f -> do- case decodeBS $ fromFeedText $ getFeedTitle f of- "" -> noop- t -> showNote (UnquotedString ('"' : t ++ "\""))- case findDownloads url f of+ let feedtitle = '"' : decodeBS (fromFeedText $ getFeedTitle f) ++ "\""+ unless (null feedtitle) $+ showNote (UnquotedString feedtitle)+ let feeddesc = if null feedtitle+ then url+ else feedtitle+ case findDownloads url f feeddesc of [] -> debugfeedcontent tmpf "bad feed content; no enclosures to download" l -> do record (Just (Just l))@@ -202,6 +205,7 @@ data ToDownload = ToDownload { feedurl :: URLString+ , feeddescription :: String , location :: DownloadLocation , itemid :: Maybe B.ByteString -- Either the parsed or unparsed date.@@ -240,8 +244,8 @@ ai = ActionItemOther (Just "gathering known urls") si = SeekInput [] -findDownloads :: URLString -> Feed -> [ToDownload]-findDownloads u f = catMaybes $ map mk (feedItems f)+findDownloads :: URLString -> Feed -> String -> [ToDownload]+findDownloads u f feeddesc = catMaybes $ map mk (feedItems f) where mk i = case getItemEnclosure i of Just (enclosureurl, _, _) ->@@ -253,6 +257,7 @@ Nothing -> Nothing mk' i l = ToDownload { feedurl = u+ , feeddescription = feeddesc , location = l , itemid = case getItemId i of Just (_, iid) -> Just (fromFeedText iid)@@ -412,7 +417,7 @@ | null ks -> do broken <- checkFeedBroken (feedurl todownload) when broken $- void $ feedProblem url "download failed"+ void $ feedProblem (feedurl todownload) "download failed" liftIO $ atomically $ putTMVar cv broken next $ return False | otherwise -> do@@ -468,9 +473,11 @@ (go `onException` recordfailure) where recordfailure = do- void $ feedProblem url "download failed"+ void $ feedProblem (feedurl todownload) "download failed" liftIO $ atomically $ tryPutTMVar cv False go = do+ showNote $ UnquotedString $+ "from " ++ feeddescription todownload maybeAddJSONField "url" url a @@ -582,6 +589,7 @@ iurl <- youtube_url i return $ ToDownload { feedurl = url+ , feeddescription = url , location = MediaLink iurl , itemid = Just (encodeBS iurl) , itempubdate =
Command/MatchExpression.hs view
@@ -14,7 +14,6 @@ import Logs.Group import qualified Data.Map as M-import qualified Data.Set as S cmd :: Command cmd = noCommit $@@ -90,7 +89,7 @@ , liftIO exitFailure ) where- checkmatcher matcher = checkMatcher' matcher (matchinfo o) NoLiveUpdate S.empty+ checkmatcher matcher = checkMatcher' matcher (matchinfo o) NoLiveUpdate mempty bail :: String -> IO a bail s = do
Command/Sync.hs view
@@ -627,8 +627,8 @@ Nothing -> noop Just b -> do let (branch, subdir) = splitRemoteAnnexTrackingBranchSubdir b- if canImportKeys remote importcontent- then do+ ifM (canImportKeys remote importcontent)+ ( do addunlockedmatcher <- addUnlockedMatcher Command.Import.seekRemote remote branch subdir importcontent (CheckGitIgnore True) addunlockedmatcher [] -- Importing generates a branch@@ -638,7 +638,8 @@ -- mergeing it. mc <- mergeConfig True void $ mergeRemote remote currbranch mc o- else warning $ UnquotedString $ "Cannot import from " ++ Remote.name remote ++ " when not syncing content."+ , warning $ UnquotedString $ "Cannot import from " ++ Remote.name remote ++ " when not syncing content."+ ) where wantpull = remoteAnnexPull (Remote.gitconfig remote) @@ -651,10 +652,12 @@ pullThirdPartyPopulated :: SyncOptions -> Remote -> CommandSeek pullThirdPartyPopulated o remote | not (pullOption o) || not wantpull = noop- | not (canImportKeys remote False) = noop- | otherwise = void $ includeCommandAction $ starting "list" ai si $- Command.Import.listContents' remote ImportTree (CheckGitIgnore False) go+ | otherwise = + whenM (canImportKeys remote False)+ start where+ start = void $ includeCommandAction $ starting "list" ai si $+ Command.Import.listContents' remote ImportTree (CheckGitIgnore False) go go (Just importable) = importChanges remote ImportTree False True importable >>= \case ImportFinished postexportlogupdate imported -> do (_t, updatestate) <- recordImportTree remote ImportTree Nothing imported postexportlogupdate
Database/Benchmark.hs view
@@ -11,6 +11,7 @@ module Database.Benchmark (benchmarkDbs) where import Annex.Common+import Annex.Tmp import Types.Benchmark #ifdef WITH_BENCHMARK import qualified Database.Keys.SQL as SQL@@ -30,18 +31,21 @@ benchmarkDbs :: CriterionMode -> Integer -> Annex () #ifdef WITH_BENCHMARK-benchmarkDbs mode n = withTmpDirIn (literalOsPath ".") (literalOsPath "benchmark") $ \tmpdir -> do- db <- benchDb tmpdir n- liftIO $ runMode mode- [ bgroup "keys database"- [ getAssociatedFilesHitBench db- , getAssociatedFilesMissBench db- , getAssociatedKeyHitBench db- , getAssociatedKeyMissBench db- , addAssociatedFileOldBench db- , addAssociatedFileNewBench db+benchmarkDbs mode n = withOtherTmp $ \othertmpdir -> do+ withTmpDirIn othertmpdir (literalOsPath "benchmark") go+ where+ go tmpdir = do+ db <- benchDb tmpdir n+ liftIO $ runMode mode+ [ bgroup "keys database"+ [ getAssociatedFilesHitBench db+ , getAssociatedFilesMissBench db+ , getAssociatedKeyHitBench db+ , getAssociatedKeyMissBench db+ , addAssociatedFileOldBench db+ , addAssociatedFileNewBench db+ ] ]- ] #else benchmarkDbs _ = giveup "not built with criterion, cannot benchmark" #endif@@ -68,15 +72,17 @@ addAssociatedFileOldBench :: BenchDb -> Benchmark addAssociatedFileOldBench (BenchDb h num _) = bench ("addAssociatedFile to (old)") $ nfIO $ do- n <- getStdRandom (randomR (1,num))- SQL.addAssociatedFile (keyN n) (fileN n) (SQL.WriteHandle h)+ forM_ [1..num] $ \_ -> do+ n <- getStdRandom (randomR (1,num))+ SQL.addAssociatedFile (keyN n) (fileN n) (SQL.WriteHandle h) H.flushDbQueue h addAssociatedFileNewBench :: BenchDb -> Benchmark addAssociatedFileNewBench (BenchDb h num mv) = bench ("addAssociatedFile to (new)") $ nfIO $ do n <- takeMVar mv- putMVar mv (n+1)- SQL.addAssociatedFile (keyN n) (fileN (num+n)) (SQL.WriteHandle h)+ putMVar mv (n+num)+ forM_ [n..n+num] $ \n' ->+ SQL.addAssociatedFile (keyN n) (fileN (num+n')) (SQL.WriteHandle h) H.flushDbQueue h populateAssociatedFiles :: H.DbQueue -> Integer -> IO ()
+ Git/Config/Url.hs view
@@ -0,0 +1,167 @@+{- git-config http.<url>.* handling+ -+ - Copyright 2026 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++{-# LANGUAGE OverloadedStrings #-}++module Git.Config.Url (+ getHttpConfig,+ httpConfigKeys,+ prop_httpConfigKeys_sane,+) where++import qualified Data.Map as M+import Network.URI+import Data.Function++import Common+import Git+import Git.Types+import Utility.Glob++{- Gets any per-url settings from the git config for a http.foo ConfigKey.+ -+ - If there are non, falls back to the non-url-spcecific config, if any,+ - or the provided fallback value.+ -+ - See git-config(1)'s documentation of http.<url>.* for the details.+ -}+getHttpConfig :: ConfigKey -> ConfigValue -> URI -> Repo -> ConfigValue+getHttpConfig key fallback url repo = fromMaybe fallback $+ case httpConfigKeys key url (config repo) of+ [] -> Nothing+ (k:_) -> M.lookup k (config repo)++{- Gets any per-url config keys for a non-url-specific input+ - http.foo ConfigKey that match the provided url.+ -+ - The list is ordered by decreasing precedance and includes the input+ - ConfigKey at the end when it's part of the RepoConfig.+ -}+httpConfigKeys :: ConfigKey -> URI -> RepoConfig -> [ConfigKey]+httpConfigKeys nonurlspecifickey@(ConfigKey key) urltomatch c =+ let l = map fst + $ reverse $ sortBy precedence + $ mapMaybe matching (M.keys c)+ in if M.member nonurlspecifickey c+ then l ++ [nonurlspecifickey]+ else l+ where+ httpprefix = "http."+ keysuffix = case decodeBS key of+ ('h':'t':'t':'p':'.':rest) -> '.' : rest+ v -> v+ httpprefixlen = length httpprefix+ keysuffixlen = length keysuffix++ extracturlfromkey = parseURI + . reverse . drop keysuffixlen . reverse + . drop httpprefixlen++ precedence (_k1, u1) (_k2, u2) =+ (compare `on` (length . uriPath)) u1 u2+ <> (compare `on` (uriUserInfo <$$> uriAuthority)) u1 u2+ + matching k@(ConfigKey ck) = + let sk = decodeBS ck+ in if httpprefix `isPrefixOf` sk && keysuffix `isSuffixOf` sk && k /= nonurlspecifickey+ then do+ u <- extracturlfromkey sk+ let same f = f u == f urltomatch+ if same uriScheme+ && (same (uriRegName <$$> uriAuthority)+ || subdomainwildcardmatch u)+ && same getportordefault+ && (same uriPath+ || pathslashprefix u)+ && (same getusername+ || getusername u == Nothing)+ then Just (k, u)+ else Nothing+ else Nothing+ + getportordefault u = do+ a <- uriAuthority u+ if null (uriPort a)+ then case uriScheme u of+ "http:" -> return ":80"+ "https:" -> return ":443"+ _ -> Nothing+ else return (uriPort a)++ getusername u = do+ a <- uriAuthority u+ let (user, _pass) = break (== ':') (uriUserInfo a)+ let username = fst (break (== '@') user)+ if null username+ then Nothing+ else return username++ pathslashprefix u = + let p = if "/" `isSuffixOf` uriPath u+ then uriPath u+ else uriPath u ++ "/"+ in p `isPrefixOf` uriPath urltomatch++ subdomainwildcardmatch u =+ subdomainwildcardmatch' (uridomains u) (uridomains urltomatch)+ + subdomainwildcardmatch' [] [] = True+ subdomainwildcardmatch' [] _ = False+ subdomainwildcardmatch' _ [] = False+ subdomainwildcardmatch' (a:as) (b:bs)+ | a == b = subdomainwildcardmatch' as bs+ | otherwise =+ let g = compileGlob a CaseInsensitive (GlobFilePath False)+ in if matchGlob g b+ then subdomainwildcardmatch' as bs+ else False++ uridomains u = case uriRegName <$> uriAuthority u of+ Nothing -> []+ Just d -> splitc '.' d++prop_httpConfigKeys_sane :: Bool+prop_httpConfigKeys_sane = and prop_httpConfigKeys_tests++prop_httpConfigKeys_tests :: [Bool]+prop_httpConfigKeys_tests =+ [ httpConfigKeys (ConfigKey "http.foo") u c ==+ [ "http.http://user@example.com/foo/bar.foo"+ , "http.http://example.com/foo/bar.foo"+ , "http.http://example.com/foo.foo"+ , "http.http://example.com.foo"+ , "http.foo"+ ]+ , httpConfigKeys (ConfigKey "http.bar") u c ==+ [ "http.http://*.com.bar" ]+ , httpConfigKeys (ConfigKey "http.baz") u c ==+ [ "http.http://example.com:80.baz" ]+ , httpConfigKeys (ConfigKey "http.baz") uwithport c ==+ [ "http.http://example.com:8080.baz" ]+ ]+ where+ u = fromMaybe (error "internal") $ + parseURI "http://user:password@example.com/foo/bar/"+ uwithport = fromMaybe (error "internal") $ + parseURI "http://user:password@example.com:8080/foo/bar/"+ c = M.fromList $ map (\k -> (ConfigKey k, ConfigValue "dummy value"))+ [ "http.foo"+ , "http.http://example.co.foo"+ , "http.http://example.com.foo"+ , "https.http://example.com.foo"+ , "http.http://example.com/fo.foo"+ , "http.http://example.com/foo.foo"+ , "http.http://example.com/foo/ba.foo"+ , "http.http://example.com/foo/bar.foo"+ , "http.http://user@example.com/foo/bar.foo"+ , "http.http://nonmatchingexample.com.foo"+ , "http.http://*.bar"+ , "http.http://*.com.bar"+ , "http.http://example.com:80.baz"+ , "http.http://example.com:443.baz"+ , "http.http://example.com:8080.baz"+ ]
Git/Credential.hs view
@@ -19,7 +19,6 @@ import qualified Data.Map as M import Network.URI-import Network.HTTP.Types import Network.HTTP.Types.Header import Control.Concurrent.STM
Limit.hs view
@@ -1,6 +1,6 @@ {- user-specified limits on files to act on -- - Copyright 2011-2025 Joey Hess <id@joeyh.name>+ - Copyright 2011-2026 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -67,7 +67,7 @@ run matcher i = do (match, desc) <- runWriterT $ Utility.Matcher.matchMrun' matcher $ \o ->- matchAction o NoLiveUpdate S.empty i+ matchAction o NoLiveUpdate mempty i explain (mkActionItem i) $ UnquotedString <$> Utility.Matcher.describeMatchResult (\o -> matchDesc o . Just) desc@@ -333,14 +333,14 @@ , matchNegationUnstable = False , matchDesc = "in" =? s }- checkinuuid u notpresent key+ checkinuuid u (AssumeNotPresent notpresent) key | null date = do us <- Remote.keyLocations key return $ u `elem` us && u `S.notMember` notpresent | otherwise = do us <- loggedLocationsHistorical (RefDate date) key return $ u `elem` us- checkinhere notpresent key+ checkinhere (AssumeNotPresent notpresent) key | S.null notpresent = inAnnex key | otherwise = do u <- getUUID@@ -415,8 +415,10 @@ [v, n] -> case parsetrustspec v of Just checker -> go n $ checktrust checker Nothing -> go n $ checkgroup (toGroup v)- [n] -> go n $ const $ return True- _ -> Left "bad value for copies"+ _ -> case splitc '=' want of+ [gl, n] -> go n $ checkgrouplimit (parseGroupLimit gl)+ [n] -> go n $ const $ return True+ _ -> Left "bad value for copies" where go num good = case readish num of Nothing -> Left "bad number for copies"@@ -431,12 +433,15 @@ , matchNegationUnstable = False , matchDesc = "copies" =? want }- go' n good notpresent key = do+ go' n good (AssumeNotPresent notpresent) key = do us <- filter (`S.notMember` notpresent) <$> (filterM good =<< Remote.keyLocations key) return $ numCopiesCount us >= n checktrust checker u = checker <$> lookupTrust u checkgroup g u = S.member g <$> lookupGroups u+ checkgrouplimit gl u = do+ m <- uuidsByGroup <$> groupMap+ return (checkGroupLimit gl m u) parsetrustspec s | "+" `isSuffixOf` s = (<=) <$> readTrustLevel (beginning s) | otherwise = (==) <$> readTrustLevel s@@ -446,7 +451,7 @@ addLackingCopies desc approx = addLimit . limitLackingCopies desc approx limitLackingCopies :: String -> Bool -> MkLimit Annex-limitLackingCopies desc approx want = case readish want of+limitLackingCopies desc approx want = case readish numwant of Just needed -> Right $ MatchFiles { matchAction = const $ \notpresent mi -> flip checkKey mi $ go mi needed notpresent@@ -460,13 +465,25 @@ } Nothing -> Left "bad value for number of lacking copies" where- go mi needed notpresent key =- limitCheckNumCopies approx mi notpresent key vs+ go mi needed notpresent key = case (groupwant, grouplimit) of+ (Nothing, []) -> check (const True)+ (Just g, _) -> do+ s <- groupUUIDs g <$> groupMap+ check (`S.member` s)+ (Nothing, gl) -> do+ m <- uuidsByGroup <$> groupMap+ check (checkGroupLimit gl m) where+ check uuidp = limitCheckNumCopies approx mi notpresent uuidp key vs vs nhave numcopies' = numcopies' - nhave >= needed+ (groupwant, grouplimit, numwant) = case splitc ':' want of+ (g:n:[]) -> (Just (toGroup g), [], n)+ _ -> case splitc '=' want of+ (gl:n:[]) -> (Nothing, parseGroupLimit gl, n)+ _ -> (Nothing, [], want) -limitCheckNumCopies :: Bool -> MatchInfo -> AssumeNotPresent -> Key -> (Int -> Int -> v) -> Annex v-limitCheckNumCopies approx mi notpresent key vs = do+limitCheckNumCopies :: Bool -> MatchInfo -> AssumeNotPresent -> (UUID -> Bool) -> Key -> (Int -> Int -> v) -> Annex v+limitCheckNumCopies approx mi (AssumeNotPresent notpresent) uuidp key vs = do numcopies <- if approx then approxNumCopies else case mi of@@ -474,7 +491,7 @@ matchFile fi MatchingInfo {} -> approxNumCopies MatchingUserInfo {} -> approxNumCopies- us <- filter (`S.notMember` notpresent)+ us <- filter (\u -> uuidp u && u `S.notMember` notpresent) <$> (trustExclude UnTrusted =<< Remote.keyLocations key) return $ numCopiesCheck'' us vs numcopies where@@ -547,9 +564,8 @@ limitInAllGroup :: Annex GroupMap -> MkLimit Annex limitInAllGroup getgroupmap groupname = Right $ MatchFiles- { matchAction = const $ \notpresent mi -> do- m <- getgroupmap- let want = fromMaybe S.empty $ M.lookup (toGroup groupname) $ uuidsByGroup m+ { matchAction = const $ \(AssumeNotPresent notpresent) mi -> do+ want <- groupUUIDs (toGroup groupname) <$> getgroupmap if S.null want then return True -- optimisation: Check if a wanted uuid is notpresent.@@ -579,7 +595,7 @@ limitOnlyInGroup getgroupmap groupname = Right $ MatchFiles { matchAction = const $ \notpresent mi -> do m <- getgroupmap- let want = fromMaybe S.empty $ M.lookup (toGroup groupname) $ uuidsByGroup m+ let want = groupUUIDs (toGroup groupname) m if S.null want then return False else checkKey (check notpresent want) mi@@ -592,7 +608,7 @@ , matchDesc = "onlyingroup" =? groupname } where- check notpresent want key = do+ check (AssumeNotPresent notpresent) want key = do locs <- S.fromList <$> Remote.keyLocations key let present = locs `S.difference` notpresent return $ not (S.null $ present `S.intersection` want)@@ -604,34 +620,47 @@ limitBalanced' "balanced" fullybalanced mu groupname limitBalanced' :: String -> MatchFiles Annex -> Maybe UUID -> MkLimit Annex-limitBalanced' termname fullybalanced mu groupname = do- let checknumcopies = ":lackingcopies" `isSuffixOf` groupname- enoughcopies <- if checknumcopies- then limitLackingCopies termname False "1"- else limitCopies $ if ':' `elem` groupname- then groupname- else groupname ++ ":1"- let checkenoughcopies = if checknumcopies then id else not+limitBalanced' termname fullybalanced mu want = do+ limitcopies <- if checklackingcopies+ then limitLackingCopies termname False wantlackingcopies+ else limitCopies $ if ':' `elem` want+ then want+ else want ++ ":1" let present = limitPresent mu- let combo f = f present || f fullybalanced || f enoughcopies- Right $ MatchFiles- { matchAction = \lu a i ->- ifM (Annex.getRead Annex.rebalance)- ( matchAction fullybalanced lu a i- , matchAction present lu a i <||>- ((checkenoughcopies <$> matchAction enoughcopies lu a i)- <&&> matchAction fullybalanced lu a i+ let combo f = f present || f fullybalanced || f limitcopies+ let matchaction lu a i =+ let match f = matchAction f lu a i+ in ifM (Annex.getRead Annex.rebalance)+ ( match fullybalanced+ , if checklackingcopies+ then match present <||>+ (match limitcopies+ <&&> match fullybalanced )- )+ else match present <||>+ ((not <$> match limitcopies)+ <&&> match fullybalanced+ )+ )+ Right $ MatchFiles+ { matchAction = matchaction , matchNeedsFileName = combo matchNeedsFileName , matchNeedsFileContent = combo matchNeedsFileContent , matchNeedsKey = combo matchNeedsKey , matchNeedsLocationLog = combo matchNeedsLocationLog , matchNeedsLiveRepoSize = True , matchNegationUnstable = combo matchNegationUnstable- , matchDesc = termname =? groupname+ , matchDesc = termname =? want }-+ where+ (checklackingcopies, wantlackingcopies) = + case splitc ':' want of+ [g, want']+ | want' == "lackingcopies" -> (True, "1")+ | "lackingcopies="` isPrefixOf` want' ->+ let (_, sgrouplimit) = break (== '=') want'+ in (True, drop 1 sgrouplimit ++ "+" ++ g ++ "=1")+ _ -> (False, "") limitFullyBalanced :: Maybe UUID -> Annex GroupMap -> MkLimit Annex limitFullyBalanced = limitFullyBalanced' "fullybalanced"@@ -682,9 +711,19 @@ [g] -> go g (Right 1) [g, n] | n == "lackingcopies" -> go g $ - Left $ \mi notpresent key -> - let vs nhave numcopies = numcopies - nhave- in limitCheckNumCopies False mi notpresent key vs+ Left $ \mi notpresent key -> do+ s <- getgids g <$> groupMap+ let others = flip S.notMember s+ calcnumcopiesneeded mi notpresent key others+ | "lackingcopies="` isPrefixOf` n -> go g $+ Left $ \mi notpresent key -> do+ m <- groupMap+ let s = getgids g m+ let (_, sgrouplimit) = break (== '=') n+ let gl = parseGroupLimit (drop 1 sgrouplimit)+ let others = \u -> u `S.notMember` s+ && checkGroupLimit gl (uuidsByGroup m) u+ calcnumcopiesneeded mi notpresent key others | otherwise -> maybe (Left $ "bad number for " ++ termname) (go g . Right)@@ -693,7 +732,12 @@ where go s n = limitFullyBalanced''' filtercandidates termname mu getgroupmap (toGroup s) n want+ getgids = groupUUIDs . toGroup + calcnumcopiesneeded mi notpresent key others =+ let calc nothers numcopies = numcopies - nothers+ in limitCheckNumCopies False mi notpresent others key calc+ limitFullyBalanced''' :: (Int -> Key -> S.Set UUID -> Annex (S.Set UUID)) -> String@@ -705,8 +749,7 @@ limitFullyBalanced''' filtercandidates termname mu getgroupmap g getn want = Right $ MatchFiles { matchAction = \lu notpresent mi -> flip checkKey mi $ \key -> do gm <- getgroupmap- let groupmembers = fromMaybe S.empty $- M.lookup g (uuidsByGroup gm)+ let groupmembers = groupUUIDs g gm n <- case getn of Right n -> pure n Left a -> a mi notpresent key@@ -964,3 +1007,30 @@ (=?) :: String -> String -> (Maybe Bool -> Utility.Matcher.MatchDesc) k =? v = matchDescSimple (k ++ "=" ++ v)++data GroupLimit+ = GroupInclude Group+ | GroupExclude Group+ deriving (Show)++parseGroupLimit :: String -> [GroupLimit]+parseGroupLimit = go GroupInclude+ where+ go b s = case break (\c -> c == '+' || c == '-') s of+ ("", "") -> []+ ("", ('+':s')) -> go GroupInclude s'+ ("", ('-':s')) -> go GroupExclude s'+ (groupname, s') -> b (toGroup groupname) : go GroupInclude s'++checkGroupLimit :: Ord t => [GroupLimit] -> M.Map Group (S.Set t) -> t -> Bool+checkGroupLimit gl m u = + let (includes, excludes) = partition fst (map decompose gl)+ in (any member includes || null includes) && not (any member excludes)+ where+ decompose (GroupInclude g) = (True, g)+ decompose (GroupExclude g) = (False, g)++ member (_, g) = + case M.lookup g m of+ Nothing -> False+ Just s -> u `S.member` s
Remote/Adb.hs view
@@ -101,7 +101,7 @@ , importActions = importUnsupported , exportImportActions = ExportImportActions { listImportableOrExportedContents = listImportableOrExportedContentsM serial adir c- , importKey = Nothing+ , importKeyWithContentIdentifier = pure Nothing , retrieveExportWithContentIdentifier = retrieveExportWithContentIdentifierM serial adir , storeExportWithContentIdentifier = storeExportWithContentIdentifierM serial adir , removeExportWithContentIdentifier = removeExportWithContentIdentifierM serial adir
Remote/Borg.hs view
@@ -98,7 +98,8 @@ , importActions = importUnsupported , exportImportActions = ExportImportActions { listImportableOrExportedContents = listImportableOrExportedContentsM u borgrepo c- , importKey = Just ThirdPartyPopulated.importKey+ , importKeyWithContentIdentifier = pure $ + Just ThirdPartyPopulated.importKey , retrieveExportWithContentIdentifier = retrieveExportWithContentIdentifierM borgrepo , checkPresentExportWithContentIdentifier = checkPresentExportWithContentIdentifierM borgrepo -- This remote is thirdPartyPopulated, so these
Remote/Directory.hs view
@@ -119,7 +119,8 @@ , importActions = importUnsupported , exportImportActions = ExportImportActions { listImportableOrExportedContents = listImportableOrExportedContentsM ii dir- , importKey = Just (importKeyM ii dir)+ , importKeyWithContentIdentifier = pure $ + Just (importKeyWithContentIdentifierM ii dir) , retrieveExportWithContentIdentifier = retrieveExportWithContentIdentifierM ii dir cow , storeExportWithContentIdentifier = storeExportWithContentIdentifierM ii dir cow fastcopy , removeExportWithContentIdentifier = removeExportWithContentIdentifierM ii dir@@ -432,8 +433,8 @@ let ic' = replaceInode 0 ic in ContentIdentifier (encodeBS (showInodeCache ic')) -importKeyM :: IgnoreInodes -> OsPath -> ExportLocation -> ContentIdentifier -> ByteSize -> MeterUpdate -> Annex (Maybe Key)-importKeyM ii dir loc cid _sz p = do+importKeyWithContentIdentifierM :: IgnoreInodes -> OsPath -> ExportLocation -> ContentIdentifier -> ByteSize -> MeterUpdate -> Annex (Maybe Key)+importKeyWithContentIdentifierM ii dir loc cid _sz p = do backend <- chooseBackend f k <- fst <$> genKey ks p backend currcid <- liftIO $ mkContentIdentifier ii absf
Remote/External.hs view
@@ -129,6 +129,7 @@ let importactions = if importsupported then ImportActions { listImportableContents = listImportableContentsM external+ , importKey = importKeyM external , retrieveImport = retrieveImportM external gc , checkPresentImport = checkPresentImportM external gc }@@ -612,6 +613,32 @@ go _ _ UNSUPPORTED_REQUEST = result Nothing go _ _ _ = Nothing +importKeyM+ :: External+ -> Annex (Maybe (ImportLocation -> ContentIdentifier -> ByteSize -> MeterUpdate -> Annex (Maybe Key)))+importKeyM external = + withExternalState external $ \st ->+ return $ if importKeyExtensionEnabled (externalExtensions st)+ then Just go+ else Nothing+ where+ go loc cid sz p =+ handleRequestImport' external loc (IMPORTKEY sz cid) Nothing $ \case+ IMPORTKEY_SUCCESS k ->+ result (Just k)+ IMPORTKEY_FAILURE err ->+ giveup err+ IMPORTKEY_SKIP ->+ result Nothing+ DELEGATE ps -> Just $ do+ delegate <- getDelegateRemote external ps+ importKey (importActions delegate) >>= \case+ Just a -> Result <$> a loc cid sz p+ Nothing -> giveup "IMPORTKEY delegated to a special remote that does not support it"+ UNSUPPORTED_REQUEST ->+ giveup "IMPORTKEY not implemented by external special remote, but it claimed to support it"+ _ -> Nothing+ {- Sends a Request to the external remote, and waits for it to generate - a Response. That is fed into the responsehandler, which should return - the action to run for it (or Nothing if there's a protocol error).@@ -990,25 +1017,25 @@ liftIO (atomically $ takeTMVar (externalAsync external)) >>= \case UncheckedExternalAsync -> do (st, extensions) <- startExternal' external- `onException` store UncheckedExternalAsync+ `onException` storeasync UncheckedExternalAsync if asyncExtensionEnabled extensions then do annexrunner <- Annex.makeRunner relay <- liftIO $ runRelayToExternalAsync external st annexrunner st' <- liftIO $ asyncRelayExternalState relay- store (ExternalAsync relay)+ storeasync (ExternalAsync relay) return st' else do- store NoExternalAsync+ storeasync NoExternalAsync return st v@NoExternalAsync -> do- store v+ storeasync v fst <$> startExternal' external v@(ExternalAsync relay) -> do- store v+ storeasync v liftIO $ asyncRelayExternalState relay where- store = liftIO . atomically . putTMVar (externalAsync external)+ storeasync = liftIO . atomically . putTMVar (externalAsync external) startExternal' :: External -> Annex (ExternalState, ExtensionList) startExternal' external = do@@ -1040,9 +1067,10 @@ , externalPrepared = pv , externalConfig = cv , externalConfigChanges = ccv+ , externalExtensions = ExtensionList [] } extensions <- startproto st- return (st, extensions)+ return (st { externalExtensions = extensions }, extensions) where (externalcmd, externalparams) = case externalProgram external of ExternalType t -> ("git-annex-remote-" ++ t, [])
Remote/External/AsyncExtension.hs view
@@ -53,6 +53,7 @@ , externalPrepared = externalPrepared st , externalConfig = externalConfig st , externalConfigChanges = externalConfigChanges st+ , externalExtensions = externalExtensions st } type ReceiveQueue = TBMChan String
Remote/External/Types.hs view
@@ -18,6 +18,7 @@ ExtensionList(..), supportedExtensionList, asyncExtensionEnabled,+ importKeyExtensionEnabled, ExternalAsync(..), ExternalAsyncRelay(..), Proto.parseMessage,@@ -109,6 +110,7 @@ , externalPrepared :: TMVar PrepareStatus , externalConfig :: TMVar ParsedRemoteConfig , externalConfigChanges :: TMVar (RemoteConfig -> RemoteConfig)+ , externalExtensions :: ExtensionList } type PID = Int@@ -124,6 +126,7 @@ , "UNAVAILABLERESPONSE" , "TRANSFER-RETRIEVE-URL" , "CHECKPRESENT-URL"+ , importKeyExtension , "DELEGATE" , asyncExtension ]@@ -134,6 +137,12 @@ asyncExtensionEnabled :: ExtensionList -> Bool asyncExtensionEnabled l = asyncExtension `elem` fromExtensionList l +importKeyExtension :: String+importKeyExtension = "IMPORTKEY"++importKeyExtensionEnabled :: ExtensionList -> Bool+importKeyExtensionEnabled l = importKeyExtension `elem` fromExtensionList l+ -- When the async extension is in use, a single external process -- is started and used for all requests. data ExternalAsync@@ -198,6 +207,7 @@ | IMPORT ImportLocation | RETRIEVEIMPORT FilePath | CHECKPRESENTIMPORT SafeKey+ | IMPORTKEY Size ContentIdentifier deriving (Show) -- Does PREPARE need to have been sent before this request?@@ -269,6 +279,11 @@ [ "CHECKPRESENTIMPORT" , Proto.serialize key ]+ formatMessage (IMPORTKEY size cid) = Proto.mkMessage+ [ "IMPORTKEY"+ , Proto.serialize size+ , Proto.serialize cid+ ] -- Responses the external remote can make to requests. data Response@@ -318,6 +333,9 @@ | RETRIEVEIMPORT_SUCCESS | RETRIEVEIMPORT_FAILURE ErrorMsg | RETRIEVEIMPORT_URL URLString+ | IMPORTKEY_SUCCESS Key+ | IMPORTKEY_FAILURE ErrorMsg+ | IMPORTKEY_SKIP | DELEGATE [String] | UNSUPPORTED_REQUEST deriving (Show)@@ -369,6 +387,9 @@ parseCommand "RETRIEVEIMPORT-SUCCESS" = Proto.parse0 RETRIEVEIMPORT_SUCCESS parseCommand "RETRIEVEIMPORT-FAILURE" = Proto.parse1 RETRIEVEIMPORT_FAILURE parseCommand "RETRIEVEIMPORT-URL" = Proto.parse1 RETRIEVEIMPORT_URL+ parseCommand "IMPORTKEY-SUCCESS" = Proto.parse1 IMPORTKEY_SUCCESS+ parseCommand "IMPORTKEY-FAILURE" = Proto.parse1 IMPORTKEY_FAILURE+ parseCommand "IMPORTKEY-SKIP" = Proto.parse0 IMPORTKEY_SKIP parseCommand "DELEGATE" = Proto.parseList DELEGATE parseCommand "UNSUPPORTED-REQUEST" = Proto.parse0 UNSUPPORTED_REQUEST parseCommand _ = Proto.parseFail
Remote/Helper/ExportImport.hs view
@@ -58,6 +58,7 @@ instance HasImportUnsupported (ImportActions Annex) where importUnsupported = ImportActions { listImportableContents = nope+ , importKey = nope , retrieveImport = nope , checkPresentImport = \_ _ -> return False }@@ -74,7 +75,7 @@ instance HasExportImportUnsupported (ExportImportActions Annex) where exportImportUnsupported = ExportImportActions { listImportableOrExportedContents = nope- , importKey = Nothing+ , importKeyWithContentIdentifier = pure Nothing , retrieveExportWithContentIdentifier = nope , storeExportWithContentIdentifier = nope , removeExportWithContentIdentifier = nope@@ -332,6 +333,7 @@ -- more strongly. importActionsForExportImport ciddbv = ImportActions { listImportableContents = listImportableOrExportedContents (exportImportActions r)+ , importKey = importKeyWithContentIdentifier (exportImportActions r) , retrieveImport = retrieveExportWithContentIdentifier (exportImportActions r) , checkPresentImport = checkPresentExportImport ciddbv }
Remote/Rsync.hs view
@@ -114,6 +114,7 @@ } , importActions = ImportActions { listImportableContents = listImportableContentsM o+ , importKey = pure Nothing , retrieveImport = retrieveImportM o , checkPresentImport = checkPresentImportExportM o }
Remote/S3.hs view
@@ -244,7 +244,7 @@ , importActions = importUnsupported , exportImportActions = ExportImportActions { listImportableOrExportedContents = listImportableOrExportedContentsS3 hdl this info c- , importKey = Nothing+ , importKeyWithContentIdentifier = pure Nothing , retrieveExportWithContentIdentifier = retrieveExportWithContentIdentifierS3 hdl this rs info , storeExportWithContentIdentifier = storeExportWithContentIdentifierS3 hdl this rs info magic , removeExportWithContentIdentifier = removeExportWithContentIdentifierS3 hdl this rs info
Remote/WebDAV.hs view
@@ -417,12 +417,12 @@ data DavHandle = DavHandle DAVContext DavUser DavPass URLString -type DavHandleVar = TVar (Either (Annex (Either String DavHandle)) (Either String DavHandle))+type DavHandleVar = TMVar (Either (Annex (Either String DavHandle)) (Either String DavHandle)) {- Prepares a DavHandle for later use. Does not connect to the server or do - anything else expensive. -} mkDavHandleVar :: ParsedRemoteConfig -> RemoteGitConfig -> UUID -> Annex DavHandleVar-mkDavHandleVar c gc u = liftIO $ newTVarIO $ Left $ do+mkDavHandleVar c gc u = liftIO $ newTMVarIO $ Left $ do mcreds <- getCreds c gc u case (mcreds, configUrl c) of (Just (user, pass), Just baseurl) -> do@@ -431,12 +431,16 @@ return (Right h) _ -> return $ Left "webdav credentials not available" +{- Concurrent actions are allowed to run at the same time with the same+ - DavHandle, so any use of eg setDepth will affect other actions. -} withDavHandle :: DavHandleVar -> (DavHandle -> Annex a) -> Annex a-withDavHandle hv a = liftIO (readTVarIO hv) >>= \case- Right hdl -> either giveup a hdl+withDavHandle hv a = liftIO (atomically (takeTMVar hv)) >>= \case+ Right hdl -> do+ liftIO $ atomically $ putTMVar hv (Right hdl)+ either giveup a hdl Left mkhdl -> do hdl <- mkhdl- liftIO $ atomically $ writeTVar hv (Right hdl)+ liftIO $ atomically $ putTMVar hv (Right hdl) either giveup a hdl goDAV :: DavHandle -> DAVT IO a -> IO a
Test.hs view
@@ -38,6 +38,7 @@ import qualified Git.Ref import qualified Git.LsTree import qualified Git.FilePath+import qualified Git.Config.Url #ifndef mingw32_HOST_OS import qualified Annex.Locations import qualified Git.Bundle@@ -196,6 +197,7 @@ , testProperty "prop_viewedFile_rountrips" Annex.View.ViewedFile.prop_viewedFile_roundtrips , testProperty "prop_standardGroups_parse" Logs.PreferredContent.prop_standardGroups_parse , testProperty "prop_balanced_stable" Annex.Balanced.prop_balanced_stable+ , testProperty "prop_httpConfigKeys_sane" Git.Config.Url.prop_httpConfigKeys_sane ] ++ map (uncurry testProperty) combos where combos = concat
Types/FileMatcher.hs view
@@ -1,10 +1,12 @@ {- git-annex file matcher types -- - Copyright 2013-2023 Joey Hess <id@joeyh.name>+ - Copyright 2013-2026 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -} +{-# LANGUAGE GeneralizedNewtypeDeriving #-}+ module Types.FileMatcher where import Types.UUID (UUID)@@ -83,7 +85,8 @@ type MkLimit a = String -> Either String (MatchFiles a) -type AssumeNotPresent = S.Set UUID+newtype AssumeNotPresent = AssumeNotPresent (S.Set UUID)+ deriving (Semigroup, Monoid) data MatchFiles a = MatchFiles { matchAction :: LiveUpdate -> AssumeNotPresent -> MatchInfo -> a Bool
Types/Group.hs view
@@ -1,6 +1,6 @@ {- git-annex repo groups -- - Copyright 2012-2024 Joey Hess <id@joeyh.name>+ - Copyright 2012-2026 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -10,7 +10,8 @@ fromGroup, toGroup, GroupMap(..),- emptyGroupMap+ emptyGroupMap,+ groupUUIDs ) where import Types.UUID@@ -19,9 +20,10 @@ import qualified Data.Map as M import qualified Data.Set as S-import qualified Data.ByteString as S+import qualified Data.ByteString as BS+import Data.Maybe -newtype Group = Group S.ByteString+newtype Group = Group BS.ByteString deriving (Eq, Ord, Show, Read) fromGroup :: Group -> String@@ -38,3 +40,6 @@ emptyGroupMap :: GroupMap emptyGroupMap = GroupMap M.empty M.empty M.empty++groupUUIDs :: Group -> GroupMap -> S.Set UUID+groupUUIDs g = fromMaybe S.empty . M.lookup g . uuidsByGroup
Types/Remote.hs view
@@ -387,8 +387,11 @@ -- file stored on the remote is the content of an annex object, -- and return its Key, or Nothing if it is not. --- -- Throws exception on failure to access the remote.- , importKey :: Maybe (ImportLocation -> ContentIdentifier -> ByteSize -> MeterUpdate -> a (Maybe Key))+ -- The outer action should only check if the remote supports+ -- this, and return the inner action if so.+ -- The returned action throws exception on failure to access the+ -- remote.+ , importKeyWithContentIdentifier :: a (Maybe (ImportLocation -> ContentIdentifier -> ByteSize -> MeterUpdate -> a (Maybe Key))) -- Retrieves a file from the remote. Ensures that the file -- it retrieves has one of the requested ContentIdentifiers. --@@ -470,6 +473,13 @@ -- not need to uniquely identify content. -- Eg, a mtime is sufficient. { listImportableContents :: a (Maybe (ImportableContentsChunkable a (ContentIdentifier, ByteSize)))+ -- Like importKeyWithContentIdentifier, but does not need to+ -- guarantee that the file on the remote still has the same+ -- content that it did when listImportableContents returned+ -- the ContentIdentifier. The ContentIdentifier is still+ -- provided so that information in it can be used to generate the+ -- key.+ , importKey :: a (Maybe (ImportLocation -> ContentIdentifier -> ByteSize -> MeterUpdate -> a (Maybe Key))) -- Like retrieveExportWithContentIdentifier, but does not -- need to guarantee that the file it retrieves has one -- of the requested ContentIdentifiers.
Utility/Url.hs view
@@ -55,8 +55,9 @@ import qualified Utility.FileIO as F import Network.URI-import Network.HTTP.Types-import Network.HTTP.Types.Header (hAcceptEncoding, hContentDisposition, hContentRange)+import Network.HTTP.Types.Status+import Network.HTTP.Types.Method+import Network.HTTP.Types.Header import qualified System.FilePath.Posix as UrlPath import qualified Data.CaseInsensitive as CI import qualified Data.ByteString as B
+ cabal.project view
@@ -0,0 +1,4 @@+packages:+ .+jobs: $ncpus+semaphore: True
git-annex.cabal view
@@ -1,5 +1,5 @@ Name: git-annex-Version: 10.20260717+Version: 10.20260901 Cabal-Version: 1.12 License: AGPL-3 Maintainer: Joey Hess <id@joeyh.name>@@ -37,6 +37,7 @@ stack.yaml stack-botan.yaml stack-NoLLMDependencies.yaml+ cabal.project README CHANGELOG NEWS@@ -160,11 +161,6 @@ Flag Production Description: Enable production build (slower build; faster binary) -Flag ParallelBuild- Description: Enable building in parallel- Default: False- Manual: True- Flag Botan Description: Build with the Botan C++ library for faster hashing Default: False@@ -311,7 +307,9 @@ Build-Depends: base (>= 4.18.2.1 && < 4.23), ram (< 0.21.0),- persistent (>= 2.13.3) && (< 2.15.0.0)+ persistent (>= 2.13.3) && (< 2.15.0.0),+ warp (< 3.4.11),+ magic (<= 1.1) else Build-Depends: base (>= 4.18.2.1 && < 5),@@ -328,9 +326,6 @@ else GHC-Options: -O0 - if flag(ParallelBuild)- GHC-Options: -j- -- Avoid linking with unused dynamic libraries. if os(linux) || os(freebsd) GHC-Options: -optl-Wl,--as-needed@@ -539,7 +534,7 @@ CPP-Options: -DWITH_TORRENTPARSER if flag(MagicMime)- Build-Depends: magic+ Build-Depends: magic (<= 1.1) CPP-Options: -DWITH_MAGICMIME if flag(Benchmark)@@ -844,6 +839,7 @@ Git.Command Git.Command.Batch Git.Config+ Git.Config.Url Git.ConfigTypes Git.Construct Git.Credential
stack-NoLLMDependencies.yaml view
@@ -2,7 +2,6 @@ git-annex: NoLLMDependencies: true production: true- parallelbuild: true assistant: true torrentparser: true magicmime: false@@ -19,12 +18,14 @@ pkg-config: false packages: - '.'-resolver: lts-24.26+resolver: lts-24.52 extra-deps: - aws-0.25.2 - file-io-0.2.0 - blake3-0.3-- xxhash-ffi-0.3.1 - ram-0.20.1 - persistent-2.14.6.3 - persistent-sqlite-2.13.3.0+- yesod-form-1.7.9.2+- yesod-static-1.6.1.2+- magic-1.1
stack-botan.yaml view
@@ -2,7 +2,6 @@ git-annex: NoLLMDependencies: false production: true- parallelbuild: true assistant: true torrentparser: true magicmime: false@@ -19,11 +18,11 @@ pkg-config: false packages: - '.'-resolver: lts-24.26+resolver: lts-24.52 extra-deps: - aws-0.25.2 - file-io-0.2.0 - botan-low-0.2.0.1 - botan-bindings-0.3.0.0 - blake3-0.3-- xxhash-ffi-0.3.1+- magic-1.1
stack.yaml view
@@ -2,7 +2,6 @@ git-annex: NoLLMDependencies: false production: true- parallelbuild: true assistant: true torrentparser: true magicmime: false@@ -19,9 +18,9 @@ pkg-config: false packages: - '.'-resolver: lts-24.26+resolver: lts-24.52 extra-deps: - aws-0.25.2 - file-io-0.2.0 - blake3-0.3-- xxhash-ffi-0.3.1+- magic-1.1