1{- git-annex monad
2 -
3 - Copyright 2010-2021 Joey Hess <id@joeyh.name>
4 -
5 - Licensed under the GNU AGPL version 3 or higher.
6 -}
7
8{-# LANGUAGE GeneralizedNewtypeDeriving, BangPatterns #-}
9
10module Annex (
11	Annex,
12	AnnexState(..),
13	AnnexRead(..),
14	new,
15	run,
16	eval,
17	makeRunner,
18	getRead,
19	getState,
20	changeState,
21	withState,
22	setFlag,
23	setField,
24	setOutput,
25	getFlag,
26	getField,
27	addCleanupAction,
28	gitRepo,
29	inRepo,
30	fromRepo,
31	calcRepo,
32	getGitConfig,
33	overrideGitConfig,
34	changeGitRepo,
35	adjustGitRepo,
36	addGitConfigOverride,
37	getGitConfigOverrides,
38	getRemoteGitConfig,
39	withCurrentState,
40	changeDirectory,
41	getGitRemotes,
42	incError,
43) where
44
45import Common
46import qualified Git
47import qualified Git.Config
48import qualified Git.Construct
49import Annex.Fixup
50import Git.HashObject
51import Git.CheckAttr
52import Git.CheckIgnore
53import qualified Git.Hook
54import qualified Git.Queue
55import Types.Key
56import Types.Backend
57import Types.GitConfig
58import qualified Types.Remote
59import Types.Crypto
60import Types.BranchState
61import Types.TrustLevel
62import Types.Group
63import Types.Messages
64import Types.Concurrency
65import Types.UUID
66import Types.FileMatcher
67import Types.NumCopies
68import Types.LockCache
69import Types.DesktopNotify
70import Types.CleanupActions
71import Types.AdjustedBranch
72import Types.WorkerPool
73import Types.IndexFiles
74import Types.CatFileHandles
75import Types.RemoteConfig
76import Types.TransferrerPool
77import Types.VectorClock
78import Annex.VectorClock.Utility
79import Annex.Debug.Utility
80import qualified Database.Keys.Handle as Keys
81import Utility.InodeCache
82import Utility.Url
83import Utility.ResourcePool
84import Utility.HumanTime
85
86import "mtl" Control.Monad.Reader
87import Control.Concurrent
88import Control.Concurrent.STM
89import qualified Control.Monad.Fail as Fail
90import qualified Data.Map.Strict as M
91import qualified Data.Set as S
92import Data.Time.Clock.POSIX
93
94{- git-annex's monad is a ReaderT around an AnnexState stored in a MVar,
95 - and an AnnexRead. The MVar is not exposed outside this module.
96 -
97 - Note that when an Annex action fails and the exception is caught,
98 - any changes the action has made to the AnnexState are retained,
99 - due to the use of the MVar to store the state.
100 -}
101newtype Annex a = Annex { runAnnex :: ReaderT (MVar AnnexState, AnnexRead) IO a }
102	deriving (
103		Monad,
104		MonadIO,
105		MonadReader (MVar AnnexState, AnnexRead),
106		MonadCatch,
107		MonadThrow,
108		MonadMask,
109		Fail.MonadFail,
110		Functor,
111		Applicative,
112		Alternative
113	)
114
115-- Values that can be read, but not modified by an Annex action.
116data AnnexRead = AnnexRead
117	{ activekeys :: TVar (M.Map Key ThreadId)
118	, activeremotes :: MVar (M.Map (Types.Remote.RemoteA Annex) Integer)
119	, keysdbhandle :: Keys.DbHandle
120	, sshstalecleaned :: TMVar Bool
121	, signalactions :: TVar (M.Map SignalAction (Int -> IO ()))
122	, transferrerpool :: TransferrerPool
123	, debugenabled :: Bool
124	, debugselector :: DebugSelector
125	, ciphers :: TMVar (M.Map StorableCipher Cipher)
126	}
127
128newAnnexRead :: GitConfig -> IO AnnexRead
129newAnnexRead c = do
130	emptyactivekeys <- newTVarIO M.empty
131	emptyactiveremotes <- newMVar M.empty
132	kh <- Keys.newDbHandle
133	sc <- newTMVarIO False
134	si <- newTVarIO M.empty
135	tp <- newTransferrerPool
136	cm <- newTMVarIO M.empty
137	return $ AnnexRead
138		{ activekeys = emptyactivekeys
139		, activeremotes = emptyactiveremotes
140		, keysdbhandle = kh
141		, sshstalecleaned = sc
142		, signalactions = si
143		, transferrerpool = tp
144		, debugenabled = annexDebug c
145		, debugselector = debugSelectorFromGitConfig c
146		, ciphers = cm
147		}
148
149-- Values that can change while running an Annex action.
150data AnnexState = AnnexState
151	{ repo :: Git.Repo
152	, repoadjustment :: (Git.Repo -> IO Git.Repo)
153	, gitconfig :: GitConfig
154	, gitconfigadjustment :: (GitConfig -> GitConfig)
155	, gitconfigoverride :: [String]
156	, gitremotes :: Maybe [Git.Repo]
157backend :: Maybe (BackendA Annex)
158	, remotes :: [Types.Remote.RemoteA Annex]
159	, output :: MessageState
160	, concurrency :: ConcurrencySetting
161	, force :: Bool
162	, fast :: Bool
163	, daemon :: Bool
164	, branchstate :: BranchState
165	, repoqueue :: Maybe (Git.Queue.Queue Annex)
166	, catfilehandles :: CatFileHandles
167	, hashobjecthandle :: Maybe HashObjectHandle
168	, checkattrhandle :: Maybe (ResourcePool CheckAttrHandle)
169	, checkignorehandle :: Maybe (ResourcePool CheckIgnoreHandle)
170	, forcebackend :: Maybe String
171	, globalnumcopies :: Maybe NumCopies
172	, globalmincopies :: Maybe MinCopies
173	, forcenumcopies :: Maybe NumCopies
174	, forcemincopies :: Maybe MinCopies
175	, limit :: ExpandableMatcher Annex
176	, timelimit :: Maybe (Duration, POSIXTime)
177	, sizelimit :: Maybe (TVar Integer)
178	, uuiddescmap :: Maybe UUIDDescMap
179	, preferredcontentmap :: Maybe (FileMatcherMap Annex)
180	, requiredcontentmap :: Maybe (FileMatcherMap Annex)
181	, remoteconfigmap :: Maybe (M.Map UUID RemoteConfig)
182	, forcetrust :: TrustMap
183	, trustmap :: Maybe TrustMap
184	, groupmap :: Maybe GroupMap
185	, lockcache :: LockCache
186	, flags :: M.Map String Bool
187	, fields :: M.Map String String
188	, cleanupactions :: M.Map CleanupAction (Annex ())
189	, sentinalstatus :: Maybe SentinalStatus
190	, useragent :: Maybe String
191	, errcounter :: Integer
192	, skippedfiles :: Bool
193	, adjustedbranchrefreshcounter :: Integer
194	, unusedkeys :: Maybe (S.Set Key)
195	, tempurls :: M.Map Key URLString
196	, existinghooks :: M.Map Git.Hook.Hook Bool
197	, desktopnotify :: DesktopNotify
198	, workers :: Maybe (TMVar (WorkerPool (AnnexState, AnnexRead)))
199	, cachedcurrentbranch :: (Maybe (Maybe Git.Branch, Maybe Adjustment))
200	, cachedgitenv :: Maybe (AltIndexFile, FilePath, [(String, String)])
201	, urloptions :: Maybe UrlOptions
202	, insmudgecleanfilter :: Bool
203	, getvectorclock :: IO CandidateVectorClock
204	}
205
206newAnnexState :: GitConfig -> Git.Repo -> IO AnnexState
207newAnnexState c r = do
208	o <- newMessageState
209	vc <- startVectorClock
210	return $ AnnexState
211		{ repo = r
212		, repoadjustment = return
213		, gitconfig = c
214		, gitconfigadjustment = id
215		, gitconfigoverride = []
216		, gitremotes = Nothing
217		, backend = Nothing
218		, remotes = []
219		, output = o
220		, concurrency = ConcurrencyCmdLine NonConcurrent
221		, force = False
222		, fast = False
223		, daemon = False
224		, branchstate = startBranchState
225		, repoqueue = Nothing
226		, catfilehandles = catFileHandlesNonConcurrent
227		, hashobjecthandle = Nothing
228		, checkattrhandle = Nothing
229		, checkignorehandle = Nothing
230		, forcebackend = Nothing
231		, globalnumcopies = Nothing
232		, globalmincopies = Nothing
233		, forcenumcopies = Nothing
234		, forcemincopies = Nothing
235		, limit = BuildingMatcher []
236		, timelimit = Nothing
237		, sizelimit = Nothing
238		, uuiddescmap = Nothing
239		, preferredcontentmap = Nothing
240		, requiredcontentmap = Nothing
241		, remoteconfigmap = Nothing
242		, forcetrust = M.empty
243		, trustmap = Nothing
244		, groupmap = Nothing
245		, lockcache = M.empty
246		, flags = M.empty
247		, fields = M.empty
248		, cleanupactions = M.empty
249		, sentinalstatus = Nothing
250		, useragent = Nothing
251		, errcounter = 0
252		, skippedfiles = False
253		, adjustedbranchrefreshcounter = 0
254		, unusedkeys = Nothing
255		, tempurls = M.empty
256		, existinghooks = M.empty
257		, desktopnotify = mempty
258		, workers = Nothing
259		, cachedcurrentbranch = Nothing
260		, cachedgitenv = Nothing
261		, urloptions = Nothing
262		, insmudgecleanfilter = False
263		, getvectorclock = vc
264		}
265
266{- Makes an Annex state object for the specified git repo.
267 - Ensures the config is read, if it was not already, and performs
268 - any necessary git repo fixups. -}
269new :: Git.Repo -> IO (AnnexState, AnnexRead)
270new r = do
271	r' <- Git.Config.read r
272	let c = extractGitConfig FromGitConfig r'
273	st <- newAnnexState c =<< fixupRepo r' c
274	rd <- newAnnexRead c
275	return (st, rd)
276
277{- Performs an action in the Annex monad from a starting state,
278 - returning a new state. -}
279run :: (AnnexState, AnnexRead) -> Annex a -> IO (a, (AnnexState, AnnexRead))
280run (st, rd) a = do
281	mv <- newMVar st
282	run' mv rd a
283
284run' :: MVar AnnexState -> AnnexRead -> Annex a -> IO (a, (AnnexState, AnnexRead))
285run' mvar rd a = do
286	r <- runReaderT (runAnnex a) (mvar, rd)
287		`onException` (flush rd)
288	flush rd
289	st <- takeMVar mvar
290	return (r, (st, rd))
291  where
292	flush = Keys.flushDbQueue . keysdbhandle
293
294{- Performs an action in the Annex monad from a starting state,
295 - and throws away the changed state. -}
296eval :: (AnnexState, AnnexRead) -> Annex a -> IO a
297eval v a = fst <$> run v a
298
299{- Makes a runner action, that allows diving into IO and from inside
300 - the IO action, running an Annex action. -}
301makeRunner :: Annex (Annex a -> IO a)
302makeRunner = do
303	(mvar, rd) <- ask
304	return $ \a -> do
305		(r, (s, _rd)) <- run' mvar rd a
306		putMVar mvar s
307		return r
308
309getRead :: (AnnexRead -> v) -> Annex v
310getRead selector = selector . snd <$> ask
311
312getState :: (AnnexState -> v) -> Annex v
313getState selector = do
314	mvar <- fst <$> ask
315	st <- liftIO $ readMVar mvar
316	return $ selector st
317
318changeState :: (AnnexState -> AnnexState) -> Annex ()
319changeState modifier = do
320	mvar <- fst <$> ask
321	liftIO $ modifyMVar_ mvar $ return . modifier
322
323withState :: (AnnexState -> IO (AnnexState, b)) -> Annex b
324withState modifier = do
325	mvar <- fst <$> ask
326	liftIO $ modifyMVar mvar modifier
327
328{- Sets a flag to True -}
329setFlag :: String -> Annex ()
330setFlag flag = changeState $ \st ->
331	st { flags = M.insert flag True $ flags st }
332
333{- Sets a field to a value -}
334setField :: String -> String -> Annex ()
335setField field value = changeState $ \st ->
336	st { fields = M.insert field value $ fields st }
337
338{- Adds a cleanup action to perform. -}
339addCleanupAction :: CleanupAction -> Annex () -> Annex ()
340addCleanupAction k a = changeState $ \st ->
341	st { cleanupactions = M.insert k a $ cleanupactions st }
342
343{- Sets the type of output to emit. -}
344setOutput :: OutputType -> Annex ()
345setOutput o = changeState $ \st ->
346	let m = output st
347	in st { output = m { outputType = adjustOutputType (outputType m) o } }
348
349{- Checks if a flag was set. -}
350getFlag :: String -> Annex Bool
351getFlag flag = fromMaybe False . M.lookup flag <$> getState flags
352
353{- Gets the value of a field. -}
354getField :: String -> Annex (Maybe String)
355getField field = M.lookup field <$> getState fields
356
357{- Returns the annex's git repository. -}
358gitRepo :: Annex Git.Repo
359gitRepo = getState repo
360
361{- Runs an IO action in the annex's git repository. -}
362inRepo :: (Git.Repo -> IO a) -> Annex a
363inRepo a = liftIO . a =<< gitRepo
364
365{- Extracts a value from the annex's git repisitory. -}
366fromRepo :: (Git.Repo -> a) -> Annex a
367fromRepo a = a <$> gitRepo
368
369{- Calculates a value from an annex's git repository and its GitConfig. -}
370calcRepo :: (Git.Repo -> GitConfig -> IO a) -> Annex a
371calcRepo a = do
372	s <- getState id
373	liftIO $ a (repo s) (gitconfig s)
374
375{- Gets the GitConfig settings. -}
376getGitConfig :: Annex GitConfig
377getGitConfig = getState gitconfig
378
379{- Overrides a GitConfig setting. The modification persists across
380 - reloads of the repo's config. -}
381overrideGitConfig :: (GitConfig -> GitConfig) -> Annex ()
382overrideGitConfig f = changeState $ \st -> st
383	{ gitconfigadjustment = gitconfigadjustment st . f
384	, gitconfig = f (gitconfig st)
385	}
386
387{- Adds an adjustment to the Repo data. Adjustments persist across reloads
388 - of the repo's config.
389 -
390 - Note that the action may run more than once, and should avoid eg,
391 - appending the same value to a repo's config when run repeatedly.
392 -}
393adjustGitRepo :: (Git.Repo -> IO Git.Repo) -> Annex ()
394adjustGitRepo a = do
395	changeState $ \st -> st { repoadjustment = \r -> repoadjustment st r >>= a }
396	changeGitRepo =<< gitRepo
397
398{- Adds git config setting, like "foo=bar". It will be passed with -c
399 - to git processes. The config setting is also recorded in the Repo,
400 - and the GitConfig is updated. -}
401addGitConfigOverride :: String -> Annex ()
402addGitConfigOverride v = do
403	adjustGitRepo $ \r ->
404		Git.Config.store (encodeBS v) Git.Config.ConfigList $
405			r { Git.gitGlobalOpts = go (Git.gitGlobalOpts r) }
406	changeState $ \st -> st { gitconfigoverride = v : gitconfigoverride st }
407  where
408	-- Remove any prior occurrance of the setting to avoid
409	-- building up many of them when the adjustment is run repeatedly,
410	-- and add the setting to the end.
411	go [] = [Param "-c", Param v]
412	go (Param "-c": Param v':rest) | v' == v = go rest
413	go (c:rest) = c : go rest
414
415{- Values that were passed to addGitConfigOverride. -}
416getGitConfigOverrides :: Annex [String]
417getGitConfigOverrides = reverse <$> getState gitconfigoverride
418
419{- Changing the git Repo data also involves re-extracting its GitConfig. -}
420changeGitRepo :: Git.Repo -> Annex ()
421changeGitRepo r = do
422	repoadjuster <- getState repoadjustment
423	gitconfigadjuster <- getState gitconfigadjustment
424	r' <- liftIO $ repoadjuster r
425	changeState $ \st -> st
426		{ repo = r'
427		, gitconfig = gitconfigadjuster $
428			extractGitConfig FromGitConfig r'
429		}
430
431{- Gets the RemoteGitConfig from a remote, given the Git.Repo for that
432 - remote. -}
433getRemoteGitConfig :: Git.Repo -> Annex RemoteGitConfig
434getRemoteGitConfig r = do
435	g <- gitRepo
436	liftIO $ atomically $ extractRemoteGitConfig g (Git.repoDescribe r)
437
438{- Converts an Annex action into an IO action, that runs with a copy
439 - of the current Annex state.
440 -
441 - Use with caution; the action should not rely on changing the
442 - state, as it will be thrown away. -}
443withCurrentState :: Annex a -> Annex (IO a)
444withCurrentState a = do
445	(mvar, rd) <- ask
446	st <- liftIO $ readMVar mvar
447	return $ eval (st, rd) a
448
449{- It's not safe to use setCurrentDirectory in the Annex monad,
450 - because the git repo paths are stored relative.
451 - Instead, use this.
452 -}
453changeDirectory :: FilePath -> Annex ()
454changeDirectory d = do
455	r <- liftIO . Git.adjustPath absPath =<< gitRepo
456	liftIO $ setCurrentDirectory d
457	r' <- liftIO $ Git.relPath r
458	changeState $ \st -> st { repo = r' }
459
460incError :: Annex ()
461incError = changeState $ \st ->
462	let !c = errcounter st + 1
463	    !st' = st { errcounter = c }
464	in st'
465
466getGitRemotes :: Annex [Git.Repo]
467getGitRemotes = do
468	st <- getState id
469	case gitremotes st of
470		Just rs -> return rs
471		Nothing -> do
472			rs <- liftIO $ Git.Construct.fromRemotes (repo st)
473			changeState $ \st' -> st' { gitremotes = Just rs }
474			return rs
475