git-annex-10.20260421: Annex/DisableRemote.hs
{- disable a remote
-
- Copyright 2026 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
{-# LANGUAGE OverloadedStrings #-}
module Annex.DisableRemote (disableRemote) where
import Git
import Annex.Common
import qualified Annex
import qualified Git.Remote.Remove
import qualified Git.Ref
import Types.Remote
import Annex.Journal
import qualified Annex.Branch
import Annex.Branch.Transitions
import Types.Transitions
import Logs
import Logs.Remote.Pure
import Logs.MapLog
import Logs.Transfer
import Types.Transfer
import qualified Database.Export
import qualified Database.Fsck
import qualified Database.RepoSize
import qualified Database.ContentIdentifier
import qualified Utility.OsString as OS
import Data.ByteString.Builder
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.ByteString.Lazy as L
disableRemote :: Remote -> RemoteName -> [Remote] -> Annex ()
disableRemote r remotename remotelist = do
let uniqueuuid = not $
any (\r' -> uuid r' == uuid r && name r' /= name r) remotelist
when uniqueuuid $
-- If there are transfers to/from the remote still
-- running, this fails. That's why it's run early.
removeTransferLogs (uuid r)
cleanPrivateJournal r uniqueuuid
when uniqueuuid $ do
-- Each uuid has its own export and fsck database,
-- so always remove them, so long as this is the
-- only remote using this uuid.
Database.Export.removeDb (uuid r)
Database.Fsck.removeDb (uuid r)
-- These databases are updated from information
-- in the git-annex branch, so there is no point in
-- removing the uuid from them unless it's private.
whenM (isPrivateUUID (uuid r)) $ do
Database.RepoSize.removeUUID (uuid r)
Database.ContentIdentifier.removeUUID (uuid r) True
removeFsckState (uuid r)
removeImportLog (uuid r)
removeCredFiles (uuid r)
removeRemoteLockFile (uuid r)
removeRemoteStateFile (uuid r)
inRepo $ Git.Remote.Remove.remove remotename
removeRemoteTrackingBranches remotename
-- Remove any remote branches.
-- This is done because git remote remove only removes the configured
-- remote tracking branch, not other remote branches.
removeRemoteTrackingBranches :: String -> Annex ()
removeRemoteTrackingBranches remotename = do
branches <- filter (\b -> branchprefix `isPrefixOf` fromRef b)
. map snd
<$> inRepo Git.Ref.list
forM_ branches $ \b ->
inRepo $ Git.Ref.delete' b
where
branchprefix = "refs/remotes/" ++ remotename ++ "/"
isPrivateUUID :: UUID -> Annex Bool
isPrivateUUID u =
(\c -> u `S.member` annexPrivateRepos c)
<$> Annex.getGitConfig
{- Remove a private remote's uuid from the private journal
- entirely when it is the only remote using that uuid.
-
- And, when the remote is a sameas remote, its config is stored
- under its config-uuid. Remove that from the private remote log.
-}
cleanPrivateJournal :: Remote -> Bool -> Annex ()
cleanPrivateJournal r uniqueuuid
| uniqueuuid == True = do
whenM (isPrivateUUID (uuid r)) $ do
gc <- Annex.getGitConfig
let tc = filterBranch (\u -> u /= uuid r) gc
let handlestale = \_ b -> return (b, Nothing)
lockJournal $ \jl ->
overPrivateJournalFileContents handlestale Just
(go jl tc)
removeconfiguuid
| otherwise = removeconfiguuid
where
go jl tc getfilecontents = getfilecontents >>= \case
Just (_, p, Just (b, _)) -> do
cleaner jl tc p b
go jl tc getfilecontents
Just (_, _, Nothing) ->
go jl tc getfilecontents
Nothing -> return ()
cleaner jl tc p b = case tc p b of
PreserveFile -> return ()
ChangeFile builder ->
setlocaljournal jl (RegardingUUID [uuid r]) p builder
removeconfiguuid = case remoteAnnexConfigUUID (gitconfig r) of
Nothing -> return ()
Just cu -> whenM (isPrivateUUID cu) $
lockJournal $ \jl ->
getJournalFile jl (GetPrivate True) remoteLog >>= \case
JournalledContent b ->
scrub jl cu b
PossiblyStaleJournalledContent b ->
scrub jl cu b
NoJournalledContent ->
return ()
where
scrub jl cu b =
setlocaljournal jl (RegardingUUID [cu]) remoteLog $
buildRemoteConfigLog $ MapLog $ M.delete cu $
fromMapLog $ parseRemoteConfigLog b
setlocaljournal jl ru p builder =
let b' = toLazyByteString builder
in if L.null b'
then deleteJournalFile jl ru p
else do
b'' <- Annex.Branch.getLocal' (GetPrivate False) p
if b'' == b'
then deleteJournalFile jl ru p
else setJournalFile jl ru p builder
removeFsckState :: UUID -> Annex ()
removeFsckState u = do
d <- fromRepo (gitAnnexFsckStateDir u)
liftIO $ whenM (doesDirectoryExist d) $
removeDirectoryRecursive d
f <- fromRepo (gitAnnexFsckResultsLog u)
liftIO $ removeWhenExistsWith removeFile f
removeImportLog :: UUID -> Annex ()
removeImportLog u = do
f <- calcRepo' (gitAnnexImportLog u)
liftIO $ removeWhenExistsWith removeFile f
removeTransferLogs :: UUID -> Annex ()
removeTransferLogs u = do
-- getTransfers calls checkTransfer, which cleans up
-- transfer log files for transfers that are no longer running.
whenM (any foru <$> getTransfers) $
error "Active trasfers, cannot disable the remote."
forM_ [Upload, Download] $ \direction -> do
d <- fromRepo $ gitAnnexTransferUUIDDirectionDir u direction
liftIO $ void $ tryNonAsync $ removeDirectory d
d' <- fromRepo $ gitAnnexFailedTransferDir u direction
liftIO $ void $ tryNonAsync $ removeDirectory d'
clearFailedTransfers u
where
foru (t, _) = transferUUID t == u
removeCredFiles :: UUID -> Annex ()
removeCredFiles u = do
d <- fromRepo gitAnnexCredsDir
liftIO $ whenM (doesDirectoryExist d) $ do
mapM_ (removeWhenExistsWith removeFile)
=<< filter foru <$> dirContents d
where
us = fromUUID u
-- Remotes that use creds always include their UUID as part of the
-- filename. However, some remotes (Remote.External) need more than
-- one creds file, and add a "-foo" suffix to the UUID.
foru p =
let f = takeFileName p
in f == us || (us <> literalOsPath "-") `OS.isPrefixOf` f
removeRemoteLockFile :: UUID -> Annex ()
removeRemoteLockFile u = do
f <- fromRepo $ gitAnnexRemoteLockFile u
liftIO $ removeWhenExistsWith removeFile f
removeRemoteStateFile :: UUID -> Annex ()
removeRemoteStateFile u = do
f <- fromRepo $ gitAnnexRemoteStateFile u
liftIO $ removeWhenExistsWith removeFile f