implement annex.privateremote and remote.name.private configs
authorJoey Hess <joeyh@joeyh.name>
Fri, 23 Apr 2021 18:21:57 +0000 (14:21 -0400)
committerJoey Hess <joeyh@joeyh.name>
Fri, 23 Apr 2021 18:21:57 +0000 (14:21 -0400)
The slightly unusual parsing in Types.GitConfig avoids the need to look
at the remote list to get configs of remotes. annexPrivateRepos combines
all the configs, and will only be calculated once, so it's nice and
fast.

privateUUIDsKnown and regardingPrivateUUID now need to read from the
annex mvar, so are not entirely free. But that overhead can be optimised
away, as seen in getJournalFileStale. The other call sites didn't seem
worth optimising to save a single MVar access. The feature should have
impreceptable speed overhead when not being used.

Annex/Branch.hs
Annex/Journal.hs
Git/Remote.hs
Types/GitConfig.hs
doc/todo/hiding_a_repository.mdwn

index d637e60701b51d481a5f79538a0034a1f242a24c..2d906af33e83d9053d1ea6044475cec1d1a58851 100644 (file)
@@ -185,7 +185,7 @@ updateTo' pairs = do
                else do
                        mergedrefs <- getMergedRefs
                        filterM isnewer (excludeset mergedrefs unignoredrefs)
-       journalclean <- if null tomerge
+       journalcleaned <- if null tomerge
                {- Even when no refs need to be merged, the index
                 - may still be updated if the branch has gotten ahead 
                 - of the index, or just if the journal is dirty. -}
@@ -210,12 +210,12 @@ updateTo' pairs = do
                else do
                        lockJournal $ go branchref dirty tomerge
                        return True
+       journalclean <- if journalcleaned
+               then not <$> privateUUIDsKnown
+               else pure False
        return $ UpdateMade
                { refsWereMerged = not (null tomerge)
                , journalClean = journalclean 
-                       -- TODO need private index, then this can be
-                       -- removed
-                       && not privateUUIDsKnown
                }
   where
        excludeset s = filter (\(r, _) -> S.notMember r s)
@@ -336,7 +336,7 @@ maybeChange ru file f = lockJournal $ \jl -> do
 
 {- Only get private information when the RegardingUUID is itself private. -}
 getToChange :: RegardingUUID -> RawFilePath -> Annex L.ByteString
-getToChange = getLocal' . GetPrivate . regardingPrivateUUID
+getToChange ru f = flip getLocal' f . GetPrivate =<< regardingPrivateUUID ru
 
 {- Records new content of a file into the journal.
  -
@@ -452,11 +452,12 @@ files = do
 {- Lists all files currently in the journal. There may be duplicates in
  - the list when using a private journal. -}
 journalledFiles :: Annex [RawFilePath]
-journalledFiles
-       | privateUUIDsKnown = (++)
+journalledFiles = ifM privateUUIDsKnown
+       ( (++)
                <$> getJournalledFilesStale gitAnnexPrivateJournalDir
                <*> getJournalledFilesStale gitAnnexJournalDir
-       | otherwise = getJournalledFilesStale gitAnnexJournalDir
+       , getJournalledFilesStale gitAnnexJournalDir
+       )
 
 {- Files in the branch, not including any from journalled changes,
  - and without updating the branch. -}
index 9d67d5ba87c6562db645b44c83dd90a6e67315ae..29fdece78279a4ba3977aeb14b7161e0873b71bc 100644 (file)
 module Annex.Journal where
 
 import Annex.Common
+import qualified Annex
 import qualified Git
 import Annex.Perms
 import Annex.Tmp
 import Annex.LockFile
 import Utility.Directory.Stream
 
+import qualified Data.Set as S
 import qualified Data.ByteString.Lazy as L
-import qualified Data.ByteString as S
+import qualified Data.ByteString as B
 import qualified System.FilePath.ByteString as P
 import Data.ByteString.Builder
 import Data.Char
@@ -42,21 +44,27 @@ instance Journalable Builder where
 {- When a file in the git-annex branch is changed, this indicates what
  - repository UUID (or in some cases, UUIDs) a change is regarding.
  -
- - Using this lets changes regarding private UUIDs be written to the
- - private index, rather than to the main branch index, so it does
- - not get exposed to other remotes.
+ - Using this lets changes regarding private UUIDs be stored separately
+ - from the git-annex branch, so its information does not get exposed
+ - outside the repo.
  -}
 data RegardingUUID = RegardingUUID [UUID]
 
-regardingPrivateUUID :: RegardingUUID -> Bool
-regardingPrivateUUID (RegardingUUID []) = False
-regardingPrivateUUID (RegardingUUID _) = True -- TODO
+regardingPrivateUUID :: RegardingUUID -> Annex Bool
+regardingPrivateUUID (RegardingUUID []) = pure False
+regardingPrivateUUID (RegardingUUID us) = do
+       s <- annexPrivateRepos <$> Annex.getGitConfig
+       return (any (flip S.member s) us)
 
--- Are any private UUIDs known to exist? If so, extra work has to be done,
--- to check for information separately recorded for them, outside the usual
--- locations.
-privateUUIDsKnown :: Bool
-privateUUIDsKnown = True -- TODO
+{- Are any private UUIDs known to exist? If so, extra work has to be done,
+ - to check for information separately recorded for them, outside the usual
+ - locations.
+ -}
+privateUUIDsKnown :: Annex Bool
+privateUUIDsKnown = privateUUIDsKnown' <$> Annex.getState id
+
+privateUUIDsKnown' :: Annex.AnnexState -> Bool
+privateUUIDsKnown' = not . S.null . annexPrivateRepos . Annex.gitconfig
 
 {- Records content for a file in the branch to the journal.
  -
@@ -69,9 +77,10 @@ privateUUIDsKnown = True -- TODO
  -}
 setJournalFile :: Journalable content => JournalLocked -> RegardingUUID -> RawFilePath -> content -> Annex ()
 setJournalFile _jl ru file content = withOtherTmp $ \tmp -> do
-       jd <- fromRepo $ if regardingPrivateUUID ru
-               then gitAnnexPrivateJournalDir
-               else gitAnnexJournalDir
+       jd <- fromRepo =<< ifM (regardingPrivateUUID ru)
+               ( return gitAnnexPrivateJournalDir
+               , return gitAnnexJournalDir
+               )
        createAnnexDirectory jd
        -- journal file is written atomically
        let jfile = journalFile file
@@ -98,8 +107,12 @@ data GetPrivate = GetPrivate Bool
  - laziness doesn't matter much, as the files are not very large.
  -}
 getJournalFileStale :: GetPrivate -> RawFilePath -> Annex (Maybe L.ByteString)
