2015-12-09 21:00:37 +00:00
|
|
|
{- Sqlite database of information about Keys
|
2015-12-07 17:42:03 +00:00
|
|
|
-
|
2019-10-23 18:06:11 +00:00
|
|
|
- Copyright 2015-2019 Joey Hess <id@joeyh.name>
|
2016-01-01 19:09:42 +00:00
|
|
|
-
|
2019-03-13 19:48:14 +00:00
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
2015-12-07 17:42:03 +00:00
|
|
|
-}
|
|
|
|
|
2016-01-12 17:31:13 +00:00
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
2020-04-07 17:27:11 +00:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2015-12-07 17:42:03 +00:00
|
|
|
|
2015-12-09 21:00:37 +00:00
|
|
|
module Database.Keys (
|
2015-12-07 17:42:03 +00:00
|
|
|
DbHandle,
|
2016-05-16 18:49:12 +00:00
|
|
|
closeDb,
|
2015-12-09 21:00:37 +00:00
|
|
|
addAssociatedFile,
|
|
|
|
getAssociatedFiles,
|
2015-12-15 17:05:23 +00:00
|
|
|
getAssociatedKey,
|
2015-12-09 21:00:37 +00:00
|
|
|
removeAssociatedFile,
|
2015-12-09 21:47:05 +00:00
|
|
|
storeInodeCaches,
|
2018-08-22 19:28:57 +00:00
|
|
|
storeInodeCaches',
|
2015-12-09 21:47:05 +00:00
|
|
|
addInodeCaches,
|
|
|
|
getInodeCaches,
|
|
|
|
removeInodeCaches,
|
2019-10-23 18:06:11 +00:00
|
|
|
isInodeKnown,
|
2016-10-17 18:58:33 +00:00
|
|
|
runWriter,
|
2015-12-07 17:42:03 +00:00
|
|
|
) where
|
|
|
|
|
2016-01-11 19:52:11 +00:00
|
|
|
import qualified Database.Keys.SQL as SQL
|
2015-12-07 17:42:03 +00:00
|
|
|
import Database.Types
|
2015-12-23 22:34:51 +00:00
|
|
|
import Database.Keys.Handle
|
2015-12-23 18:59:58 +00:00
|
|
|
import qualified Database.Queue as H
|
2017-02-13 21:30:28 +00:00
|
|
|
import Database.Init
|
2016-01-20 20:36:33 +00:00
|
|
|
import Annex.Locations
|
|
|
|
import Annex.Common hiding (delete)
|
2016-01-01 19:50:59 +00:00
|
|
|
import qualified Annex
|
2015-12-07 17:42:03 +00:00
|
|
|
import Annex.LockFile
|
2018-08-21 20:48:20 +00:00
|
|
|
import Annex.CatFile
|
2018-08-22 19:28:57 +00:00
|
|
|
import Annex.Content.PointerFile
|
|
|
|
import Annex.Link
|
2015-12-09 21:00:37 +00:00
|
|
|
import Utility.InodeCache
|
2015-12-09 21:47:05 +00:00
|
|
|
import Annex.InodeSentinal
|
2018-08-21 20:48:20 +00:00
|
|
|
import Git
|
2016-01-01 19:09:42 +00:00
|
|
|
import Git.FilePath
|
2018-08-21 20:48:20 +00:00
|
|
|
import Git.Command
|
|
|
|
import Git.Types
|
2018-08-22 17:04:12 +00:00
|
|
|
import Git.Index
|
2021-01-04 17:12:28 +00:00
|
|
|
import Config.Smudge
|
2020-11-05 22:45:37 +00:00
|
|
|
import qualified Utility.RawFilePath as R
|
2015-12-23 22:34:51 +00:00
|
|
|
|
2019-12-18 20:45:03 +00:00
|
|
|
import qualified Data.ByteString as S
|
2020-04-07 17:27:11 +00:00
|
|
|
import qualified Data.ByteString.Char8 as S8
|
2019-12-18 20:45:03 +00:00
|
|
|
import qualified System.FilePath.ByteString as P
|
|
|
|
|
2015-12-23 22:34:51 +00:00
|
|
|
{- Runs an action that reads from the database.
|
|
|
|
-
|
|
|
|
- If the database doesn't already exist, it's not created; mempty is
|
|
|
|
- returned instead. This way, when the keys database is not in use,
|
|
|
|
- there's minimal overhead in checking it.
|
|
|
|
-
|
|
|
|
- If the database is already open, any writes are flushed to it, to ensure
|
|
|
|
- consistency.
|
|
|
|
-
|
|
|
|
- Any queued writes will be flushed before the read.
|
|
|
|
-}
|
2016-01-11 19:52:11 +00:00
|
|
|
runReader :: Monoid v => (SQL.ReadHandle -> Annex v) -> Annex v
|
2016-07-19 18:02:49 +00:00
|
|
|
runReader a = do
|
2020-04-17 21:09:29 +00:00
|
|
|
h <- Annex.getState Annex.keysdbhandle
|
2016-07-19 18:02:49 +00:00
|
|
|
withDbState h go
|
2015-12-23 22:34:51 +00:00
|
|
|
where
|
2016-02-12 18:15:28 +00:00
|
|
|
go DbUnavailable = return (mempty, DbUnavailable)
|
2015-12-23 22:34:51 +00:00
|
|
|
go st@(DbOpen qh) = do
|
|
|
|
liftIO $ H.flushDbQueue qh
|
2016-01-11 19:52:11 +00:00
|
|
|
v <- a (SQL.ReadHandle qh)
|
2015-12-23 22:34:51 +00:00
|
|
|
return (v, st)
|
|
|
|
go DbClosed = do
|
|
|
|
st' <- openDb False DbClosed
|
|
|
|
v <- case st' of
|
2016-01-11 19:52:11 +00:00
|
|
|
(DbOpen qh) -> a (SQL.ReadHandle qh)
|
2015-12-23 22:34:51 +00:00
|
|
|
_ -> return mempty
|
|
|
|
return (v, st')
|
|
|
|
|
2016-01-11 19:52:11 +00:00
|
|
|
runReaderIO :: Monoid v => (SQL.ReadHandle -> IO v) -> Annex v
|
|
|
|
runReaderIO a = runReader (liftIO . a)
|
2015-12-23 22:34:51 +00:00
|
|
|
|
|
|
|
{- Runs an action that writes to the database. Typically this is used to
|
|
|
|
- queue changes, which will be flushed at a later point.
|
|
|
|
-
|
|
|
|
- The database is created if it doesn't exist yet. -}
|
2016-01-11 19:52:11 +00:00
|
|
|
runWriter :: (SQL.WriteHandle -> Annex ()) -> Annex ()
|
2016-07-19 18:02:49 +00:00
|
|
|
runWriter a = do
|
2020-04-17 21:09:29 +00:00
|
|
|
h <- Annex.getState Annex.keysdbhandle
|
2015-12-23 22:34:51 +00:00
|
|
|
withDbState h go
|
|
|
|
where
|
|
|
|
go st@(DbOpen qh) = do
|
2016-01-11 19:52:11 +00:00
|
|
|
v <- a (SQL.WriteHandle qh)
|
2015-12-23 22:34:51 +00:00
|
|
|
return (v, st)
|
|
|
|
go st = do
|
|
|
|
st' <- openDb True st
|
|
|
|
v <- case st' of
|
2016-01-11 19:52:11 +00:00
|
|
|
DbOpen qh -> a (SQL.WriteHandle qh)
|
2015-12-23 22:34:51 +00:00
|
|
|
_ -> error "internal"
|
2015-12-24 17:06:03 +00:00
|
|
|
return (v, st')
|
2015-12-23 22:34:51 +00:00
|
|
|
|
2016-01-11 19:52:11 +00:00
|
|
|
runWriterIO :: (SQL.WriteHandle -> IO ()) -> Annex ()
|
|
|
|
runWriterIO a = runWriter (liftIO . a)
|
2015-12-23 22:34:51 +00:00
|
|
|
|
|
|
|
{- Opens the database, perhaps creating it if it doesn't exist yet.
|
2015-12-16 17:24:45 +00:00
|
|
|
-
|
|
|
|
- Multiple readers and writers can have the database open at the same
|
|
|
|
- time. Database.Handle deals with the concurrency issues.
|
|
|
|
- The lock is held while opening the database, so that when
|
|
|
|
- the database doesn't exist yet, one caller wins the lock and
|
|
|
|
- can create it undisturbed.
|
|
|
|
-}
|
2015-12-23 22:34:51 +00:00
|
|
|
openDb :: Bool -> DbState -> Annex DbState
|
|
|
|
openDb _ st@(DbOpen _) = return st
|
2016-02-12 18:15:28 +00:00
|
|
|
openDb False DbUnavailable = return DbUnavailable
|
|
|
|
openDb createdb _ = catchPermissionDenied permerr $ withExclusiveLock gitAnnexKeysDbLock $ do
|
2015-12-09 21:00:37 +00:00
|
|
|
dbdir <- fromRepo gitAnnexKeysDb
|
2020-11-05 22:45:37 +00:00
|
|
|
let db = dbdir P.</> "db"
|
|
|
|
dbexists <- liftIO $ R.doesPathExist db
|
2015-12-23 22:34:51 +00:00
|
|
|
case (dbexists, createdb) of
|
|
|
|
(True, _) -> open db
|
|
|
|
(False, True) -> do
|
2017-02-13 21:30:28 +00:00
|
|
|
initDb db SQL.createTables
|
2015-12-23 22:34:51 +00:00
|
|
|
open db
|
2016-02-12 18:15:28 +00:00
|
|
|
(False, False) -> return DbUnavailable
|
2015-12-23 22:34:51 +00:00
|
|
|
where
|
2016-02-12 18:15:28 +00:00
|
|
|
-- If permissions don't allow opening the database, treat it as if
|
|
|
|
-- it does not exist.
|
|
|
|
permerr e = case createdb of
|
|
|
|
False -> return DbUnavailable
|
|
|
|
True -> throwM e
|
2018-08-21 20:48:20 +00:00
|
|
|
|
|
|
|
open db = do
|
2018-08-22 19:28:57 +00:00
|
|
|
qh <- liftIO $ H.openDbQueue H.MultiWriter db SQL.containedTable
|
|
|
|
reconcileStaged qh
|
|
|
|
return $ DbOpen qh
|
2015-12-09 18:55:47 +00:00
|
|
|
|
2016-05-16 18:49:12 +00:00
|
|
|
{- Closes the database if it was open. Any writes will be flushed to it.
|
|
|
|
-
|
|
|
|
- This does not normally need to be called; the database will auto-close
|
|
|
|
- when the handle is garbage collected. However, this can be used to
|
|
|
|
- force a re-read of the database, in case another process has written
|
|
|
|
- data to it.
|
|
|
|
-}
|
|
|
|
closeDb :: Annex ()
|
2020-04-17 21:09:29 +00:00
|
|
|
closeDb = liftIO . closeDbHandle =<< Annex.getState Annex.keysdbhandle
|
2016-05-16 18:49:12 +00:00
|
|
|
|
2016-01-05 21:22:19 +00:00
|
|
|
addAssociatedFile :: Key -> TopFilePath -> Annex ()
|
2019-10-29 16:28:01 +00:00
|
|
|
addAssociatedFile k f = runWriterIO $ SQL.addAssociatedFile k f
|
2015-12-07 17:42:03 +00:00
|
|
|
|
2015-12-09 21:00:37 +00:00
|
|
|
{- Note that the files returned were once associated with the key, but
|
2015-12-07 17:42:03 +00:00
|
|
|
- some of them may not be any longer. -}
|
2016-01-05 21:22:19 +00:00
|
|
|
getAssociatedFiles :: Key -> Annex [TopFilePath]
|
2019-10-29 16:28:01 +00:00
|
|
|
getAssociatedFiles = runReaderIO . SQL.getAssociatedFiles
|
2015-12-07 17:42:03 +00:00
|
|
|
|
2015-12-15 17:05:23 +00:00
|
|
|
{- Gets any keys that are on record as having a particular associated file.
|
|
|
|
- (Should be one or none but the database doesn't enforce that.) -}
|
2016-01-05 21:22:19 +00:00
|
|
|
getAssociatedKey :: TopFilePath -> Annex [Key]
|
2019-10-29 16:28:01 +00:00
|
|
|
getAssociatedKey = runReaderIO . SQL.getAssociatedKey
|
2015-12-15 17:05:23 +00:00
|
|
|
|
2016-01-05 21:22:19 +00:00
|
|
|
removeAssociatedFile :: Key -> TopFilePath -> Annex ()
|
2019-10-29 16:28:01 +00:00
|
|
|
removeAssociatedFile k = runWriterIO . SQL.removeAssociatedFile k
|
2015-12-23 22:34:51 +00:00
|
|
|
|
2015-12-09 21:47:05 +00:00
|
|
|
{- Stats the files, and stores their InodeCaches. -}
|
2019-12-11 18:12:22 +00:00
|
|
|
storeInodeCaches :: Key -> [RawFilePath] -> Annex ()
|
2018-08-22 19:28:57 +00:00
|
|
|
storeInodeCaches k fs = storeInodeCaches' k fs []
|
|
|
|
|
2019-12-11 18:12:22 +00:00
|
|
|
storeInodeCaches' :: Key -> [RawFilePath] -> [InodeCache] -> Annex ()
|
2018-08-22 19:28:57 +00:00
|
|
|
storeInodeCaches' k fs ics = withTSDelta $ \d ->
|
|
|
|
addInodeCaches k . (++ ics) . catMaybes
|
2019-12-11 18:12:22 +00:00
|
|
|
=<< liftIO (mapM (\f -> genInodeCache f d) fs)
|
2015-12-09 21:47:05 +00:00
|
|
|
|
|
|
|
addInodeCaches :: Key -> [InodeCache] -> Annex ()
|
2019-10-29 16:28:01 +00:00
|
|
|
addInodeCaches k is = runWriterIO $ SQL.addInodeCaches k is
|
2015-12-09 21:00:37 +00:00
|
|
|
|
2015-12-09 21:47:05 +00:00
|
|
|
{- A key may have multiple InodeCaches; one for the annex object, and one
|
|
|
|
- for each pointer file that is a copy of it. -}
|
|
|
|
getInodeCaches :: Key -> Annex [InodeCache]
|
2019-10-29 16:28:01 +00:00
|
|
|
getInodeCaches = runReaderIO . SQL.getInodeCaches
|
2015-12-09 21:47:05 +00:00
|
|
|
|
|
|
|
removeInodeCaches :: Key -> Annex ()
|
2019-10-29 16:28:01 +00:00
|
|
|
removeInodeCaches = runWriterIO . SQL.removeInodeCaches
|
2018-08-21 20:48:20 +00:00
|
|
|
|
2019-10-23 18:06:11 +00:00
|
|
|
isInodeKnown :: InodeCache -> SentinalStatus -> Annex Bool
|
|
|
|
isInodeKnown i s = or <$> runReaderIO ((:[]) <$$> SQL.isInodeKnown i s)
|
|
|
|
|
2018-08-21 20:48:20 +00:00
|
|
|
{- Looks at staged changes to find when unlocked files are copied/moved,
|
|
|
|
- and updates associated files in the keys database.
|
|
|
|
-
|
|
|
|
- Since staged changes can be dropped later, does not remove any
|
|
|
|
- associated files; only adds new associated files.
|
|
|
|
-
|
|
|
|
- This needs to be run before querying the keys database so that
|
|
|
|
- information is consistent with the state of the repository.
|
|
|
|
-
|
2018-08-22 17:04:12 +00:00
|
|
|
- To avoid unncessary work, the index file is statted, and if it's not
|
2018-08-21 20:48:20 +00:00
|
|
|
- changed since last time this was run, nothing is done.
|
2018-08-22 17:04:12 +00:00
|
|
|
-
|
|
|
|
- Note that this is run with a lock held, so only one process can be
|
|
|
|
- running this at a time.
|
2018-08-22 19:28:57 +00:00
|
|
|
-
|
|
|
|
- This also cleans up after a race between eg a git mv and git-annex
|
|
|
|
- get/drop/similar. If git moves the file between this being run and the
|
|
|
|
- get/drop, the moved file won't be updated for the get/drop.
|
|
|
|
- The next time this runs, it will see the staged change. It then checks
|
|
|
|
- if the worktree file's content availability does not match the git-annex
|
|
|
|
- content availablity, and makes changes as necessary to reconcile them.
|
|
|
|
-
|
|
|
|
- Note that if a commit happens before this runs again, it won't see
|
|
|
|
- the staged change. Instead, during the commit, git will run the clean
|
|
|
|
- filter. If a drop missed the file then the file is added back into the
|
|
|
|
- annex. If a get missed the file then the clean filter populates the
|
|
|
|
- file.
|
2018-08-21 20:48:20 +00:00
|
|
|
-}
|
2018-08-22 19:28:57 +00:00
|
|
|
reconcileStaged :: H.DbQueue -> Annex ()
|
2019-08-30 17:54:57 +00:00
|
|
|
reconcileStaged qh = do
|
2018-08-22 17:04:12 +00:00
|
|
|
gitindex <- inRepo currentIndexFile
|
2020-10-29 18:20:57 +00:00
|
|
|
indexcache <- fromRawFilePath <$> fromRepo gitAnnexKeysDbIndexCache
|
2020-11-05 22:45:37 +00:00
|
|
|
withTSDelta (liftIO . genInodeCache gitindex) >>= \case
|
2018-08-22 17:04:12 +00:00
|
|
|
Just cur ->
|
|
|
|
liftIO (maybe Nothing readInodeCache <$> catchMaybeIO (readFile indexcache)) >>= \case
|
|
|
|
Nothing -> go cur indexcache
|
|
|
|
Just prev -> ifM (compareInodeCaches prev cur)
|
|
|
|
( noop
|
|
|
|
, go cur indexcache
|
|
|
|
)
|
|
|
|
Nothing -> noop
|
2018-08-21 20:48:20 +00:00
|
|
|
where
|
2018-08-22 17:04:12 +00:00
|
|
|
go cur indexcache = do
|
2020-04-07 17:27:11 +00:00
|
|
|
(l, cleanup) <- inRepo $ pipeNullSplit' diff
|
|
|
|
changed <- procdiff l False
|
2018-08-22 17:04:12 +00:00
|
|
|
void $ liftIO cleanup
|
|
|
|
-- Flush database changes immediately
|
|
|
|
-- so other processes can see them.
|
|
|
|
when changed $
|
|
|
|
liftIO $ H.flushDbQueue qh
|
|
|
|
liftIO $ writeFile indexcache $ showInodeCache cur
|
|
|
|
|
2018-08-21 20:48:20 +00:00
|
|
|
diff =
|
2018-09-13 17:55:25 +00:00
|
|
|
-- Avoid running smudge or clean filters, since we want the
|
|
|
|
-- raw output, and they would block trying to access the
|
|
|
|
-- locked database. The --raw normally avoids git diff
|
|
|
|
-- running them, but older versions of git need this.
|
2021-01-04 17:12:28 +00:00
|
|
|
bypassSmudgeConfig ++
|
|
|
|
-- Avoid using external diff command, which would be slow.
|
|
|
|
-- (The -G option may make it be used otherwise.)
|
|
|
|
[ Param "-c", Param "diff.external="
|
2018-08-21 20:48:20 +00:00
|
|
|
, Param "diff"
|
|
|
|
, Param "--cached"
|
|
|
|
, Param "--raw"
|
|
|
|
, Param "-z"
|
2020-01-07 16:29:37 +00:00
|
|
|
, Param "--no-abbrev"
|
2018-08-21 20:48:20 +00:00
|
|
|
-- Optimization: Only find pointer files. This is not
|
|
|
|
-- perfect. A file could start with this and not be a
|
|
|
|
-- pointer file. And a pointer file that is replaced with
|
|
|
|
-- a non-pointer file will match this.
|
2019-11-26 19:27:22 +00:00
|
|
|
, Param $ "-G^" ++ fromRawFilePath (toInternalGitPath $
|
2019-12-18 20:45:03 +00:00
|
|
|
P.pathSeparator `S.cons` objectDir')
|
2018-08-21 20:48:20 +00:00
|
|
|
-- Don't include files that were deleted, because this only
|
|
|
|
-- wants to update information for files that are present
|
|
|
|
-- in the index.
|
|
|
|
, Param "--diff-filter=AMUT"
|
|
|
|
-- Disable rename detection.
|
|
|
|
, Param "--no-renames"
|
|
|
|
-- Avoid other complications.
|
|
|
|
, Param "--ignore-submodules=all"
|
|
|
|
, Param "--no-ext-diff"
|
|
|
|
]
|
|
|
|
|
2020-04-07 17:27:11 +00:00
|
|
|
procdiff (info:file:rest) changed
|
|
|
|
| ":" `S.isPrefixOf` info = case S8.words info of
|
|
|
|
(_colonsrcmode:dstmode:_srcsha:dstsha:_change:[])
|
|
|
|
-- Only want files, not symlinks
|
|
|
|
| dstmode /= fmtTreeItemType TreeSymlink -> do
|
|
|
|
maybe noop (reconcile (asTopFilePath file))
|
|
|
|
=<< catKey (Ref dstsha)
|
|
|
|
procdiff rest True
|
|
|
|
| otherwise -> procdiff rest changed
|
|
|
|
_ -> return changed -- parse failed
|
2018-08-22 17:04:12 +00:00
|
|
|
procdiff _ changed = return changed
|
2018-08-21 20:48:20 +00:00
|
|
|
|
2018-08-22 19:28:57 +00:00
|
|
|
-- Note that database writes done in here will not necessarily
|
|
|
|
-- be visible to database reads also done in here.
|
|
|
|
reconcile file key = do
|
2019-10-29 16:28:01 +00:00
|
|
|
liftIO $ SQL.addAssociatedFileFast key file (SQL.WriteHandle qh)
|
|
|
|
caches <- liftIO $ SQL.getInodeCaches key (SQL.ReadHandle qh)
|
2018-08-22 19:28:57 +00:00
|
|
|
keyloc <- calcRepo (gitAnnexLocation key)
|
|
|
|
keypopulated <- sameInodeCache keyloc caches
|
2019-12-09 17:49:05 +00:00
|
|
|
p <- fromRepo $ fromTopFilePath file
|
2019-12-11 18:12:22 +00:00
|
|
|
filepopulated <- sameInodeCache p caches
|
2018-08-22 19:28:57 +00:00
|
|
|
case (keypopulated, filepopulated) of
|
|
|
|
(True, False) ->
|
2019-12-11 18:12:22 +00:00
|
|
|
populatePointerFile (Restage True) key keyloc p >>= \case
|
2018-08-22 19:28:57 +00:00
|
|
|
Nothing -> return ()
|
|
|
|
Just ic -> liftIO $
|
2019-10-29 16:28:01 +00:00
|
|
|
SQL.addInodeCaches key [ic] (SQL.WriteHandle qh)
|
2018-08-22 19:28:57 +00:00
|
|
|
(False, True) -> depopulatePointerFile key p
|
|
|
|
_ -> return ()
|