packages feed

git-annex 10.20240227 → 10.20240430

raw patch · 68 files changed

+973/−378 lines, 68 filesdep +unbounded-delays

Dependencies added: unbounded-delays

Files

Annex/AdjustedBranch.hs view
@@ -468,19 +468,30 @@ 			(commitAuthorMetaData basiscommit) 			(commitCommitterMetaData basiscommit) 			(mkcommit cmode)-	mkcommit cmode = Git.Branch.commitTree cmode+	-- Make sure that the exact message is used in the commit,+	-- since that message is looked for later.+	-- After git-annex 10.20240227, it's possible to use+	-- commitTree instead of this, but this is being kept+	-- for some time, for compatability with older versions.+	mkcommit cmode = Git.Branch.commitTreeExactMessage cmode 		adjustedBranchCommitMessage parents treesha  {- This message should never be changed. -} adjustedBranchCommitMessage :: String adjustedBranchCommitMessage = "git-annex adjusted branch" +{- Allow for a trailing newline after the message. -}+hasAdjustedBranchCommitMessage :: Commit -> Bool+hasAdjustedBranchCommitMessage c = +	dropWhileEnd (\x -> x == '\n' || x == '\r') (commitMessage c) +		== adjustedBranchCommitMessage+ findAdjustingCommit :: AdjBranch -> Annex (Maybe Commit) findAdjustingCommit (AdjBranch b) = go =<< catCommit b   where 	go Nothing = return Nothing 	go (Just c)-		| commitMessage c == adjustedBranchCommitMessage = return (Just c)+		| hasAdjustedBranchCommitMessage c = return (Just c) 		| otherwise = case commitParent c of 			[p] -> go =<< catCommit p 			_ -> return Nothing@@ -540,7 +551,7 @@ 		return (Right parent) 	go origsha parent pastadjcommit (sha:l) = catCommit sha >>= \case 		Just c-			| commitMessage c == adjustedBranchCommitMessage ->+			| hasAdjustedBranchCommitMessage c -> 				go origsha parent True l 			| pastadjcommit -> 				reverseAdjustedCommit parent adj (sha, c) origbranch@@ -577,7 +588,7 @@ 			(commitAuthorMetaData basiscommit) 			(commitCommitterMetaData basiscommit) $ 				Git.Branch.commitTree cmode-					(commitMessage basiscommit)+					[commitMessage basiscommit] 					[commitparent] treesha 		return (Right revadjcommit) 
Annex/AdjustedBranch/Merge.hs view
@@ -153,7 +153,8 @@ 			then do 				cmode <- annexCommitMode <$> Annex.getGitConfig 				c <- inRepo $ Git.Branch.commitTree cmode-					("Merged " ++ fromRef tomerge) [adjmergecommit]+					["Merged " ++ fromRef tomerge]+					[adjmergecommit] 					(commitTree currentcommit) 				inRepo $ Git.Branch.update "updating adjusted branch" currbranch c 				propigateAdjustedCommits origbranch adj
Annex/Branch.hs view
@@ -52,7 +52,7 @@ import qualified System.FilePath.ByteString as P import System.PosixCompat.Files (isRegularFile) -import Annex.Common hiding (append)+import Annex.Common import Types.BranchState import Annex.BranchState import Annex.Journal@@ -945,9 +945,9 @@ 	addedt <- inRepo $ Git.Tree.graftTree treeish graftpoint origtree 	cmode <- annexCommitMode <$> Annex.getGitConfig 	c <- inRepo $ Git.Branch.commitTree cmode-		"graft" [branchref] addedt+		["graft"] [branchref] addedt 	c' <- inRepo $ Git.Branch.commitTree cmode-		"graft cleanup" [c] origtree+		["graft cleanup"] [c] origtree 	inRepo $ Git.Branch.update' fullname c' 	-- The tree in c' is the same as the tree in branchref, 	-- and the index was updated to that above, so it's safe to
Annex/Common.hs view
@@ -12,5 +12,5 @@ import Messages as X import Git.Quote as X #ifndef mingw32_HOST_OS-import System.Posix.IO as X hiding (createPipe)+import System.Posix.IO as X hiding (createPipe, append) #endif
Annex/Content.hs view
@@ -103,7 +103,9 @@ import Utility.InodeCache import Utility.CopyFile import Utility.Metered+#ifndef mingw32_HOST_OS import Utility.FileMode+#endif import qualified Utility.RawFilePath as R  import qualified System.FilePath.ByteString as P@@ -439,7 +441,7 @@ 	alreadyhave = liftIO $ R.removeLink src  checkSecureHashes :: Key -> Annex (Maybe String)-checkSecureHashes key = ifM (Backend.isCryptographicallySecure key)+checkSecureHashes key = ifM (Backend.isCryptographicallySecureKey key) 	( return Nothing 	, ifM (annexSecureHashesOnly <$> Annex.getGitConfig) 		( return $ Just $ "annex.securehashesonly blocked adding " ++ decodeBS (formatKeyVariety (fromKey keyVariety key)) ++ " key"
Annex/ExternalAddonProcess.hs view
@@ -1,6 +1,6 @@ {- External addon processes for special remotes and backends.  -- - Copyright 2013-2020 Joey Hess <id@joeyh.name>+ - Copyright 2013-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -33,16 +33,16 @@ 	= ProgramNotInstalled String 	| ProgramFailure String -startExternalAddonProcess :: String -> ExternalAddonPID -> Annex (Either ExternalAddonStartError ExternalAddonProcess)-startExternalAddonProcess basecmd pid = do+startExternalAddonProcess :: String -> [CommandParam] -> ExternalAddonPID -> Annex (Either ExternalAddonStartError ExternalAddonProcess)+startExternalAddonProcess basecmd ps pid = do 	errrelayer <- mkStderrRelayer 	g <- Annex.gitRepo 	cmdpath <- liftIO $ searchPath basecmd 	liftIO $ start errrelayer g cmdpath   where 	start errrelayer g cmdpath = do-		(cmd, ps) <- maybe (pure (basecmd, [])) findShellCommand cmdpath-		let basep = (proc cmd (toCommand ps))+		(cmd, cmdps) <- maybe (pure (basecmd, [])) findShellCommand cmdpath+		let basep = (proc cmd (toCommand (cmdps ++ ps))) 			{ std_in = CreatePipe 			, std_out = CreatePipe 			, std_err = CreatePipe
Annex/Import.hs view
@@ -86,7 +86,7 @@ 	{ importCommitTracking :: Maybe Sha 	-- ^ Current commit on the remote tracking branch. 	, importCommitMode :: Git.Branch.CommitMode-	, importCommitMessage :: String+	, importCommitMessages :: [String] 	}  {- Buils a commit for an import from a special remote.@@ -251,7 +251,7 @@  	mkcommit parents tree = inRepo $ Git.Branch.commitTree 		(importCommitMode importcommitconfig)-		(importCommitMessage importcommitconfig)+		(importCommitMessages importcommitconfig) 		parents 		tree 
Annex/Locations.hs view
@@ -506,7 +506,7 @@ gitAnnexWebPrivKey :: Git.Repo -> FilePath gitAnnexWebPrivKey r = fromRawFilePath $ gitAnnexDir r P.</> "privkey.pem" -{- .git/annex/feeds/ is used to record per-key (url) state by importfeeds -}+{- .git/annex/feeds/ is used to record per-key (url) state by importfeed -} gitAnnexFeedStateDir :: Git.Repo -> RawFilePath gitAnnexFeedStateDir r = P.addTrailingPathSeparator $ 	gitAnnexDir r P.</> "feedstate"
Annex/RemoteTrackingBranch.hs view
@@ -77,7 +77,7 @@ 	cmode <- annexCommitMode <$> Annex.getGitConfig 	inRepo $ Git.Branch.commitTree 			cmode-			"remote tracking branch"+			["remote tracking branch"] 			[commitsha, importedhistory] 			treesha 
Annex/Transfer.hs view
@@ -1,6 +1,6 @@ {- git-annex transfers  -- - Copyright 2012-2023 Joey Hess <id@joeyh.name>+ - Copyright 2012-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -42,7 +42,7 @@ import Annex.WorkerPool import Annex.TransferrerPool import Annex.StallDetection-import Backend (isCryptographicallySecure)+import Backend (isCryptographicallySecureKey) import Types.StallDetection import qualified Utility.RawFilePath as R @@ -128,9 +128,10 @@   where 	go = do 		info <- liftIO $ startTransferInfo afile-		(meter, tfile, createtfile, metervar) <- mkProgressUpdater t info+		(tfile, lckfile, moldlckfile) <- fromRepo $ transferFileAndLockFile t+		(meter, createtfile, metervar) <- mkProgressUpdater t info tfile 		mode <- annexFileMode-		(lck, inprogress) <- prep tfile createtfile mode+		(lck, inprogress) <- prep lckfile moldlckfile createtfile mode 		if inprogress && not ignorelock 			then do 				warning "transfer already in progress, or unable to take transfer lock"@@ -139,51 +140,75 @@ 				v <- retry 0 info metervar $ 					detectStallsAndSuggestConfig stalldetection metervar $ 						transferaction meter-				liftIO $ cleanup tfile lck+				liftIO $ cleanup tfile lckfile moldlckfile lck 				if observeBool v 					then removeFailedTransfer t 					else recordFailedTransfer t info 				return v 	-	prep :: RawFilePath -> Annex () -> ModeSetter -> Annex (Maybe LockHandle, Bool)+	prep :: RawFilePath -> Maybe RawFilePath -> Annex () -> ModeSetter -> Annex (Maybe (LockHandle, Maybe LockHandle), Bool) #ifndef mingw32_HOST_OS-	prep tfile createtfile mode = catchPermissionDenied (const prepfailed) $ do-		let lck = transferLockFile tfile-		createAnnexDirectory $ P.takeDirectory lck-		tryLockExclusive (Just mode) lck >>= \case+	prep lckfile moldlckfile createtfile mode = catchPermissionDenied (const prepfailed) $ do+		createAnnexDirectory $ P.takeDirectory lckfile+		tryLockExclusive (Just mode) lckfile >>= \case 			Nothing -> return (Nothing, True) 			-- Since the lock file is removed in cleanup, 			-- there's a race where different processes 			-- may have a deleted and a new version of the same 			-- lock file open. checkSaneLock guards against 			-- that.-			Just lockhandle -> ifM (checkSaneLock lck lockhandle)-				( do-					createtfile-					return (Just lockhandle, False)+			Just lockhandle -> ifM (checkSaneLock lckfile lockhandle)+				( case moldlckfile of+					Nothing -> do+						createtfile+						return (Just (lockhandle, Nothing), False)+					Just oldlckfile -> do+						createAnnexDirectory $ P.takeDirectory oldlckfile+						tryLockExclusive (Just mode) oldlckfile >>= \case+							Nothing -> do+								liftIO $ dropLock lockhandle+								return (Nothing, True)+							Just oldlockhandle -> ifM (checkSaneLock oldlckfile oldlockhandle)+								( do+									createtfile+									return (Just (lockhandle, Just oldlockhandle), False)+								, do+									liftIO $ dropLock oldlockhandle+									liftIO $ dropLock lockhandle+									return (Nothing, True)+								) 				, do 					liftIO $ dropLock lockhandle 					return (Nothing, True) 				) #else-	prep tfile createtfile _mode = catchPermissionDenied (const prepfailed) $ do-		let lck = transferLockFile tfile-		createAnnexDirectory $ P.takeDirectory lck-		catchMaybeIO (liftIO $ lockExclusive lck) >>= \case-			Nothing -> return (Nothing, False)-			Just Nothing -> return (Nothing, True)-			Just (Just lockhandle) -> do-				createtfile-				return (Just lockhandle, False)+	prep lckfile moldlckfile createtfile _mode = catchPermissionDenied (const prepfailed) $ do+		createAnnexDirectory $ P.takeDirectory lckfile+		catchMaybeIO (liftIO $ lockExclusive lckfile) >>= \case+			Just (Just lockhandle) -> case moldlckfile of+				Nothing -> do+					createtfile+					return (Just (lockhandle, Nothing), False)+				Just oldlckfile -> do+					createAnnexDirectory $ P.takeDirectory oldlckfile+					catchMaybeIO (liftIO $ lockExclusive oldlckfile) >>= \case+						Just (Just oldlockhandle) -> do+							createtfile+							return (Just (lockhandle, Just oldlockhandle), False)+						_ -> do+							liftIO $ dropLock lockhandle+							return (Nothing, False)+			_ -> return (Nothing, False) #endif 	prepfailed = return (Nothing, False) -	cleanup _ Nothing = noop-	cleanup tfile (Just lockhandle) = do-		let lck = transferLockFile tfile+	cleanup _ _ _ Nothing = noop+	cleanup tfile lckfile moldlckfile (Just (lockhandle, moldlockhandle)) = do 		void $ tryIO $ R.removeLink tfile #ifndef mingw32_HOST_OS-		void $ tryIO $ R.removeLink lck+		void $ tryIO $ R.removeLink lckfile+		maybe noop (void . tryIO . R.removeLink) moldlckfile+		maybe noop dropLock moldlockhandle 		dropLock lockhandle #else 		{- Windows cannot delete the lockfile until the lock@@ -191,8 +216,10 @@ 		 - process that takes the lock before it's removed, 		 - so ignore failure to remove. 		 -}+		maybe noop dropLock moldlockhandle 		dropLock lockhandle-		void $ tryIO $ R.removeLink lck+		void $ tryIO $ R.removeLink lckfile+		maybe noop (void . tryIO . R.removeLink) moldlckfile #endif  	retry numretries oldinfo metervar run =@@ -279,7 +306,7 @@ 		(pure (Types.Backend.isCryptographicallySecure eventualbackend)) 		(Types.Backend.backendVariety eventualbackend) 	Nothing -> go-		(isCryptographicallySecure k)+		(isCryptographicallySecureKey k) 		(fromKey keyVariety k)   where 	go checksecure variety = ifM checksecure
Annex/Verify.hs view
@@ -12,6 +12,7 @@ 	shouldVerify, 	verifyKeyContentPostRetrieval, 	verifyKeyContent,+	verifyKeyContent', 	Verification(..), 	unVerified, 	warnUnverifiableInsecure,@@ -97,16 +98,32 @@ 				resumeVerifyKeyContent k f iv 			_ -> verifyKeyContent k f +-- When possible, does an incremental verification, because that can be+-- faster. Eg, the VURL backend can need to try multiple checksums and only+-- with an incremental verification does it avoid reading files twice. verifyKeyContent :: Key -> RawFilePath -> Annex Bool verifyKeyContent k f = verifyKeySize k f <&&> verifyKeyContent' k f +-- Does not verify size. verifyKeyContent' :: Key -> RawFilePath -> Annex Bool verifyKeyContent' k f =  	Backend.maybeLookupBackendVariety (fromKey keyVariety k) >>= \case 		Nothing -> return True-		Just b -> case Types.Backend.verifyKeyContent b of-			Nothing -> return True-			Just verifier -> verifier k f+		Just b -> case (Types.Backend.verifyKeyContentIncrementally b, Types.Backend.verifyKeyContent b) of+			(Nothing, Nothing) -> return True+			(Just mkiv, mverifier) -> do+				iv <- mkiv k+				showAction (UnquotedString (descIncrementalVerifier iv))+				res <- liftIO $ catchDefaultIO Nothing $+					withBinaryFile (fromRawFilePath f) ReadMode $ \h -> do+						feedIncrementalVerifier h iv+						finalizeIncrementalVerifier iv+				case res of+					Just res' -> return res'+					Nothing -> case mverifier of+						Nothing -> return True+						Just verifier -> verifier k f+			(Nothing, Just verifier) -> verifier k f  resumeVerifyKeyContent :: Key -> RawFilePath -> IncrementalVerifier -> Annex Bool resumeVerifyKeyContent k f iv = liftIO (positionIncrementalVerifier iv) >>= \case@@ -132,17 +149,18 @@ 			liftIO $ catchDefaultIO (Just False) $ 				withBinaryFile (fromRawFilePath f) ReadMode $ \h -> do 					hSeek h AbsoluteSeek endpos-					feedincremental h+					feedIncrementalVerifier h iv 					finalizeIncrementalVerifier iv 	-	feedincremental h = do-		b <- S.hGetSome h chunk-		if S.null b-			then return ()-			else do-				updateIncrementalVerifier iv b-				feedincremental h-+feedIncrementalVerifier :: Handle -> IncrementalVerifier -> IO ()+feedIncrementalVerifier h iv = do+	b <- S.hGetSome h chunk+	if S.null b+		then return ()+		else do+			updateIncrementalVerifier iv b+			feedIncrementalVerifier h iv+  where 	chunk = 65536  verifyKeySize :: Key -> RawFilePath -> Annex Bool
Annex/View.hs view
@@ -387,7 +387,7 @@ prop_view_roundtrips (AssociatedFile (Just f)) metadata visible = or 	[ B.null (P.takeFileName f) && B.null (P.takeDirectory f) 	, viewTooLarge view-	, all hasfields (viewedFiles view (viewedFileFromReference' Nothing) (fromRawFilePath f) metadata)+	, all hasfields (viewedFiles view (viewedFileFromReference' Nothing Nothing) (fromRawFilePath f) metadata) 	]   where 	view = View (Git.Ref "foo") $@@ -577,7 +577,7 @@ 			cmode <- annexCommitMode <$> Annex.getGitConfig 			let msg = "updated " ++ fromRef (branchView view madj) 			let parent = catMaybes [oldcommit]-			inRepo (Git.Branch.commitTree cmode msg parent newtree)+			inRepo (Git.Branch.commitTree cmode [msg] parent newtree) 		else return Nothing  {- Diff between currently checked out branch and staged changes, and
Annex/View/ViewedFile.hs view
@@ -1,6 +1,6 @@ {- filenames (not paths) used in views  -- - Copyright 2014-2023 Joey Hess <id@joeyh.name>+ - Copyright 2014-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -19,6 +19,7 @@  import Annex.Common import Utility.QuickCheck+import Backend.Utilities (maxExtensions)  import qualified Data.ByteString as S @@ -37,10 +38,12 @@  - So, from dir/subdir/file.foo, generate file_%dir%subdir%.foo  -} viewedFileFromReference :: GitConfig -> MkViewedFile-viewedFileFromReference g = viewedFileFromReference' (annexMaxExtensionLength g)+viewedFileFromReference g = viewedFileFromReference'+	(annexMaxExtensionLength g)+	(annexMaxExtensions g) -viewedFileFromReference' :: Maybe Int -> MkViewedFile-viewedFileFromReference' maxextlen f = concat $+viewedFileFromReference' :: Maybe Int -> Maybe Int -> MkViewedFile+viewedFileFromReference' maxextlen maxextensions f = concat $ 	[ escape (fromRawFilePath base') 	, if null dirs then "" else "_%" ++ intercalate "%" (map escape dirs) ++ "%" 	, escape $ fromRawFilePath $ S.concat extensions'@@ -51,11 +54,12 @@ 	(base, extensions) = case maxextlen of 		Nothing -> splitShortExtensions (toRawFilePath basefile') 		Just n -> splitShortExtensions' (n+1) (toRawFilePath basefile')-	{- Limit to two extensions maximum. -}+	{- Limit number of extensions. -}+	maxextensions' = fromMaybe maxExtensions maxextensions 	(base', extensions')-		| length extensions <= 2 = (base, extensions)+		| length extensions <= maxextensions' = (base, extensions) 		| otherwise = -			let (es,more) = splitAt 2 (reverse extensions)+			let (es,more) = splitAt maxextensions' (reverse extensions) 			in (base <> mconcat (reverse more), reverse es) 	{- On Windows, if the filename looked like "dir/c:foo" then 	 - basefile would look like it contains a drive letter, which will@@ -101,7 +105,8 @@ 	-- Relative filenames wanted, not directories. 	| any (isPathSeparator) (end f ++ beginning f) = True 	| isAbsolute f || isDrive f = True-	| otherwise = dir == dirFromViewedFile (viewedFileFromReference' Nothing f)+	| otherwise = dir == dirFromViewedFile +		(viewedFileFromReference' Nothing Nothing f)   where 	f = fromTestableFilePath tf 	dir = joinPath $ beginning $ splitDirectories f
Assistant/Threads/TransferPoller.hs view
@@ -45,7 +45,7 @@ 		{- Otherwise, this code polls the upload progress 		 - by reading the transfer info file. -} 		| otherwise = do-			let f = transferFile t g+			let (f, _, _) = transferFileAndLockFile t g 			mi <- liftIO $ catchDefaultIO Nothing $ 				readTransferInfoFile Nothing (fromRawFilePath f) 			maybe noop (newsize t info . bytesComplete) mi
Backend.hs view
@@ -1,6 +1,6 @@ {- git-annex key/value backends  -- - Copyright 2010-2021 Joey Hess <id@joeyh.name>+ - Copyright 2010-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -10,6 +10,7 @@ module Backend ( 	builtinList, 	defaultBackend,+	defaultHashBackend, 	genKey, 	getBackend, 	chooseBackend,@@ -17,9 +18,12 @@ 	lookupBuiltinBackendVariety, 	maybeLookupBackendVariety, 	isStableKey,+	isCryptographicallySecureKey, 	isCryptographicallySecure, ) where +import qualified Data.Map as M+ import Annex.Common import qualified Annex import Annex.CheckAttr@@ -27,20 +31,15 @@ import Types.KeySource import qualified Types.Backend as B import Utility.Metered---- When adding a new backend, import it here and add it to the builtinList.-import qualified Backend.Hash-import qualified Backend.WORM-import qualified Backend.URL-import qualified Backend.External--import qualified Data.Map as M+import Backend.Variety+import qualified Backend.VURL  {- Built-in backends. Does not include externals. -} builtinList :: [Backend]-builtinList = Backend.Hash.backends ++ Backend.WORM.backends ++ Backend.URL.backends+builtinList = regularBackendList ++ Backend.VURL.backends -{- Backend to use by default when generating a new key. -}+{- Backend to use by default when generating a new key. Takes git config+ - and --backend option into account. -} defaultBackend :: Annex Backend defaultBackend = maybe cache return =<< Annex.getState Annex.backend   where@@ -49,7 +48,7 @@ 			=<< Annex.getRead Annex.forcebackend 		b <- case n of 			Just name | valid name -> lookupname name-			_ -> pure (Prelude.head builtinList)+			_ -> pure defaultHashBackend 		Annex.changeState $ \s -> s { Annex.backend = Just b } 		return b 	valid name = not (null name)@@ -99,22 +98,24 @@ lookupBuiltinBackendVariety v = fromMaybe (giveup (unknownBackendVarietyMessage v)) $ 	maybeLookupBuiltinBackendVariety v -maybeLookupBackendVariety :: KeyVariety -> Annex (Maybe Backend)-maybeLookupBackendVariety (ExternalKey s hasext) =-	Just <$> Backend.External.makeBackend s hasext-maybeLookupBackendVariety v = -	pure $ M.lookup v varietyMap- maybeLookupBuiltinBackendVariety :: KeyVariety -> Maybe Backend maybeLookupBuiltinBackendVariety v = M.lookup v varietyMap +maybeLookupBackendVariety :: KeyVariety -> Annex (Maybe Backend)+maybeLookupBackendVariety v = maybeLookupBackendVarietyMap v varietyMap+ varietyMap :: M.Map KeyVariety Backend-varietyMap = M.fromList $ zip (map B.backendVariety builtinList) builtinList+varietyMap = makeVarietyMap builtinList  isStableKey :: Key -> Annex Bool isStableKey k = maybe False (`B.isStableKey` k)  	<$> maybeLookupBackendVariety (fromKey keyVariety k) -isCryptographicallySecure :: Key -> Annex Bool-isCryptographicallySecure k = maybe False B.isCryptographicallySecure-	<$> maybeLookupBackendVariety (fromKey keyVariety k)+isCryptographicallySecureKey :: Key -> Annex Bool+isCryptographicallySecureKey k = maybe +	(pure False)+	(\b -> B.isCryptographicallySecureKey b k)+	=<< maybeLookupBackendVariety (fromKey keyVariety k)++isCryptographicallySecure :: Backend -> Bool+isCryptographicallySecure = B.isCryptographicallySecure
Backend/External.hs view
@@ -73,6 +73,7 @@ 		, fastMigrate = Nothing 		, isStableKey = const isstable 		, isCryptographicallySecure = iscryptographicallysecure+		, isCryptographicallySecureKey = const (pure iscryptographicallysecure) 		} makeBackend' ebname hasext (Left _) = return $ unavailBackend ebname hasext @@ -87,6 +88,7 @@ 		, fastMigrate = Nothing 		, isStableKey = const False 		, isCryptographicallySecure = False+		, isCryptographicallySecureKey = const (pure False) 		}  genKeyExternal :: ExternalBackendName -> HasExt -> KeySource -> MeterUpdate -> Annex Key@@ -213,7 +215,7 @@ -- using it. newExternalState :: ExternalBackendName -> HasExt -> ExternalAddonPID -> Annex ExternalState newExternalState ebname hasext pid = do-	st <- startExternalAddonProcess basecmd pid+	st <- startExternalAddonProcess basecmd [] pid 	st' <- case st of 		Left (ProgramNotInstalled msg) -> warnonce msg >> return st 		Left (ProgramFailure msg) -> warnonce msg >> return st
Backend/Hash.hs view
@@ -11,6 +11,7 @@ 	backends, 	testKeyBackend, 	keyHash,+	descChecksum, ) where  import Annex.Common@@ -52,8 +53,10 @@ cryptographicallySecure SHA1Hash = False cryptographicallySecure MD5Hash = False -{- Order is slightly significant; want SHA256 first, and more general- - sizes earlier. -}+{- Order is significant. The first hash is the default one that git-annex+ - uses, and must be cryptographically secure. + -+ - Also, want more common sizes earlier than uncommon sizes. -} hashes :: [Hash] hashes = concat  	[ map (SHA2Hash . HashSize) [256, 512, 224, 384]@@ -81,6 +84,8 @@ 	, fastMigrate = Just trivialMigrate 	, isStableKey = const True 	, isCryptographicallySecure = cryptographicallySecure hash+	, isCryptographicallySecureKey = const $ pure $+		cryptographicallySecure hash 	}  genBackendE :: Hash -> Backend@@ -164,12 +169,15 @@ 	, not (hasExt (fromKey keyVariety key)) && keyHash key /= S.fromShort (fromKey keyName key) 	] -trivialMigrate :: Key -> Backend -> AssociatedFile -> Annex (Maybe Key)-trivialMigrate oldkey newbackend afile = trivialMigrate' oldkey newbackend afile-	<$> (annexMaxExtensionLength <$> Annex.getGitConfig)+trivialMigrate :: Key -> Backend -> AssociatedFile -> Bool -> Annex (Maybe Key)+trivialMigrate oldkey newbackend afile _inannex = do+	c <- Annex.getGitConfig+	return $ trivialMigrate' oldkey newbackend afile+		(annexMaxExtensionLength c)+		(annexMaxExtensions c) -trivialMigrate' :: Key -> Backend -> AssociatedFile -> Maybe Int -> Maybe Key-trivialMigrate' oldkey newbackend afile maxextlen+trivialMigrate' :: Key -> Backend -> AssociatedFile -> Maybe Int -> Maybe Int -> Maybe Key+trivialMigrate' oldkey newbackend afile maxextlen maxexts 	{- Fast migration from hashE to hash backend. -} 	| migratable && hasExt oldvariety = Just $ alterKey oldkey $ \d -> d 		{ keyName = S.toShort (keyHash oldkey)@@ -180,7 +188,7 @@ 		AssociatedFile Nothing -> Nothing 		AssociatedFile (Just file) -> Just $ alterKey oldkey $ \d -> d 			{ keyName = S.toShort $ keyHash oldkey -				<> selectExtension maxextlen file+				<> selectExtension maxextlen maxexts file 			, keyVariety = newvariety 			} 	{- Upgrade to fix bad previous migration that created a
Backend/URL.hs view
@@ -1,6 +1,6 @@-{- git-annex "URL" backend -- keys whose content is available from urls.+{- git-annex URL backend -- keys whose content is available from urls.  -- - Copyright 2011 Joey Hess <id@joeyh.name>+ - Copyright 2011-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -14,28 +14,30 @@ import Types.Key import Types.Backend import Backend.Utilities+import Backend.VURL.Utilities (migrateFromURLToVURL)  backends :: [Backend]-backends = [backend]+backends = [backendURL] -backend :: Backend-backend = Backend+backendURL :: Backend+backendURL = Backend 	{ backendVariety = URLKey 	, genKey = Nothing 	, verifyKeyContent = Nothing 	, verifyKeyContentIncrementally = Nothing 	, canUpgradeKey = Nothing-	, fastMigrate = Nothing+	, fastMigrate = Just migrateFromURLToVURL 	-- The content of an url can change at any time, so URL keys are 	-- not stable. 	, isStableKey = const False 	, isCryptographicallySecure = False+	, isCryptographicallySecureKey = const (pure False) 	}  {- Every unique url has a corresponding key. -}-fromUrl :: String -> Maybe Integer -> Key-fromUrl url size = mkKey $ \k -> k+fromUrl :: String -> Maybe Integer -> Bool -> Key+fromUrl url size verifiable = mkKey $ \k -> k 	{ keyName = genKeyName url-	, keyVariety = URLKey+	, keyVariety = if verifiable then VURLKey else URLKey 	, keySize = size 	}
Backend/Utilities.hs view
@@ -1,6 +1,6 @@ {- git-annex backend utilities  -- - Copyright 2012-2020 Joey Hess <id@joeyh.name>+ - Copyright 2012-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -45,20 +45,24 @@  - file that the key was generated from.  -} addE :: KeySource -> (KeyVariety -> KeyVariety) -> Key -> Annex Key addE source sethasext k = do-	maxlen <- annexMaxExtensionLength <$> Annex.getGitConfig-	let ext = selectExtension maxlen (keyFilename source)+	c <- Annex.getGitConfig+	let ext = selectExtension+		(annexMaxExtensionLength c)+		(annexMaxExtensions c)+		(keyFilename source) 	return $ alterKey k $ \d -> d 		{ keyName = keyName d <> S.toShort ext 		, keyVariety = sethasext (keyVariety d) 		} -selectExtension :: Maybe Int -> RawFilePath -> S.ByteString-selectExtension maxlen f+selectExtension :: Maybe Int -> Maybe Int -> RawFilePath -> S.ByteString+selectExtension maxlen maxextensions f 	| null es = "" 	| otherwise = S.intercalate "." ("":es)   where 	es = filter (not . S.null) $ reverse $-		take 2 $ filter (S.all validInExtension) $+		take (fromMaybe maxExtensions maxextensions) $+		filter (S.all validInExtension) $ 		takeWhile shortenough $ 		reverse $ S.split (fromIntegral (ord '.')) (P.takeExtensions f') 	shortenough e = S.length e <= fromMaybe maxExtensionLen maxlen@@ -75,3 +79,6 @@  maxExtensionLen :: Int maxExtensionLen = 4 -- long enough for "jpeg"++maxExtensions :: Int+maxExtensions = 2 -- include both extensions of "tar.gz"
+ Backend/VURL.hs view
@@ -0,0 +1,102 @@+{- git-annex VURL backend -- like URL, but with hash-based verification+ - of transfers between git-annex repositories.+ -+ - Copyright 2024 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Backend.VURL (+	backends,+) where++import Annex.Common+import Types.Key+import Types.Backend+import Logs.EquivilantKeys+import Backend.Variety+import Backend.Hash (descChecksum)+import Utility.Hash+import Backend.VURL.Utilities++backends :: [Backend]+backends = [backendVURL]++backendVURL :: Backend+backendVURL = Backend+	{ backendVariety = VURLKey+	, genKey = Nothing+	, verifyKeyContent = Just $ \k f -> do+		equivkeys k >>= \case+			-- Normally there will always be an key+			-- recorded when a VURL's content is available,+			-- because downloading the content from the web in+			-- the first place records one.+			[] -> return False+			eks -> do+				let check ek = getbackend ek >>= \case+					Nothing -> pure False+					Just b -> case verifyKeyContent b of+						Just verify -> verify ek f+						Nothing -> pure False+				anyM check eks+	, verifyKeyContentIncrementally = Just $ \k -> do+		-- Run incremental verifiers for each equivilant key together,+		-- and see if any of them succeed.+		eks <- equivkeys k+		let get = \ek -> getbackend ek >>= \case+			Nothing -> pure Nothing+			Just b -> case verifyKeyContentIncrementally b of+				Nothing -> pure Nothing+				Just va -> Just <$> va ek+		l <- catMaybes <$> forM eks get+		return $ IncrementalVerifier+			{ updateIncrementalVerifier = \s ->+				forM_ l $ flip updateIncrementalVerifier s+			-- If there are no equivilant keys recorded somehow,+			-- or if none of them support incremental verification,+			-- this will return Nothing, which indicates that+			-- incremental verification was not able to be+			-- performed.+			, finalizeIncrementalVerifier = do+				r <- forM l finalizeIncrementalVerifier+				return $ case catMaybes r of+					[] -> Nothing+					r' -> Just (or r')+			, unableIncrementalVerifier = +				forM_ l unableIncrementalVerifier+			, positionIncrementalVerifier =+				getM positionIncrementalVerifier l+			, descIncrementalVerifier = descChecksum+			} +	, canUpgradeKey = Nothing+	, fastMigrate = Just migrateFromVURLToURL+	-- Even if a hash is recorded on initial download from the web and+	-- is used to verify every subsequent transfer including other+	-- downloads from the web, in a split-brain situation there+	-- can be more than one hash and different versions of the content.+	-- So the content is not stable.+	, isStableKey = const False+	-- Not all keys using this backend are necessarily +	-- cryptographically secure.+	, isCryptographicallySecure = False+	-- A key is secure when all recorded equivilant keys are.+	-- If there are none recorded yet, it's secure because when+	-- downloaded, an equivilant key that is cryptographically secure+	-- will be constructed then.+	, isCryptographicallySecureKey = \k ->+		equivkeys k >>= \case+			[] -> return True+			l -> do+				let check ek = getbackend ek >>= \case+					Nothing -> pure False+					Just b -> isCryptographicallySecureKey b ek+				allM check l+	}+  where+	equivkeys k = filter allowedequiv <$> getEquivilantKeys k+	-- Don't allow using VURL keys as equivilant keys, because that+	-- could let a crafted git-annex branch cause an infinite loop.+	allowedequiv ek = fromKey keyVariety ek /= VURLKey+	varietymap = makeVarietyMap regularBackendList+	getbackend ek = maybeLookupBackendVarietyMap (fromKey keyVariety ek) varietymap
+ Backend/VURL/Utilities.hs view
@@ -0,0 +1,57 @@+{- git-annex VURL backend utilities+ -+ - Copyright 2024 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Backend.VURL.Utilities where++import Annex.Common+import Types.Key+import Types.Backend+import Types.KeySource+import Logs.EquivilantKeys+import qualified Backend.Hash+import Utility.Metered++migrateFromURLToVURL :: Key -> Backend -> AssociatedFile -> Bool -> Annex (Maybe Key)+migrateFromURLToVURL oldkey newbackend _af inannex+	| inannex && fromKey keyVariety oldkey == URLKey && backendVariety newbackend == VURLKey = do+		let newkey = mkKey $ const $+			(keyData oldkey)+				{ keyVariety = VURLKey }+		contentfile <- calcRepo (gitAnnexLocation oldkey)+		generateEquivilantKey hashbackend contentfile >>= \case+			Nothing -> return Nothing+			Just ek -> do+				setEquivilantKey newkey ek+				return (Just newkey)+	| otherwise = return Nothing+  where+	-- Relies on the first hash being cryptographically secure, and the+	-- default hash used by git-annex.+	hashbackend = Prelude.head Backend.Hash.backends++migrateFromVURLToURL :: Key -> Backend -> AssociatedFile -> Bool -> Annex (Maybe Key)+migrateFromVURLToURL oldkey newbackend _af _+	| fromKey keyVariety oldkey == VURLKey && backendVariety newbackend == URLKey =+		return $ Just $ mkKey $ const $+			(keyData oldkey)+				{ keyVariety = URLKey }+	| otherwise = return Nothing++-- The Backend must use a cryptographically secure hash.+generateEquivilantKey :: Backend -> RawFilePath -> Annex (Maybe Key)+generateEquivilantKey b f =+	case genKey b of+		Just genkey -> do+			showSideAction (UnquotedString Backend.Hash.descChecksum)+			Just <$> genkey source nullMeterUpdate+		Nothing -> return Nothing+  where+	source = KeySource+		{ keyFilename = mempty -- avoid adding any extension+		, contentLocation = f+		, inodeCache = Nothing+		}
+ Backend/Variety.hs view
@@ -0,0 +1,39 @@+{- git-annex backend varieties+ -+ - Copyright 2012-2024 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Backend.Variety where++import qualified Data.Map as M++import Annex.Common+import Types.Key+import Types.Backend+import qualified Backend.External++-- When adding a new backend, import it here and add it to the builtinList.+import qualified Backend.Hash+import qualified Backend.WORM+import qualified Backend.URL++{- Regular backends. Does not include externals or VURL. -}+regularBackendList :: [Backend]+regularBackendList = Backend.Hash.backends +	++ Backend.WORM.backends +	++ Backend.URL.backends++{- The default hashing backend. -}+defaultHashBackend :: Backend+defaultHashBackend = Prelude.head regularBackendList++makeVarietyMap :: [Backend] -> M.Map KeyVariety Backend+makeVarietyMap l = M.fromList $ zip (map backendVariety l) l++maybeLookupBackendVarietyMap :: KeyVariety -> M.Map KeyVariety Backend -> Annex (Maybe Backend)+maybeLookupBackendVarietyMap (ExternalKey s hasext) _varitymap =+	Just <$> Backend.External.makeBackend s hasext+maybeLookupBackendVarietyMap v varietymap = +	pure $ M.lookup v varietymap
Backend/WORM.hs view
@@ -33,6 +33,7 @@ 	, fastMigrate = Just removeProblemChars 	, isStableKey = const True 	, isCryptographicallySecure = False+	, isCryptographicallySecureKey = const (pure False) 	}  {- The key includes the file size, modification time, and the@@ -58,8 +59,8 @@ needsUpgrade key = 	any (`S8.elem` S.fromShort (fromKey keyName key)) [' ', '\r'] -removeProblemChars :: Key -> Backend -> AssociatedFile -> Annex (Maybe Key)-removeProblemChars oldkey newbackend _+removeProblemChars :: Key -> Backend -> AssociatedFile -> Bool -> Annex (Maybe Key)+removeProblemChars oldkey newbackend _ _ 	| migratable = return $ Just $ alterKey oldkey $ \d -> d 		{ keyName = S.toShort $ encodeBS $ reSanitizeKeyName $ decodeBS $ S.fromShort $ keyName d } 	| otherwise = return Nothing
CHANGELOG view
@@ -1,3 +1,31 @@+git-annex (10.20240430) upstream; urgency=medium++  * Bug fix: While redundant concurrent transfers were already+    prevented in most cases, it failed to prevent the case where+    two different repositories were sending the same content to+    the same repository.+  * addurl, importfeed: Added --verifiable option, which improves+    the safety of --fast or --relaxed by letting the content of+    annexed files be verified with a checksum that is calculated+    on a later download from the web. This will become the default later.+  * Added rclone special remote, which can be used without needing+    to install the git-annex-remote-rclone program. This needs+    a forthcoming version of rclone (1.67.0), which supports +    "rclone gitannex".+  * sync, assist, import: Allow -m option to be specified multiple+    times, to provide additional paragraphs for the commit message.+  * reregisterurl: New command that can change an url from being+    used by a special remote to being used by the web remote.+  * annex.maxextensions configuration controls how many filename+    extensions to preserve.+  * find: Fix --help for --copies.+    Thanks, Gergely Risko+  * Windows: Fix escaping output to terminal when using old+    versions of MinTTY.+  * Added dependency on unbounded-delays.++ -- Joey Hess <id@joeyh.name>  Tue, 30 Apr 2024 15:26:32 -0400+ git-annex (10.20240227) upstream; urgency=medium    * importfeed: Added --scrape option, which uses yt-dlp to screen scrape@@ -326,7 +354,7 @@     that use exporttree=yes. In some cases this could result in the wrong     content being exported to, or retrieved from the remote.   * Support VERSION 2 in the external special remote protocol, which is-    identical to VERSION 1, but avoids external remote programs neededing+    identical to VERSION 1, but avoids external remote programs needing     to work around the above bug. External remote program that support     exporttree=yes are recommended to be updated to send VERSION 2. @@ -526,7 +554,7 @@   * Fix updating git index file after getting an unlocked file      when annex.stalldetection is set.   * restage: New git-annex command, handles restaging unlocked files.-  * test: Added --test-with-git-config option.+  * test: Added --test-git-config option.   * Run annex.freezecontent-command and annex.thawcontent-command     when on a crippled filesystem.     Thanks, Reiko Asakura
@@ -33,11 +33,6 @@            © 2014 Robie Basak <robie@justgohome.co.uk> License: GPL-3+ -Files: Utility/ThreadScheduler.hs-Copyright: 2011 Bas van Dijk & Roel van Dijk-           2012, 2013 Joey Hess <id@joeyh.name>-License: BSD-2-clause- Files: Utility/Attoparsec.hs Copyright: 2019 Joey Hess <id@joeyh.name>            2007-2015 Bryan O'Sullivan
CmdLine/GitAnnex.hs view
@@ -34,6 +34,7 @@ import qualified Command.FromKey import qualified Command.RegisterUrl import qualified Command.UnregisterUrl+import qualified Command.ReregisterUrl import qualified Command.SetKey import qualified Command.DropKey import qualified Command.Transferrer@@ -196,6 +197,7 @@ 	, Command.FromKey.cmd 	, Command.RegisterUrl.cmd 	, Command.UnregisterUrl.cmd+	, Command.ReregisterUrl.cmd 	, Command.SetKey.cmd 	, Command.DropKey.cmd 	, Command.Transferrer.cmd
CmdLine/GitAnnex/Options.hs view
@@ -298,7 +298,7 @@ 		<> completeRemotes 		) 	, annexOption (setAnnexState . Limit.addCopies) $ strOption-		( long "copies" <> short 'C' <> metavar paramRemote+		( long "copies" <> short 'C' <> metavar paramNumber 		<> help "skip files with fewer copies" 		<> hidden 		)
Command/AddUrl.hs view
@@ -1,6 +1,6 @@ {- git-annex command  -- - Copyright 2011-2021 Joey Hess <id@joeyh.name>+ - Copyright 2011-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -62,6 +62,7 @@  data DownloadOptions = DownloadOptions 	{ relaxedOption :: Bool+	, verifiableOption :: Bool 	, rawOption :: Bool 	, noRawOption :: Bool 	, rawExceptOption :: Maybe (DeferredParse Remote)@@ -96,9 +97,14 @@ parseDownloadOptions withfileoptions = DownloadOptions 	<$> switch 		( long "relaxed"-		<> help "skip size check"+		<> help "accept whatever content is downloaded from web even if it changes" 		) 	<*> switch+		( long "verifiable"+		<> short 'V'+		<> help "improve later verification of --fast or --relaxed content"+		)+	<*> switch 		( long "raw" 		<> help "disable special handling for torrents, youtube-dl, etc" 		)@@ -215,7 +221,7 @@  downloadRemoteFile :: AddUnlockedMatcher -> Remote -> DownloadOptions -> URLString -> RawFilePath -> Maybe Integer -> Annex (Maybe Key) downloadRemoteFile addunlockedmatcher r o uri file sz = checkCanAdd o file $ \canadd -> do-	let urlkey = Backend.URL.fromUrl uri sz+	let urlkey = Backend.URL.fromUrl uri sz (verifiableOption o) 	createWorkTreeDirectory (parentDir file) 	ifM (Annex.getRead Annex.fast <||> pure (relaxedOption o)) 		( do@@ -344,7 +350,7 @@ downloadWeb addunlockedmatcher o url urlinfo file = 	go =<< downloadWith' downloader urlkey webUUID url file   where-	urlkey = addSizeUrlKey urlinfo $ Backend.URL.fromUrl url Nothing+	urlkey = addSizeUrlKey urlinfo $ Backend.URL.fromUrl url Nothing (verifiableOption o) 	downloader f p = Url.withUrlOptions $ downloadUrl False urlkey p Nothing [url] f 	go Nothing = return Nothing 	go (Just (tmp, backend)) = ifM (useYoutubeDl o <&&> liftIO (isHtmlFile (fromRawFilePath tmp)))@@ -388,7 +394,7 @@ 							warning (UnquotedString dlcmd <> " did not download anything") 							return Nothing 		mediaurl = setDownloader url YoutubeDownloader-		mediakey = Backend.URL.fromUrl mediaurl Nothing+		mediakey = Backend.URL.fromUrl mediaurl Nothing (verifiableOption o) 		-- Does the already annexed file have the mediaurl 		-- as an url? If so nothing to do. 		alreadyannexed dest k = do@@ -436,7 +442,7 @@ 	-- used to prevent two threads running concurrently when that would 	-- likely fail. 	ai = OnlyActionOn urlkey (ActionItemOther (Just (UnquotedString url)))-	urlkey = Backend.URL.fromUrl url Nothing+	urlkey = Backend.URL.fromUrl url Nothing (verifiableOption (downloadOptions o))  showDestinationFile :: RawFilePath -> Annex () showDestinationFile file = do@@ -539,12 +545,12 @@ 		return Nothing   where 	nomedia = do-		let key = Backend.URL.fromUrl url (Url.urlSize urlinfo)+		let key = Backend.URL.fromUrl url (Url.urlSize urlinfo) (verifiableOption o) 		nodownloadWeb' o addunlockedmatcher url key file 	usemedia mediafile = do 		let dest = youtubeDlDestFile o file mediafile 		let mediaurl = setDownloader url YoutubeDownloader-		let mediakey = Backend.URL.fromUrl mediaurl Nothing+		let mediakey = Backend.URL.fromUrl mediaurl Nothing (verifiableOption o) 		nodownloadWeb' o addunlockedmatcher mediaurl mediakey dest  youtubeDlDestFile :: DownloadOptions -> RawFilePath -> RawFilePath -> RawFilePath
Command/ExamineKey.hs view
@@ -78,6 +78,6 @@ 		Just v -> getParsed v >>= \b -> 			maybeLookupBackendVariety (fromKey keyVariety ik) >>= \case 				Just ib -> case fastMigrate ib of-					Just fm -> fromMaybe ik <$> fm ik b af+					Just fm -> fromMaybe ik <$> fm ik b af False 					Nothing -> pure ik 				Nothing -> pure ik
Command/Export.hs view
@@ -406,39 +406,46 @@ 	oldloc = mkExportLocation $ getTopFilePath oldf  startMoveToTempName :: Remote -> ExportHandle -> TopFilePath -> Key -> CommandStart-startMoveToTempName r db f ek = -	starting ("rename " ++ name r) ai si $+startMoveToTempName r db f ek = case renameExport (exportActions r) of+	Just _ -> starting ("rename " ++ name r) ai si $ 		performRename r db ek loc tmploc+	Nothing -> starting ("unexport " ++ name r) ai' si $+		performUnexport r db [ek] loc   where 	loc = mkExportLocation f' 	f' = getTopFilePath f 	tmploc = exportTempName ek 	ai = ActionItemOther $ Just $  		QuotedPath f' <> " -> " <> QuotedPath (fromExportLocation tmploc)+	ai' = ActionItemTreeFile (fromExportLocation loc) 	si = SeekInput []  startMoveFromTempName :: Remote -> ExportHandle -> Key -> TopFilePath -> CommandStart-startMoveFromTempName r db ek f = do-	let tmploc = exportTempName ek-	let ai = ActionItemOther $ Just $-		QuotedPath (fromExportLocation tmploc) <> " -> " <> QuotedPath f'-	stopUnless (liftIO $ elem tmploc <$> getExportedLocation db ek) $+startMoveFromTempName r db ek f = case renameExport (exportActions r) of+	Just _ -> stopUnless (liftIO $ elem tmploc <$> getExportedLocation db ek) $ 		starting ("rename " ++ name r) ai si $ 			performRename r db ek tmploc loc+	Nothing -> starting ("unexport " ++ name r) ai' si $+		performUnexport r db [ek] tmploc   where 	loc = mkExportLocation f' 	f' = getTopFilePath f+	tmploc = exportTempName ek+	ai = ActionItemOther $ Just $+		QuotedPath (fromExportLocation tmploc) <> " -> " <> QuotedPath f'+	ai' = ActionItemTreeFile (fromExportLocation tmploc) 	si = SeekInput []  performRename :: Remote -> ExportHandle -> Key -> ExportLocation -> ExportLocation -> CommandPerform-performRename r db ek src dest =-	tryNonAsync (renameExport (exportActions r) ek src dest) >>= \case+performRename r db ek src dest = case renameExport (exportActions r) of+	Just renameaction -> tryNonAsync (renameaction ek src dest) >>= \case 		Right (Just ()) -> next $ cleanupRename r db ek src dest 		Left err -> do 			warning $ UnquotedString $ "rename failed (" ++ show err ++ "); deleting instead" 			fallbackdelete-		-- remote does not support renaming 		Right Nothing -> fallbackdelete+	-- remote does not support renaming+	Nothing -> fallbackdelete   where 	fallbackdelete = performUnexport r db [ek] src 
Command/FilterBranch.hs view
@@ -189,7 +189,7 @@ 	liftIO $ removeWhenExistsWith removeLink tmpindex 	cmode <- annexCommitMode <$> Annex.getGitConfig 	cmessage <- Annex.Branch.commitMessage-	c <- inRepo $ Git.commitTree cmode cmessage [] t+	c <- inRepo $ Git.commitTree cmode [cmessage] [] t 	liftIO $ putStrLn (fromRef c)   where 	ww = WarnUnmatchLsFiles "filter-branch"
Command/FromKey.hs view
@@ -94,7 +94,7 @@ keyOpt' :: String -> Either String Key keyOpt' s = case parseURIPortable s of 	Just u | not (isKeyPrefix (uriScheme u)) ->-		Right $ Backend.URL.fromUrl s Nothing+		Right $ Backend.URL.fromUrl s Nothing False 	_ -> case deserializeKey s of 		Just k -> Right k 		Nothing -> Left $ "bad key/url " ++ s
Command/Fsck.hs view
@@ -1,6 +1,6 @@ {- git-annex command  -- - Copyright 2010-2022 Joey Hess <id@joeyh.name>+ - Copyright 2010-2023 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -16,6 +16,7 @@ import qualified Types.Backend import qualified Backend import Annex.Content+import Annex.Verify #ifndef mingw32_HOST_OS import Annex.Version import Annex.Content.Presence@@ -127,7 +128,7 @@ 		(numcopies, _mincopies) <- getFileNumMinCopies file 		case from of 			Nothing -> go $ perform key file backend numcopies-			Just r -> go $ performRemote key afile backend numcopies r+			Just r -> go $ performRemote key afile numcopies r   where 	go = runFsck inc si (mkActionItem (key, afile)) key 	afile = AssociatedFile (Just file)@@ -144,7 +145,7 @@ 		, verifyAssociatedFiles key keystatus file 		, verifyWorkTree key file 		, checkKeySize key keystatus ai-		, checkBackend backend key keystatus afile+		, checkBackend key keystatus afile 		, checkKeyUpgrade backend key ai afile 		, checkKeyNumCopies key afile numcopies 		]@@ -154,8 +155,8 @@  {- To fsck a remote, the content is retrieved to a tmp file,  - and checked locally. -}-performRemote :: Key -> AssociatedFile -> Backend -> NumCopies -> Remote -> Annex Bool-performRemote key afile backend numcopies remote =+performRemote :: Key -> AssociatedFile -> NumCopies -> Remote -> Annex Bool+performRemote key afile numcopies remote = 	dispatch =<< Remote.hasKey remote key   where 	dispatch (Left err) = do@@ -178,7 +179,7 @@ 		, case fmap snd lv of 			Just Verified -> return True 			_ -> withLocalCopy (fmap fst lv) $-				checkBackendRemote backend key remote ai+				checkBackendRemote key remote ai 		, checkKeyNumCopies key afile numcopies 		] 	ai = mkActionItem (key, afile)@@ -215,18 +216,18 @@ startKey from inc (si, key, ai) numcopies = 	Backend.maybeLookupBackendVariety (fromKey keyVariety key) >>= \case 		Nothing -> stop-		Just backend -> runFsck inc si ai key $+		Just _ -> runFsck inc si ai key $ 			case from of-				Nothing -> performKey key backend numcopies-				Just r -> performRemote key (AssociatedFile Nothing) backend numcopies r+				Nothing -> performKey key numcopies+				Just r -> performRemote key (AssociatedFile Nothing) numcopies r -performKey :: Key -> Backend -> NumCopies -> Annex Bool-performKey key backend numcopies = do+performKey :: Key -> NumCopies -> Annex Bool+performKey key numcopies = do 	keystatus <- getKeyStatus key 	check 		[ verifyLocationLog key keystatus (mkActionItem key) 		, checkKeySize key keystatus (mkActionItem key)-		, checkBackend backend key keystatus (AssociatedFile Nothing)+		, checkBackend key keystatus (AssociatedFile Nothing) 		, checkKeyNumCopies key (AssociatedFile Nothing) numcopies 		] @@ -328,8 +329,8 @@ 	{- Warn when annex.securehashesonly is set and content using an  	 - insecure hash is present. This should only be able to happen 	 - if the repository already contained the content before the-	 - config was set. -}-	whenM (pure present <&&> (not <$> Backend.isCryptographicallySecure key)) $+	 - config was set, or of course if a hash was broken. -}+	whenM (pure present <&&> (not <$> Backend.isCryptographicallySecureKey key)) $ 		whenM (annexSecureHashesOnly <$> Annex.getGitConfig) $ 			warning $ "** Despite annex.securehashesonly being set, " <> QuotedPath obj <> " has content present in the annex using an insecure " <> UnquotedString (decodeBS (formatKeyVariety (fromKey keyVariety key))) <> " key" @@ -496,18 +497,18 @@  {- Runs the backend specific check on a key's content object.  -- - When a annex.this is set, an unlocked file may be a hard link to the object.+ - When a annex.thin is set, an unlocked file may be a hard link to the object.  - Thus when the user modifies the file, the object will be modified and  - not pass the check, and we don't want to find an error in this case.  -}-checkBackend :: Backend -> Key -> KeyStatus -> AssociatedFile -> Annex Bool-checkBackend backend key keystatus afile = do+checkBackend :: Key -> KeyStatus -> AssociatedFile -> Annex Bool+checkBackend key keystatus afile = do 	content <- calcRepo (gitAnnexLocation key) 	ifM (pure (isKeyUnlockedThin keystatus) <&&> (not <$> isUnmodified key content)) 		( nocheck 		, do 			mic <- withTSDelta (liftIO . genInodeCache content)-			ifM (checkBackendOr badContent backend key content ai)+			ifM (checkBackendOr badContent key content ai) 				( do 					checkInodeCache key content mic ai 					return True@@ -519,22 +520,19 @@  	ai = mkActionItem (key, afile) -checkBackendRemote :: Backend -> Key -> Remote -> ActionItem -> RawFilePath -> Annex Bool-checkBackendRemote backend key remote ai localcopy =-	checkBackendOr (badContentRemote remote localcopy) backend key localcopy ai+checkBackendRemote :: Key -> Remote -> ActionItem -> RawFilePath -> Annex Bool+checkBackendRemote key remote ai localcopy =+	checkBackendOr (badContentRemote remote localcopy) key localcopy ai -checkBackendOr :: (Key -> Annex String) -> Backend -> Key -> RawFilePath -> ActionItem -> Annex Bool-checkBackendOr bad backend key file ai =-	case Types.Backend.verifyKeyContent backend of-		Just verifier -> do-			ok <- verifier key file-			unless ok $ do-				msg <- bad key-				warning $ actionItemDesc ai-					<> ": Bad file content; "-					<> UnquotedString msg-			return ok-		Nothing -> return True+checkBackendOr :: (Key -> Annex String) -> Key -> RawFilePath -> ActionItem -> Annex Bool+checkBackendOr bad key file ai = do+	ok <- verifyKeyContent' key file+	unless ok $ do+		msg <- bad key+		warning $ actionItemDesc ai+			<> ": Bad file content; "+			<> UnquotedString msg+	return ok  {- Check, if there are InodeCaches recorded for a key, that one of them  - matches the object file. There are situations where the InodeCache
Command/Import.hs view
@@ -1,6 +1,6 @@ {- git-annex command  -- - Copyright 2012-2021 Joey Hess <id@joeyh.name>+ - Copyright 2012-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -70,7 +70,7 @@ 		, importToSubDir :: Maybe FilePath 		, importContent :: Bool 		, checkGitIgnoreOption :: CheckGitIgnore-		, messageOption :: Maybe String+		, messageOption :: [String] 		}  optParser :: CmdParamsDesc -> Parser ImportOptions@@ -82,7 +82,7 @@ 		) 	dupmode <- fromMaybe Default <$> optional duplicateModeParser 	ic <- Command.Add.checkGitIgnoreSwitch-	message <- optional (strOption+	message <- many (strOption 		( long "message" <> short 'm' <> metavar "MSG" 		<> help "commit message" 		))@@ -322,8 +322,8 @@ 	verifyEnoughCopiesToDrop [] key Nothing needcopies mincopies [] preverified tocheck 		(const yes) no -seekRemote :: Remote -> Branch -> Maybe TopFilePath -> Bool -> CheckGitIgnore -> Maybe String -> CommandSeek-seekRemote remote branch msubdir importcontent ci mimportmessage = do+seekRemote :: Remote -> Branch -> Maybe TopFilePath -> Bool -> CheckGitIgnore -> [String] -> CommandSeek+seekRemote remote branch msubdir importcontent ci importmessages = do 	importtreeconfig <- case msubdir of 		Nothing -> return ImportTree 		Just subdir ->@@ -336,7 +336,7 @@ 	 	trackingcommit <- fromtrackingbranch Git.Ref.sha 	cmode <- annexCommitMode <$> Annex.getGitConfig-	let importcommitconfig = ImportCommitConfig trackingcommit cmode importmessage+	let importcommitconfig = ImportCommitConfig trackingcommit cmode importmessages' 	let commitimport = commitRemote remote branch tb trackingcommit importtreeconfig importcommitconfig  	importabletvar <- liftIO $ newTVarIO Nothing@@ -353,9 +353,9 @@ 				includeCommandAction $  					commitimport imported   where-	importmessage = fromMaybe -		("import from " ++ Remote.name remote)-		mimportmessage+	importmessages'+		| null importmessages = ["import from " ++ Remote.name remote]+		| otherwise = importmessages  	tb = mkRemoteTrackingBranch remote branch 
Command/ImportFeed.hs view
@@ -283,7 +283,7 @@ 	Enclosure url -> startdownloadenclosure url 	MediaLink linkurl -> do 		let mediaurl = setDownloader linkurl YoutubeDownloader-		let mediakey = Backend.URL.fromUrl mediaurl Nothing+		let mediakey = Backend.URL.fromUrl mediaurl Nothing (verifiableOption (downloadOptions opts)) 		-- Old versions of git-annex that used quvi might have 		-- used the quviurl for this, so check if it's known 		-- to avoid adding it a second time.@@ -638,7 +638,7 @@ 		=<< feedState url  feedState :: URLString -> Annex RawFilePath-feedState url = fromRepo $ gitAnnexFeedState $ fromUrl url Nothing+feedState url = fromRepo $ gitAnnexFeedState $ fromUrl url Nothing False  {- The feed library parses the feed to Text, and does not use the  - filesystem encoding to do it, so when the locale is not unicode
Command/Migrate.hs view
@@ -126,7 +126,7 @@ 		| knowngoodcontent = finish =<< tweaksize newkey 		| otherwise = stopUnless checkcontent $ 			finish =<< tweaksize newkey-	checkcontent = Command.Fsck.checkBackend oldbackend oldkey KeyPresent afile+	checkcontent = Command.Fsck.checkBackend oldkey KeyPresent afile 	finish newkey = ifM (Command.ReKey.linkKey file oldkey newkey) 		( do 			_ <- copyMetaData oldkey newkey@@ -149,7 +149,7 @@ 			} 		newkey <- fst <$> genKey source nullMeterUpdate newbackend 		return $ Just (newkey, False)-	genkey (Just fm) = fm oldkey newbackend afile >>= \case+	genkey (Just fm) = fm oldkey newbackend afile True >>= \case 		Just newkey -> return (Just (newkey, True)) 		Nothing -> genkey Nothing 	tweaksize k
+ Command/ReregisterUrl.hs view
@@ -0,0 +1,79 @@+{- git-annex command+ -+ - Copyright 2015-2024 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Command.ReregisterUrl where++import Command+import Logs.Web+import Command.FromKey (keyOpt, keyOpt')+import qualified Remote+import Git.Types++cmd :: Command+cmd = withAnnexOptions [jsonOptions] $ command "reregisterurl"+	SectionPlumbing "updates url registration information"+	(paramKey)+	(seek <$$> optParser)++data ReregisterUrlOptions = ReregisterUrlOptions+	{ keyOpts :: CmdParams+	, batchOption :: BatchMode+	, moveFromOption :: Maybe (DeferredParse Remote)+	}++optParser :: CmdParamsDesc -> Parser ReregisterUrlOptions+optParser desc = ReregisterUrlOptions+	<$> cmdParams desc+	<*> parseBatchOption False+	<*> optional (mkParseRemoteOption <$> parseMoveFromOption)++parseMoveFromOption :: Parser RemoteName+parseMoveFromOption = strOption+	( long "move-from" <> metavar paramRemote+	<> completeRemotes+	)++seek :: ReregisterUrlOptions -> CommandSeek+seek o = case (batchOption o, keyOpts o) of+	(Batch fmt, _) -> seekBatch o fmt+	(NoBatch, ps) -> commandAction (start o ps)++seekBatch :: ReregisterUrlOptions -> BatchFormat -> CommandSeek+seekBatch o fmt = batchOnly Nothing (keyOpts o) $+	batchInput fmt (pure . parsebatch) $+		batchCommandAction . start' o+  where+	parsebatch l = case keyOpt' l of+		Left e -> Left e+		Right k -> Right k++start :: ReregisterUrlOptions -> [String] -> CommandStart+start o (keyname:[]) = start' o (si, keyOpt keyname)+  where+	si = SeekInput [keyname]+start _ _ = giveup "specify a key"++start' :: ReregisterUrlOptions -> (SeekInput, Key) -> CommandStart+start' o (si, key) =+	starting "reregisterurl" ai si $+		perform o key+  where+	ai = ActionItemKey key++perform :: ReregisterUrlOptions -> Key -> CommandPerform+perform o key = maybe (pure Nothing) (Just <$$> getParsed) (moveFromOption o) >>= \case+	Nothing -> next $ return True+	Just r -> do+		us <- map fst+			. filter (\(_, d) -> d == OtherDownloader)+			. map getDownloader+			<$> getUrls key+		us' <- filterM (\u -> (== r) <$> Remote.claimingUrl u) us+		forM_ us' $ \u -> do+			setUrlMissing key (setDownloader u OtherDownloader)+			setUrlPresent key u+		next $ return True
Command/Sync.hs view
@@ -1,7 +1,7 @@ {- git-annex command  -  - Copyright 2011 Joachim Breitner <mail@joachim-breitner.de>- - 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.  -}@@ -105,7 +105,7 @@ 	, notOnlyAnnexOption :: Bool 	, commitOption :: Bool 	, noCommitOption :: Bool-	, messageOption :: Maybe String+	, messageOption :: [String] 	, pullOption :: Bool 	, pushOption :: Bool 	, contentOption :: Maybe Bool@@ -125,7 +125,7 @@ 		, notOnlyAnnexOption = False 		, commitOption = False 		, noCommitOption = False-		, messageOption = Nothing+		, messageOption = [] 		, pullOption = False 		, pushOption = False 		, contentOption = Just False@@ -169,8 +169,8 @@ 			( long "no-commit" 			<> help "avoid git commit"  			))-	<*> unlessmode [SyncMode, AssistMode] Nothing-		(optional (strOption+	<*> unlessmode [SyncMode, AssistMode] []+		(many (strOption 			( long "message" <> short 'm' <> metavar "MSG" 			<> help "commit message" 			)))@@ -402,17 +402,18 @@  commit :: SyncOptions -> CommandStart commit o = stopUnless shouldcommit $ starting "commit" ai si $ do-	commitmessage <- maybe commitMsg return (messageOption o) 	Annex.Branch.commit =<< Annex.Branch.commitMessage+	mopts <- concatMap (\msg -> [Param "-m", Param msg])+		<$> if null (messageOption o)+			then (:[]) <$> commitMsg+			else pure (messageOption o) 	next $ do 		showOutput 		let cmode = Git.Branch.ManualCommit 		cquiet <- Git.Branch.CommitQuiet <$> commandProgressDisabled-		void $ inRepo $ Git.Branch.commitCommand cmode cquiet-			[ Param "-a"-			, Param "-m"-			, Param commitmessage-			]+		void $ inRepo $ Git.Branch.commitCommand+			cmode cquiet+			([ Param "-a" ] ++ mopts) 		return True   where 	shouldcommit = notOnlyAnnex o <&&>@@ -426,7 +427,8 @@ commitMsg = do 	u <- getUUID 	m <- uuidDescMap-	return $ "git-annex in " ++ maybe "unknown" fromUUIDDesc (M.lookup u m)+	return $ "git-annex in "+		++ maybe "unknown" fromUUIDDesc (M.lookup u m)  mergeLocal :: [Git.Merge.MergeConfig] -> SyncOptions -> CurrBranch -> CommandStart mergeLocal mergeconfig o currbranch = stopUnless (notOnlyAnnex o) $@@ -578,7 +580,7 @@ 			let (branch, subdir) = splitRemoteAnnexTrackingBranchSubdir b 			if canImportKeys remote importcontent 				then do-					Command.Import.seekRemote remote branch subdir importcontent (CheckGitIgnore True) Nothing+					Command.Import.seekRemote remote branch subdir importcontent (CheckGitIgnore True) [] 					-- Importing generates a branch 					-- that is not initially connected 					-- to the current branch, so allow
Database/Keys.hs view
@@ -352,7 +352,7 @@ 		, Param "-z" 		, Param "--no-abbrev" 		-- Optimization: Limit to pointer files and annex symlinks.-		-- This is not perfect. A file could contain with this and not+		-- This is not perfect. A file could contain this and not 		-- be a pointer file. And a pointer file that is replaced with 		-- a non-pointer file will match this. This is only a 		-- prefilter so that's ok.
Git/Branch.hs view
@@ -1,6 +1,6 @@ {- git branch stuff  -- - Copyright 2011-2021 Joey Hess <id@joeyh.name>+ - Copyright 2011-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -178,7 +178,7 @@ 	tree <- writeTree repo 	ifM (cancommit tree) 		( do-			sha <- commitTree commitmode message parentrefs tree repo+			sha <- commitTree commitmode [message] parentrefs tree repo 			update' branch sha repo 			return $ Just sha 		, return Nothing@@ -207,8 +207,21 @@ 	go nullh = pipeReadStrict' (\p -> p { std_err = UseHandle nullh })  		[Param "write-tree"] repo -commitTree :: CommitMode -> String -> [Ref] -> Ref -> Repo -> IO Sha-commitTree commitmode message parentrefs tree repo =+commitTree :: CommitMode -> [String] -> [Ref] -> Ref -> Repo -> IO Sha+commitTree commitmode messages parentrefs tree repo =+	getSha "commit-tree" $ pipeReadStrict ps repo+  where+	ps = [Param "commit-tree", Param (fromRef tree)]+		++ applyCommitModeForCommitTree commitmode baseparams repo+	baseparams = map Param $+		concatMap (\r -> ["-p", fromRef r]) parentrefs+			++ concatMap (\msg -> ["-m", msg]) messages++-- commitTree passes the commit message to git with -m, which can cause it+-- to get modified slightly (eg adding trailing newline). This variant uses+-- the exact commit message that is provided.+commitTreeExactMessage :: CommitMode -> String -> [Ref] -> Ref -> Repo -> IO Sha+commitTreeExactMessage commitmode message parentrefs tree repo = 	getSha "commit-tree" $ 		pipeWriteRead ([Param "commit-tree", Param (fromRef tree)] ++ ps) 			sendmsg repo
Git/UnionMerge.hs view
@@ -87,11 +87,11 @@ mergeFile info file hashhandle h = case S8.words info of 	[_colonmode, _bmode, asha, bsha, _status] ->  		case filter (`notElem` nullShas) [Ref asha, Ref bsha] of-		[] -> return Nothing-		(sha:[]) -> use sha-		shas -> use-			=<< either return (hashBlob hashhandle . L8.unlines)-			=<< calcMerge . zip shas <$> mapM getcontents shas+			[] -> return Nothing+			(sha:[]) -> use sha+			shas -> use+				=<< either return (hashBlob hashhandle . L8.unlines)+				=<< calcMerge . zip shas <$> mapM getcontents shas 	_ -> return Nothing   where 	use sha = return $ Just $
Limit.hs view
@@ -575,7 +575,7 @@  limitSecureHash :: MatchFiles Annex limitSecureHash = MatchFiles-	{ matchAction = const $ checkKey isCryptographicallySecure+	{ matchAction = const $ checkKey isCryptographicallySecureKey 	, matchNeedsFileName = False 	, matchNeedsFileContent = False 	, matchNeedsKey = True
Logs.hs view
@@ -1,6 +1,6 @@ {- git-annex log file names  -- - Copyright 2013-2023 Joey Hess <id@joeyh.name>+ - Copyright 2013-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -35,7 +35,9 @@ 	| isRemoteStateLog f = Just NewUUIDBasedLog 	| isRemoteContentIdentifierLog f = Just NewUUIDBasedLog 	| isRemoteMetaDataLog f = Just RemoteMetaDataLog-	| isMetaDataLog f || f `elem` otherTopLevelLogs = Just OtherLog+	| isMetaDataLog f+		|| f `elem` otherTopLevelLogs+		|| isEquivilantKeyLog f = Just OtherLog 	| otherwise = (LocationLog <$> locationLogFileKey config f) 		<|> (ChunkLog <$> extLogFileKey chunkLogExt f) 		<|> (UrlLog  <$> urlLogFileKey f)@@ -70,6 +72,7 @@ 	, remoteMetaDataLogFile config k 	, remoteContentIdentifierLogFile config k 	, chunkLogFile config k+	, equivilantKeysLogFile config k 	] ++ oldurlLogs config k  {- All uuid-based logs stored in the top of the git-annex branch. -}@@ -207,6 +210,18 @@  chunkLogExt :: S.ByteString chunkLogExt = ".log.cnk"++{- The filename of the equivilant keys log for a given key. -}+equivilantKeysLogFile :: GitConfig -> Key -> RawFilePath+equivilantKeysLogFile config key = +	(branchHashDir config key P.</> keyFile key)+		<> equivilantKeyLogExt++equivilantKeyLogExt :: S.ByteString+equivilantKeyLogExt = ".log.ek"++isEquivilantKeyLog :: RawFilePath -> Bool+isEquivilantKeyLog path = equivilantKeyLogExt `S.isSuffixOf` path  {- The filename of the metadata log for a given key. -} metaDataLogFile :: GitConfig -> Key -> RawFilePath
+ Logs/EquivilantKeys.hs view
@@ -0,0 +1,31 @@+{- Logs listing keys that are equivilant to a key.+ -+ - Copyright 2024 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++{-# LANGUAGE BangPatterns #-}++module Logs.EquivilantKeys (+	getEquivilantKeys,+	setEquivilantKey,+) where++import Annex.Common+import qualified Annex+import Logs+import Logs.Presence+import qualified Annex.Branch++getEquivilantKeys :: Key -> Annex [Key]+getEquivilantKeys key = do+	config <- Annex.getGitConfig+	nub . mapMaybe (deserializeKey' . fromLogInfo)+		<$> presentLogInfo (equivilantKeysLogFile config key)++setEquivilantKey :: Key -> Key -> Annex ()+setEquivilantKey key equivkey = do+	config <- Annex.getGitConfig+	addLog (Annex.Branch.RegardingUUID []) (equivilantKeysLogFile config key)+		InfoPresent (LogInfo (serializeKey' equivkey))
Logs/Transfer.hs view
@@ -1,6 +1,6 @@ {- git-annex transfer information files and lock files  -- - Copyright 2012-2021 Joey Hess <id@joeyh.name>+ - Copyright 2012-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -56,26 +56,24 @@ 		<*> Just (fromMaybe 0 $ bytesComplete info)  {- Generates a callback that can be called as transfer progresses to update- - the transfer info file. Also returns the file it'll be updating, - - an action that sets up the file with appropriate permissions,- - which should be run after locking the transfer lock file, but- - before using the callback, and a TVar that can be used to read- - the number of bytes processed so far. -}-mkProgressUpdater :: Transfer -> TransferInfo -> Annex (MeterUpdate, RawFilePath, Annex (), TVar (Maybe BytesProcessed))-mkProgressUpdater t info = do-	tfile <- fromRepo $ transferFile t+ - the transfer info file. Also returns an action that sets up the file with+ - appropriate permissions, which should be run after locking the transfer+ - lock file, but before using the callback, and a TVar that can be used to+ - read the number of bytes processed so far. -}+mkProgressUpdater :: Transfer -> TransferInfo -> RawFilePath -> Annex (MeterUpdate, Annex (), TVar (Maybe BytesProcessed))+mkProgressUpdater t info tfile = do 	let createtfile = void $ tryNonAsync $ writeTransferInfoFile info tfile 	tvar <- liftIO $ newTVarIO Nothing 	loggedtvar <- liftIO $ newTVarIO 0-	return (liftIO . updater (fromRawFilePath tfile) tvar loggedtvar, tfile, createtfile, tvar)+	return (liftIO . updater (fromRawFilePath tfile) tvar loggedtvar, createtfile, tvar)   where-	updater tfile tvar loggedtvar new = do+	updater tfile' tvar loggedtvar new = do 		old <- atomically $ swapTVar tvar (Just new) 		let oldbytes = maybe 0 fromBytesProcessed old 		let newbytes = fromBytesProcessed new 		when (newbytes - oldbytes >= mindelta) $ do 			let info' = info { bytesComplete = Just newbytes }-			_ <- tryIO $ updateTransferInfoFile info' tfile+			_ <- tryIO $ updateTransferInfoFile info' tfile' 			atomically $ writeTVar loggedtvar newbytes  	{- The minimum change in bytesComplete that is worth@@ -102,33 +100,40 @@ {- If a transfer is still running, returns its TransferInfo.  -   - If no transfer is running, attempts to clean up the stale- - lock and info files. This can happen if a transfer process was- - interrupted.+ - lock and info files, which can be left behind when a transfer+ - process was interrupted.  -} checkTransfer :: Transfer -> Annex (Maybe TransferInfo) checkTransfer t = debugLocks $ do-	tfile <- fromRepo $ transferFile t-	let lck = transferLockFile tfile-	let cleanstale = do+	(tfile, lck, moldlck) <- fromRepo $ transferFileAndLockFile t+	let deletestale = do 		void $ tryIO $ R.removeLink tfile 		void $ tryIO $ R.removeLink lck+		maybe noop (void . tryIO . R.removeLink) moldlck #ifndef mingw32_HOST_OS 	v <- getLockStatus lck-	case v of+	v' <- case (moldlck, v) of+		(Nothing, _) -> pure v+		(_, StatusLockedBy pid) -> pure (StatusLockedBy pid)+		(Just oldlck, _) -> getLockStatus oldlck+	case v' of 		StatusLockedBy pid -> liftIO $ catchDefaultIO Nothing $ 			readTransferInfoFile (Just pid) (fromRawFilePath tfile) 		_ -> do-			-- Take a non-blocking lock while deleting-			-- the stale lock file. Ignore failure-			-- due to permissions problems, races, etc.-			void $ tryIO $ do-				mode <- annexFileMode-				r <- tryLockExclusive (Just mode) lck-				case r of-					Just lockhandle -> liftIO $ do-						cleanstale+			mode <- annexFileMode+			-- Ignore failure due to permissions, races, etc.+			void $ tryIO $ tryLockExclusive (Just mode) lck >>= \case+				Just lockhandle -> case moldlck of+					Nothing -> liftIO $ do+						deletestale 						dropLock lockhandle-					_ -> noop+					Just oldlck -> tryLockExclusive (Just mode) oldlck >>= \case+						Just oldlockhandle -> liftIO $ do+							deletestale+							dropLock oldlockhandle+							dropLock lockhandle+						Nothing -> liftIO $ dropLock lockhandle+				Nothing -> noop 			return Nothing #else 	v <- liftIO $ lockShared lck@@ -137,7 +142,7 @@ 			readTransferInfoFile Nothing (fromRawFilePath tfile) 		Just lockhandle -> do 			dropLock lockhandle-			cleanstale+			deletestale 			return Nothing #endif @@ -198,24 +203,45 @@ 	failedtfile <- fromRepo $ failedTransferFile t 	writeTransferInfoFile info failedtfile -{- The transfer information file to use for a given Transfer. -}-transferFile :: Transfer -> Git.Repo -> RawFilePath-transferFile (Transfer direction u kd) r =-	transferDir direction r-		P.</> B8.filter (/= '/') (fromUUID u)-		P.</> keyFile (mkKey (const kd))+{- The transfer information file and transfer lock file + - to use for a given Transfer. + -+ - The transfer lock file used for an Upload includes the UUID.+ - This allows multiple transfers of the same key to different remote+ - repositories run at the same time, while preventing multiple+ - transfers of the same key to the same remote repository.+ -+ - The transfer lock file used for a Download does not include the UUID.+ - This prevents multiple transfers of the same key into the local+ - repository at the same time.+ -+ - Since old versions of git-annex (10.20240227 and older) used to + - include the UUID in the transfer lock file for a Download, this also+ - returns a second lock file for Downloads, which has to be locked+ - in order to interoperate with the old git-annex processes.+ - Lock order is the same as return value order. + - At some point in the future, when old git-annex processes are no longer+ - a concern, this complication can be removed.+ -}+transferFileAndLockFile :: Transfer -> Git.Repo -> (RawFilePath, RawFilePath, Maybe RawFilePath)+transferFileAndLockFile (Transfer direction u kd) r =+	case direction of+		Upload -> (transferfile, uuidlockfile, Nothing)+		Download -> (transferfile, nouuidlockfile, Just uuidlockfile)+  where+	td = transferDir direction r+	fu = B8.filter (/= '/') (fromUUID u)+	kf = keyFile (mkKey (const kd))+	lckkf = "lck." <> kf+	transferfile = td P.</> fu P.</> kf+	uuidlockfile = td P.</> fu P.</> lckkf+	nouuidlockfile = td P.</> "lck" P.</> lckkf  {- The transfer information file to use to record a failed Transfer -} failedTransferFile :: Transfer -> Git.Repo -> RawFilePath failedTransferFile (Transfer direction u kd) r =  	failedTransferDir u direction r 		P.</> keyFile (mkKey (const kd))--{- The transfer lock file corresponding to a given transfer info file. -}-transferLockFile :: RawFilePath -> RawFilePath-transferLockFile infofile = -	let (d, f) = P.splitFileName infofile-	in P.combine d ("lck." <> f)  {- Parses a transfer information filename to a Transfer. -} parseTransferFile :: FilePath -> Maybe Transfer
Remote/Adb.hs view
@@ -94,7 +94,7 @@ 			, versionedExport = False 			, checkPresentExport = checkPresentExportM serial adir 			, removeExportDirectory = Just $ removeExportDirectoryM serial adir-			, renameExport = renameExportM serial adir+			, renameExport = Just $ renameExportM serial adir 			} 		, importActions = ImportActions 			{ listImportableContents = listImportableContentsM serial adir c
Remote/BitTorrent.hs view
@@ -173,7 +173,7 @@  {- A Key corresponding to the URL of a torrent file. -} torrentUrlKey :: URLString -> Annex Key-torrentUrlKey u = return $ fromUrl (fst $ torrentUrlNum u) Nothing+torrentUrlKey u = return $ fromUrl (fst $ torrentUrlNum u) Nothing False  {- Temporary filename to use to store the torrent file. -} tmpTorrentFile :: URLString -> Annex RawFilePath
Remote/Directory.hs view
@@ -112,7 +112,7 @@ 				-- Not needed because removeExportLocation 				-- auto-removes empty directories. 				, removeExportDirectory = Nothing-				, renameExport = renameExportM dir+				, renameExport = Just $ renameExportM dir 				} 			, importActions = ImportActions 				{ listImportableContents = listImportableContentsM ii dir
Remote/External.hs view
@@ -1,6 +1,6 @@ {- External special remote interface.  -- - 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.  -}@@ -9,7 +9,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE RankNTypes #-} -module Remote.External (remote) where+module Remote.External where  import Remote.External.Types import Remote.External.AsyncExtension@@ -48,10 +48,10 @@ remote = specialRemoteType $ RemoteType 	{ typename = "external" 	, enumerate = const (findSpecialRemotes "externaltype")-	, generate = gen-	, configParser = remoteConfigParser-	, setup = externalSetup-	, exportSupported = checkExportSupported+	, generate = gen remote Nothing+	, configParser = remoteConfigParser Nothing+	, setup = externalSetup Nothing Nothing+	, exportSupported = checkExportSupported Nothing 	, importSupported = importUnsupported 	, thirdPartyPopulated = False 	}@@ -62,15 +62,15 @@ readonlyField :: RemoteConfigField readonlyField = Accepted "readonly" -gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> RemoteStateHandle -> Annex (Maybe Remote)-gen r u rc gc rs+gen :: RemoteType -> Maybe ExternalProgram -> Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> RemoteStateHandle -> Annex (Maybe Remote)+gen rt externalprogram r u rc gc rs 	-- readonly mode only downloads urls; does not use external program-	| externaltype == "readonly" = do+	| externalprogram' == ExternalType "readonly" = do 		c <- parsedRemoteConfig remote rc 		cst <- remoteCost gc c expensiveRemoteCost 		let rmt = mk c cst (pure GloballyAvailable) 			Nothing-			(externalInfo externaltype)+			(externalInfo externalprogram') 			Nothing 			Nothing 			exportUnsupported@@ -83,7 +83,7 @@ 			rmt 	| otherwise = do 		c <- parsedRemoteConfig remote rc-		external <- newExternal externaltype (Just u) c (Just gc)+		external <- newExternal externalprogram' (Just u) c (Just gc) 			(Git.remoteName r) (Just rs) 		Annex.addCleanupAction (RemoteCleanup u) $ stopExternal external 		cst <- getCost external r gc c@@ -98,7 +98,7 @@ 				, versionedExport = False 				, checkPresentExport = checkPresentExportM external 				, removeExportDirectory = Just $ removeExportDirectoryM external-				, renameExport = renameExportM external+				, renameExport = Just $ renameExportM external 				} 			else exportUnsupported 		-- Cheap exportSupported that replaces the expensive@@ -150,21 +150,29 @@ 			, appendonly = False 			, untrustworthy = False 			, availability = avail-			, remotetype = remote +			, remotetype = rt  				{ exportSupported = cheapexportsupported }-			, mkUnavailable = gen r u rc-				(gc { remoteAnnexExternalType = Just "!dne!" }) rs+			, mkUnavailable =+				let dneprogram = case externalprogram of+					Just (ExternalCommand _ _) -> Just (ExternalType "!dne!")+					_ -> Nothing+				    dnegc = gc { remoteAnnexExternalType = Just "!dne!" }+				in gen rt dneprogram r u rc dnegc rs 			, getInfo = togetinfo 			, claimUrl = toclaimurl 			, checkUrl = tocheckurl 			, remoteStateHandle = rs 			}-	externaltype = fromMaybe (giveup "missing externaltype") (remoteAnnexExternalType gc)+	externalprogram' = case externalprogram of+		Just p -> p+		Nothing -> ExternalType $ +			fromMaybe (giveup "missing externaltype")+				(remoteAnnexExternalType gc) -externalSetup :: SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)-externalSetup _ mu _ c gc = do+externalSetup :: Maybe ExternalProgram -> Maybe (String, String) -> SetupStage -> Maybe UUID -> Maybe CredPair -> RemoteConfig -> RemoteGitConfig -> Annex (RemoteConfig, UUID)+externalSetup externalprogram setgitconfig _ mu _ c gc = do 	u <- maybe (liftIO genUUID) return mu-	pc <- either giveup return $ parseRemoteConfig c lenientRemoteConfigParser+	pc <- either giveup return $ parseRemoteConfig c (lenientRemoteConfigParser externalprogram) 	let readonlyconfig = getRemoteConfigValue readonlyField pc == Just True 	let externaltype = if readonlyconfig 		then "readonly"@@ -181,8 +189,9 @@ 			setConfig (remoteAnnexConfig (fromJust (lookupName c)) "readonly") (boolConfig True) 			return c' 		else do-			pc' <- either giveup return $ parseRemoteConfig c' lenientRemoteConfigParser-			external <- newExternal externaltype (Just u) pc' (Just gc) Nothing Nothing+			pc' <- either giveup return $ parseRemoteConfig c' (lenientRemoteConfigParser externalprogram)+			let p = fromMaybe (ExternalType externaltype) externalprogram+			external <- newExternal p (Just u) pc' (Just gc) Nothing Nothing 			-- Now that we have an external, ask it to LISTCONFIGS,  			-- and re-parse the RemoteConfig strictly, so we can 			-- error out if the user provided an unexpected config.@@ -200,17 +209,20 @@ 				liftIO . atomically . readTMVar . externalConfigChanges 			return (changes c') -	gitConfigSpecialRemote u c'' [("externaltype", externaltype)]+	gitConfigSpecialRemote u c''+		[ fromMaybe ("externaltype", externaltype) setgitconfig ] 	return (M.delete readonlyField c'', u) -checkExportSupported :: ParsedRemoteConfig -> RemoteGitConfig -> Annex Bool-checkExportSupported c gc = do+checkExportSupported :: Maybe ExternalProgram -> ParsedRemoteConfig -> RemoteGitConfig -> Annex Bool+checkExportSupported Nothing c gc = do 	let externaltype = fromMaybe (giveup "Specify externaltype=") $ 		remoteAnnexExternalType gc <|> getRemoteConfigValue externaltypeField c 	if externaltype == "readonly" 		then return False-		else checkExportSupported' -			=<< newExternal externaltype Nothing c (Just gc) Nothing Nothing+		else checkExportSupported (Just (ExternalType externaltype)) c gc+checkExportSupported (Just externalprogram) c gc = +	checkExportSupported' +		=<< newExternal externalprogram Nothing c (Just gc) Nothing Nothing  checkExportSupported' :: External -> Annex Bool checkExportSupported' external = go `catchNonAsync` (const (return False))@@ -658,7 +670,7 @@ 		n <- succ <$> readTVar (externalLastPid external) 		writeTVar (externalLastPid external) n 		return n-	AddonProcess.startExternalAddonProcess basecmd pid >>= \case+	AddonProcess.startExternalAddonProcess externalcmd externalparams pid >>= \case 		Left (AddonProcess.ProgramFailure err) -> do 			unusable err 		Left (AddonProcess.ProgramNotInstalled err) ->@@ -666,8 +678,8 @@ 				(Just rname, Just True) -> unusable $ unlines 					[ err 					, "This remote has annex-readonly=true, and previous versions of"-					, "git-annex would tried to download from it without"-					, "installing " ++ basecmd ++ ". If you want that, you need to set:"+					, "git-annex would try to download from it without"+					, "installing " ++ externalcmd ++ ". If you want that, you need to set:" 					, "git config remote." ++ rname ++ ".annex-externaltype readonly" 					] 				_ -> unusable err@@ -686,7 +698,9 @@ 			extensions <- startproto st 			return (st, extensions)   where-	basecmd = "git-annex-remote-" ++ externalType external+	(externalcmd, externalparams) = case externalProgram external of+		ExternalType t -> ("git-annex-remote-" ++ t, [])+		ExternalCommand c ps -> (c, ps) 	startproto st = do 		receiveMessage st external 			(const Nothing)@@ -707,13 +721,13 @@ 		case filter (`notElem` fromExtensionList supportedExtensionList) (fromExtensionList exwanted) of 			[] -> return exwanted 			exrest -> unusable $ unwords $-				[ basecmd+				[ externalcmd 				, "requested extensions that this version of git-annex does not support:" 				] ++ exrest  	unusable msg = do 		warning (UnquotedString msg)-		giveup ("unable to use external special remote " ++ basecmd)+		giveup ("unable to use external special remote " ++ externalcmd)  stopExternal :: External -> Annex () stopExternal external = liftIO $ do@@ -825,12 +839,13 @@   where 	supported u = snd (getDownloader u) == WebDownloader 			-externalInfo :: ExternalType -> Annex [(String, String)]-externalInfo et = return [("externaltype", et)]+externalInfo :: ExternalProgram -> Annex [(String, String)]+externalInfo (ExternalType et) = return [("externaltype", et)]+externalInfo (ExternalCommand _ _) = return []  getInfoM :: External -> Annex [(String, String)] getInfoM external = (++)-	<$> externalInfo (externalType external)+	<$> externalInfo (externalProgram external) 	<*> handleRequest external GETINFO Nothing (collect [])   where 	collect l req = case req of@@ -847,34 +862,41 @@  {- All unknown configs are passed through in case the external program  - uses them. -}-lenientRemoteConfigParser :: RemoteConfigParser-lenientRemoteConfigParser =-	addRemoteConfigParser specialRemoteConfigParsers baseRemoteConfigParser+lenientRemoteConfigParser :: Maybe ExternalProgram -> RemoteConfigParser+lenientRemoteConfigParser externalprogram =+	addRemoteConfigParser specialRemoteConfigParsers (baseRemoteConfigParser externalprogram) -baseRemoteConfigParser :: RemoteConfigParser-baseRemoteConfigParser = RemoteConfigParser-	{ remoteConfigFieldParsers =-		[ optionalStringParser externaltypeField-			(FieldDesc "type of external special remote to use")-		, trueFalseParser readonlyField (Just False)-			(FieldDesc "enable readonly mode")-		]+baseRemoteConfigParser :: Maybe ExternalProgram -> RemoteConfigParser+baseRemoteConfigParser externalprogram = RemoteConfigParser+	{ remoteConfigFieldParsers = if isJust extcommand+		then []+		else +			[ optionalStringParser externaltypeField+				(FieldDesc "type of external special remote to use")+			, trueFalseParser readonlyField (Just False)+				(FieldDesc "enable readonly mode")+			] 	, remoteConfigRestPassthrough = Just 		( const True-		, [("*", FieldDesc "all other parameters are passed to external special remote program")]+		, [("*", FieldDesc $ "all other parameters are passed to " ++ fromMaybe "external special remote program" extcommand)] 		) 	}+  where+	extcommand = case externalprogram of+		Just (ExternalCommand c _) -> Just c+		_ -> Nothing  {- When the remote supports LISTCONFIGS, only accept the ones it listed.  - When it does not, accept all configs. -} strictRemoteConfigParser :: External -> Annex RemoteConfigParser strictRemoteConfigParser external = listConfigs external >>= \case-	Nothing -> return lenientRemoteConfigParser+	Nothing -> return lcp 	Just l -> do 		let s = S.fromList (map fst l) 		let listed f = S.member (fromProposedAccepted f) s-		return $ lenientRemoteConfigParser-			{ remoteConfigRestPassthrough = Just (listed, l) }+		return $ lcp { remoteConfigRestPassthrough = Just (listed, l) }+  where+	lcp = lenientRemoteConfigParser (Just (externalProgram external))  listConfigs :: External -> Annex (Maybe [(Setting, FieldDesc)]) listConfigs external = handleRequest external LISTCONFIGS Nothing (collect [])@@ -886,20 +908,21 @@ 		UNSUPPORTED_REQUEST -> result Nothing 		_ -> Nothing -remoteConfigParser :: RemoteConfig -> Annex RemoteConfigParser-remoteConfigParser c+remoteConfigParser :: Maybe ExternalProgram -> RemoteConfig -> Annex RemoteConfigParser+remoteConfigParser externalprogram c 	-- No need to start the external when there is no config to parse, 	-- or when everything in the config was already accepted; in those 	-- cases the lenient parser will do the same thing as the strict 	-- parser.-	| M.null (M.filter isproposed c) = return lenientRemoteConfigParser-	| otherwise = case parseRemoteConfig c baseRemoteConfigParser of-		Left _ -> return lenientRemoteConfigParser+	| M.null (M.filter isproposed c) = return (lenientRemoteConfigParser externalprogram)+	| otherwise = case parseRemoteConfig c (baseRemoteConfigParser externalprogram) of+		Left _ -> return (lenientRemoteConfigParser externalprogram) 		Right pc -> case (getRemoteConfigValue externaltypeField pc, getRemoteConfigValue readonlyField pc) of-			(Nothing, _) -> return lenientRemoteConfigParser-			(_, Just True) -> return lenientRemoteConfigParser+			(Nothing, _) -> return (lenientRemoteConfigParser externalprogram)+			(_, Just True) -> return (lenientRemoteConfigParser externalprogram) 			(Just externaltype, _) -> do-				external <- newExternal externaltype Nothing pc Nothing Nothing Nothing+				let p = fromMaybe (ExternalType externaltype) externalprogram+				external <- newExternal p Nothing pc Nothing Nothing Nothing 				strictRemoteConfigParser external   where 	isproposed (Accepted _) = False
Remote/External/Types.hs view
@@ -1,6 +1,6 @@ {- External special remote data types.  -- - Copyright 2013-2020 Joey Hess <id@joeyh.name>+ - Copyright 2013-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -12,7 +12,7 @@ module Remote.External.Types ( 	External(..), 	newExternal,-	ExternalType,+	ExternalProgram(..), 	ExternalState(..), 	PrepareStatus(..), 	ExtensionList(..),@@ -64,7 +64,7 @@ import qualified Data.ByteString.Short as S (fromShort)  data External = External-	{ externalType :: ExternalType+	{ externalProgram :: ExternalProgram 	, externalUUID :: Maybe UUID 	, externalState :: TVar [ExternalState] 	-- ^ Contains states for external special remote processes@@ -77,9 +77,9 @@ 	, externalAsync :: TMVar ExternalAsync 	} -newExternal :: ExternalType -> Maybe UUID -> ParsedRemoteConfig -> Maybe RemoteGitConfig -> Maybe RemoteName -> Maybe RemoteStateHandle -> Annex External-newExternal externaltype u c gc rn rs = liftIO $ External-	<$> pure externaltype+newExternal :: ExternalProgram -> Maybe UUID -> ParsedRemoteConfig -> Maybe RemoteGitConfig -> Maybe RemoteName -> Maybe RemoteStateHandle -> Annex External+newExternal p u c gc rn rs = liftIO $ External+	<$> pure p 	<*> pure u 	<*> atomically (newTVar []) 	<*> atomically (newTVar 0)@@ -89,7 +89,12 @@ 	<*> pure rs 	<*> atomically (newTMVar UncheckedExternalAsync) -type ExternalType = String+data ExternalProgram+	= ExternalType String+	-- ^ "git-annex-remote-" is prepended to this to get the program+	| ExternalCommand String [CommandParam]+	-- ^ to use a program with a different name, and parameters+	deriving (Show, Eq)  data ExternalState = ExternalState 	{ externalSend :: forall t. (Proto.Sendable t, ToAsyncWrapped t) => t -> IO ()
Remote/Helper/ExportImport.hs view
@@ -41,7 +41,7 @@ 		, removeExport = nope 		, versionedExport = False 		, removeExportDirectory = nope-		, renameExport = \_ _ _ -> return Nothing+		, renameExport = Nothing 		} 	 where 	 	nope = giveup "export not supported"@@ -257,7 +257,7 @@ 		-- renameExport is optional, and the remote's 		-- implementation may lose modifications to the file 		-- (by eg copying and then deleting) so don't use it-		, renameExport = \_ _ _ -> return Nothing+		, renameExport = Nothing 		, checkPresentExport = checkPresentImport ciddbv 		} 	
Remote/Helper/ReadOnly.hs view
@@ -34,7 +34,7 @@ 			{ storeExport = readonlyStoreExport 			, removeExport = readonlyRemoveExport 			, removeExportDirectory = Just readonlyRemoveExportDirectory-			, renameExport = readonlyRenameExport+			, renameExport = Nothing 			} 		, importActions = (importActions r) 			{ storeExportWithContentIdentifier = readonlyStoreExportWithContentIdentifier@@ -61,9 +61,6 @@  readonlyRemoveExportDirectory :: ExportDirectory -> Annex () readonlyRemoveExportDirectory _ = readonlyFail--readonlyRenameExport :: Key -> ExportLocation -> ExportLocation -> Annex (Maybe ())-readonlyRenameExport _ _ _ = return Nothing  readonlyStoreExportWithContentIdentifier :: FilePath -> Key -> ExportLocation -> [ContentIdentifier] -> MeterUpdate -> Annex ContentIdentifier readonlyStoreExportWithContentIdentifier _ _ _ _ _ = readonlyFail
Remote/List.hs view
@@ -37,6 +37,7 @@ import qualified Remote.GitLFS import qualified Remote.HttpAlso import qualified Remote.Borg+import qualified Remote.Rclone import qualified Remote.Hook import qualified Remote.External @@ -59,6 +60,7 @@ 	, Remote.GitLFS.remote 	, Remote.HttpAlso.remote 	, Remote.Borg.remote+	, Remote.Rclone.remote 	, Remote.Hook.remote 	, Remote.External.remote 	]
+ Remote/Rclone.hs view
@@ -0,0 +1,31 @@+{- Rclone special remote, using "rclone gitannex"+ -+ - Copyright 2024 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU AGPL version 3 or higher.+ -}++module Remote.Rclone (remote) where++import Types+import Types.Remote+import Remote.Helper.Special+import Remote.Helper.ExportImport+import Utility.SafeCommand+import qualified Remote.External as External+import Remote.External.Types++remote :: RemoteType+remote = specialRemoteType $ RemoteType+	{ typename = "rclone"+	, enumerate = const (findSpecialRemotes "rclone")+	, generate = External.gen remote p+	, configParser = External.remoteConfigParser p+	, setup = External.externalSetup p setgitconfig +	, exportSupported = External.checkExportSupported p+	, importSupported = importUnsupported+	, thirdPartyPopulated = False+	}+  where+	p = Just $ ExternalCommand "rclone" [Param "gitannex"]+	setgitconfig = Just ("rclone", "true")
Remote/Rsync.hs view
@@ -106,7 +106,7 @@ 				, versionedExport = False 				, checkPresentExport = checkPresentExportM o 				, removeExportDirectory = Just (removeExportDirectoryM o)-				, renameExport = renameExportM o+				, renameExport = Just $ renameExportM o 				} 			, importActions = importUnsupported 			, whereisKey = Nothing
Remote/S3.hs view
@@ -228,7 +228,7 @@ 				, checkPresentExport = checkPresentExportS3 hdl this info 				-- S3 does not have directories. 				, removeExportDirectory = Nothing-				, renameExport = renameExportS3 hdl this rs info+				, renameExport = Just $ renameExportS3 hdl this rs info 				} 			, importActions = ImportActions                                 { listImportableContents = listImportableContentsS3 hdl this info c
Remote/Web.hs view
@@ -1,6 +1,6 @@ {- Web remote.  -- - 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.  -}@@ -11,6 +11,7 @@ import Types.Remote import Types.ProposedAccepted import Types.Creds+import Types.Key import Remote.Helper.Special import Remote.Helper.ExportImport import qualified Git@@ -27,6 +28,9 @@ import Annex.YoutubeDl import Annex.SpecialRemote.Config import Logs.Remote+import Logs.EquivilantKeys+import Backend+import Backend.VURL.Utilities (generateEquivilantKey)  import qualified Data.Map as M @@ -123,23 +127,59 @@ 			, show (length urls) 			, "known url(s) failed" 			]+	+	isyoutube (_, YoutubeDownloader) = True+	isyoutube _ = False  	dl ([], ytus) = flip getM (map fst ytus) $ \u -> 		ifM (youtubeDlTo key u dest p)-			( return (Just UnVerified)+			( postdl UnVerified 			, return Nothing 			) 	dl (us, ytus) = do 		iv <- startVerifyKeyContentIncrementally vc key 		ifM (Url.withUrlOptions $ downloadUrl True key p iv (map fst us) dest) 			( finishVerifyKeyContentIncrementally iv >>= \case-				(True, v) -> return (Just v)+				(True, v) -> postdl v 				(False, _) -> dl ([], ytus) 			, dl ([], ytus) 			) -	isyoutube (_, YoutubeDownloader) = True-	isyoutube _ = False+	postdl v@Verified = return (Just v)+	postdl v+		-- For a VURL key that was not verified on download, +		-- need to generate a hashed key for the content downloaded+		-- from the web, and record it for later use verifying this+		-- content.+		--+		-- But when the VURL key has a known size, and already has a+		-- recorded hashed key, don't record a new key, since the+		-- content on the web is expected to be stable for such a key.+		| fromKey keyVariety key == VURLKey =+			case fromKey keySize key of+				Nothing -> +					getEquivilantKeys key+						>>= recordvurlkey+				Just _ -> do+					eks <- getEquivilantKeys key+					if null eks+						then recordvurlkey eks+						else return (Just v)+		| otherwise = return (Just v)+	+	recordvurlkey eks = do+		-- Make sure to pick a backend that is cryptographically+		-- secure.+		db <- defaultBackend+		let b = if isCryptographicallySecure db+			then db+			else defaultHashBackend+		generateEquivilantKey b (toRawFilePath dest) >>= \case+			Nothing -> return Nothing+			Just ek -> do+				unless (ek `elem` eks) $+					setEquivilantKey key ek+				return (Just Verified)  uploadKey :: Key -> AssociatedFile -> MeterUpdate -> Annex () uploadKey _ _ _ = giveup "upload to web not supported"
Remote/WebDAV.hs view
@@ -104,7 +104,7 @@ 				, versionedExport = False 				, removeExportDirectory = Just $ 					removeExportDirectoryDav hdl-				, renameExport = renameExportDav hdl+				, renameExport = Just $ renameExportDav hdl 				} 			, importActions = importUnsupported 			, whereisKey = Nothing
Types/Backend.hs view
@@ -2,7 +2,7 @@  -  - Most things should not need this, using Types instead  -- - Copyright 2010-2021 Joey Hess <id@joeyh.name>+ - Copyright 2010-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -29,12 +29,17 @@ 	, canUpgradeKey :: Maybe (Key -> Bool) 	-- Checks if there is a fast way to migrate a key to a different 	-- backend (ie, without re-hashing).-	, fastMigrate :: Maybe (Key -> BackendA a -> AssociatedFile -> a (Maybe Key))+	-- The Bool is true if the content of the key has been verified to+	-- be inAnnex.+	, fastMigrate :: Maybe (Key -> BackendA a -> AssociatedFile -> Bool -> a (Maybe Key)) 	-- Checks if a key is known (or assumed) to always refer to the 	-- same data. 	, isStableKey :: Key -> Bool-	-- Checks if a key is verified using a cryptographically secure hash.+	-- Are all keys using this backend verified using a cryptographically+	-- secure hash? 	, isCryptographicallySecure :: Bool+	-- Checks if a key is verified using a cryptographically secure hash.+	, isCryptographicallySecureKey :: Key -> a Bool 	}  instance Show (BackendA a) where
Types/GitConfig.hs view
@@ -136,6 +136,7 @@ 	, annexAllowedIPAddresses :: String 	, annexAllowUnverifiedDownloads :: Bool 	, annexMaxExtensionLength :: Maybe Int+	, annexMaxExtensions :: Maybe Int 	, annexJobs :: Concurrency 	, annexCacheCreds :: Bool 	, annexAutoUpgradeRepository :: Bool@@ -244,6 +245,7 @@ 	, annexAllowUnverifiedDownloads = (== Just "ACKTHPPT") $ 		getmaybe (annexConfig "security.allow-unverified-downloads") 	, annexMaxExtensionLength = getmayberead (annexConfig "maxextensionlength")+	, annexMaxExtensions = getmayberead (annexConfig "maxextensions") 	, annexJobs = fromMaybe NonConcurrent $  		parseConcurrency =<< getmaybe (annexConfig "jobs") 	, annexCacheCreds = getbool (annexConfig "cachecreds") True
Types/Key.hs view
@@ -1,6 +1,6 @@ {- git-annex Key data type  -- - Copyright 2011-2020 Joey Hess <id@joeyh.name>+ - Copyright 2011-2024 Joey Hess <id@joeyh.name>  -  - Licensed under the GNU AGPL version 3 or higher.  -}@@ -134,12 +134,12 @@ 	c ?: (Just b) = sepbefore (char7 c <> b) 	_ ?: Nothing = mempty -{- This is a strict parser for security reasons; a key- - can contain only 4 fields, which all consist only of numbers.+{- This is a strict parser for security reasons; in addition to keyName,+ - a key can contain only 4 fields, which all consist only of numbers.  - Any key containing other fields, or non-numeric data will fail  - to parse.  -- - If a key contained non-numeric fields, they could be used to+ - If a key contained other non-numeric fields, they could be used to  - embed data used in a SHA1 collision attack, which would be a  - problem since the keys are committed to git.  -}@@ -218,6 +218,7 @@ 	| MD5Key HasExt 	| WORMKey 	| URLKey+	| VURLKey 	-- A key that is handled by some external backend. 	| ExternalKey S.ByteString HasExt  	-- Some repositories may contain keys of other varieties,@@ -251,6 +252,7 @@ hasExt (MD5Key (HasExt b)) = b hasExt WORMKey = False hasExt URLKey = False+hasExt VURLKey = False hasExt (ExternalKey _ (HasExt b)) = b hasExt (OtherKey s) = (snd <$> S8.unsnoc s) == Just 'E' @@ -279,6 +281,7 @@ 	MD5Key e -> adde e "MD5" 	WORMKey -> "WORM" 	URLKey -> "URL"+	VURLKey -> "VURL" 	ExternalKey s e -> adde e ("X" <> s) 	OtherKey s -> s   where@@ -343,6 +346,7 @@ parseKeyVariety "MD5E"         = MD5Key (HasExt True) parseKeyVariety "WORM"         = WORMKey parseKeyVariety "URL"          = URLKey+parseKeyVariety "VURL"         = VURLKey parseKeyVariety b 	| "X" `S.isPrefixOf` b =  		let b' = S.tail b
Types/Remote.hs view
@@ -302,7 +302,7 @@ 	-- 	-- Throws an exception if the remote cannot be accessed, or 	-- the file doesn't exist or cannot be renamed.-	, renameExport :: Key -> ExportLocation -> ExportLocation -> a (Maybe ())+	, renameExport :: Maybe (Key -> ExportLocation -> ExportLocation -> a (Maybe ())) 	}  data ImportActions a = ImportActions
Utility/FileSystemEncoding.hs view
@@ -21,17 +21,18 @@ 	truncateFilePath, ) where -import qualified GHC.Foreign as GHC import qualified GHC.IO.Encoding as Encoding import System.IO-import System.IO.Unsafe import System.FilePath.ByteString (RawFilePath, encodeFilePath, decodeFilePath) import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as L-import Data.ByteString.Unsafe (unsafePackMallocCStringLen) #ifdef mingw32_HOST_OS import qualified Data.ByteString.UTF8 as S8 import qualified Data.ByteString.Lazy.UTF8 as L8+#else+import qualified GHC.Foreign as GHC+import System.IO.Unsafe+import Data.ByteString.Unsafe (unsafePackMallocCStringLen) #endif  {- Makes all subsequent Handles that are opened, as well as stdio Handles,
Utility/MoveFile.hs view
@@ -31,9 +31,6 @@ import qualified Utility.RawFilePath as R import Author -copyright :: Copyright-copyright = author JoeyHess (2022-11)- {- Moves one filename to another.  - First tries a rename, but falls back to moving across devices if needed. -} moveFile :: RawFilePath -> RawFilePath -> IO ()@@ -80,4 +77,7 @@ 		case r of 			(Left _) -> return False 			(Right s) -> return $ isDirectory s++copyright :: Copyright+copyright = author JoeyHess (2022-11) #endif
Utility/StatelessOpenPGP.hs view
@@ -29,11 +29,15 @@ import Utility.Tmp #endif import Utility.Tmp.Dir+import Author  import Control.Concurrent.Async import Control.Monad.IO.Class import qualified Data.ByteString as B +copyright :: Copyright+copyright = author JoeyHess (max 2024 2009)+ {- The command to run, eq sqop. -} newtype SOPCmd = SOPCmd { unSOPCmd :: String } @@ -148,7 +152,8 @@ 		return (passwordfd, frompipe, toh, t) 	let cleanup (_, frompipe, toh, t) = liftIO $ do 		closeFd frompipe-		hClose toh+		when copyright $+			hClose toh 		cancel t 	bracket setup cleanup $ \(passwordfd, _, _, _) -> 		go (Just emptydirectory) (passwordfd ++ params)@@ -186,7 +191,7 @@ 			Just (EmptyDirectory d) -> Just d 			Nothing -> Nothing 		}-	bracket (setup p) cleanup (go p)+	copyright =<< bracket (setup p) cleanup (go p)   where 	setup = liftIO . createProcess 	cleanup = liftIO . cleanupProcess
Utility/Terminal.hs view
@@ -16,7 +16,6 @@ import System.IO #ifdef mingw32_HOST_OS import System.Win32.MinTTY (isMinTTYHandle)-import System.Win32.File import System.Win32.Types import Graphics.Win32.Misc import Control.Exception@@ -41,5 +40,5 @@ 				then return (IsTerminal False) 				else do 					b' <- isMinTTYHandle h'-					return (IsTerminal b)+					return (IsTerminal b') #endif
Utility/ThreadScheduler.hs view
@@ -1,7 +1,6 @@ {- thread scheduling  -- - Copyright 2012, 2013 Joey Hess <id@joeyh.name>- - Copyright 2011 Bas van Dijk & Roel van Dijk+ - Copyright 2012-2024 Joey Hess <id@joeyh.name>  -  - License: BSD-2-clause  -}@@ -19,8 +18,9 @@ ) where  import Control.Monad-import Control.Concurrent+import qualified Control.Concurrent.Thread.Delay as Unbounded #ifndef mingw32_HOST_OS+import Control.Concurrent import Control.Monad.IfElse import System.Posix.IO #endif@@ -44,20 +44,9 @@ threadDelaySeconds :: Seconds -> IO () threadDelaySeconds (Seconds n) = unboundDelay (fromIntegral n * oneSecond) -{- Like threadDelay, but not bounded by an Int.- -- - There is no guarantee that the thread will be rescheduled promptly when the- - delay has expired, but the thread will never continue to run earlier than- - specified.- - - - Taken from the unbounded-delay package to avoid a dependency for 4 lines- - of code.- -}+{- Like threadDelay, but not bounded by an Int. -} unboundDelay :: Microseconds -> IO ()-unboundDelay time = do-	let maxWait = min time $ toInteger (maxBound :: Int)-	threadDelay $ fromInteger maxWait-	when (maxWait /= time) $ unboundDelay (time - maxWait)+unboundDelay = Unbounded.delay  {- Pauses the main thread, letting children run until program termination. -} waitForTermination :: IO ()
git-annex.cabal view
@@ -1,5 +1,5 @@ Name: git-annex-Version: 10.20240227+Version: 10.20240430 Cabal-Version: 1.12 License: AGPL-3 Maintainer: Joey Hess <id@joeyh.name>@@ -267,6 +267,7 @@    split,    attoparsec (>= 0.13.2.2),    concurrent-output (>= 1.10),+   unbounded-delays,    QuickCheck (>= 2.10.0),    tasty (>= 1.2),    tasty-hunit,@@ -582,6 +583,9 @@     Backend.Hash     Backend.URL     Backend.Utilities+    Backend.Variety+    Backend.VURL+    Backend.VURL.Utilities     Backend.WORM     Benchmark     Build.BundledPrograms@@ -681,6 +685,7 @@     Command.ReadPresentKey     Command.RecvKey     Command.RegisterUrl+    Command.ReregisterUrl     Command.Reinit     Command.Reinject     Command.RemoteDaemon@@ -811,6 +816,7 @@     Logs.ContentIdentifier.Pure     Logs.Difference     Logs.Difference.Pure+    Logs.EquivilantKeys     Logs.Export     Logs.Export.Pure     Logs.File@@ -894,6 +900,7 @@     Remote.List     Remote.List.Util     Remote.P2P+    Remote.Rclone     Remote.Rsync     Remote.Rsync.RsyncUrl     Remote.S3