-getJournalFileStale (GetPrivate getprivate) file = inRepo $ \g -> 
-       if getprivate && privateUUIDsKnown
+getJournalFileStale (GetPrivate getprivate) file = do
+       -- Optimisation to avoid a second MVar access.
+       st <- Annex.getState id
+       let g = Annex.repo st
+       liftIO $
+               if getprivate && privateUUIDsKnown' st
                then do
                        x <- getfrom (gitAnnexJournalDir g)
                        y <- getfrom (gitAnnexPrivateJournalDir g)
@@ -110,7 +123,7 @@ getJournalFileStale (GetPrivate getprivate) file = inRepo $ \g ->
   where
        jfile = journalFile file
        getfrom d = catchMaybeIO $
-               L.fromStrict <$> S.readFile (fromRawFilePath (d P.</> jfile))
+               L.fromStrict <$> B.readFile (fromRawFilePath (d P.</> jfile))
 
 {- List of existing journal files in a journal directory, but without locking,
  - may miss new ones just being added, or may have false positives if the
@@ -145,12 +158,12 @@ journalDirty getjournaldir = do
  - in the journal directory.
  -}
 journalFile :: RawFilePath -> RawFilePath
-journalFile file = S.concatMap mangle file
+journalFile file = B.concatMap mangle file
   where
        mangle c
