git-annex 10.20250416 → 10.20250520
raw patch · 12 files changed
+122/−63 lines, 12 files
Files
- Annex.hs +2/−0
- Annex/NumCopies.hs +13/−7
- CHANGELOG +16/−0
- Command/Map.hs +10/−10
- Command/Smudge.hs +0/−2
- Command/WhereUsed.hs +1/−1
- Git.hs +0/−2
- Git/Construct.hs +32/−13
- Limit.hs +45/−26
- Logs/File.hs +1/−0
- Remote/Bup.hs +1/−1
- git-annex.cabal +1/−1
Annex.hs view
@@ -197,6 +197,7 @@ , checkignorehandle :: Maybe (ResourcePool CheckIgnoreHandle) , globalnumcopies :: Maybe (Maybe NumCopies) , globalmincopies :: Maybe (Maybe MinCopies)+ , nummincopiesattrcache :: Maybe (OsPath, (Maybe NumCopies, Maybe MinCopies)) , limit :: ExpandableMatcher Annex , timelimit :: Maybe (Duration, POSIXTime) , sizelimit :: Maybe (TVar Integer)@@ -253,6 +254,7 @@ , checkignorehandle = Nothing , globalnumcopies = Nothing , globalmincopies = Nothing+ , nummincopiesattrcache = Nothing , limit = BuildingMatcher [] , timelimit = Nothing , sizelimit = Nothing
Annex/NumCopies.hs view
@@ -1,6 +1,6 @@ {- git-annex numcopies configuration and checking -- - Copyright 2014-2024 Joey Hess <id@joeyh.name>+ - Copyright 2014-2025 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -181,12 +181,18 @@ ] getNumMinCopiesAttr :: OsPath -> Annex (Maybe NumCopies, Maybe MinCopies)-getNumMinCopiesAttr file =- checkAttrs ["annex.numcopies", "annex.mincopies"] file >>= \case- (n:m:[]) -> return- ( configuredNumCopies <$> readish n- , configuredMinCopies <$> readish m- )+getNumMinCopiesAttr file = Annex.getState Annex.nummincopiesattrcache >>= \case+ Just (cfile, v) | cfile == file -> return v+ _ -> checkAttrs ["annex.numcopies", "annex.mincopies"] file >>= \case+ (n:m:[]) -> do+ let v =+ ( configuredNumCopies <$> readish n+ , configuredMinCopies <$> readish m+ )+ Annex.changeState $ \s -> s + { Annex.nummincopiesattrcache = Just (file, v)+ }+ return v _ -> error "internal" {- Checks if numcopies are satisfied for a file by running a comparison
CHANGELOG view
@@ -1,3 +1,19 @@+git-annex (10.20250520) upstream; urgency=medium++ * Preferred content now supports "balanced=groupname:lackingcopies"+ to make files be evenly balanced amoung as many repositories as are+ needed to satisfy numcopies.+ * map: Fix buggy handling of remotes that are bare git repositories+ accessed via ssh.+ * map: Avoid looping forever with mutually recursive paths between+ repositories accessed via ssh.+ * whereused: Fix bug that could find matches from grafts + in remote git-annex branches.+ * Windows: Fix bug that can cause git status to show annexed files as+ modified when built with OsPath.++ -- Joey Hess <id@joeyh.name> Tue, 20 May 2025 10:25:47 -0400+ git-annex (10.20250416) upstream; urgency=medium * Added the mask special remote.
Command/Map.hs view
@@ -1,6 +1,6 @@ {- git-annex command -- - Copyright 2010 Joey Hess <id@joeyh.name>+ - Copyright 2010-2025 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -26,9 +26,6 @@ import qualified Remote.Helper.Ssh as Ssh import qualified Utility.Dot as Dot --- a link from the first repository to the second (its remote)-data Link = Link Git.Repo Git.Repo- -- a repo and its remotes type RepoRemotes = (Git.Repo, [Git.Repo]) @@ -68,7 +65,7 @@ return True runViewer file ((c, ps):rest) = ifM (liftIO $ inSearchPath c) ( do- showLongNote $ UnquotedString $ "running: " ++ c ++ unwords (toCommand ps)+ showLongNote $ UnquotedString $ "running: " ++ c ++ " " ++ unwords (toCommand ps) showOutput liftIO $ boolSystem c ps , runViewer file rest@@ -157,7 +154,7 @@ Just DeadTrusted -> Dot.fillColor "grey" s Nothing -> Dot.fillColor "white" s -{- Recursively searches out remotes starting with the specified repo. -}+{- Recursively searches out remotes starting with the specified local repo. -} spider :: Git.Repo -> Annex [RepoRemotes] spider r = spider' [r] [] spider' :: [Git.Repo] -> [RepoRemotes] -> Annex [RepoRemotes]@@ -169,15 +166,18 @@ -- The remotes will be relative to r', and need to be -- made absolute for later use.- remotes <- mapM (absRepo r')- =<< (liftIO $ Git.Construct.fromRemotes r')- + remotes <- mapM (absRepo r') =<<+ if Git.repoIsUrl r+ then liftIO $ Git.Construct.fromRemoteUrlRemotes r'+ else liftIO $ Git.Construct.fromRemotes r'+ spider' (rs ++ remotes) ((r', remotes):known) {- Converts repos to a common absolute form. -} absRepo :: Git.Repo -> Git.Repo -> Annex Git.Repo absRepo reference r- | Git.repoIsUrl reference = return $ Git.Construct.localToUrl reference r+ | Git.repoIsUrl reference = return $+ Git.Construct.localToUrl reference r | Git.repoIsUrl r = return r | otherwise = liftIO $ do r' <- Git.Construct.fromPath =<< absPath (Git.repoPath r)
Command/Smudge.hs view
@@ -112,7 +112,6 @@ discardreststdin emitpointer stop- where -- Handles everything except the IO of the file content. clean'@@ -135,7 +134,6 @@ , inSmudgeCleanFilter go ) where- go = case mk of Right (Just k) -> do addingExistingLink file k $ do
Command/WhereUsed.hs view
@@ -89,7 +89,7 @@ [ Param ("--exclude=*/" ++ fromRef (Annex.Branch.name)) , Param "--glob=*" -- Also search remote branches- , Param ("--exclude=" ++ fromRef (Annex.Branch.name))+ , Param ("--exclude=*/" ++ fromRef (Annex.Branch.name)) , Param "--remotes=*" -- And search tags. , Param "--tags=*"
Git.hs view
@@ -89,8 +89,6 @@ localGitDir Repo { location = Local { gitdir = d } } = d localGitDir _ = giveup "unknown localGitDir" -{- Some code needs to vary between URL and normal repos,- - or bare and non-bare, these functions help with that. -} repoIsUrl :: Repo -> Bool repoIsUrl Repo { location = Url _ } = True repoIsUrl Repo { location = UnparseableUrl _ } = True
Git/Construct.hs view
@@ -1,6 +1,6 @@ {- Construction of Git Repo objects -- - Copyright 2010-2023 Joey Hess <id@joeyh.name>+ - Copyright 2010-2025 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -18,6 +18,7 @@ remoteNamed, remoteNamedFromKey, fromRemotes,+ fromRemoteUrlRemotes, fromRemoteLocation, repoAbsPath, checkForRepo,@@ -97,14 +98,15 @@ - or is invalid, because git can also function despite remotes having - such urls, only failing if such a remote is used. -}-fromUrl :: String -> IO Repo-fromUrl url- | not (isURI url) = fromUrl' $ escapeURIString isUnescapedInURI url- | otherwise = fromUrl' url+fromUrl :: Bool -> String -> IO Repo+fromUrl fileurlislocal url+ | not (isURI url) = fromUrl' fileurlislocal $+ escapeURIString isUnescapedInURI url+ | otherwise = fromUrl' fileurlislocal url -fromUrl' :: String -> IO Repo-fromUrl' url- | "file://" `isPrefixOf` url = case parseURIPortable url of+fromUrl' :: Bool -> String -> IO Repo+fromUrl' fileurlislocal url+ | "file://" `isPrefixOf` url && fileurlislocal = case parseURIPortable url of Just u -> fromAbsPath $ toOsPath $ unEscapeString $ uriPath u Nothing -> pure $ newFrom $ UnparseableUrl url | otherwise = case parseURIPortable url of@@ -123,25 +125,42 @@ | repoIsUrl r = r | otherwise = case (Url.authority reference, Url.scheme reference) of (Just auth, Just s) -> - let absurl = concat+ let referencepath = fromMaybe "" $ Url.path reference+ absurl = concat [ s , "//" , auth- , fromOsPath (repoPath r)+ , fromOsPath $ simplifyPath $+ toOsPath referencepath </> repoPath r ] in r { location = Url $ fromJust $ parseURIPortable absurl } _ -> r {- Calculates a list of a repo's configured remotes, by parsing its config. -} fromRemotes :: Repo -> IO [Repo]-fromRemotes repo = catMaybes <$> mapM construct remotepairs+fromRemotes = fromRemotes' fromRemoteLocation++fromRemotes' :: (String -> Bool -> Repo -> IO Repo) -> Repo -> IO [Repo]+fromRemotes' fromremotelocation repo = catMaybes <$> mapM construct remotepairs where filterconfig f = filter f $ M.toList $ config repo filterkeys f = filterconfig (\(k,_) -> f k) remotepairs = filterkeys isRemoteUrlKey construct (k,v) = remoteNamedFromKey k $- fromRemoteLocation (fromConfigValue v) False repo+ fromremotelocation (fromConfigValue v) False repo +{- Calculates a list of a remote repo's configured remotes, by parsing its+ - config. Unlike fromRemotes, this does not do any local path checking. + - The remote repo must have an url path. -}+fromRemoteUrlRemotes :: Repo -> IO [Repo]+fromRemoteUrlRemotes = fromRemotes' go+ where+ go s knownurl repo = + case parseRemoteLocation s knownurl repo of+ RemotePath p -> pure $ localToUrl repo $ + newFrom $ LocalUnknown $ toOsPath p+ RemoteUrl u -> fromUrl False u+ {- Sets the name of a remote when constructing the Repo to represent it. -} remoteNamed :: String -> IO Repo -> IO Repo remoteNamed n constructor = do@@ -167,7 +186,7 @@ fromRemoteLocation s knownurl repo = gen $ parseRemoteLocation s knownurl repo where gen (RemotePath p) = fromRemotePath p repo- gen (RemoteUrl u) = fromUrl u+ gen (RemoteUrl u) = fromUrl True u {- Constructs a Repo from the path specified in the git remotes of - another Repo. -}
Limit.hs view
@@ -1,6 +1,6 @@ {- user-specified limits on files to act on -- - Copyright 2011-2024 Joey Hess <id@joeyh.name>+ - Copyright 2011-2025 Joey Hess <id@joeyh.name> - - Licensed under the GNU AGPL version 3 or higher. -}@@ -459,19 +459,26 @@ } Nothing -> Left "bad value for number of lacking copies" where- go mi needed notpresent key = do- numcopies <- if approx- then approxNumCopies- else case mi of- MatchingFile fi -> getGlobalFileNumCopies $- matchFile fi- MatchingInfo {} -> approxNumCopies- MatchingUserInfo {} -> approxNumCopies- us <- filter (`S.notMember` notpresent)- <$> (trustExclude UnTrusted =<< Remote.keyLocations key)- let vs nhave numcopies' = numcopies' - nhave >= needed- return $ numCopiesCheck'' us vs numcopies+ go mi needed notpresent key =+ limitCheckNumCopies approx mi notpresent key vs+ where+ vs nhave numcopies' = numcopies' - nhave >= needed++limitCheckNumCopies :: Bool -> MatchInfo -> AssumeNotPresent -> Key -> (Int -> Int -> v) -> Annex v+limitCheckNumCopies approx mi notpresent key vs = do+ numcopies <- if approx+ then approxNumCopies+ else case mi of+ MatchingFile fi -> getGlobalFileNumCopies $+ matchFile fi+ MatchingInfo {} -> approxNumCopies+ MatchingUserInfo {} -> approxNumCopies+ us <- filter (`S.notMember` notpresent)+ <$> (trustExclude UnTrusted =<< Remote.keyLocations key)+ return $ numCopiesCheck'' us vs numcopies+ where approxNumCopies = fromMaybe defaultNumCopies <$> getGlobalNumCopies+ {- Match keys that are unused. - @@ -597,17 +604,21 @@ limitBalanced' :: String -> MatchFiles Annex -> Maybe UUID -> MkLimit Annex limitBalanced' termname fullybalanced mu groupname = do- copies <- limitCopies $ if ':' `elem` groupname- then groupname- else groupname ++ ":1"+ let checknumcopies = ":lackingcopies" `isSuffixOf` groupname+ enoughcopies <- if checknumcopies+ then limitLackingCopies termname False "1"+ else limitCopies $ if ':' `elem` groupname+ then groupname+ else groupname ++ ":1"+ let checkenoughcopies = if checknumcopies then id else not let present = limitPresent mu- let combo f = f present || f fullybalanced || f copies+ let combo f = f present || f fullybalanced || f enoughcopies Right $ MatchFiles { matchAction = \lu a i -> ifM (Annex.getRead Annex.rebalance) ( matchAction fullybalanced lu a i , matchAction present lu a i <||>- ((not <$> matchAction copies lu a i)+ ((checkenoughcopies <$> matchAction enoughcopies lu a i) <&&> matchAction fullybalanced lu a i ) )@@ -667,11 +678,16 @@ -> MkLimit Annex limitFullyBalanced'' filtercandidates termname mu getgroupmap want = case splitc ':' want of- [g] -> go g 1- [g, n] -> maybe- (Left $ "bad number for " ++ termname)- (go g)- (readish n)+ [g] -> go g (Right 1)+ [g, n]+ | n == "lackingcopies" -> go g $ + Left $ \mi notpresent key -> + let vs nhave numcopies = numcopies - nhave+ in limitCheckNumCopies False mi notpresent key vs+ | otherwise -> maybe+ (Left $ "bad number for " ++ termname)+ (go g . Right)+ (readish n) _ -> Left $ "bad value for " ++ termname where go s n = limitFullyBalanced''' filtercandidates termname mu@@ -683,13 +699,16 @@ -> Maybe UUID -> Annex GroupMap -> Group- -> Int+ -> Either (MatchInfo -> AssumeNotPresent -> Key -> Annex Int) Int -> MkLimit Annex-limitFullyBalanced''' filtercandidates termname mu getgroupmap g n want = Right $ MatchFiles- { matchAction = \lu -> const $ checkKey $ \key -> do+limitFullyBalanced''' filtercandidates termname mu getgroupmap g getn want = Right $ MatchFiles+ { matchAction = \lu notpresent mi -> flip checkKey mi $ \key -> do gm <- getgroupmap let groupmembers = fromMaybe S.empty $ M.lookup g (uuidsByGroup gm)+ n <- case getn of+ Right n -> pure n+ Left a -> a mi notpresent key candidates <- filtercandidates n key groupmembers let wanted = if S.null candidates then False
Logs/File.hs view
@@ -145,6 +145,7 @@ cleanup (Just h) = liftIO $ hClose h go Nothing = finalizer go (Just h) = do+ liftIO $ fileEncoding h mapM_ processor =<< liftIO (lines <$> hGetContents h) liftIO $ hClose h finalizer
Remote/Bup.hs view
@@ -304,7 +304,7 @@ if "/" `isPrefixOf` r then Git.Construct.fromPath (toOsPath r) else giveup "please specify an absolute path"- | otherwise = Git.Construct.fromUrl $ "ssh://" ++ host ++ slash dir+ | otherwise = Git.Construct.fromUrl False $ "ssh://" ++ host ++ slash dir where bits = splitc ':' r host = fromMaybe "" $ headMaybe bits
git-annex.cabal view
@@ -1,5 +1,5 @@ Name: git-annex-Version: 10.20250416+Version: 10.20250520 Cabal-Version: 1.12 License: AGPL-3 Maintainer: Joey Hess <id@joeyh.name>