annex.adjustedbranchrefresh
authorJoey Hess <joeyh@joeyh.name>
Mon, 16 Nov 2020 18:09:55 +0000 (14:09 -0400)
committerJoey Hess <joeyh@joeyh.name>
Mon, 16 Nov 2020 18:27:28 +0000 (14:27 -0400)
Added annex.adjustedbranchrefresh git config to update adjusted branches
set up by git-annex adjust --unlock-present/--hide-missing.

Note, in a few cases, I was not able to make the adjusted branch
be updated in calls to moveAnnex, because information about what
file corresponds to a key is not available. They are:

* If two files point to one file, then eg, `git annex get foo` will
  update the branch to unlock foo, but will not unlock bar, because it
  does not know about it. Might be fixable by making `git annex get
  bar` do something besides skipping bar?
* git-annex-shell recvkey likewise (so sends over ssh from old versions
  of git-annex)
* git-annex setkey
* git-annex transferkey if the user does not use --file
* git-annex multicast sends keys with no associated file info

Doing a single full refresh at the end, after any incremental refresh,
will deal with those edge cases.

28 files changed:
Annex.hs
Annex/AdjustedBranch.hs
Annex/AdjustedBranch/Merge.hs [new file with mode: 0644]
Annex/Content.hs
Annex/Import.hs
Annex/Ingest.hs
CHANGELOG
Command/Get.hs
Command/Move.hs
Command/Multicast.hs
Command/ReKey.hs
Command/RecvKey.hs
Command/Reinject.hs
Command/SetKey.hs
Command/Sync.hs
Command/TestRemote.hs
Command/TransferKey.hs
Command/TransferKeys.hs
P2P/Annex.hs
Remote/Git.hs
Types/CleanupActions.hs
Types/GitConfig.hs
Upgrade/V0.hs
Upgrade/V1.hs
doc/git-annex-adjust.mdwn
doc/git-annex.mdwn
doc/todo/update_adjusted_branch_on_content_availability_change.mdwn
git-annex.cabal

index 5ae01ea2ac7ae465eb40a71e55570ea8bfaf9734..748ad96009931acd70bf41cabd14ff74d1a7de8e 100644 (file)
--- a/Annex.hs
+++ b/Annex.hs
@@ -143,6 +143,7 @@ data AnnexState = AnnexState
        , sentinalstatus :: Maybe SentinalStatus
        , useragent :: Maybe String
        , errcounter :: Integer
+       , adjustedbranchrefreshcounter :: Integer
        , unusedkeys :: Maybe (S.Set Key)
        , tempurls :: M.Map Key URLString
        , existinghooks :: M.Map Git.Hook.Hook Bool
@@ -203,6 +204,7 @@ newState c r = do
                , sentinalstatus = Nothing
                , useragent = Nothing
                , errcounter = 0
+               , adjustedbranchrefreshcounter = 0
                , unusedkeys = Nothing
                , tempurls = M.empty
                , existinghooks = M.empty
@@ -399,8 +401,8 @@ changeDirectory d = do
 
 incError :: Annex ()
 incError = changeState $ \s -> 
-       let ! c = errcounter s + 1 
-           ! s' = s { errcounter = c }
+       let !c = errcounter s + 1 
+           !s' = s { errcounter = c }
        in s'
 
 getGitRemotes :: Annex [Git.Repo]