-               | P.isPathSeparator c = S.singleton underscore
-               | c == underscore = S.pack [underscore, underscore]
-               | otherwise = S.singleton c
+               | P.isPathSeparator c = B.singleton underscore
+               | c == underscore = B.pack [underscore, underscore]
+               | otherwise = B.singleton c
        underscore = fromIntegral (ord '_')
 
 {- Converts a journal file (relative to the journal dir) back to the
@@ -159,16 +172,16 @@ fileJournal :: RawFilePath -> RawFilePath
 fileJournal = go
   where
        go b = 
-               let (h, t) = S.break (== underscore) b
-               in h <> case S.uncons t of
+               let (h, t) = B.break (== underscore) b
+               in h <> case B.uncons t of
                        Nothing -> t
-                       Just (_u, t') -> case S.uncons t' of
+                       Just (_u, t') -> case B.uncons t' of
                                Nothing -> t'                   
                                Just (w, t'')
                                        | w == underscore ->
-                                               S.cons underscore (go t'')
+                                               B.cons underscore (go t'')
                                        | otherwise -> 
-                                               S.cons P.pathSeparator (go t')
+                                               B.cons P.pathSeparator (go t')
        
        underscore = fromIntegral (ord '_')
 
index 546a6cb23d22014fd46e7b0c18121f43e003fe9c..8f5d99f0d4ff7f6420071a82fc8cb3b83d81996e 100644 (file)
@@ -1,6 +1,6 @@
 {- git remote stuff
  -
- - Copyright 2012 Joey Hess <id@joeyh.name>
+ - Copyright 2012-2021 Joey Hess <id@joeyh.name>
  -
  - Licensed under the GNU AGPL version 3 or higher.
  -}
@@ -25,7 +25,11 @@ import Git.FilePath
 
 {- Is a git config key one that specifies the url of a remote? -}
 isRemoteUrlKey :: ConfigKey -> Bool
-isRemoteUrlKey (ConfigKey k) = "remote." `S.isPrefixOf` k && ".url" `S.isSuffixOf` k
+isRemoteUrlKey = isRemoteKey "url"
+
+isRemoteKey :: S.ByteString -> ConfigKey -> Bool
+isRemoteKey want (ConfigKey k) =
+       "remote." `S.isPrefixOf` k && ("." <> want) `S.isSuffixOf` k
 
 {- Get a remote's name from the a config key such as remote.name.url
  - or any other per-remote config key. -}
index b5b66f0c77caa6090418272cb5cf80e6e435e19c..4dbfd29ed4c6b5b3352f582b756f6f657f2d45da 100644 (file)
@@ -30,6 +30,7 @@ import qualified Git.Config
 import qualified Git.Construct
 import Git.Types
 import Git.ConfigTypes
+import Git.Remote (isRemoteKey, remoteKeyToRemoteName)
 import Git.Branch (CommitMode(..))
 import Utility.DataUnits
 import Config.Cost
@@ -50,6 +51,7 @@ import Utility.Url (Scheme, mkScheme)
 
 import Control.Concurrent.STM
 import qualified Data.Set as S
+import qualified Data.Map as M
 import qualified Data.ByteString as B
 
 -- | A configurable value, that may not be fully determined yet because
@@ -136,12 +138,13 @@ data GitConfig = GitConfig
        , gcryptId :: Maybe String
        , gpgCmd :: GpgCmd
        , mergeDirectoryRenames :: Maybe String
+       , annexPrivateRepos :: S.Set UUID
        }
 
 extractGitConfig :: ConfigSource -> Git.Repo -> GitConfig
 extractGitConfig configsource r = GitConfig
        { annexVersion = RepoVersion <$> getmayberead (annexConfig "version")
-       , annexUUID = maybe NoUUID toUUID $ getmaybe (annexConfig "uuid")
+       , annexUUID = hereuuid
        , annexNumCopies = NumCopies <$> getmayberead (annexConfig "numcopies")
        , annexDiskReserve = fromMaybe onemegabyte $
                readSize dataUnits =<< getmaybe (annexConfig "diskreserve")
@@ -239,6 +242,19 @@ extractGitConfig configsource r = GitConfig
        , gcryptId = getmaybe "core.gcrypt-id"
        , gpgCmd = mkGpgCmd (getmaybe "gpg.program")
        , mergeDirectoryRenames = getmaybe "directoryrenames"
+       , annexPrivateRepos = S.fromList $ concat
+               [ if getbool (annexConfig "private") False
+                       then [hereuuid]
+                       else []
+               , let get (k, v)
+                       | Git.Config.isTrueFalse' v /= Just True = Nothing
+                       | isRemoteKey (remoteAnnexConfigEnd "private") k = do
+                               remotename <- remoteKeyToRemoteName k
+                               toUUID <$> Git.Config.getMaybe
+                                       (remoteAnnexConfig remotename "uuid") r
+                       | otherwise = Nothing
+                 in mapMaybe get (M.toList (Git.config r))
+               ]
        }
   where
        getbool k d = fromMaybe d $ getmaybebool k
@@ -255,6 +271,8 @@ extractGitConfig configsource r = GitConfig
                FromGlobalConfig -> HasGlobalConfig v
 
        onemegabyte = 1000000
+       
+       hereuuid = maybe NoUUID toUUID $ getmaybe (annexConfig "uuid")
 
 {- Merge a GitConfig that comes from git-config with one containing
  - repository-global defaults. -}
@@ -443,7 +461,10 @@ instance RemoteNameable RemoteName where
 
 {- A per-remote annex setting in git config. -}
 remoteAnnexConfig :: RemoteNameable r => r -> UnqualifiedConfigKey -> ConfigKey
-remoteAnnexConfig r key = remoteConfig r ("annex-" <> key)
+remoteAnnexConfig r = remoteConfig r . remoteAnnexConfigEnd
+
+remoteAnnexConfigEnd :: UnqualifiedConfigKey -> UnqualifiedConfigKey
+remoteAnnexConfigEnd key = "annex-" <> key
 
 {- A per-remote setting in git config. -}
 remoteConfig :: RemoteNameable r => r -> UnqualifiedConfigKey -> ConfigKey
index 6fed824cd2e7a04093c6b5acb10f53a44f75706f..982e805caf1624c44b54a6792e61cbc9f8f7f9b2 100644 (file)
@@ -157,19 +157,6 @@ later write.
 >   Annex.Branch, also need to be fixed (and may be missing journal files
 >   already?) Most fixed now. Command.Log behavior needs to be
 >   investigated still.
-> 
-> * Need to implement regardingPrivateUUID and privateUUIDsKnown,
->   which need to look at the git config to find the private uuids.
-> 
->   But that involves a mvar access, so there will be some slow down,
->   although often it will be swamped by the actual branch querying.
->   So far it's been possible to avoid any slow down from this feature
->   when it's not in use.
->   
->   Encoding inside the uuid if a repo is private avoids slowdown of
->   regardingPrivateUUID, but not privateUUIDsKnown. (So branch queries
->   still slow down). It also avoids needing to set the config before
->   writing to the branch when setting up a private repo or special remote.
 
 ## networks of hidden repos