git-annex 10.20241031 → 10.20241202
raw patch · 24 files changed
+687/−304 lines, 24 files
Files
- Annex/Path.hs +18/−4
- Assistant/Threads/Committer.hs +12/−7
- CHANGELOG +32/−0
- CmdLine/GitAnnex/Options.hs +11/−7
- CmdLine/GitRemoteAnnex.hs +21/−10
- CmdLine/Multicall.hs +28/−0
- Command/Add.hs +22/−17
- Command/AddUrl.hs +1/−1
- Command/Import.hs +1/−1
- Command/P2PHttp.hs +92/−18
- Command/Smudge.hs +8/−6
- Command/VPop.hs +13/−6
- Git/Bundle.hs +6/−0
- P2P/Http/Client.hs +22/−7
- P2P/Http/Server.hs +42/−43
- P2P/Http/State.hs +103/−42
- P2P/Http/Url.hs +15/−0
- Remote/S3.hs +108/−53
- Test.hs +90/−54
- Test/Framework.hs +25/−19
- Utility/Directory/Create.hs +2/−2
- git-annex.cabal +3/−2
- git-annex.hs +11/−5
- stack.yaml +1/−0
Annex/Path.hs view
@@ -1,6 +1,6 @@ {- git-annex program path -- - Copyright 2013-2022 Joey Hess <id@joeyh.name>+ - Copyright 2013-2024 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -18,9 +18,11 @@ import Config.Files import Utility.Env import Annex.PidLock+import CmdLine.Multicall import qualified Annex import System.Environment (getExecutablePath, getArgs, getProgName)+import qualified Data.Map as M {- A fully qualified path to the currently running git-annex program. - @@ -33,23 +35,35 @@ - getExecutablePath. It sets GIT_ANNEX_DIR to the location of the - standalone build directory, and there are wrapper scripts for git-annex - and git-annex-shell in that directory.+ -+ - When the currently running program is not git-annex, but is instead eg+ - git-annex-shell or git-remote-annex, this finds a git-annex program+ - instead. -} programPath :: IO FilePath programPath = go =<< getEnv "GIT_ANNEX_DIR" where go (Just dir) = do- name <- getProgName+ name <- reqgitannex <$> getProgName return (dir </> name) go Nothing = do- exe <- getExecutablePath+ name <- getProgName+ exe <- if isgitannex name+ then getExecutablePath+ else pure "git-annex" p <- if isAbsolute exe then return exe else fromMaybe exe <$> readProgramFile maybe cannotFindProgram return =<< searchPath p + reqgitannex name+ | isgitannex name = name+ | otherwise = "git-annex"+ isgitannex = flip M.notMember otherMulticallCommands+ {- Returns the path for git-annex that is recorded in the programFile. -} readProgramFile :: IO (Maybe FilePath)-readProgramFile = do+readProgramFile = catchDefaultIO Nothing $ do programfile <- programFile headMaybe . lines <$> readFile programfile
Assistant/Threads/Committer.hs view
@@ -45,6 +45,7 @@ import Utility.Tuple import Utility.Metered import qualified Utility.RawFilePath as R+import Git.FilePath import Data.Time.Clock import qualified Data.Set as S@@ -319,15 +320,19 @@ (LinkChange (Just key)) checksmall change- | not annexdotfiles && dotfile f =- return (Right change)- | otherwise =- ifM (liftAnnex $ checkFileMatcher NoLiveUpdate largefilematcher f)- ( return (Left change)- , return (Right change)- )+ | not annexdotfiles = do+ topfile <- liftAnnex $ + getTopFilePath <$> inRepo (toTopFilePath f)+ if dotfile topfile+ then return (Right change)+ else checkmatcher+ | otherwise = checkmatcher where f = toRawFilePath (changeFile change)+ checkmatcher = ifM (liftAnnex $ checkFileMatcher NoLiveUpdate largefilematcher f)+ ( return (Left change)+ , return (Right change)+ ) addsmall [] = noop addsmall toadd = liftAnnex $ void $ tryIO $
CHANGELOG view
@@ -1,3 +1,35 @@+git-annex (10.20241202) upstream; urgency=medium++ * add: Consistently treat files in a dotdir as dotfiles, even+ when ran inside that dotdir.+ * add: When adding a dotfile as a non-large file, mention that it's a+ dotfile.+ * p2phttp: Added --directory option which serves multiple git-annex+ repositories located inside a directory.+ * When remote.name.annexUrl is an annex+http(s) url, that+ uses the same hostname as remote.name.url, which is itself a http(s)+ url, they are assumed to share a username and password. This avoids+ unnecessary duplicate password prompts.+ * git-remote-annex: Fix a reversion introduced in version 10.20241031+ that broke cloning from a special remote.+ * git-remote-annex: Fix cloning from a special remote on a crippled+ filesystem.+ * git-remote-annex: Fix buggy behavior when annex.stalldetection is+ configured.+ * git-remote-annex: Require git version 2.31 or newer, since old+ ones had a buggy git bundle command.+ * S3: Support versioning=yes with a readonly bucket.+ (Needs aws-0.24.3)+ * S3: Send git-annex or other configured User-Agent.+ (Needs aws-0.24.3)+ * S3: Fix infinite loop and memory blowup when importing from an+ unversioned S3 bucket that is large enough to need pagination.+ * S3: Use significantly less memory when importing from a+ versioned S3 bucket.+ * vpop: Only update state after successful checkout.++ -- Joey Hess <id@joeyh.name> Mon, 02 Dec 2024 12:31:08 -0400+ git-annex (10.20241031) upstream; urgency=medium * Sped up proxied downloads from special remotes, by streaming.
CmdLine/GitAnnex/Options.hs view
@@ -511,14 +511,18 @@ -- action in `allowConcurrentOutput`. jobsOption :: [AnnexOption] jobsOption = - [ annexOption (setAnnexState . setConcurrency . ConcurrencyCmdLine) $ - option (maybeReader parseConcurrency)- ( long "jobs" <> short 'J' - <> metavar (paramNumber `paramOr` "cpus")- <> help "enable concurrent jobs"- <> hidden- )+ [ annexOption (setAnnexState . setConcurrency . ConcurrencyCmdLine)+ jobsOptionParser ]++jobsOptionParser :: Parser Concurrency+jobsOptionParser = + option (maybeReader parseConcurrency)+ ( long "jobs" <> short 'J' + <> metavar (paramNumber `paramOr` "cpus")+ <> help "enable concurrent jobs"+ <> hidden+ ) timeLimitOption :: [AnnexOption] timeLimitOption =
CmdLine/GitRemoteAnnex.hs view
@@ -48,6 +48,7 @@ import Annex.UUID import Annex.Content import Annex.Perms+import Annex.Tmp import Annex.SpecialRemote.Config import Remote.List import Remote.List.Util@@ -69,6 +70,8 @@ run :: [String] -> IO () run (remotename:url:[]) = do+ unlessM Git.Bundle.versionSupported $+ giveup "git-remote-annex needs a newer version of git" repo <- getRepo state <- Annex.new repo Annex.eval state $@@ -82,7 +85,7 @@ case parseSpecialRemoteNameUrl remotename u of Right src -> checkallowed src >>= run' u Left e -> giveup e-run (_remotename:[]) = giveup "remote url not configured"+run (remotename:[]) = giveup $ "remote url not configured for " ++ remotename run _ = giveup "expected remote name and url parameters" run' :: String -> SpecialRemoteConfig -> Annex ()@@ -719,13 +722,14 @@ -- directory. The content of manifests is not stable, and so -- it needs to re-download it fresh every time, and the object -- file should not be stored locally.- gettotmp dl = withTmpFile "GITMANIFEST" $ \tmp tmph -> do- liftIO $ hClose tmph- _ <- dl tmp- b <- liftIO (B.readFile tmp)- case parseManifest b of- Right m -> Just <$> verifyManifest rmt m- Left err -> giveup err+ gettotmp dl = withOtherTmp $ \othertmp ->+ withTmpFileIn (fromRawFilePath othertmp) "GITMANIFEST" $ \tmp tmph -> do+ liftIO $ hClose tmph+ _ <- dl tmp+ b <- liftIO (B.readFile tmp)+ case parseManifest b of+ Right m -> Just <$> verifyManifest rmt m+ Left err -> giveup err getexport _ [] = return Nothing getexport mk (loc:locs) =@@ -1127,7 +1131,7 @@ -- If the git-annex branch did not exist when this command started, -- it was created empty by this command, and this command has avoided -- making any other commits to it, writing any temporary annex branch--- changes to thre alternateJournal, which can now be discarded. +-- changes to the alternateJournal, which can now be discarded. -- -- If nothing else has written to the branch while this command was running, -- the branch will be deleted. That allows for the git-annex branch that is@@ -1150,6 +1154,11 @@ -- does not contain any hooks. Since initialization installs -- hooks, have to work around that by not initializing, and -- delete the git bundle objects.+--+-- Similarly, when on a crippled filesystem, doing initialization would+-- involve checking out an adjusted branch. But git clone wants to do its+-- own checkout. So no initialization is done then, and the git bundle+-- objects are deleted. cleanupInitialization :: StartAnnexBranch -> FilePath -> Annex () cleanupInitialization sab alternatejournaldir = void $ tryNonAsync $ do liftIO $ mapM_ removeFile =<< dirContents alternatejournaldir@@ -1171,7 +1180,7 @@ Nothing -> return () Just _ -> void $ tryNonAsync $ inRepo $ Git.Branch.delete Annex.Branch.fullname- ifM (Annex.Branch.hasSibling <&&> nonbuggygitversion)+ ifM (Annex.Branch.hasSibling <&&> nonbuggygitversion <&&> notcrippledfilesystem) ( do autoInitialize' (pure True) startupAnnex remoteList differences <- allDifferences <$> recordedDifferences@@ -1187,6 +1196,8 @@ GitBundleKey -> lockContentForRemoval k noop removeAnnex _ -> noop void $ liftIO $ tryIO $ removeDirectory (decodeBS annexobjectdir)++ notcrippledfilesystem = not <$> probeCrippledFileSystem nonbuggygitversion = liftIO $ flip notElem buggygitversions <$> Git.Version.installed
+ CmdLine/Multicall.hs view
@@ -0,0 +1,28 @@+{- git-annex multicall binary+ -+ - Copyright 2024 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module CmdLine.Multicall where++import qualified Data.Map as M++-- Commands besides git-annex that can be run by the multicall binary.+--+-- The reason git-annex itself is not included here is because the program+-- can be renamed to any other name than these and will behave the same as+-- git-annex.+data OtherMultiCallCommand+ = GitAnnexShell+ | GitRemoteAnnex+ | GitRemoteTorAnnex++otherMulticallCommands :: M.Map String OtherMultiCallCommand+otherMulticallCommands = M.fromList+ [ ("git-annex-shell", GitAnnexShell)+ , ("git-remote-annex", GitRemoteAnnex)+ , ("git-remote-tor-annex", GitRemoteTorAnnex)+ ]+
Command/Add.hs view
@@ -94,17 +94,21 @@ addunlockedmatcher <- addUnlockedMatcher annexdotfiles <- getGitConfigVal annexDotFiles let gofile includingsmall (si, file) = case largeFilesOverride o of- Nothing -> ifM (pure (annexdotfiles || not (dotfile file))- <&&> (checkFileMatcher NoLiveUpdate largematcher file - <||> Annex.getRead Annex.force))- ( start dr si file addunlockedmatcher- , if includingsmall- then ifM (annexAddSmallFiles <$> Annex.getGitConfig)- ( startSmall dr si file- , stop- )- else stop- )+ Nothing -> do+ isdotfile <- if annexdotfiles+ then pure False+ else dotfile . getTopFilePath+ <$> inRepo (toTopFilePath file)+ islarge <- checkFileMatcher NoLiveUpdate largematcher file+ <||> Annex.getRead Annex.force+ if (not isdotfile && islarge)+ then start dr si file addunlockedmatcher+ else if includingsmall+ then ifM (annexAddSmallFiles <$> Annex.getGitConfig)+ ( startSmall isdotfile dr si file+ , stop+ )+ else stop Just True -> start dr si file addunlockedmatcher Just False -> startSmallOverridden dr si file case batchOption o of@@ -136,17 +140,18 @@ dr = dryRunOption o {- Pass file off to git-add. -}-startSmall :: DryRun -> SeekInput -> RawFilePath -> CommandStart-startSmall dr si file =+startSmall :: Bool -> DryRun -> SeekInput -> RawFilePath -> CommandStart+startSmall isdotfile dr si file = liftIO (catchMaybeIO $ R.getSymbolicLinkStatus file) >>= \case Just s -> starting "add" (ActionItemTreeFile file) si $- addSmall dr file s+ addSmall isdotfile dr file s Nothing -> stop -addSmall :: DryRun -> RawFilePath -> FileStatus -> CommandPerform-addSmall dr file s = do- showNote "non-large file; adding content to git repository"+addSmall :: Bool -> DryRun -> RawFilePath -> FileStatus -> CommandPerform+addSmall isdotfile dr file s = do+ showNote $ (if isdotfile then "dotfile" else "non-large file")+ <> "; adding content to git repository" skipWhenDryRun dr $ next $ addFile Small file s startSmallOverridden :: DryRun -> SeekInput -> RawFilePath -> CommandStart
Command/AddUrl.hs view
@@ -519,7 +519,7 @@ -- than the work tree file. liftIO $ moveFile file tmp go- else Command.Add.addSmall (DryRun False) file s+ else Command.Add.addSmall False (DryRun False) file s >>= maybe noop void where go = do
Command/Import.hs view
@@ -261,7 +261,7 @@ >>= maybe stop (\addedk -> next $ Command.Add.cleanup addedk True)- , Command.Add.addSmall (DryRun False) destfile s+ , Command.Add.addSmall False (DryRun False) destfile s ) notoverwriting why = do warning $ "not overwriting existing " <> QuotedPath destfile <> " " <> UnquotedString why
Command/P2PHttp.hs view
@@ -11,11 +11,16 @@ module Command.P2PHttp where -import Command+import Command hiding (jobsOption) import P2P.Http.Server import P2P.Http.Url import qualified P2P.Protocol as P2P import Utility.Env+import Annex.UUID+import qualified Git+import qualified Git.Construct+import qualified Annex+import Types.Concurrency import Servant import qualified Network.Wai.Handler.Warp as Warp@@ -23,12 +28,14 @@ import Network.Socket (PortNumber) import qualified Data.Map as M import Data.String+import Control.Concurrent.STM cmd :: Command-cmd = noMessages $ withAnnexOptions [jobsOption] $- command "p2phttp" SectionPlumbing- "communicate in P2P protocol over http"- paramNothing (seek <$$> optParser)+cmd = noMessages $ dontCheck repoExists $ + noRepo (startIO <$$> optParser) $+ command "p2phttp" SectionPlumbing+ "communicate in P2P protocol over http"+ paramNothing (startAnnex <$$> optParser) data Options = Options { portOption :: Maybe PortNumber@@ -43,7 +50,9 @@ , unauthNoLockingOption :: Bool , wideOpenOption :: Bool , proxyConnectionsOption :: Maybe Integer+ , jobsOption :: Maybe Concurrency , clusterJobsOption :: Maybe Int+ , directoryOption :: [FilePath] } optParser :: CmdParamsDesc -> Parser Options@@ -96,32 +105,80 @@ ( long "proxyconnections" <> metavar paramNumber <> help "maximum number of idle connections when proxying" ))+ <*> optional jobsOptionParser <*> optional (option auto ( long "clusterjobs" <> metavar paramNumber <> help "number of concurrent node accesses per connection" ))+ <*> many (strOption+ ( long "directory" <> metavar paramPath+ <> help "serve repositories in subdirectories of a directory"+ )) -seek :: Options -> CommandSeek-seek o = getAnnexWorkerPool $ \workerpool ->- withP2PConnections workerpool- (fromMaybe 1 $ proxyConnectionsOption o)- (fromMaybe 1 $ clusterJobsOption o)- (go workerpool)- where- go workerpool acquireconn = liftIO $ do+startAnnex :: Options -> Annex ()+startAnnex o+ | null (directoryOption o) = ifM ((/=) NoUUID <$> getUUID)+ ( do+ authenv <- liftIO getAuthEnv+ st <- mkServerState o authenv+ liftIO $ runServer o st+ -- Run in a git repository that is not a git-annex repository.+ , liftIO $ startIO o + )+ | otherwise = liftIO $ startIO o++startIO :: Options -> IO ()+startIO o+ | null (directoryOption o) = + giveup "Use the --directory option to specify which git-annex repositories to serve."+ | otherwise = do authenv <- getAuthEnv- st <- mkP2PHttpServerState acquireconn workerpool $- mkGetServerMode authenv o+ st <- mkst authenv mempty+ runServer o st+ where+ mkst authenv oldst = do+ repos <- findRepos o+ sts <- forM repos $ \r -> do+ strd <- Annex.new r+ Annex.eval strd (mkstannex authenv oldst)+ return (mconcat sts)+ { updateRepos = updaterepos authenv+ }+ + mkstannex authenv oldst = do+ u <- getUUID+ if u == NoUUID+ then return mempty+ else case M.lookup u (servedRepos oldst) of+ Nothing -> mkServerState o authenv+ Just old -> return $ P2PHttpServerState+ { servedRepos = M.singleton u old+ , serverShutdownCleanup = mempty+ , updateRepos = mempty+ }+ + updaterepos authenv oldst = do+ newst <- mkst authenv oldst+ return $ newst+ { serverShutdownCleanup = + serverShutdownCleanup newst + <> serverShutdownCleanup oldst+ }++runServer :: Options -> P2PHttpServerState -> IO ()+runServer o mst = go `finally` serverShutdownCleanup mst+ where+ go = do let settings = Warp.setPort port $ Warp.setHost host $ Warp.defaultSettings+ mstv <- newTMVarIO mst case (certFileOption o, privateKeyFileOption o) of- (Nothing, Nothing) -> Warp.runSettings settings (p2pHttpApp st)+ (Nothing, Nothing) -> Warp.runSettings settings (p2pHttpApp mstv) (Just certfile, Just privatekeyfile) -> do let tlssettings = Warp.tlsSettingsChain certfile (chainFileOption o) privatekeyfile- Warp.runTLS tlssettings settings (p2pHttpApp st)+ Warp.runTLS tlssettings settings (p2pHttpApp mstv) _ -> giveup "You must use both --certfile and --privatekeyfile options to enable HTTPS."- port = maybe (fromIntegral defaultP2PHttpProtocolPort) fromIntegral@@ -131,6 +188,15 @@ fromString (bindOption o) +mkServerState :: Options -> M.Map Auth P2P.ServerMode -> Annex P2PHttpServerState+mkServerState o authenv = + withAnnexWorkerPool (jobsOption o) $+ mkP2PHttpServerState+ (mkGetServerMode authenv o)+ return+ (fromMaybe 1 $ proxyConnectionsOption o)+ (fromMaybe 1 $ clusterJobsOption o)+ mkGetServerMode :: M.Map Auth P2P.ServerMode -> Options -> GetServerMode mkGetServerMode _ o _ Nothing | wideOpenOption o = ServerMode@@ -197,3 +263,11 @@ case M.lookup user permmap of Nothing -> (auth, P2P.ServeReadWrite) Just perms -> (auth, perms)++findRepos :: Options -> IO [Git.Repo]+findRepos o = do+ files <- map toRawFilePath . concat+ <$> mapM dirContents (directoryOption o)+ map Git.Construct.newFrom . catMaybes + <$> mapM Git.Construct.checkForRepo files+
Command/Smudge.hs view
@@ -239,12 +239,14 @@ , checkunchanged checkwasannexed ) where- checkmatcher d- | dotfile file = ifM (getGitConfigVal annexDotFiles)- ( go- , d- )- | otherwise = go+ checkmatcher d = do+ topfile <- getTopFilePath <$> inRepo (toTopFilePath file)+ if dotfile topfile+ then ifM (getGitConfigVal annexDotFiles)+ ( go+ , d+ )+ else go where go = do matcher <- largeFilesMatcher
Command/VPop.hs view
@@ -28,22 +28,29 @@ where go Nothing = giveup "Not in a view." go (Just (v, madj)) = starting "vpop" ai si $ do- removeView v- (oldvs, vs) <- splitAt (num - 1) . filter (sameparentbranch v)+ (oldvs, vs) <- splitAt (num - 1) + . filter (sameparentbranch v)+ . filter (/= v) <$> recentViews- mapM_ removeView oldvs- case vs of- (oldv:_) -> next $ do+ let removeview = mapM_ removeView (v : oldvs)+ ok <- case vs of+ (oldv:_) -> do showOutput checkoutViewBranch oldv madj (\v' madj' -> return (branchView v' madj'))- _ -> next $ do+ _ -> do showOutput inRepo $ Git.Command.runBool [ Param "checkout" , Param $ Git.fromRef $ Git.Ref.base $ viewParentBranch v ]+ if ok+ then+ next $ do+ removeview+ return True+ else next $ return False sameparentbranch a b = viewParentBranch a == viewParentBranch b num = fromMaybe 1 $ readish =<< headMaybe ps
Git/Bundle.hs view
@@ -12,9 +12,15 @@ import Common import Git import Git.Command+import qualified Git.Version import Data.Char (ord) import qualified Data.ByteString.Char8 as S8++-- Older versions of git had a git bundle command that sometimes omitted+-- refs, and that did not properly support --stdin.+versionSupported :: IO Bool+versionSupported = not <$> Git.Version.older "2.31" listHeads :: FilePath -> Repo -> IO [(Sha, Ref)] listHeads bundle repo = map gen . S8.lines <$>
P2P/Http/Client.hs view
@@ -24,13 +24,13 @@ import Utility.Metered import Utility.FileSize import Types.NumCopies-+import Types.Remote import Annex.Common+import qualified Git #ifdef WITH_SERVANT import qualified Annex import Annex.UUID import Annex.Url-import Types.Remote import P2P.Http import P2P.Http.Url import Annex.Concurrent@@ -83,8 +83,19 @@ -> (String -> Annex a) -> ClientAction a -> Annex (Maybe a)+p2pHttpClientVersions allowedversion rmt fallback clientaction = do+ rmtrepo <- getRepo rmt+ p2pHttpClientVersions' allowedversion rmt rmtrepo fallback clientaction++p2pHttpClientVersions'+ :: (ProtocolVersion -> Bool)+ -> Remote+ -> Git.Repo+ -> (String -> Annex a)+ -> ClientAction a+ -> Annex (Maybe a) #ifdef WITH_SERVANT-p2pHttpClientVersions allowedversion rmt fallback clientaction =+p2pHttpClientVersions' allowedversion rmt rmtrepo fallback clientaction = case p2pHttpBaseUrl <$> remoteAnnexP2PHttpUrl (gitconfig rmt) of Nothing -> error "internal" Just baseurl -> do@@ -139,9 +150,13 @@ ++ " " ++ decodeBS (statusMessage (responseStatusCode resp)) - credentialbaseurl = case p2pHttpUrlString <$> remoteAnnexP2PHttpUrl (gitconfig rmt) of+ credentialbaseurl = case remoteAnnexP2PHttpUrl (gitconfig rmt) of+ Just p2phttpurl + | isP2PHttpSameHost p2phttpurl rmtrepo ->+ Git.repoLocation rmtrepo+ | otherwise ->+ p2pHttpUrlString p2phttpurl Nothing -> error "internal"- Just url -> url credauth cred = do ba <- Git.credentialBasicAuth cred@@ -159,7 +174,7 @@ M.insert (Git.CredentialBaseURL credentialbaseurl) cred cc Nothing -> noop #else-p2pHttpClientVersions _ _ fallback () = Just <$> fallback+p2pHttpClientVersions' _ _ _ fallback () = Just <$> fallback "This remote uses an annex+http url, but this version of git-annex is not built with support for that." #endif @@ -420,7 +435,7 @@ _ :<|> _ :<|> v4 :<|> v3 :<|> v2 :<|> v1 :<|> v0 :<|> _ = client p2pHttpAPI #else-clientPut _ _ _ _ _ _ _ = ()+clientPut _ _ _ _ _ _ _ _ = () #endif clientPutOffset
P2P/Http/Server.hs view
@@ -45,10 +45,10 @@ import System.IO.Unsafe import Data.Either -p2pHttpApp :: P2PHttpServerState -> Application+p2pHttpApp :: TMVar P2PHttpServerState -> Application p2pHttpApp = serve p2pHttpAPI . serveP2pHttp -serveP2pHttp :: P2PHttpServerState -> Server P2PHttpAPI+serveP2pHttp :: TMVar P2PHttpServerState -> Server P2PHttpAPI serveP2pHttp st = serveGet st :<|> serveGet st@@ -91,7 +91,7 @@ :<|> serveGetGeneric st serveGetGeneric- :: P2PHttpServerState+ :: TMVar P2PHttpServerState -> B64UUID ServerSide -> B64Key -> Maybe (B64UUID ClientSide)@@ -109,7 +109,7 @@ serveGet :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> B64UUID ServerSide -> v -> B64Key@@ -120,8 +120,8 @@ -> IsSecure -> Maybe Auth -> Handler (Headers '[DataLengthHeader] (S.SourceT IO B.ByteString))-serveGet st su apiver (B64Key k) cu bypass baf startat sec auth = do- conn <- getP2PConnection apiver st cu su bypass sec auth ReadAction id+serveGet mst su apiver (B64Key k) cu bypass baf startat sec auth = do+ (conn, st) <- getP2PConnection apiver mst cu su bypass sec auth ReadAction id bsv <- liftIO newEmptyTMVarIO endv <- liftIO newEmptyTMVarIO validityv <- liftIO newEmptyTMVarIO@@ -222,7 +222,7 @@ serveCheckPresent :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> B64UUID ServerSide -> v -> B64Key@@ -233,14 +233,14 @@ -> Handler CheckPresentResult serveCheckPresent st su apiver (B64Key k) cu bypass sec auth = do res <- withP2PConnection apiver st cu su bypass sec auth ReadAction id- $ \conn -> liftIO $ proxyClientNetProto conn $ checkPresent k+ $ \(conn, _) -> liftIO $ proxyClientNetProto conn $ checkPresent k case res of Right b -> return (CheckPresentResult b) Left err -> throwError $ err500 { errBody = encodeBL err } serveRemove :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> (RemoveResultPlus -> t) -> B64UUID ServerSide -> v@@ -252,7 +252,7 @@ -> Handler t serveRemove st resultmangle su apiver (B64Key k) cu bypass sec auth = do res <- withP2PConnection apiver st cu su bypass sec auth RemoveAction id- $ \conn ->+ $ \(conn, _) -> liftIO $ proxyClientNetProto conn $ remove Nothing k case res of (Right b, plusuuids) -> return $ resultmangle $ @@ -262,7 +262,7 @@ serveRemoveBefore :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> B64UUID ServerSide -> v -> B64Key@@ -274,7 +274,7 @@ -> Handler RemoveResultPlus serveRemoveBefore st su apiver (B64Key k) cu bypass (Timestamp ts) sec auth = do res <- withP2PConnection apiver st cu su bypass sec auth RemoveAction id- $ \conn ->+ $ \(conn, _) -> liftIO $ proxyClientNetProto conn $ removeBeforeRemoteEndTime ts k case res of@@ -285,7 +285,7 @@ serveGetTimestamp :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> B64UUID ServerSide -> v -> B64UUID ClientSide@@ -295,7 +295,7 @@ -> Handler GetTimestampResult serveGetTimestamp st su apiver cu bypass sec auth = do res <- withP2PConnection apiver st cu su bypass sec auth ReadAction id- $ \conn ->+ $ \(conn, _) -> liftIO $ proxyClientNetProto conn getTimestamp case res of Right ts -> return $ GetTimestampResult (Timestamp ts)@@ -304,7 +304,7 @@ servePut :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> (PutResultPlus -> t) -> B64UUID ServerSide -> v@@ -319,28 +319,28 @@ -> IsSecure -> Maybe Auth -> Handler t-servePut st resultmangle su apiver (Just True) _ k cu bypass baf _ _ sec auth = do- res <- withP2PConnection' apiver st cu su bypass sec auth WriteAction+servePut mst resultmangle su apiver (Just True) _ k cu bypass baf _ _ sec auth = do+ res <- withP2PConnection' apiver mst cu su bypass sec auth WriteAction (\cst -> cst { connectionWaitVar = False }) (liftIO . protoaction) servePutResult resultmangle res where- protoaction conn = servePutAction st conn k baf $ \_offset -> do+ protoaction conn = servePutAction conn k baf $ \_offset -> do net $ sendMessage DATA_PRESENT checkSuccessPlus-servePut st resultmangle su apiver _datapresent (DataLength len) k cu bypass baf moffset stream sec auth = do+servePut mst resultmangle su apiver _datapresent (DataLength len) k cu bypass baf moffset stream sec auth = do validityv <- liftIO newEmptyTMVarIO let validitycheck = local $ runValidityCheck $ liftIO $ atomically $ readTMVar validityv tooshortv <- liftIO newEmptyTMVarIO content <- liftIO $ S.unSourceT stream (gather validityv tooshortv)- res <- withP2PConnection' apiver st cu su bypass sec auth WriteAction- (\cst -> cst { connectionWaitVar = False }) $ \conn -> do+ res <- withP2PConnection' apiver mst cu su bypass sec auth WriteAction+ (\cst -> cst { connectionWaitVar = False }) $ \(conn, st) -> do liftIO $ void $ async $ checktooshort conn tooshortv- liftIO (protoaction conn content validitycheck)+ liftIO (protoaction conn st content validitycheck) servePutResult resultmangle res where- protoaction conn content validitycheck = - servePutAction st conn k baf $ \offset' ->+ protoaction conn st content validitycheck = + servePutAction (conn, st) k baf $ \offset' -> let offsetdelta = offset' - offset in case compare offset' offset of EQ -> sendContent' nullMeterUpdate (Len len)@@ -396,13 +396,12 @@ closeP2PConnection conn servePutAction- :: P2PHttpServerState- -> P2PConnectionPair+ :: (P2PConnectionPair, PerRepoServerState) -> B64Key -> Maybe B64FilePath -> (P2P.Protocol.Offset -> Proto (Maybe [UUID])) -> IO (Either SomeException (Either ProtoFailure (Maybe [UUID])))-servePutAction st conn (B64Key k) baf a = inAnnexWorker st $+servePutAction (conn, st) (B64Key k) baf a = inAnnexWorker st $ enteringStage (TransferStage Download) $ runFullProto (clientRunState conn) (clientP2PConnection conn) $ put' k af a@@ -422,7 +421,7 @@ servePut' :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> (PutResultPlus -> t) -> B64UUID ServerSide -> v@@ -440,7 +439,7 @@ servePutOffset :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> (PutOffsetResultPlus -> t) -> B64UUID ServerSide -> v@@ -452,7 +451,7 @@ -> Handler t servePutOffset st resultmangle su apiver (B64Key k) cu bypass sec auth = do res <- withP2PConnection apiver st cu su bypass sec auth WriteAction- (\cst -> cst { connectionWaitVar = False }) $ \conn ->+ (\cst -> cst { connectionWaitVar = False }) $ \(conn, _) -> liftIO $ proxyClientNetProto conn $ getPutOffset k af case res of Right offset -> return $ resultmangle $@@ -464,7 +463,7 @@ serveLockContent :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> B64UUID ServerSide -> v -> B64Key@@ -473,8 +472,8 @@ -> IsSecure -> Maybe Auth -> Handler LockResult-serveLockContent st su apiver (B64Key k) cu bypass sec auth = do- conn <- getP2PConnection apiver st cu su bypass sec auth LockAction id+serveLockContent mst su apiver (B64Key k) cu bypass sec auth = do+ (conn, st) <- getP2PConnection apiver mst cu su bypass sec auth LockAction id let lock = do lockresv <- newEmptyTMVarIO unlockv <- newEmptyTMVarIO@@ -501,7 +500,7 @@ serveKeepLocked :: APIVersion v- => P2PHttpServerState+ => TMVar P2PHttpServerState -> B64UUID ServerSide -> v -> LockID@@ -513,15 +512,15 @@ -> Maybe KeepAlive -> S.SourceT IO UnlockRequest -> Handler LockResult-serveKeepLocked st _su _apiver lckid _cu _bypass sec auth _ _ unlockrequeststream = do- checkAuthActionClass st sec auth LockAction $ \_ -> do+serveKeepLocked mst su _apiver lckid _cu _bypass sec auth _ _ unlockrequeststream = do+ checkAuthActionClass mst su sec auth LockAction $ \st _ -> do liftIO $ keepingLocked lckid st- _ <- liftIO $ S.unSourceT unlockrequeststream go+ _ <- liftIO $ S.unSourceT unlockrequeststream (go st) return (LockResult False Nothing) where- go S.Stop = dropLock lckid st- go (S.Error _err) = dropLock lckid st- go (S.Skip s) = go s- go (S.Effect ms) = ms >>= go- go (S.Yield (UnlockRequest False) s) = go s- go (S.Yield (UnlockRequest True) _) = dropLock lckid st+ go st S.Stop = dropLock lckid st+ go st (S.Error _err) = dropLock lckid st+ go st (S.Skip s) = go st s+ go st (S.Effect ms) = ms >>= go st+ go st (S.Yield (UnlockRequest False) s) = go st s+ go st (S.Yield (UnlockRequest True) _) = dropLock lckid st
P2P/Http/State.hs view
@@ -26,6 +26,8 @@ import Types.WorkerPool import Annex.WorkerPool import Annex.BranchState+import Annex.Concurrent+import Types.Concurrency import Types.Cluster import CmdLine.Action (startConcurrency) import Utility.ThreadScheduler@@ -42,8 +44,37 @@ import qualified Data.Set as S import Control.Concurrent.Async import Data.Time.Clock.POSIX+import qualified Data.Semigroup as Sem+import Prelude data P2PHttpServerState = P2PHttpServerState+ { servedRepos :: M.Map UUID PerRepoServerState+ , serverShutdownCleanup :: IO ()+ , updateRepos :: UpdateRepos+ }++type UpdateRepos = P2PHttpServerState -> IO P2PHttpServerState++instance Monoid P2PHttpServerState where+ mempty = P2PHttpServerState+ { servedRepos = mempty+ , serverShutdownCleanup = noop+ , updateRepos = const mempty+ }++instance Sem.Semigroup P2PHttpServerState where+ a <> b = P2PHttpServerState+ { servedRepos = servedRepos a <> servedRepos b+ , serverShutdownCleanup = do+ serverShutdownCleanup a+ serverShutdownCleanup b+ , updateRepos = \st -> do+ a' <- updateRepos a st+ b' <- updateRepos b st+ return (a' <> b')+ }++data PerRepoServerState = PerRepoServerState { acquireP2PConnection :: AcquireP2PConnection , annexWorkerPool :: AnnexWorkerPool , getServerMode :: GetServerMode@@ -62,8 +93,8 @@ } | CannotServeRequests -mkP2PHttpServerState :: AcquireP2PConnection -> AnnexWorkerPool -> GetServerMode -> IO P2PHttpServerState-mkP2PHttpServerState acquireconn annexworkerpool getservermode = P2PHttpServerState+mkPerRepoServerState :: AcquireP2PConnection -> AnnexWorkerPool -> GetServerMode -> IO PerRepoServerState+mkPerRepoServerState acquireconn annexworkerpool getservermode = PerRepoServerState <$> pure acquireconn <*> pure annexworkerpool <*> pure getservermode@@ -75,7 +106,7 @@ withP2PConnection :: APIVersion v => v- -> P2PHttpServerState+ -> TMVar P2PHttpServerState -> B64UUID ClientSide -> B64UUID ServerSide -> [B64UUID Bypass]@@ -83,10 +114,10 @@ -> Maybe Auth -> ActionClass -> (ConnectionParams -> ConnectionParams)- -> (P2PConnectionPair -> Handler (Either ProtoFailure a))+ -> ((P2PConnectionPair, PerRepoServerState) -> Handler (Either ProtoFailure a)) -> Handler a-withP2PConnection apiver st cu su bypass sec auth actionclass fconnparams connaction =- withP2PConnection' apiver st cu su bypass sec auth actionclass fconnparams connaction'+withP2PConnection apiver mst cu su bypass sec auth actionclass fconnparams connaction =+ withP2PConnection' apiver mst cu su bypass sec auth actionclass fconnparams connaction' where connaction' conn = connaction conn >>= \case Right r -> return r@@ -96,7 +127,7 @@ withP2PConnection' :: APIVersion v => v- -> P2PHttpServerState+ -> TMVar P2PHttpServerState -> B64UUID ClientSide -> B64UUID ServerSide -> [B64UUID Bypass]@@ -104,17 +135,17 @@ -> Maybe Auth -> ActionClass -> (ConnectionParams -> ConnectionParams)- -> (P2PConnectionPair -> Handler a)+ -> ((P2PConnectionPair, PerRepoServerState) -> Handler a) -> Handler a-withP2PConnection' apiver st cu su bypass sec auth actionclass fconnparams connaction = do- conn <- getP2PConnection apiver st cu su bypass sec auth actionclass fconnparams- connaction conn+withP2PConnection' apiver mst cu su bypass sec auth actionclass fconnparams connaction = do+ (conn, st) <- getP2PConnection apiver mst cu su bypass sec auth actionclass fconnparams+ connaction (conn, st) `finally` liftIO (releaseP2PConnection conn) getP2PConnection :: APIVersion v => v- -> P2PHttpServerState+ -> TMVar P2PHttpServerState -> B64UUID ClientSide -> B64UUID ServerSide -> [B64UUID Bypass]@@ -122,16 +153,16 @@ -> Maybe Auth -> ActionClass -> (ConnectionParams -> ConnectionParams)- -> Handler P2PConnectionPair-getP2PConnection apiver st cu su bypass sec auth actionclass fconnparams =- checkAuthActionClass st sec auth actionclass go+ -> Handler (P2PConnectionPair, PerRepoServerState)+getP2PConnection apiver mst cu su bypass sec auth actionclass fconnparams =+ checkAuthActionClass mst su sec auth actionclass go where- go servermode = liftIO (acquireP2PConnection st cp) >>= \case+ go st servermode = liftIO (acquireP2PConnection st cp) >>= \case Left (ConnectionFailed err) -> throwError err502 { errBody = encodeBL err } Left TooManyConnections -> throwError err503- Right v -> return v+ Right v -> return (v, st) where cp = fconnparams $ ConnectionParams { connectionProtocolVersion = protocolVersion apiver@@ -142,30 +173,51 @@ , connectionWaitVar = True } +getPerRepoServerState :: TMVar P2PHttpServerState -> B64UUID ServerSide -> IO (Maybe PerRepoServerState)+getPerRepoServerState mstv su = do+ mst <- atomically $ readTMVar mstv+ case lookupst mst of+ Just st -> return (Just st)+ Nothing -> do+ mst' <- atomically $ takeTMVar mstv+ mst'' <- updateRepos mst' mst'+ debug "P2P.Http" $+ "Rescanned for repositories, now serving UUIDs: "+ ++ show (M.keys (servedRepos mst''))+ atomically $ putTMVar mstv mst''+ return $ lookupst mst''+ where+ lookupst mst = M.lookup (fromB64UUID su) (servedRepos mst)+ checkAuthActionClass- :: P2PHttpServerState+ :: TMVar P2PHttpServerState+ -> B64UUID ServerSide -> IsSecure -> Maybe Auth -> ActionClass- -> (P2P.ServerMode -> Handler a)+ -> (PerRepoServerState -> P2P.ServerMode -> Handler a) -> Handler a-checkAuthActionClass st sec auth actionclass go =- case (sm, actionclass) of+checkAuthActionClass mstv su sec auth actionclass go =+ liftIO (getPerRepoServerState mstv su) >>= \case+ Just st -> select st+ Nothing -> throwError err404+ where+ select st = case (sm, actionclass) of (ServerMode { serverMode = P2P.ServeReadWrite }, _) ->- go P2P.ServeReadWrite+ go st P2P.ServeReadWrite (ServerMode { unauthenticatedLockingAllowed = True }, LockAction) ->- go P2P.ServeReadOnly+ go st P2P.ServeReadOnly (ServerMode { serverMode = P2P.ServeAppendOnly }, RemoveAction) -> throwError $ forbiddenWithoutAuth sm (ServerMode { serverMode = P2P.ServeAppendOnly }, _) ->- go P2P.ServeAppendOnly+ go st P2P.ServeAppendOnly (ServerMode { serverMode = P2P.ServeReadOnly }, ReadAction) ->- go P2P.ServeReadOnly+ go st P2P.ServeReadOnly (ServerMode { serverMode = P2P.ServeReadOnly }, _) -> throwError $ forbiddenWithoutAuth sm (CannotServeRequests, _) -> throwError basicAuthRequired- where- sm = getServerMode st sec auth+ where+ sm = getServerMode st sec auth forbiddenAction :: ServerError forbiddenAction = err403@@ -204,13 +256,14 @@ = ConnectionParams -> IO (Either ConnectionProblem P2PConnectionPair) -withP2PConnections- :: AnnexWorkerPool+mkP2PHttpServerState+ :: GetServerMode+ -> UpdateRepos -> ProxyConnectionPoolSize -> ClusterConcurrency- -> (AcquireP2PConnection -> Annex a)- -> Annex a-withP2PConnections workerpool proxyconnectionpoolsize clusterconcurrency a = do+ -> AnnexWorkerPool+ -> Annex P2PHttpServerState+mkP2PHttpServerState getservermode updaterepos proxyconnectionpoolsize clusterconcurrency workerpool = do enableInteractiveBranchAccess myuuid <- getUUID myproxies <- M.lookup myuuid <$> getProxies@@ -223,7 +276,13 @@ let endit = do liftIO $ atomically $ putTMVar endv () liftIO $ wait asyncservicer- a (acquireconn reqv) `finally` endit+ let servinguuids = myuuid : map proxyRemoteUUID (maybe [] S.toList myproxies)+ st <- liftIO $ mkPerRepoServerState (acquireconn reqv) workerpool getservermode+ return $ P2PHttpServerState+ { servedRepos = M.fromList $ zip servinguuids (repeat st)+ , serverShutdownCleanup = endit+ , updateRepos = updaterepos+ } where acquireconn reqv connparams = do respvar <- newEmptyTMVarIO@@ -487,13 +546,13 @@ wait locktid return Nothing -storeLock :: LockID -> Locker -> P2PHttpServerState -> IO ()+storeLock :: LockID -> Locker -> PerRepoServerState -> IO () storeLock lckid locker st = atomically $ do m <- takeTMVar (openLocks st) let !m' = M.insert lckid locker m putTMVar (openLocks st) m' -keepingLocked :: LockID -> P2PHttpServerState -> IO ()+keepingLocked :: LockID -> PerRepoServerState -> IO () keepingLocked lckid st = do m <- atomically $ readTMVar (openLocks st) case M.lookup lckid m of@@ -502,7 +561,7 @@ atomically $ void $ tryPutTMVar (lockerTimeoutDisable locker) () -dropLock :: LockID -> P2PHttpServerState -> IO ()+dropLock :: LockID -> PerRepoServerState -> IO () dropLock lckid st = do v <- atomically $ do m <- takeTMVar (openLocks st)@@ -520,13 +579,15 @@ Nothing -> return () Just locker -> wait (lockerThread locker) -getAnnexWorkerPool :: (AnnexWorkerPool -> Annex a) -> Annex a-getAnnexWorkerPool a = startConcurrency transferStages $- Annex.getState Annex.workers >>= \case- Nothing -> giveup "Use -Jn or set annex.jobs to configure the number of worker threads."- Just wp -> a wp+withAnnexWorkerPool :: (Maybe Concurrency) -> (AnnexWorkerPool -> Annex a) -> Annex a+withAnnexWorkerPool mc a = do+ maybe noop (setConcurrency . ConcurrencyCmdLine) mc+ startConcurrency transferStages $+ Annex.getState Annex.workers >>= \case+ Nothing -> giveup "Use -Jn or set annex.jobs to configure the number of worker threads."+ Just wp -> a wp -inAnnexWorker :: P2PHttpServerState -> Annex a -> IO (Either SomeException a)+inAnnexWorker :: PerRepoServerState -> Annex a -> IO (Either SomeException a) inAnnexWorker st = inAnnexWorker' (annexWorkerPool st) inAnnexWorker' :: AnnexWorkerPool -> Annex a -> IO (Either SomeException a)
P2P/Http/Url.hs view
@@ -15,6 +15,9 @@ import System.FilePath.Posix as P import Servant.Client (BaseUrl(..), Scheme(..)) import Text.Read+import Data.Char+import qualified Git+import qualified Git.Url #endif defaultP2PHttpProtocolPort :: Int@@ -78,4 +81,16 @@ unavailableP2PHttpUrl p = p #ifdef WITH_SERVANT { p2pHttpBaseUrl = (p2pHttpBaseUrl p) { baseUrlHost = "!dne!" } }+#endif++#ifdef WITH_SERVANT+-- When a p2phttp url is on the same host as a git repo, which also uses+-- http, the same username+password is assumed to be used for both.+isP2PHttpSameHost :: P2PHttpUrl -> Git.Repo -> Bool+isP2PHttpSameHost u repo+ | not (Git.repoIsHttp repo) = False+ | otherwise = + Just (map toLower $ baseUrlHost (p2pHttpBaseUrl u)) + ==+ (map toLower <$> (Git.Url.host repo)) #endif
Remote/S3.hs view
@@ -1,6 +1,6 @@ {- S3 remotes -- - Copyright 2011-2023 Joey Hess <id@joeyh.name>+ - Copyright 2011-2024 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -27,6 +27,8 @@ import qualified System.FilePath.Posix as Posix import Data.Char import Data.String+import Data.Maybe+import Data.Time.Clock import Network.Socket (HostName) import Network.HTTP.Conduit (Manager) import Network.HTTP.Client (responseStatus, responseBody, RequestBody(..))@@ -36,7 +38,6 @@ import Control.Monad.Catch import Control.Concurrent.STM (atomically) import Control.Concurrent.STM.TVar-import Data.Maybe import Annex.Common import Types.Remote@@ -63,8 +64,8 @@ import Utility.DataUnits import Annex.Content import qualified Annex.Url as Url-import Utility.Url (extractFromResourceT)-import Annex.Url (getUrlOptions, withUrlOptions, UrlOptions(..))+import Utility.Url (extractFromResourceT, UserAgent)+import Annex.Url (getUserAgent, getUrlOptions, withUrlOptions, UrlOptions(..)) import Utility.Env import Annex.Verify @@ -581,7 +582,7 @@ | versioning info = do rsp <- sendS3Handle h $ S3.getBucketObjectVersions (bucket info)- continuelistversioned h [] rsp+ continuelistversioned Nothing h [] rsp | otherwise = do rsp <- sendS3Handle h $ (S3.getBucket (bucket info))@@ -589,17 +590,31 @@ continuelistunversioned h [] rsp continuelistunversioned h l rsp- | S3.gbrIsTruncated rsp = do- rsp' <- sendS3Handle h $- (S3.getBucket (bucket info))- { S3.gbMarker = S3.gbrNextMarker rsp- , S3.gbPrefix = fileprefix- }- continuelistunversioned h (rsp:l) rsp'- | otherwise = return $- mkImportableContentsUnversioned info (reverse (rsp:l))- - continuelistversioned h l rsp+ | S3.gbrIsTruncated rsp =+ let marker = + S3.gbrNextMarker rsp+ <|>+ (S3.objectKey <$> lastMaybe (S3.gbrContents rsp))+ in case marker of+ Just _ -> do+ rsp' <- sendS3Handle h $+ (S3.getBucket (bucket info))+ { S3.gbMarker = marker+ , S3.gbPrefix = fileprefix+ }+ l' <- extractFromResourceT $+ extractunversioned rsp+ continuelistunversioned h (l':l) rsp'+ Nothing -> nomore+ | otherwise = nomore+ where+ nomore = do+ l' <- extractFromResourceT $+ extractunversioned rsp+ return $ mkImportableContentsUnversioned+ (reverse (l':l))++ continuelistversioned reuse h l rsp | S3.gbovrIsTruncated rsp = do rsp' <- sendS3Handle h $ (S3.getBucketObjectVersions (bucket info))@@ -607,65 +622,84 @@ , S3.gbovVersionIdMarker = S3.gbovrNextVersionIdMarker rsp , S3.gbovPrefix = fileprefix }- continuelistversioned h (rsp:l) rsp'- | otherwise = return $- mkImportableContentsVersioned info (reverse (rsp:l))+ (l', reuse') <- extractFromResourceT $+ extractversioned reuse rsp+ continuelistversioned reuse' h (l':l) rsp'+ | otherwise = do+ (l', _) <- extractFromResourceT $+ extractversioned reuse rsp+ return $ mkImportableContentsVersioned+ (reverse (l':l)) -mkImportableContentsUnversioned :: S3Info -> [S3.GetBucketResponse] -> ImportableContents (ContentIdentifier, ByteSize)-mkImportableContentsUnversioned info l = ImportableContents - { importableContents = concatMap (mapMaybe extract . S3.gbrContents) l+ extractunversioned = mapMaybe extractunversioned' . S3.gbrContents+ extractunversioned' oi = do+ loc <- bucketImportLocation info $+ T.unpack $ S3.objectKey oi+ let sz = S3.objectSize oi+ let cid = mkS3UnversionedContentIdentifier $ S3.objectETag oi+ return (loc, (cid, sz))+ + extractversioned reuse = extractversioned' reuse . S3.gbovrContents+ extractversioned' reuse [] = ([], reuse)+ extractversioned' reuse (x:xs) = case extractversioned'' reuse x of+ Just (v, reuse') -> + let (l, reuse'') = extractversioned' reuse' xs+ in (v:l, reuse'')+ Nothing -> extractversioned' reuse xs+ extractversioned'' reuse ovi@(S3.ObjectVersion {}) = do+ loc <- bucketImportLocation info $+ T.unpack $ S3.oviKey ovi+ -- Avoid storing the same filename in memory repeatedly.+ let loc' = case reuse of+ Just reuseloc | reuseloc == loc -> reuseloc+ _ -> loc+ let sz = S3.oviSize ovi+ let cid = mkS3VersionedContentIdentifier' ovi+ return (((loc', (cid, sz)), S3.oviLastModified ovi), Just loc')+ extractversioned'' _ (S3.DeleteMarker {}) = Nothing++mkImportableContentsUnversioned :: [[(ImportLocation, (ContentIdentifier, ByteSize))]] -> ImportableContents (ContentIdentifier, ByteSize)+mkImportableContentsUnversioned l = ImportableContents + { importableContents = concat l , importableHistory = [] }- where- extract oi = do- loc <- bucketImportLocation info $- T.unpack $ S3.objectKey oi- let sz = S3.objectSize oi- let cid = mkS3UnversionedContentIdentifier $ S3.objectETag oi- return (loc, (cid, sz)) -mkImportableContentsVersioned :: S3Info -> [S3.GetBucketObjectVersionsResponse] -> ImportableContents (ContentIdentifier, ByteSize)-mkImportableContentsVersioned info = build . groupfiles+mkImportableContentsVersioned :: [[((ImportLocation, (ContentIdentifier, ByteSize)), UTCTime)]] -> ImportableContents (ContentIdentifier, ByteSize)+mkImportableContentsVersioned = build . groupfiles where+ ovilastmodified = snd+ loc = fst . fst+ build [] = ImportableContents [] [] build l = let (l', v) = latestversion l in ImportableContents- { importableContents = mapMaybe extract v+ { importableContents = map fst v , importableHistory = case build l' of ImportableContents [] [] -> [] h -> [h] }- - extract ovi@(S3.ObjectVersion {}) = do- loc <- bucketImportLocation info $- T.unpack $ S3.oviKey ovi- let sz = S3.oviSize ovi- let cid = mkS3VersionedContentIdentifier' ovi- return (loc, (cid, sz))- extract (S3.DeleteMarker {}) = Nothing -- group files so all versions of a file are in a sublist, -- with the newest first. S3 uses such an order, so it's just a -- matter of breaking up the response list into sublists.- groupfiles = groupBy (\a b -> S3.oviKey a == S3.oviKey b) - . concatMap S3.gbovrContents+ groupfiles = groupBy (\a b -> loc a == loc b) . concat latestversion [] = ([], []) latestversion ([]:rest) = latestversion rest latestversion l@((first:_old):remainder) =- go (S3.oviLastModified first) [first] remainder+ go (ovilastmodified first) [first] remainder where go mtime c [] = (removemostrecent mtime l, reverse c) go mtime c ([]:rest) = go mtime c rest go mtime c ((latest:_old):rest) = - let !mtime' = max mtime (S3.oviLastModified latest)+ let !mtime' = max mtime (ovilastmodified latest) in go mtime' (latest:c) rest removemostrecent _ [] = [] removemostrecent mtime ([]:rest) = removemostrecent mtime rest removemostrecent mtime (i@(curr:old):rest)- | S3.oviLastModified curr == mtime =+ | ovilastmodified curr == mtime = old : removemostrecent mtime rest | otherwise = i : removemostrecent mtime rest@@ -885,10 +919,11 @@ Just creds -> go =<< liftIO (genCredentials creds) Nothing -> return (Left S3HandleNeedCreds) where- s3cfg = s3Configuration c go awscreds = do- let awscfg = AWS.Configuration AWS.Timestamp awscreds debugMapper Nothing ou <- getUrlOptions+ ua <- getUserAgent+ let awscfg = AWS.Configuration AWS.Timestamp awscreds debugMapper Nothing+ let s3cfg = s3Configuration (Just ua) c return $ Right $ S3Handle (httpManager ou) awscfg s3cfg withS3Handle :: S3HandleVar -> (Either S3HandleProblem S3Handle -> Annex a) -> Annex a@@ -907,13 +942,20 @@ needS3Creds :: UUID -> String needS3Creds u = missingCredPairFor "S3" (AWS.creds u) -s3Configuration :: ParsedRemoteConfig -> S3.S3Configuration AWS.NormalQuery-s3Configuration c = cfg+s3Configuration :: Maybe UserAgent -> ParsedRemoteConfig -> S3.S3Configuration AWS.NormalQuery+#if MIN_VERSION_aws(0,24,3)+s3Configuration ua c = cfg+#else+s3Configuration _ua c = cfg+#endif { S3.s3Port = port , S3.s3RequestStyle = case getRemoteConfigValue requeststyleField c of Just "path" -> S3.PathStyle Just s -> giveup $ "bad S3 requeststyle value: " ++ s Nothing -> S3.s3RequestStyle cfg+#if MIN_VERSION_aws(0,24,3)+ , S3.s3UserAgent = T.pack <$> ua+#endif } where h = fromJust $ getRemoteConfigValue hostField c@@ -1157,7 +1199,7 @@ , Just ("versioning", if versioning info then "yes" else "no") ] where- s3c = s3Configuration c+ s3c = s3Configuration Nothing c showstorageclass (S3.OtherStorageClass t) = T.unpack t showstorageclass sc = show sc @@ -1341,11 +1383,24 @@ where enableversioning b = do #if MIN_VERSION_aws(0,21,1)- showAction "enabling bucket versioning"+ showAction "checking bucket versioning" hdl <- mkS3HandleVar c gc u+ let setversioning = S3.putBucketVersioning b S3.VersioningEnabled withS3HandleOrFail u hdl $ \h ->- void $ liftIO $ runResourceT $ sendS3Handle h $- S3.putBucketVersioning b S3.VersioningEnabled+#if MIN_VERSION_aws(0,24,3)+ liftIO $ runResourceT $+ tryS3 (sendS3Handle h setversioning) >>= \case+ Right _ -> return ()+ Left err -> do+ res <- sendS3Handle h $+ S3.getBucketVersioning b+ case S3.gbvVersioning res of+ Just S3.VersioningEnabled -> return ()+ _ -> giveup $ "This bucket does not have versioning enabled, and enabling it failed: "+ ++ T.unpack (S3.s3ErrorMessage err)+#else+ void $ liftIO $ runResourceT $ sendS3Handle h setversioning+#endif #else showLongNote $ unlines [ "This version of git-annex cannot auto-enable S3 bucket versioning."
Test.hs view
@@ -1,6 +1,6 @@ {- git-annex test suite -- - Copyright 2010-2023 Joey Hess <id@joeyh.name>+ - Copyright 2010-2024 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -37,6 +37,7 @@ import qualified Git.Ref import qualified Git.LsTree import qualified Git.FilePath+import qualified Git.Bundle import qualified Annex.Locations #ifndef mingw32_HOST_OS import qualified Types.GitConfig@@ -282,6 +283,8 @@ [ testCase "add dup" test_add_dup , testCase "add extras" test_add_extras , testCase "add moved link" test_add_moved+ , testCase "git-remote-annex" (test_git_remote_annex False)+ , testCase "git-remote-annex exporttree" (test_git_remote_annex True) , testCase "readonly remote" test_readonly_remote , testCase "ignore deleted files" test_ignore_deleted_files , testCase "metadata" test_metadata@@ -421,6 +424,39 @@ annexed_present wormannexedfile checkbackend wormannexedfile backendWORM +test_git_remote_annex :: Bool -> Assertion+#ifndef mingw32_HOST_OS+test_git_remote_annex exporttree+ | exporttree = + runtest ["importtree=yes", "exporttree=yes"] $+ git_annex "export" ["master", "--to=foo"] "export"+ | otherwise = + runtest [] $ + git_annex "copy" ["--to=foo"] "copy"+ where+ runtest cfg populate = whenM Git.Bundle.versionSupported $ + intmpclonerepo $ do+ let cfg' = ["type=directory", "encryption=none", "directory=dir"] ++ cfg+ createDirectory "dir"+ git_annex "initremote" ("foo":("uuid=" ++ diruuid):cfg') "initremote"+ git_annex "get" [] "get failed"+ () <- populate+ git "config" ["remote.foo.url", "annex::"] "git config"+ -- git push does not always propagate nonzero exit+ -- status from git-remote-annex, so remember the+ -- transcript and display it if clone fails+ pushtranscript <- testProcess' "git" ["push", "foo", "master", "git-annex"] Nothing (== True) (const True) "git push"+ git "clone" ["annex::"++diruuid++"?"++intercalate "&" cfg', "clonedir"]+ ("git clone from special remote (after git push with output: " ++ pushtranscript ++ ")")+ inpath "clonedir" $+ git_annex "get" [annexedfile] "get from origin special remote"+ diruuid="89ddefa4-a04c-11ef-87b5-e880882a4f98"+#else+test_git_remote_annex exporttree =+ -- git-remote-annex is not currently installed on Windows+ return ()+#endif+ test_add_moved :: Assertion test_add_moved = intmpclonerepo $ do git_annex "get" [annexedfile] "get failed"@@ -440,10 +476,10 @@ withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do pair r1 r2- indir r1 $ do+ intopdir r1 $ do git_annex "get" [annexedfile] "get failed in first repo" make_readonly r1- indir r2 $ do+ intopdir r2 $ do git_annex "sync" ["r1", "--no-push", "--no-content"] "sync with readonly repo" git_annex "get" [annexedfile, "--from", "r1"] "get from readonly repo" git "remote" ["rm", "origin"] "remote rm"@@ -1234,7 +1270,7 @@ withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> withtmpclonerepo $ \r3 -> do- forM_ [r1, r2, r3] $ \r -> indir r $ do+ forM_ [r1, r2, r3] $ \r -> intopdir r $ do when (r /= r1) $ git "remote" ["add", "r1", "../" ++ r1] "remote add" when (r /= r2) $@@ -1243,11 +1279,11 @@ git "remote" ["add", "r3", "../" ++ r3] "remote add" git_annex "get" [annexedfile] "get" git "remote" ["rm", "origin"] "remote rm"- forM_ [r3, r2, r1] $ \r -> indir r $+ forM_ [r3, r2, r1] $ \r -> intopdir r $ git_annex "sync" ["--no-content"] ("sync in " ++ r)- forM_ [r3, r2] $ \r -> indir r $+ forM_ [r3, r2] $ \r -> intopdir r $ git_annex "drop" ["--force", annexedfile] ("drop in " ++ r)- indir r1 $ do+ intopdir r1 $ do git_annex "sync" ["--no-content"] "sync in r1" git_annex_expectoutput "find" ["--in", "r3"] [] {- This was the bug. The sync@@ -1261,19 +1297,19 @@ test_conflict_resolution_movein_regression = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do let rname r = if r == r1 then "r1" else "r2"- forM_ [r1, r2] $ \r -> indir r $ do+ forM_ [r1, r2] $ \r -> intopdir r $ do {- Get all files, see check below. -} git_annex "get" [] "get" disconnectOrigin pair r1 r2- forM_ [r1, r2] $ \r -> indir r $ do+ forM_ [r1, r2] $ \r -> intopdir r $ do {- Set up a conflict. -} let newcontent = content annexedfile ++ rname r git_annex "unlock" [annexedfile] "unlock" writecontent annexedfile newcontent {- Sync twice in r1 so it gets the conflict resolution - update from r2 -}- forM_ [r1, r2, r1] $ \r -> indir r $+ forM_ [r1, r2, r1] $ \r -> intopdir r $ git_annex "sync" ["--force", "--no-content"] ("sync in " ++ rname r) {- After the sync, it should be possible to get all - files. This includes both sides of the conflict,@@ -1281,7 +1317,7 @@ - - The bug caused one repo to be missing the content - of the file that had been put in it. -}- forM_ [r1, r2] $ \r -> indir r $ do+ forM_ [r1, r2] $ \r -> intopdir r $ do git_annex "get" [] ("get all files after merge conflict resolution in " ++ rname r) {- Simple case of conflict resolution; 2 different versions of annexed@@ -1290,18 +1326,18 @@ test_conflict_resolution = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do- indir r1 $ do+ intopdir r1 $ do disconnectOrigin writecontent conflictor "conflictor1" add_annex conflictor "add conflicter" git_annex "sync" ["--no-content"] "sync in r1"- indir r2 $ do+ intopdir r2 $ do disconnectOrigin writecontent conflictor "conflictor2" add_annex conflictor "add conflicter" git_annex "sync" ["--no-content"] "sync in r2" pair r1 r2- forM_ [r1,r2,r1] $ \r -> indir r $+ forM_ [r1,r2,r1] $ \r -> intopdir r $ git_annex "sync" ["--no-content"] "sync" checkmerge "r1" r1 checkmerge "r2" r2@@ -1314,7 +1350,7 @@ length v == 2 @? (what ++ " not exactly 2 variant files in: " ++ show l) conflictor `notElem` l @? ("conflictor still present after conflict resolution")- indir d $ do+ intopdir d $ do git_annex "get" v "get" git_annex_expectoutput "find" v v @@ -1323,12 +1359,12 @@ test_conflict_resolution_adjusted_branch = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> whenM (adjustedbranchsupported r2) $ do- indir r1 $ do+ intopdir r1 $ do disconnectOrigin writecontent conflictor "conflictor1" add_annex conflictor "add conflicter" git_annex "sync" ["--no-content"] "sync in r1"- indir r2 $ do+ intopdir r2 $ do disconnectOrigin writecontent conflictor "conflictor2" add_annex conflictor "add conflicter"@@ -1338,7 +1374,7 @@ -- filesystem. So, --force it. git_annex "adjust" ["--unlock", "--force"] "adjust" pair r1 r2- forM_ [r1,r2,r1] $ \r -> indir r $+ forM_ [r1,r2,r1] $ \r -> intopdir r $ git_annex "sync" ["--no-content"] "sync" checkmerge "r1" r1 checkmerge "r2" r2@@ -1351,7 +1387,7 @@ length v == 2 @? (what ++ " not exactly 2 variant files in: " ++ show l) conflictor `notElem` l @? ("conflictor still present after conflict resolution")- indir d $ do+ intopdir d $ do git_annex "get" v "get" git_annex_expectoutput "find" v v @@ -1364,12 +1400,12 @@ where check inr1 = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do- indir r1 $ do+ intopdir r1 $ do disconnectOrigin writecontent conflictor "conflictor" add_annex conflictor "add conflicter" git_annex "sync" ["--no-content"] "sync in r1"- indir r2 $ do+ intopdir r2 $ do disconnectOrigin createDirectory conflictor writecontent subfile "subfile"@@ -1377,7 +1413,7 @@ git_annex "sync" ["--no-content"] "sync in r2" pair r1 r2 let l = if inr1 then [r1, r2] else [r2, r1]- forM_ l $ \r -> indir r $+ forM_ l $ \r -> intopdir r $ git_annex "sync" ["--no-content"] "sync in mixed conflict" checkmerge "r1" r1 checkmerge "r2" r2@@ -1392,7 +1428,7 @@ @? (what ++ " conflictor variant file missing in: " ++ show l ) length v == 1 @? (what ++ " too many variant files in: " ++ show v)- indir d $ do+ intopdir d $ do git_annex "get" (conflictor:v) ("get in " ++ what) git_annex_expectoutput "find" [conflictor] [fromRawFilePath (Git.FilePath.toInternalGitPath (toRawFilePath subfile))] git_annex_expectoutput "find" v v@@ -1406,23 +1442,23 @@ where check inr1 = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do- indir r1 $ do+ intopdir r1 $ do disconnectOrigin writecontent conflictor "conflictor" add_annex conflictor "add conflicter" git_annex "sync" ["--no-content"] "sync in r1"- indir r2 $+ intopdir r2 $ disconnectOrigin pair r1 r2- indir r2 $ do+ intopdir r2 $ do git_annex "sync" ["--no-content"] "sync in r2" git_annex "get" [conflictor] "get conflictor" git_annex "unlock" [conflictor] "unlock conflictor" writecontent conflictor "newconflictor"- indir r1 $+ intopdir r1 $ removeWhenExistsWith R.removeLink (toRawFilePath conflictor) let l = if inr1 then [r1, r2, r1] else [r2, r1, r2]- forM_ l $ \r -> indir r $+ forM_ l $ \r -> intopdir r $ git_annex "sync" ["--no-content"] "sync" checkmerge "r1" r1 checkmerge "r2" r2@@ -1446,12 +1482,12 @@ where check inr1 = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do- indir r1 $ do+ intopdir r1 $ do disconnectOrigin writecontent conflictor "conflictor" add_annex conflictor "add conflicter" git_annex "sync" ["--no-content"] "sync in r1"- indir r2 $ do+ intopdir r2 $ do disconnectOrigin writecontent conflictor nonannexed_content git "config"@@ -1462,7 +1498,7 @@ git_annex "sync" ["--no-content"] "sync in r2" pair r1 r2 let l = if inr1 then [r1, r2] else [r2, r1]- forM_ l $ \r -> indir r $+ forM_ l $ \r -> intopdir r $ git_annex "sync" ["--no-content"] "sync" checkmerge "r1" r1 checkmerge "r2" r2@@ -1496,19 +1532,19 @@ check inr1 = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> whenM (checkRepo (Types.coreSymlinks <$> Annex.getGitConfig) r1) $ do- indir r1 $ do+ intopdir r1 $ do disconnectOrigin writecontent conflictor "conflictor" add_annex conflictor "add conflicter" git_annex "sync" ["--no-content"] "sync in r1"- indir r2 $ do+ intopdir r2 $ do disconnectOrigin R.createSymbolicLink (toRawFilePath symlinktarget) (toRawFilePath "conflictor") git "add" [conflictor] "git add conflictor" git_annex "sync" ["--no-content"] "sync in r2" pair r1 r2 let l = if inr1 then [r1, r2] else [r2, r1]- forM_ l $ \r -> indir r $+ forM_ l $ \r -> intopdir r $ git_annex "sync" ["--no-content"] "sync" checkmerge "r1" r1 checkmerge "r2" r2@@ -1543,19 +1579,19 @@ where check remoteconflictor = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do- indir r1 $ do+ intopdir r1 $ do disconnectOrigin createDirectoryIfMissing True (fromRawFilePath (parentDir (toRawFilePath remoteconflictor))) writecontent remoteconflictor annexedcontent add_annex conflictor "add remoteconflicter" git_annex "sync" ["--no-content"] "sync in r1"- indir r2 $ do+ intopdir r2 $ do disconnectOrigin writecontent conflictor localcontent pair r1 r2 -- this case is intentionally not handled -- since the user can recover on their own easily- indir r2 $ git_annex_shouldfail "sync" ["--no-content"] "sync should not succeed"+ intopdir r2 $ git_annex_shouldfail "sync" ["--no-content"] "sync should not succeed" conflictor = "conflictor" localcontent = "local" annexedcontent = "annexed"@@ -1568,18 +1604,18 @@ withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> withtmpclonerepo $ \r3 -> do- indir r1 $ do+ intopdir r1 $ do writecontent conflictor "conflictor" git_annex "add" [conflictor] "add conflicter" git_annex "sync" ["--no-content"] "sync in r1" check_is_link conflictor "r1"- indir r2 $ do+ intopdir r2 $ do createDirectory conflictor writecontent (conflictor </> "subfile") "subfile" git_annex "add" [conflictor] "add conflicter" git_annex "sync" ["--no-content"] "sync in r2" check_is_link (conflictor </> "subfile") "r2"- indir r3 $ do+ intopdir r3 $ do writecontent conflictor "conflictor" git_annex "add" [conflictor] "add conflicter" git_annex "sync" ["--no-content"] "sync in r1"@@ -1599,26 +1635,26 @@ test_mixed_lock_conflict_resolution = withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do- indir r1 $ do+ intopdir r1 $ do disconnectOrigin writecontent conflictor "conflictor" git_annex "add" [conflictor] "add conflicter" git_annex "sync" ["--no-content"] "sync in r1"- indir r2 $ do+ intopdir r2 $ do disconnectOrigin writecontent conflictor "conflictor" git_annex "add" [conflictor] "add conflicter" git_annex "unlock" [conflictor] "unlock conflicter" git_annex "sync" ["--no-content"] "sync in r2" pair r1 r2- forM_ [r1,r2,r1] $ \r -> indir r $+ forM_ [r1,r2,r1] $ \r -> intopdir r $ git_annex "sync" ["--no-content"] "sync" checkmerge "r1" r1 checkmerge "r2" r2 where conflictor = "conflictor" variantprefix = conflictor ++ ".variant"- checkmerge what d = indir d $ do+ checkmerge what d = intopdir d $ do l <- getDirectoryContents "." let v = filter (variantprefix `isPrefixOf`) l length v == 0@@ -1643,14 +1679,14 @@ checkmerge "r2" r2 where conflictor = "conflictor"- setup r = indir r $ whensupported $ do+ setup r = intopdir r $ whensupported $ do disconnectOrigin git_annex "upgrade" [] "upgrade" git_annex "adjust" ["--unlock", "--force"] "adjust" writecontent conflictor "conflictor" git_annex "add" [conflictor] "add conflicter" git_annex "sync" ["--no-content"] "sync"- checkmerge what d = indir d $ whensupported $ do+ checkmerge what d = intopdir d $ whensupported $ do git_annex "sync" ["--no-content"] ("sync should not work in " ++ what) l <- getDirectoryContents "." conflictor `elem` l@@ -1664,7 +1700,7 @@ test_adjusted_branch_subtree_regression :: Assertion test_adjusted_branch_subtree_regression = withtmpclonerepo $ \r -> whenM (adjustedbranchsupported r) $ do- indir r $ do+ intopdir r $ do disconnectOrigin origbranch <- annexeval origBranch git_annex "upgrade" [] "upgrade"@@ -2086,31 +2122,31 @@ withtmpclonerepo $ \r1 -> withtmpclonerepo $ \r2 -> do pair r1 r2- indir r1 $ do+ intopdir r1 $ do disconnectOrigin writecontent wormannexedfile $ content wormannexedfile git_annex "add" [wormannexedfile, "--backend=WORM"] "add" git_annex "sync" ["--no-content"] "sync"- indir r2 $ do+ intopdir r2 $ do disconnectOrigin git_annex "sync" ["--no-content"] "sync"- indir r1 $ do+ intopdir r1 $ do git_annex "sync" ["--no-content"] "sync"- indir r2 $ do+ intopdir r2 $ do git_annex "get" [wormannexedfile] "get" git_annex "drop" [wormannexedfile] "drop" git_annex "get" [wormannexedfile] "get" git_annex "drop" [wormannexedfile] "drop"- indir r1 $ do+ intopdir r1 $ do git_annex "drop" ["--force", wormannexedfile] "drop" git_annex "sync" ["--no-content"] "sync" git_annex "forget" ["--force"] "forget" git_annex "sync" ["--no-content"] "sync" emptylog- indir r2 $ do+ intopdir r2 $ do git_annex "sync" ["--no-content"] "sync" emptylog- indir r1 $ do+ intopdir r1 $ do git_annex "sync" ["--no-content"] "sync" emptylog where
Test/Framework.hs view
@@ -73,7 +73,11 @@ -- In debug mode, the output is allowed to pass through. -- So the output does not get checked in debug mode. testProcess :: String -> [String] -> Maybe [(String, String)] -> (Bool -> Bool) -> (String -> Bool) -> String -> Assertion-testProcess command params environ expectedret expectedtranscript faildesc = do+testProcess command params environ expectedret expectedtranscript faildesc =+ void $ testProcess' command params environ expectedret expectedtranscript faildesc++testProcess' :: String -> [String] -> Maybe [(String, String)] -> (Bool -> Bool) -> (String -> Bool) -> String -> IO String+testProcess' command params environ expectedret expectedtranscript faildesc = do let p = (proc command params) { env = environ } debug <- testDebug . testOptions <$> getTestMode if debug@@ -81,10 +85,12 @@ ret <- withCreateProcess p $ \_ _ _ pid -> waitForProcess pid (expectedret (ret == ExitSuccess)) @? (faildesc ++ " failed with unexpected exit code")+ return "" else do (transcript, ret) <- Utility.Process.Transcript.processTranscript' p Nothing (expectedret ret) @? (faildesc ++ " failed with unexpected exit code (transcript follows)\n" ++ transcript) (expectedtranscript transcript) @? (faildesc ++ " failed with unexpected output (transcript follows)\n" ++ transcript)+ return transcript -- Run git. (Do not use to run git-annex as the one being tested -- may not be in path.)@@ -140,12 +146,12 @@ a `finally` Annex.Action.stopCoProcesses innewrepo :: IO () -> IO ()-innewrepo a = withgitrepo $ \r -> indir r a+innewrepo a = withgitrepo $ \r -> intopdir r a inmainrepo :: IO a -> IO a inmainrepo a = do d <- mainrepodir- indir d a+ intopdir d a with_ssh_origin :: (Assertion -> Assertion) -> (Assertion -> Assertion) with_ssh_origin cloner a = cloner $ do@@ -160,7 +166,7 @@ config = "remote.origin.url" intmpclonerepo :: Assertion -> Assertion-intmpclonerepo a = withtmpclonerepo $ \r -> indir r a+intmpclonerepo a = withtmpclonerepo $ \r -> intopdir r a checkRepo :: Types.Annex a -> FilePath -> IO a checkRepo getval d = do@@ -170,11 +176,11 @@ intmpbareclonerepo :: Assertion -> Assertion intmpbareclonerepo a = withtmpclonerepo' (newCloneRepoConfig { bareClone = True } ) $- \r -> indir r a+ \r -> intopdir r a intmpsharedclonerepo :: Assertion -> Assertion intmpsharedclonerepo a = withtmpclonerepo' (newCloneRepoConfig { sharedClone = True } ) $- \r -> indir r a+ \r -> intopdir r a withtmpclonerepo :: (FilePath -> Assertion) -> Assertion withtmpclonerepo = withtmpclonerepo' newCloneRepoConfig@@ -200,14 +206,19 @@ maindir <- mainrepodir bracket (setuprepo maindir) return a -indir :: FilePath -> IO a -> IO a-indir dir a = do+intopdir :: FilePath -> IO a -> IO a+intopdir dir a = do+ topdir <- Utility.Env.getEnvDefault "TOPDIR" (error "TOPDIR not set")+ inpath (topdir ++ "/" ++ dir) a++inpath :: FilePath -> IO a -> IO a+inpath path a = do currdir <- getCurrentDirectory -- Assertion failures throw non-IO errors; catch -- any type of error and change back to currdir before -- rethrowing. r <- bracket_- (changeToTopDir dir)+ (setCurrentDirectory path) (setCurrentDirectory currdir) (tryNonAsync a) case r of@@ -215,7 +226,7 @@ Left e -> throwM e adjustedbranchsupported :: FilePath -> IO Bool-adjustedbranchsupported repo = indir repo $ Annex.AdjustedBranch.isGitVersionSupported+adjustedbranchsupported repo = intopdir repo $ Annex.AdjustedBranch.isGitVersionSupported setuprepo :: FilePath -> IO FilePath setuprepo dir = do@@ -248,7 +259,7 @@ ] git "clone" cloneparams "git clone" configrepo new- indir new $ do+ intopdir new $ do ver <- annexVersion <$> getTestMode git_annex "init" [ "-q"@@ -257,12 +268,12 @@ ] "git annex init" unless (bareClone cfg) $- indir new $+ intopdir new $ setupTestMode return new configrepo :: FilePath -> IO ()-configrepo dir = indir dir $ do+configrepo dir = intopdir dir $ do -- ensure git is set up to let commits happen git "config" ["user.name", "Test User"] "git config"@@ -556,11 +567,6 @@ git "commit" ["--allow-empty", "-m", "empty"] "git commit failed" git_annex "adjust" ["--unlock"] "git annex adjust failed" -changeToTopDir :: FilePath -> IO ()-changeToTopDir t = do- topdir <- Utility.Env.getEnvDefault "TOPDIR" (error "TOPDIR not set")- setCurrentDirectory $ topdir ++ "/" ++ t- tmpdir :: String tmpdir = ".t" @@ -687,7 +693,7 @@ {- Set up repos as remotes of each other. -} pair :: FilePath -> FilePath -> Assertion-pair r1 r2 = forM_ [r1, r2] $ \r -> indir r $ do+pair r1 r2 = forM_ [r1, r2] $ \r -> intopdir r $ do when (r /= r1) $ git "remote" ["add", "r1", "../" ++ r1] "remote add" when (r /= r2) $
Utility/Directory/Create.hs view
@@ -77,11 +77,11 @@ | null dirs -> liftIO $ unlessM (doesDirectoryExist (fromRawFilePath topdir)) $ ioError $ customerror doesNotExistErrorType $- "createDirectoryFrom: " ++ fromRawFilePath topdir ++ " does not exist"+ "createDirectoryUnder: " ++ fromRawFilePath topdir ++ " does not exist" | otherwise -> createdirs $ map (topdir P.</>) (reverse (scanl1 (P.</>) dirs)) _ -> liftIO $ ioError $ customerror userErrorType- ("createDirectoryFrom: not located in " ++ unwords (map fromRawFilePath topdirs))+ ("createDirectoryUnder: not located in " ++ unwords (map fromRawFilePath topdirs)) where customerror t s = mkIOError t s Nothing (Just (fromRawFilePath dir0))
git-annex.cabal view
@@ -1,5 +1,5 @@ Name: git-annex-Version: 10.20241031+Version: 10.20241202 Cabal-Version: 1.12 License: AGPL-3 Maintainer: Joey Hess <id@joeyh.name>@@ -633,9 +633,10 @@ CmdLine.GitAnnexShell.Checks CmdLine.GitAnnexShell.Fields CmdLine.AnnexSetter- CmdLine.Option+ CmdLine.Multicall CmdLine.GitRemoteAnnex CmdLine.GitRemoteTorAnnex+ CmdLine.Option CmdLine.Seek CmdLine.Usage Command
git-annex.hs view
@@ -10,7 +10,9 @@ import System.Environment (getArgs, getProgName) import System.FilePath import Network.Socket (withSocketsDo)+import qualified Data.Map as M +import CmdLine.Multicall import qualified CmdLine.GitAnnex import qualified CmdLine.GitAnnexShell import qualified CmdLine.GitRemoteAnnex@@ -34,11 +36,15 @@ #endif run ps =<< getProgName where- run ps n = case takeFileName n of- "git-annex-shell" -> CmdLine.GitAnnexShell.run ps- "git-remote-annex" -> CmdLine.GitRemoteAnnex.run ps- "git-remote-tor-annex" -> CmdLine.GitRemoteTorAnnex.run ps- _ -> CmdLine.GitAnnex.run Test.optParser Test.runner Benchmark.mkGenerator ps+ run ps n = case M.lookup (takeFileName n) otherMulticallCommands of+ Just GitAnnexShell -> CmdLine.GitAnnexShell.run ps+ Just GitRemoteAnnex -> CmdLine.GitRemoteAnnex.run ps+ Just GitRemoteTorAnnex -> CmdLine.GitRemoteTorAnnex.run ps+ Nothing -> CmdLine.GitAnnex.run+ Test.optParser+ Test.runner+ Benchmark.mkGenerator+ ps #ifdef mingw32_HOST_OS {- On Windows, if HOME is not set, probe it and set it.
stack.yaml view
@@ -16,3 +16,4 @@ resolver: nightly-2024-07-29 extra-deps: - filepath-bytestring-1.4.100.3.2+- aws-0.24.3