index 8e6eb4dc8b5772783397ae203e23e80abc958264..14467565aa0c53a607d174dfa3dd46e16dbe0ea8 100644 (file)
@@ -22,10 +22,18 @@ module Annex.AdjustedBranch (
        getAdjustment,
        enterAdjustedBranch,
        updateAdjustedBranch,
+       adjustedBranchRefresh,
        adjustBranch,
+       adjustTree,
        adjustToCrippledFileSystem,
-       mergeToAdjustedBranch,
        propigateAdjustedCommits,
+       propigateAdjustedCommits',
+       commitAdjustedTree,
+       commitAdjustedTree',
+       BasisBranch(..),
+       basisBranch,
+       setBasisBranch,
+       preventCommits,
        AdjustedClone(..),
        checkAdjustedClone,
        checkVersionSupported,
@@ -43,7 +51,6 @@ import qualified Git.Ref
 import qualified Git.Command
 import qualified Git.Tree
 import qualified Git.DiffTree
-import qualified Git.Merge
 import Git.Tree (TreeItem(..))
 import Git.Sha
 import Git.Env
@@ -53,19 +60,13 @@ import qualified Git.LockFile
 import qualified Git.Version
 import Annex.CatFile
 import Annex.Link
-import Annex.AutoMerge
-import Annex.Content
-import Annex.Tmp
-import Annex.GitOverlay
-import Utility.Tmp.Dir
-import Utility.CopyFile
-import Utility.Directory.Create
+import Annex.Content.Presence
+import Annex.CurrentBranch
+import Types.CleanupActions
 import qualified Database.Keys
 import Config
 
 import qualified Data.Map as M
-import qualified Data.ByteString as S
-import qualified System.FilePath.ByteString as P
 
 class AdjustTreeItem t where
        -- How to perform various adjustments to a TreeItem.
@@ -272,6 +273,52 @@ updateAdjustedBranch adj (AdjBranch currbranch) origbranch
                -- adjustment is stable.
                return True
 
+{- Passed an action that, if it succeeds may get or drop the Key associated
+ - with the file. When the adjusted branch needs to be refreshed to reflect
+ - those changes, it's handled here.
+ -
+ - Note that the AssociatedFile must be verified by this to point to the
+ - Key. In some cases, the value was provided by the user and might not
+ - really be an associated file.
+ -}
+adjustedBranchRefresh :: AssociatedFile -> Annex a -> Annex a
+adjustedBranchRefresh _af a = do
+       r <- a
+       annexAdjustedBranchRefresh <$> Annex.getGitConfig >>= \case
+               0 -> return ()
+               n -> go n
+       return r
+  where
+       go n = getCurrentBranch >>= \case
+               (Just origbranch, Just adj) ->
+                       unless (adjustmentIsStable adj) $
+                               ifM (checkcounter n)
+                                       ( update adj origbranch
+                                       , Annex.addCleanup AdjustedBranchUpdate $
+                                               update adj origbranch
+                                       )
+               _ -> return ()
+       
+       checkcounter n
+               -- Special case, 1 (or true) refreshes only at shutdown.
+               | n == 1 = pure False
+               | n == 2 = pure True
+               | otherwise = Annex.withState $ \s -> 
+                       let !c = Annex.adjustedbranchrefreshcounter s + 1
+                           !enough = c >= pred n
+                           !c' = if enough then 0 else c
+                           !s' = s { Annex.adjustedbranchrefreshcounter = c' }
+                           in pure (s', enough)
+       
+       -- TODO This is very slow when run a lot of times. 
+       -- Incrementally adjust only the AssociatedFile.
+       -- However, this should be run once at shutdown then,
+       -- because other files than the provided AssociatedFile
+       -- can need to be updated in some edge cases.
+       update adj origbranch = do
+               let adjbranch = originalToAdjusted origbranch adj
+               void $ updateAdjustedBranch adj adjbranch origbranch
+
 adjustToCrippledFileSystem :: Annex ()
 adjustToCrippledFileSystem = do
        warning "Entering an adjusted branch where files are unlocked as this filesystem does not support locked files."
@@ -389,137 +436,6 @@ findAdjustingCommit (AdjBranch b) = go =<< catCommit b
                        [p] -> go =<< catCommit p
                        _ -> return Nothing
 
-{- Update the currently checked out adjusted branch, merging the provided
- - branch into it. Note that the provided branch should be a non-adjusted
- - branch. -}
-mergeToAdjustedBranch :: Branch -> (OrigBranch, Adjustment) -> [Git.Merge.MergeConfig] -> Bool -> Git.Branch.CommitMode -> Annex Bool
-mergeToAdjustedBranch tomerge (origbranch, adj) mergeconfig canresolvemerge commitmode = catchBoolIO $
-       join $ preventCommits go
-  where
-       adjbranch@(AdjBranch currbranch) = originalToAdjusted origbranch adj
-       basis = basisBranch adjbranch
-
-       go commitsprevented =
-               ifM (inRepo $ Git.Branch.changed currbranch tomerge)
-                       ( do
-                               (updatedorig, _) <- propigateAdjustedCommits'
-                                       origbranch adj commitsprevented
-                               changestomerge updatedorig
-                       , nochangestomerge
-                       )
-
-       nochangestomerge = return $ return True
-
-       {- Since the adjusted branch changes files, merging tomerge
-        - directly into it would likely result in unncessary merge
-        - conflicts. To avoid those conflicts, instead merge tomerge into
-        - updatedorig. The result of the merge can the be
-        - adjusted to yield the final adjusted branch.
-        -
-        - In order to do a merge into a ref that is not checked out,
-        - set the work tree to a temp directory, and set GIT_DIR
-        - to another temp directory, in which HEAD contains the
-        - updatedorig sha. GIT_COMMON_DIR is set to point to the real
-        - git directory, and so git can read and write objects from there,
-        - but will use GIT_DIR for HEAD and index.
-        -
-        - (Doing the merge this way also lets it run even though the main
-        - index file is currently locked.)
-        -}
-       changestomerge (Just updatedorig) = withOtherTmp $ \othertmpdir -> do
-               git_dir <- fromRepo Git.localGitDir
-               let git_dir' = fromRawFilePath git_dir
-               tmpwt <- fromRepo gitAnnexMergeDir
-               withTmpDirIn (fromRawFilePath othertmpdir) "git" $ \tmpgit -> withWorkTreeRelated tmpgit $
-                       withemptydir git_dir tmpwt $ withWorkTree tmpwt $ do
-                               liftIO $ writeFile (tmpgit </> "HEAD") (fromRef updatedorig)
-                               -- Copy in refs and packed-refs, to work
-                               -- around bug in git 2.13.0, which
-                               -- causes it not to look in GIT_DIR for refs.
-                               refs <- liftIO $ dirContentsRecursive $
-                                       git_dir' </> "refs"
-                               let refs' = (git_dir' </> "packed-refs") : refs
-                               liftIO $ forM_ refs' $ \src ->
-                                       whenM (doesFileExist src) $ do
-                                               dest <- relPathDirToFile git_dir
-                                                       (toRawFilePath src)
-                                               let dest' = toRawFilePath tmpgit P.</> dest
-                                               createDirectoryUnder git_dir
-                                                       (P.takeDirectory dest')
-                                               void $ createLinkOrCopy src
-                                                       (fromRawFilePath dest')
-                               -- This reset makes git merge not care
-                               -- that the work tree is empty; otherwise
-                               -- it will think that all the files have
-                               -- been staged for deletion, and sometimes
-                               -- the merge includes these deletions
-                               -- (for an unknown reason).
-                               -- http://thread.gmane.org/gmane.comp.version-control.git/297237
-                               inRepo $ Git.Command.run [Param "reset", Param "HEAD", Param "--quiet"]
-                               showAction $ "Merging into " ++ fromRef (Git.Ref.base origbranch)
-                               merged <- autoMergeFrom' tomerge Nothing mergeconfig commitmode True
-                                       (const $ resolveMerge (Just updatedorig) tomerge True)
-                               if merged
-                                       then do
-                                               !mergecommit <- liftIO $ extractSha
-                                                       <$> S.readFile (tmpgit </> "HEAD")
-                                               -- This is run after the commit lock is dropped.
-                                               return $ postmerge mergecommit
-                                       else return $ return False
-       changestomerge Nothing = return $ return False
-       
-       withemptydir git_dir d a = bracketIO setup cleanup (const a)
-         where
-               setup = do
-                       whenM (doesDirectoryExist d) $
-                               removeDirectoryRecursive d
-                       createDirectoryUnder git_dir (toRawFilePath d)
-               cleanup _ = removeDirectoryRecursive d
-
-       {- A merge commit has been made between the basisbranch and 
-        - tomerge. Update the basisbranch and origbranch to point
-        - to that commit, adjust it to get the new adjusted branch,
-        - and check it out.
-        -
-        - But, there may be unstaged work tree changes that conflict, 
-        - so the check out is done by making a normal merge of
-        - the new adjusted branch.
-        -}
-       postmerge (Just mergecommit) = do
-               setBasisBranch basis mergecommit
-               inRepo $ Git.Branch.update' origbranch mergecommit
-               adjtree <- adjustTree adj (BasisBranch mergecommit)
-               adjmergecommit <- commitAdjustedTree adjtree (BasisBranch mergecommit)
-               -- Make currbranch be the parent, so that merging
-               -- this commit will be a fast-forward.
-               adjmergecommitff <- commitAdjustedTree' adjtree (BasisBranch mergecommit) [currbranch]
-               showAction "Merging into adjusted branch"
-               ifM (autoMergeFrom adjmergecommitff (Just currbranch) mergeconfig commitmode canresolvemerge)
-                       ( reparent adjtree adjmergecommit =<< getcurrentcommit
-                       , return False
-                       )
-       postmerge Nothing = return False
-
-       -- Now that the merge into the adjusted branch is complete,
-       -- take the tree from that merge, and attach it on top of the
-       -- adjmergecommit, if it's different.
-       reparent adjtree adjmergecommit (Just currentcommit) = do
-               if (commitTree currentcommit /= adjtree)
-                       then do
-                               cmode <- annexCommitMode <$> Annex.getGitConfig
-                               c <- inRepo $ Git.Branch.commitTree cmode
-                                       ("Merged " ++ fromRef tomerge) [adjmergecommit]
-                                       (commitTree currentcommit)
-                               inRepo $ Git.Branch.update "updating adjusted branch" currbranch c
-                               propigateAdjustedCommits origbranch adj
-                       else inRepo $ Git.Branch.update "updating adjusted branch" currbranch adjmergecommit
-               return True
-       reparent _ _ Nothing = return False
-
-       getcurrentcommit = inRepo Git.Branch.currentUnsafe >>= \case
-               Nothing -> return Nothing
-               Just c -> catCommit c
-
 {- Check for any commits present on the adjusted branch that have not yet
  - been propigated to the basis branch, and propigate them to the basis
  - branch and from there on to the orig branch.
diff --git a/Annex/AdjustedBranch/Merge.hs b/Annex/AdjustedBranch/Merge.hs
new file mode 100644 (file)
index 0000000..c2cce87
--- /dev/null
@@ -0,0 +1,164 @@
+{- adjusted branch merging
+ -
+ - Copyright 2016-2020 Joey Hess <id@joeyh.name>
+ -
+ - Licensed under the GNU AGPL version 3 or higher.
+ -}
+
+{-# LANGUAGE BangPatterns, OverloadedStrings #-}
+
+module Annex.AdjustedBranch.Merge (
+       mergeToAdjustedBranch,
+) where
+
+import Annex.Common
+import Annex.AdjustedBranch
+import qualified Annex
+import Git
+import Git.Types
+import qualified Git.Branch
+import qualified Git.Ref
+import qualified Git.Command
+import qualified Git.Merge
+import Git.Sha
+import Annex.CatFile
+import Annex.AutoMerge
+import Annex.Tmp
+import Annex.GitOverlay
+import Utility.Tmp.Dir
+import Utility.CopyFile
+import Utility.Directory.Create
+
+import qualified Data.ByteString as S
+import qualified System.FilePath.ByteString as P
+
+{- Update the currently checked out adjusted branch, merging the provided
+ - branch into it. Note that the provided branch should be a non-adjusted
+ - branch. -}
+mergeToAdjustedBranch :: Branch -> (OrigBranch, Adjustment) -> [Git.Merge.MergeConfig] -> Bool -> Git.Branch.CommitMode -> Annex Bool
+mergeToAdjustedBranch tomerge (origbranch, adj) mergeconfig canresolvemerge commitmode = catchBoolIO $
+       join $ preventCommits go
+  where
+       adjbranch@(AdjBranch currbranch) = originalToAdjusted origbranch adj
+       basis = basisBranch adjbranch
+
+       go commitsprevented =
+               ifM (inRepo $ Git.Branch.changed currbranch tomerge)
+                       ( do
+                               (updatedorig, _) <- propigateAdjustedCommits'
+                                       origbranch adj commitsprevented
+                               changestomerge updatedorig
+                       , nochangestomerge
+                       )
+
+       nochangestomerge = return $ return True
+
+       {- Since the adjusted branch changes files, merging tomerge
+        - directly into it would likely result in unncessary merge
+        - conflicts. To avoid those conflicts, instead merge tomerge into
+        - updatedorig. The result of the merge can the be
+        - adjusted to yield the final adjusted branch.
+        -
+        - In order to do a merge into a ref that is not checked out,
+        - set the work tree to a temp directory, and set GIT_DIR
+        - to another temp directory, in which HEAD contains the
+        - updatedorig sha. GIT_COMMON_DIR is set to point to the real
+        - git directory, and so git can read and write objects from there,
+        - but will use GIT_DIR for HEAD and index.
+        -
+        - (Doing the merge this way also lets it run even though the main
+        - index file is currently locked.)
+        -}
+       changestomerge (Just updatedorig) = withOtherTmp $ \othertmpdir -> do
+               git_dir <- fromRepo Git.localGitDir
+               let git_dir' = fromRawFilePath git_dir
+               tmpwt <- fromRepo gitAnnexMergeDir
+               withTmpDirIn (fromRawFilePath othertmpdir) "git" $ \tmpgit -> withWorkTreeRelated tmpgit $
+                       withemptydir git_dir tmpwt $ withWorkTree tmpwt $ do
+                               liftIO $ writeFile (tmpgit </> "HEAD") (fromRef updatedorig)
+                               -- Copy in refs and packed-refs, to work
+                               -- around bug in git 2.13.0, which
+                               -- causes it not to look in GIT_DIR for refs.
+                               refs <- liftIO $ dirContentsRecursive $
+                                       git_dir' </> "refs"
+                               let refs' = (git_dir' </> "packed-refs") : refs
+                               liftIO $ forM_ refs' $ \src ->
+                                       whenM (doesFileExist src) $ do
+                                               dest <- relPathDirToFile git_dir
+                                                       (toRawFilePath src)
+                                               let dest' = toRawFilePath tmpgit P.</> dest
+                                               createDirectoryUnder git_dir
+                                                       (P.takeDirectory dest')
+                                               void $ createLinkOrCopy src
+                                                       (fromRawFilePath dest')
+                               -- This reset makes git merge not care
+                               -- that the work tree is empty; otherwise
+                               -- it will think that all the files have
+                               -- been staged for deletion, and sometimes
+                               -- the merge includes these deletions
+                               -- (for an unknown reason).
+                               -- http://thread.gmane.org/gmane.comp.version-control.git/297237
+                               inRepo $ Git.Command.run [Param "reset", Param "HEAD", Param "--quiet"]
+                               showAction $ "Merging into " ++ fromRef (Git.Ref.base origbranch)
+                               merged <- autoMergeFrom' tomerge Nothing mergeconfig commitmode True
+                                       (const $ resolveMerge (Just updatedorig) tomerge True)
+                               if merged
+                                       then do
+                                               !mergecommit <- liftIO $ extractSha
+                                                       <$> S.readFile (tmpgit </> "HEAD")
+                                               -- This is run after the commit lock is dropped.
+                                               return $ postmerge mergecommit
+                                       else return $ return False
+       changestomerge Nothing = return $ return False
+       
+       withemptydir git_dir d a = bracketIO setup cleanup (const a)
+         where
+               setup = do
+                       whenM (doesDirectoryExist d) $
+                               removeDirectoryRecursive d
+                       createDirectoryUnder git_dir (toRawFilePath d)
+               cleanup _ = removeDirectoryRecursive d
+
+       {- A merge commit has been made between the basisbranch and 
+        - tomerge. Update the basisbranch and origbranch to point
+        - to that commit, adjust it to get the new adjusted branch,
+        - and check it out.
+        -
+        - But, there may be unstaged work tree changes that conflict, 
+        - so the check out is done by making a normal merge of
+        - the new adjusted branch.
+        -}
+       postmerge (Just mergecommit) = do
+               setBasisBranch basis mergecommit
+               inRepo $ Git.Branch.update' origbranch mergecommit
+               adjtree <- adjustTree adj (BasisBranch mergecommit)
+               adjmergecommit <- commitAdjustedTree adjtree (BasisBranch mergecommit)
+               -- Make currbranch be the parent, so that merging
+               -- this commit will be a fast-forward.
+               adjmergecommitff <- commitAdjustedTree' adjtree (BasisBranch mergecommit) [currbranch]
+               showAction "Merging into adjusted branch"
+               ifM (autoMergeFrom adjmergecommitff (Just currbranch) mergeconfig commitmode canresolvemerge)
+                       ( reparent adjtree adjmergecommit =<< getcurrentcommit
+                       , return False
+                       )
+       postmerge Nothing = return False
+
+       -- Now that the merge into the adjusted branch is complete,
+       -- take the tree from that merge, and attach it on top of the
+       -- adjmergecommit, if it's different.
+       reparent adjtree adjmergecommit (Just currentcommit) = do
+               if (commitTree currentcommit /= adjtree)
+                       then do
+                               cmode <- annexCommitMode <$> Annex.getGitConfig
+                               c <- inRepo $ Git.Branch.commitTree cmode
+                                       ("Merged " ++ fromRef tomerge) [adjmergecommit]
+                                       (commitTree currentcommit)
+                               inRepo $ Git.Branch.update "updating adjusted branch" currbranch c
+                               propigateAdjustedCommits origbranch adj
+                       else inRepo $ Git.Branch.update "updating adjusted branch" currbranch adjmergecommit
+               return True
+       reparent _ _ Nothing = return False
+
+       getcurrentcommit = inRepo Git.Branch.currentUnsafe >>= \case
+               Nothing -> return Nothing
+               Just c -> catCommit c
index e39351d781f8e3da9cd64571c271d1229df5a6bc..3bdaacd45a5f01a07ac1d9dce5356ab910b93a4e 100644 (file)
@@ -76,6 +76,7 @@ import Annex.Link
 import Annex.LockPool
 import Annex.UUID
 import Annex.InodeSentinal
+import Annex.AdjustedBranch (adjustedBranchRefresh)
 import Messages.Progress
 import Types.Remote (RetrievalSecurityPolicy(..))
 import Types.NumCopies
@@ -203,15 +204,15 @@ lockContentUsing locker key fallback a = do
 {- Runs an action, passing it the temp file to get,
  - and if the action succeeds, verifies the file matches
  - the key and moves the file into the annex as a key's content. -}
-getViaTmp :: RetrievalSecurityPolicy -> VerifyConfig -> Key -> (RawFilePath -> Annex (Bool, Verification)) -> Annex Bool
-getViaTmp rsp v key action = checkDiskSpaceToGet key False $
-       getViaTmpFromDisk rsp v key action
+getViaTmp :: RetrievalSecurityPolicy -> VerifyConfig -> Key -> AssociatedFile -> (RawFilePath -> Annex (Bool, Verification)) -> Annex Bool
+getViaTmp rsp v key af action = checkDiskSpaceToGet key False $
+       getViaTmpFromDisk rsp v key af action
 
 {- Like getViaTmp, but does not check that there is enough disk space
  - for the incoming key. For use when the key content is already on disk
  - and not being copied into place. -}
-getViaTmpFromDisk :: RetrievalSecurityPolicy -> VerifyConfig -> Key -> (RawFilePath -> Annex (Bool, Verification)) -> Annex Bool
-getViaTmpFromDisk rsp v key action = checkallowed $ do
+getViaTmpFromDisk :: RetrievalSecurityPolicy -> VerifyConfig -> Key -> AssociatedFile -> (RawFilePath -> Annex (Bool, Verification)) -> Annex Bool
+getViaTmpFromDisk rsp v key af action = checkallowed $ do
        tmpfile <- prepTmp key
        resuming <- liftIO $ R.doesPathExist tmpfile
        (ok, verification) <- action tmpfile
@@ -226,7 +227,7 @@ getViaTmpFromDisk rsp v key action = checkallowed $ do
                else verification
        if ok
                then ifM (verifyKeyContent rsp v verification' key tmpfile)
-                       ( ifM (pruneTmpWorkDirBefore tmpfile (moveAnnex key))
+                       ( ifM (pruneTmpWorkDirBefore tmpfile (moveAnnex key af))
                                ( do
                                        logStatus key InfoPresent
                                        return True
@@ -329,8 +330,8 @@ withTmp key action = do
  - accepted into the repository. Will display a warning message in this
  - case. May also throw exceptions in some cases.
  -}
-moveAnnex :: Key -> RawFilePath -> Annex Bool
-moveAnnex key src = ifM (checkSecureHashes' key)
+moveAnnex :: Key -> AssociatedFile -> RawFilePath -> Annex Bool
+moveAnnex key af src = ifM (checkSecureHashes' key)
        ( do
                withObjectLoc key storeobject
                return True
@@ -339,7 +340,7 @@ moveAnnex key src = ifM (checkSecureHashes' key)
   where
        storeobject dest = ifM (liftIO $ R.doesPathExist dest)
                ( alreadyhave
-               , modifyContent dest $ do
+               , adjustedBranchRefresh af $ modifyContent dest $ do
                        freezeContent src
                        liftIO $ moveFile
                                (fromRawFilePath src)
@@ -500,8 +501,7 @@ cleanObjectLoc key cleaner = do
                maybe noop (const $ removeparents dir (n-1))
                        <=< catchMaybeIO $ removeDirectory (fromRawFilePath dir)
 
-{- Removes a key's file from .git/annex/objects/
- -}
+{- Removes a key's file from .git/annex/objects/ -}
 removeAnnex :: ContentRemovalLock -> Annex ()
 removeAnnex (ContentRemovalLock key) = withObjectLoc key $ \file ->
        cleanObjectLoc key $ do
@@ -515,7 +515,8 @@ removeAnnex (ContentRemovalLock key) = withObjectLoc key $ \file ->
        -- Check associated pointer file for modifications, and reset if
        -- it's unmodified.
        resetpointer file = ifM (isUnmodified key file)
-               ( depopulatePointerFile key file
+               ( adjustedBranchRefresh (AssociatedFile (Just file)) $
+                       depopulatePointerFile key file
                -- Modified file, so leave it alone.
                -- If it was a hard link to the annex object,
                -- that object might have been frozen as part of the
index 0cb257c1035722645e18a4c655c79ca918915ac5..57d7b5b2c11d2e8be529b8f71ea2c8b16aeb7b76 100644 (file)
@@ -460,7 +460,7 @@ importKeys remote importtreeconfig importcontent importablecontents = do
                                        ia loc cid (fromRawFilePath tmpfile)
                                        (pure k)
                                        (combineMeterUpdate p' p)
-                               ok <- moveAnnex k' tmpfile
+                               ok <- moveAnnex k' af tmpfile
                                when ok $
                                        logStatus k InfoPresent
                                return (Just (k', ok))
@@ -503,7 +503,7 @@ importKeys remote importtreeconfig importcontent importablecontents = do
                                p
                        case keyGitSha k of
                                Nothing -> do
-                                       ok <- moveAnnex k tmpfile
+                                       ok <- moveAnnex k af tmpfile
                                        when ok $ do
                                                recordcidkey cidmap db cid k
                                                logStatus k InfoPresent
index 0e5a7a2452f83a705e6f3d1f36cede619f37173d..6aeeea25cd8d7c6030c9f11c8c050fbdaddcbaca 100644 (file)
@@ -177,13 +177,19 @@ ingest' preferredbackend meterupdate (Just (LockedDown cfg source)) mk restage =
        go _ _ Nothing = failure "failed to generate a key"
 
        golocked key mcache s =
-               tryNonAsync (moveAnnex key $ contentLocation source) >>= \case
+               tryNonAsync (moveAnnex key naf (contentLocation source)) >>= \case
                        Right True -> do
                                populateAssociatedFiles key source restage
                                success key mcache s            
                        Right False -> giveup "failed to add content to annex"
                        Left e -> restoreFile (keyFilename source) key e
 
+       -- moveAnnex uses the AssociatedFile provided to it to unlock
+       -- locked files when getting a file in an adjusted branch.
+       -- That case does not apply here, where we're adding an unlocked
+       -- file, so provide it nothing.
+       naf = AssociatedFile Nothing
+
        gounlocked key (Just cache) s = do
                -- Remove temp directory hard link first because
                -- linkToAnnex falls back to copying if a file
@@ -352,7 +358,7 @@ addAnnexedFile ci matcher file key mtmp = ifM (addUnlocked matcher mi)
                stagePointerFile file mode =<< hashPointerFile key
                Database.Keys.addAssociatedFile key =<< inRepo (toTopFilePath file)
                case mtmp of
-                       Just tmp -> ifM (moveAnnex key tmp)
+                       Just tmp -> ifM (moveAnnex key af tmp)
                                ( linkunlocked mode >> return True
                                , writepointer mode >> return False
                                )
@@ -363,10 +369,11 @@ addAnnexedFile ci matcher file key mtmp = ifM (addUnlocked matcher mi)
        , do
                addLink ci file key Nothing
                case mtmp of
-                       Just tmp -> moveAnnex key tmp
+                       Just tmp -> moveAnnex key af tmp
                        Nothing -> return True
        )
   where
+       af = AssociatedFile (Just file)
        mi = case mtmp of
                Just tmp -> MatchingFile $ FileInfo
                        { contentFile = Just tmp
index 9489507055b677964b313af4b5911b3d3414b87e..87daf47413f1995b99553d37880e00cb6392e72e 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -3,6 +3,8 @@ git-annex (8.20201117) UNRELEASED; urgency=medium
   * adjust: New --unlock-present mode which locks files whose content is not
     present (so the broken symlink is visible), while unlocking files whose
     content is present.
+  * Added annex.adjustedbranchrefresh git config to update adjusted
+    branches set up by git-annex adjust --unlock-present/--hide-missing.
   * examinekey: Added a "file" format variable for consistency with find,
     and for easier scripting.
 
index ffb40df89c3ba9aa8b764bb38c7288e945f3ea36..c31b2c0bd7b34abc1338337d97a8797be183a89e 100644 (file)
@@ -114,7 +114,7 @@ getKey' key afile = dispatch
                | Remote.hasKeyCheap r =
                        either (const False) id <$> Remote.hasKey r key
                | otherwise = return True
-       docopy r witness = getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) key $ \dest ->
+       docopy r witness = getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) key afile $ \dest ->
                download (Remote.uuid r) key afile stdRetry
                        (\p -> do
                                showAction $ "from " ++ Remote.name r
index 1e5a282752a40b97b31fa49ec0f7935625be079b..114f2507afb3878ffdbcb5abd0aa2bc0f047bd55 100644 (file)
@@ -225,7 +225,7 @@ fromPerform src removewhen key afile = do
   where
        get = notifyTransfer Download afile $ 
                download (Remote.uuid src) key afile stdRetry $ \p ->
-                       getViaTmp (Remote.retrievalSecurityPolicy src) (RemoteVerify src) key $ \t ->
+                       getViaTmp (Remote.retrievalSecurityPolicy src) (RemoteVerify src) key afile $ \t ->
                                Remote.verifiedAction $ Remote.retrieveKeyFile src key afile (fromRawFilePath t) p
        
        dispatch _ _ False = stop -- failed
index bb423aea4eb99c66c72f9c9a1eb718033e42eb9a..f6e29b6f90588739b878138d92d43db8a9c17d16 100644 (file)
@@ -212,7 +212,7 @@ storeReceived f = do
                        warning $ "Received a file " ++ f ++ " that is not a git-annex key. Deleting this file."
                        liftIO $ removeWhenExistsWith removeLink f
                Just k -> void $
-                       getViaTmpFromDisk RetrievalVerifiableKeysSecure AlwaysVerify k $ \dest -> unVerified $
+                       getViaTmpFromDisk RetrievalVerifiableKeysSecure AlwaysVerify k (AssociatedFile Nothing) $ \dest -> unVerified $
                                liftIO $ catchBoolIO $ do
                                        rename f (fromRawFilePath dest)
                                        return True
index 4f325f4d8748224ef38aeed53a5c894eb47a6b13..ec94d98856aa92a3117d0ca7101e9887018cbb3c 100644 (file)
@@ -90,7 +90,7 @@ linkKey file oldkey newkey = ifM (isJust <$> isAnnexLink file)
         - This avoids hard linking to content linked to an
         - unlocked file, which would leave the new key unlocked
         - and vulnerable to corruption. -}
-       ( getViaTmpFromDisk RetrievalAllKeysSecure DefaultVerify newkey $ \tmp -> unVerified $ do
+       ( getViaTmpFromDisk RetrievalAllKeysSecure DefaultVerify newkey (AssociatedFile Nothing) $ \tmp -> unVerified $ do
                oldobj <- calcRepo (gitAnnexLocation oldkey)
                isJust <$> linkOrCopy' (return True) newkey oldobj tmp Nothing
        , do
index 3455acc8765789f84468fa0222512968b8afe187..df81ecc99ddc5854ac6b70b9918ef68a057517f0 100644 (file)
@@ -33,7 +33,7 @@ start (_, key) = fieldTransfer Download key $ \_p -> do
        let verify = if fromunlocked then AlwaysVerify else DefaultVerify
        -- This matches the retrievalSecurityPolicy of Remote.Git
        let rsp = RetrievalAllKeysSecure
-       ifM (getViaTmp rsp verify key go)
+       ifM (getViaTmp rsp verify key (AssociatedFile Nothing) go)
                ( do
                        -- forcibly quit after receiving one key,
                        -- and shutdown cleanly
index 69bace8a98066bd49ae361fc7c6752b87b433abd..c49b234004b0cc63a4e722d894afcfdb905e112b 100644 (file)
@@ -88,7 +88,7 @@ perform src key = ifM move
        )
   where
        move = checkDiskSpaceToGet key False $
-               moveAnnex key src
+               moveAnnex key (AssociatedFile Nothing) src
 
 cleanup :: Key -> CommandCleanup
 cleanup key = do
index 859a5871e55e6888dd27b974fccb50219081a8d5..ec15cf87c71a5862beb77ae435629065a7afc79c 100644 (file)
@@ -35,7 +35,7 @@ perform file key = do
        -- the file might be on a different filesystem, so moveFile is used
        -- rather than simply calling moveAnnex; disk space is also
        -- checked this way.
-       ok <- getViaTmp RetrievalAllKeysSecure DefaultVerify key $ \dest -> unVerified $
+       ok <- getViaTmp RetrievalAllKeysSecure DefaultVerify key (AssociatedFile Nothing) $ \dest -> unVerified $
                if dest /= file
                        then liftIO $ catchBoolIO $ do
                                moveFile (fromRawFilePath file) (fromRawFilePath dest)
index 8fa3f497429f7b3b7c84282584d354cf50aeaede..966cba340e6a01f889d38095d8bf2a65f28539d5 100644 (file)
@@ -60,6 +60,7 @@ import Logs.Export
 import Logs.PreferredContent
 import Annex.AutoMerge
 import Annex.AdjustedBranch
+import Annex.AdjustedBranch.Merge
 import Annex.Ssh
 import Annex.BloomFilter
 import Annex.UpdateInstead
index 7258f9eb08f33089d50c0ec552f408a096990de0..0f15ae6b41544619e6ea8d35c608e7de0f62102d 100644 (file)
@@ -294,7 +294,7 @@ test runannex mkr mkk =
                Just b -> case Types.Backend.verifyKeyContent b of
                        Nothing -> return True
                        Just verifier -> verifier k (serializeKey' k)
-       get r k = getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest ->
+       get r k = getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k (AssociatedFile Nothing) $ \dest ->
                tryNonAsync (Remote.retrieveKeyFile r k (AssociatedFile Nothing) (fromRawFilePath dest) nullMeterUpdate) >>= \case
                        Right v -> return (True, v)
                        Left _ -> return (False, UnVerified)
@@ -368,13 +368,13 @@ testUnavailable runannex mkr mkk =
        , check (`notElem` [Right True, Right False]) "checkPresent" $ \r k ->
                Remote.checkPresent r k
        , check (== Right False) "retrieveKeyFile" $ \r k ->
-               getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest ->
+               getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k (AssociatedFile Nothing) $ \dest ->
                        tryNonAsync (Remote.retrieveKeyFile r k (AssociatedFile Nothing) (fromRawFilePath dest) nullMeterUpdate) >>= \case
                                Right v -> return (True, v)
                                Left _ -> return (False, UnVerified)
        , check (== Right False) "retrieveKeyFileCheap" $ \r k -> case Remote.retrieveKeyFileCheap r of
                Nothing -> return False
-               Just a -> getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k $ \dest -> 
+               Just a -> getViaTmp (Remote.retrievalSecurityPolicy r) (RemoteVerify r) k (AssociatedFile Nothing) $ \dest -> 
                        unVerified $ isRight
                                <$> tryNonAsync (a k (AssociatedFile Nothing) (fromRawFilePath dest))
        ]
@@ -436,7 +436,7 @@ randKey sz = withTmpFile "randkey" $ \f h -> do
        k <- case Types.Backend.genKey Backend.Hash.testKeyBackend of
                Just a -> a ks nullMeterUpdate
                Nothing -> giveup "failed to generate random key (backend problem)"
-       _ <- moveAnnex k (toRawFilePath f)
+       _ <- moveAnnex k (AssociatedFile Nothing) (toRawFilePath f)
        return k
 
 getReadonlyKey :: Remote -> FilePath -> Annex Key
index 43f0eb254f53b05984040143fa5c4ce69bc492f0..b7f3cc917725e8c7f2c26d06b05c0f1d6a4d0592 100644 (file)
@@ -63,7 +63,7 @@ toPerform key file remote = go Upload file $
 fromPerform :: Key -> AssociatedFile -> Remote -> CommandPerform
 fromPerform key file remote = go Upload file $
        download (uuid remote) key file stdRetry $ \p ->
-               getViaTmp (retrievalSecurityPolicy remote) (RemoteVerify remote) key $ \t ->
+               getViaTmp (retrievalSecurityPolicy remote) (RemoteVerify remote) key file $ \t ->
                        tryNonAsync (Remote.retrieveKeyFile remote key file (fromRawFilePath t) p) >>= \case
                                Right v -> return (True, v)     
                                Left e -> do
index 4d13f3d28fa3521bd97f5b6aa11ebe1b83e9bcf7..f0ac31be97e4b0cbf23902e1f2854d3810a9dd97 100644 (file)
@@ -47,7 +47,7 @@ start = do
                                                return True
                | otherwise = notifyTransfer direction file $
                        download (Remote.uuid remote) key file stdRetry $ \p ->
-                               getViaTmp (Remote.retrievalSecurityPolicy remote) (RemoteVerify remote) key $ \t -> do
+                               getViaTmp (Remote.retrievalSecurityPolicy remote) (RemoteVerify remote) key file $ \t -> do
                                        r <- tryNonAsync (Remote.retrieveKeyFile remote key file (fromRawFilePath t) p) >>= \case
                                                Left e -> do
                                                        warning (show e)
index 737d96be9184871c1109004eacbafe52893db808..41f5f3b5dd72606eeb889479c844ebc78d37d5da 100644 (file)
@@ -76,7 +76,7 @@ runLocal runst runner a = case a of
                v <- tryNonAsync $ do
                        let runtransfer ti = 
                                Right <$> transfer download k af (\p ->
-                                       getViaTmp rsp DefaultVerify k $ \tmp ->
+                                       getViaTmp rsp DefaultVerify k af $ \tmp ->
                                                storefile (fromRawFilePath tmp) o l getb validitycheck p ti)
                        let fallback = return $ Left $
                                ProtoFailureMessage "transfer already in progress, or unable to take transfer lock"
index 97fee8cb923db0b611504baeb05af007177986d4..0a44f6854837f59761b9d4b3c0609072785b6428 100644 (file)
@@ -690,7 +690,7 @@ copyToRemote' repo r st@(State connpool duc _ _ _) key file meterupdate
                                copier <- mkCopier hardlink st params
                                let verify = Annex.Content.RemoteVerify r
                                let rsp = RetrievalAllKeysSecure
-                               res <- Annex.Content.getViaTmp rsp verify key $ \dest ->
+                               res <- Annex.Content.getViaTmp rsp verify key file $ \dest ->
                                        metered (Just (combineMeterUpdate meterupdate p)) key $ \_ p' -> 
                                                copier object (fromRawFilePath dest) p' (liftIO checksuccessio)
                                Annex.Content.saveState True
index 337b7eadf17f57b884383751dd132dde83258ddc..23dc7e748a4d1adf03918019c341831d0626987a 100644 (file)
@@ -16,6 +16,7 @@ data CleanupAction
        | StopHook UUID
        | FsckCleanup
        | SshCachingCleanup
+       | AdjustedBranchUpdate
        | TorrentCleanup URLString
        | OtherTmpCleanup
        deriving (Eq, Ord)
index c4e3abbf8e492cb23a503596401427b6314deaa6..7de6b62e46948c578dd1461ed82d6c0b29329842 100644 (file)
@@ -125,6 +125,7 @@ data GitConfig = GitConfig
        , annexAutoUpgradeRepository :: Bool
        , annexCommitMode :: CommitMode
        , annexSkipUnknown :: Bool
+       , annexAdjustedBranchRefresh :: Integer
        , coreSymlinks :: Bool
        , coreSharedRepository :: SharedRepository
        , receiveDenyCurrentBranch :: DenyCurrentBranch
@@ -219,6 +220,10 @@ extractGitConfig configsource r = GitConfig
                then ManualCommit
                else AutomaticCommit
        , annexSkipUnknown = getbool (annexConfig "skipunknown") True
+       , annexAdjustedBranchRefresh = fromMaybe
+               -- parse as bool if it's not a number
+               (if getbool "adjustedbranchrefresh" False then 1 else 0)
+               (getmayberead (annexConfig "adjustedbranchrefresh"))
        , coreSymlinks = getbool "core.symlinks" True
        , coreSharedRepository = getSharedRepository r
        , receiveDenyCurrentBranch = getDenyCurrentBranch r
index bddc0bd000d37d031dee5bb5e51aa9ee9fcf5583..ca8601e2243782b6ff2b9e1dcf5faaf98fc790da 100644 (file)
@@ -19,7 +19,8 @@ upgrade = do
        olddir <- fromRawFilePath <$> fromRepo gitAnnexDir
        keys <- getKeysPresent0 olddir
        forM_ keys $ \k ->
-               moveAnnex k $ toRawFilePath $ olddir </> keyFile0 k
+               moveAnnex k (AssociatedFile Nothing)
+                       (toRawFilePath $ olddir </> keyFile0 k)
 
        -- update the symlinks to the key files
        -- No longer needed here; V1.upgrade does the same thing
index 64a21e7f717a262debd8efa4b79f6eac742e7411..f69025ea06a493d609d8740c8ad0e4fc7e19c19d 100644 (file)
@@ -80,7 +80,7 @@ moveContent = do
                let d = parentDir f'
                liftIO $ allowWrite d
                liftIO $ allowWrite f'
-               _ <- moveAnnex k f'
+               _ <- moveAnnex k (AssociatedFile Nothing) f'
                liftIO $ removeDirectory (fromRawFilePath d)
 
 updateSymlinks :: Annex ()
index 125272b86af14598aa2a31d339e5f055bc8c3cea..97b7518a84ff2d67bc7033497f41f3323fb86ebf 100644 (file)
@@ -82,7 +82,8 @@ and will also propagate commits back to the original branch.
   branch.
   
   To update the adjusted branch to reflect changes to content availability, 
-  run `git annex adjust --hide-missing` again.
+  run `git annex adjust --hide-missing` again. Or, to automate updates,
+  set the `annex.adjustedbranchrefresh` config.
 
   Despite missing files being hidden, `git annex sync --content` will
   still operate on them, and can be used to download missing
@@ -104,7 +105,8 @@ and will also propagate commits back to the original branch.
   not be broken symlinks.
   
   To update the adjusted branch to reflect changes to content availability, 
-  run `git annex adjust --unlock-present` again. Or use `git-annex sync
+  run `git annex adjust --unlock-present` again. Or, to automate updates,
+  set the `annex.adjustedbranchrefresh` config. Or use `git-annex sync
   --content`, which updates the branch after transferring content.
 
 # SEE ALSO
index 5240b3e46ce20836df5560861c10b73c7a47ce6d..1c49bf408b634fd9e60075770ae068985bb65050 100644 (file)
@@ -1016,6 +1016,22 @@ Like other git commands, git-annex is configured via `.git/config`.
 
   When the `--batch` option is used, this configuration is ignored.
 
+* `annex.adjustedbranchrefresh`
+
+  When [[git-annex-adjust]](1) is used to set up an adjusted branch
+  that needs to be refreshed after getting or dropping files, this config
+  controls how frequently the branch is refreshed. 
+
+  Refreshing the branch takes some time, so doing it after every file
+  can be too slow. The default value is 0 (or false), which does not
+  refresh the branch. 1 (or true) will refresh once, after git-annex
+  has made other changes. Higher values refresh after approximately that
+  many files need to be updated. Ie, 2 refreshes after every file,
+  and 100 after every 99 files.
+
+  (If git-annex gets faster in the future, refresh rates will increase
+  proportional to the speed improvements.)
+
 * `annex.queuesize`
 
   git-annex builds a queue of git commands, in order to combine similar
index f41b25f12934d186762c5118dd5e908e1c183a54..133382e11bade5aef049002da5194b9dec12ec15 100644 (file)
@@ -10,6 +10,18 @@ it makes sense to do that? And for that matter, can it be done efficiently
 enough to do it more frequently? After every file or after some number of
 files, or after processing all files in a (sub-)tree?
 
+> Since the answer to that will change over time, let's make a config for
+> it. annex.adjustedbranchrefresh
+> 
+> The config can start out as a range from 0 up that indicates how
+> infrequently to update the branch for this. With 0 being never refresh,
+> 1 being refresh the minimum (once at shutdown), 2 being refresh after 
+> 1 file, 100 after every 99 files, etc.
+> If refreshing gets twice as fast, divide the numbers by two, etc.
+> If it becomes sufficiently fast that the overhead doesn't matter,
+> it can change to a simple boolean. Since 0 is false and > 0 is true,
+> in git config, the old values will still work. (done)
+
 Investigation of the obvious things that make it slow follows:
 
 ## efficient branch adjustment
index f2dc13ec2056b26d6fb531762852c11782d69d69..f5db5cc5c75fc4eb2cdb10903932f578fe3e745c 100644 (file)
@@ -609,6 +609,7 @@ Executable git-annex
     Annex
     Annex.Action
     Annex.AdjustedBranch
+    Annex.AdjustedBranch.Merge
     Annex.AdjustedBranch.Name
     Annex.AutoMerge
     Annex.BloomFilter