split upgrade into v9 and v10

v10 will run 1 year after the upgrade to v9, to give time for any v8
processes to die. Until that point, the v10 upgrade will be tried by
every process but deferred, so added support for deferring upgrades.

The upgrade prevention lock file that will be used by v10 is not yet
implemented, so it does not yet defer.

Sponsored-by: Dartmouth College's Datalad project
This commit is contained in:
Joey Hess 2022-01-19 13:06:31 -04:00
parent 4f7b8ce09d
commit 856ce5cf5f
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
14 changed files with 142 additions and 65 deletions

View file

@ -19,19 +19,19 @@ import qualified Annex
import qualified Data.Map as M import qualified Data.Map as M
defaultVersion :: RepoVersion defaultVersion :: RepoVersion
defaultVersion = RepoVersion 8 defaultVersion = RepoVersion 10
latestVersion :: RepoVersion latestVersion :: RepoVersion
latestVersion = RepoVersion 9 latestVersion = RepoVersion 10
supportedVersions :: [RepoVersion] supportedVersions :: [RepoVersion]
supportedVersions = map RepoVersion [8, 9] supportedVersions = map RepoVersion [8, 9, 10]
upgradeableVersions :: [RepoVersion] upgradeableVersions :: [RepoVersion]
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
upgradeableVersions = map RepoVersion [0..8] upgradeableVersions = map RepoVersion [0..10]
#else #else
upgradeableVersions = map RepoVersion [2..8] upgradeableVersions = map RepoVersion [2..10]
#endif #endif
autoUpgradeableVersions :: M.Map RepoVersion RepoVersion autoUpgradeableVersions :: M.Map RepoVersion RepoVersion
@ -41,6 +41,8 @@ autoUpgradeableVersions = M.fromList
, (RepoVersion 5, latestVersion) , (RepoVersion 5, latestVersion)
, (RepoVersion 6, latestVersion) , (RepoVersion 6, latestVersion)
, (RepoVersion 7, latestVersion) , (RepoVersion 7, latestVersion)
, (RepoVersion 8, latestVersion)
, (RepoVersion 9, latestVersion)
] ]
versionField :: ConfigKey versionField :: ConfigKey
@ -57,5 +59,5 @@ removeVersion = unsetConfig versionField
versionNeedsWritableContentFiles :: Maybe RepoVersion -> Bool versionNeedsWritableContentFiles :: Maybe RepoVersion -> Bool
versionNeedsWritableContentFiles (Just v) versionNeedsWritableContentFiles (Just v)
| v >= RepoVersion 9 = False | v >= RepoVersion 10 = False
versionNeedsWritableContentFiles _ = True versionNeedsWritableContentFiles _ = True

10
Types/Upgrade.hs Normal file
View file

@ -0,0 +1,10 @@
{- git-annex upgrade types
-
- Copyright 2022 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
module Types.Upgrade where
data UpgradeResult = UpgradeSuccess | UpgradeFailed | UpgradeDeferred

View file

@ -1,6 +1,6 @@
{- git-annex upgrade support {- git-annex upgrade support
- -
- Copyright 2010-2020 Joey Hess <id@joeyh.name> - Copyright 2010-2022 Joey Hess <id@joeyh.name>
- -
- Licensed under the GNU AGPL version 3 or higher. - Licensed under the GNU AGPL version 3 or higher.
-} -}
@ -10,6 +10,7 @@
module Upgrade where module Upgrade where
import Annex.Common import Annex.Common
import Types.Upgrade
import qualified Annex import qualified Annex
import qualified Git import qualified Git
import Config import Config
@ -27,6 +28,7 @@ import qualified Upgrade.V5
import qualified Upgrade.V6 import qualified Upgrade.V6
import qualified Upgrade.V7 import qualified Upgrade.V7
import qualified Upgrade.V8 import qualified Upgrade.V8
import qualified Upgrade.V9
import qualified Data.Map as M import qualified Data.Map as M
@ -63,25 +65,27 @@ needsUpgrade v
upgrade :: Bool -> RepoVersion -> Annex Bool upgrade :: Bool -> RepoVersion -> Annex Bool
upgrade automatic destversion = do upgrade automatic destversion = do
upgraded <- go =<< getVersion (upgraded, newversion) <- go =<< getVersion
when upgraded when upgraded $
postupgrade postupgrade newversion
return upgraded return upgraded
where where
go (Just v) go (Just v)
| v >= destversion = return True | v >= destversion = return (True, Just v)
| otherwise = ifM upgradingRemote | otherwise = ifM upgradingRemote
( upgraderemote ( upgraderemote
, ifM (up v) , up v >>= \case
( go (Just (RepoVersion (fromRepoVersion v + 1))) UpgradeSuccess -> go (Just (incrversion v) )
, return False UpgradeFailed -> return (False, Just v)
) UpgradeDeferred -> return (True, Just v)
) )
go _ = return True go Nothing = return (True, Nothing)
postupgrade = ifM upgradingRemote incrversion v = RepoVersion (fromRepoVersion v + 1)
postupgrade newversion = ifM upgradingRemote
( reloadConfig ( reloadConfig
, setVersion destversion , maybe noop setVersion newversion
) )
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
@ -98,7 +102,8 @@ upgrade automatic destversion = do
up (RepoVersion 6) = Upgrade.V6.upgrade automatic up (RepoVersion 6) = Upgrade.V6.upgrade automatic
up (RepoVersion 7) = Upgrade.V7.upgrade automatic up (RepoVersion 7) = Upgrade.V7.upgrade automatic
up (RepoVersion 8) = Upgrade.V8.upgrade automatic up (RepoVersion 8) = Upgrade.V8.upgrade automatic
up _ = return True up (RepoVersion 9) = Upgrade.V9.upgrade automatic
up _ = return UpgradeDeferred
-- Upgrade local remotes by running git-annex upgrade in them. -- Upgrade local remotes by running git-annex upgrade in them.
-- This avoids complicating the upgrade code by needing to handle -- This avoids complicating the upgrade code by needing to handle
@ -111,8 +116,8 @@ upgrade automatic destversion = do
] ]
(\p -> p { cwd = Just rp }) (\p -> p { cwd = Just rp })
(\_ _ _ pid -> waitForProcess pid >>= return . \case (\_ _ _ pid -> waitForProcess pid >>= return . \case
ExitSuccess -> True ExitSuccess -> (True, Nothing)
_ -> False _ -> (False, Nothing)
) )
upgradingRemote :: Annex Bool upgradingRemote :: Annex Bool

View file

@ -8,10 +8,11 @@
module Upgrade.V0 where module Upgrade.V0 where
import Annex.Common import Annex.Common
import Types.Upgrade
import Annex.Content import Annex.Content
import qualified Upgrade.V1 import qualified Upgrade.V1
upgrade :: Annex Bool upgrade :: Annex UpgradeResult
upgrade = do upgrade = do
showAction "v0 to v1" showAction "v0 to v1"

View file

@ -17,6 +17,7 @@ import qualified Data.ByteString.Lazy as L
import qualified System.FilePath.ByteString as P import qualified System.FilePath.ByteString as P
import Annex.Common import Annex.Common
import Types.Upgrade
import Annex.Content import Annex.Content
import Annex.Link import Annex.Link
import Annex.Perms import Annex.Perms
@ -53,7 +54,7 @@ import qualified Upgrade.V2
-- Something similar to the migrate subcommand could be used, -- Something similar to the migrate subcommand could be used,
-- and users could then run that at their leisure. -- and users could then run that at their leisure.
upgrade :: Annex Bool upgrade :: Annex UpgradeResult
upgrade = do upgrade = do
showAction "v1 to v2" showAction "v1 to v2"

View file

@ -8,6 +8,7 @@
module Upgrade.V2 where module Upgrade.V2 where
import Annex.Common import Annex.Common
import Types.Upgrade
import qualified Git import qualified Git
import qualified Git.Command import qualified Git.Command
import qualified Git.Ref import qualified Git.Ref
@ -38,7 +39,7 @@ olddir g
- * Remove stuff that used to be needed in .gitattributes. - * Remove stuff that used to be needed in .gitattributes.
- * Commit changes. - * Commit changes.
-} -}
upgrade :: Annex Bool upgrade :: Annex UpgradeResult
upgrade = do upgrade = do
showAction "v2 to v3" showAction "v2 to v3"
bare <- fromRepo Git.repoIsLocalBare bare <- fromRepo Git.repoIsLocalBare
@ -63,7 +64,7 @@ upgrade = do
unless bare push unless bare push
return True return UpgradeSuccess
locationLogs :: Annex [(Key, FilePath)] locationLogs :: Annex [(Key, FilePath)]
locationLogs = do locationLogs = do

View file

@ -8,8 +8,9 @@
module Upgrade.V4 where module Upgrade.V4 where
import Annex.Common import Annex.Common
import Types.Upgrade
{- Was only used for direct mode upgrade. v4 to v5 indirect update is a no-op, {- Was only used for direct mode upgrade. v4 to v5 indirect update is a no-op,
- and direct mode is no longer supported, so nothing needs to be done. -} - and direct mode is no longer supported, so nothing needs to be done. -}
upgrade :: Bool -> Annex Bool upgrade :: Bool -> Annex UpgradeResult
upgrade _automatic = return True upgrade _automatic = return UpgradeSuccess

View file

@ -10,6 +10,7 @@
module Upgrade.V5 where module Upgrade.V5 where
import Annex.Common import Annex.Common
import Types.Upgrade
import Config import Config
import Config.Smudge import Config.Smudge
import Annex.InodeSentinal import Annex.InodeSentinal
@ -36,7 +37,7 @@ import qualified Utility.RawFilePath as R
import qualified Data.ByteString as S import qualified Data.ByteString as S
upgrade :: Bool -> Annex Bool upgrade :: Bool -> Annex UpgradeResult
upgrade automatic = flip catchNonAsync onexception $ do upgrade automatic = flip catchNonAsync onexception $ do
unless automatic $ unless automatic $
showAction "v5 to v6" showAction "v5 to v6"
@ -55,11 +56,11 @@ upgrade automatic = flip catchNonAsync onexception $ do
-- use direct mode may not have created it. -- use direct mode may not have created it.
unlessM isDirect $ unlessM isDirect $
createInodeSentinalFile True createInodeSentinalFile True
return True return UpgradeSuccess
where where
onexception e = do onexception e = do
warning $ "caught exception: " ++ show e warning $ "caught exception: " ++ show e
return False return UpgradeFailed
-- git before 2.22 would OOM running git status on a large file. -- git before 2.22 would OOM running git status on a large file.
-- --

View file

@ -8,14 +8,15 @@
module Upgrade.V6 where module Upgrade.V6 where
import Annex.Common import Annex.Common
import Types.Upgrade
import Config import Config
import Annex.Hook import Annex.Hook
upgrade :: Bool -> Annex Bool upgrade :: Bool -> Annex UpgradeResult
upgrade automatic = do upgrade automatic = do
unless automatic $ unless automatic $
showAction "v6 to v7" showAction "v6 to v7"
unlessM isBareRepo $ do unlessM isBareRepo $ do
hookWrite postCheckoutHook hookWrite postCheckoutHook
hookWrite postMergeHook hookWrite postMergeHook
return True return UpgradeSuccess

View file

@ -12,6 +12,7 @@ module Upgrade.V7 where
import qualified Annex import qualified Annex
import Annex.Common import Annex.Common
import Types.Upgrade
import Annex.CatFile import Annex.CatFile
import qualified Database.Keys import qualified Database.Keys
import qualified Database.Keys.SQL import qualified Database.Keys.SQL
@ -23,7 +24,7 @@ import qualified Utility.RawFilePath as R
import qualified System.FilePath.ByteString as P import qualified System.FilePath.ByteString as P
upgrade :: Bool -> Annex Bool upgrade :: Bool -> Annex UpgradeResult
upgrade automatic = do upgrade automatic = do
unless automatic $ unless automatic $
showAction "v7 to v8" showAction "v7 to v8"
@ -54,7 +55,7 @@ upgrade automatic = do
updateSmudgeFilter updateSmudgeFilter
return True return UpgradeSuccess
gitAnnexKeysDbOld :: Git.Repo -> RawFilePath gitAnnexKeysDbOld :: Git.Repo -> RawFilePath
gitAnnexKeysDbOld r = gitAnnexDir r P.</> "keys" gitAnnexKeysDbOld r = gitAnnexDir r P.</> "keys"

View file

@ -8,37 +8,25 @@
module Upgrade.V8 where module Upgrade.V8 where
import Annex.Common import Annex.Common
import Annex.Content import Types.Upgrade
import Annex.Perms import Utility.Daemon
import Git.ConfigTypes
import Types.RepoVersion
upgrade :: Bool -> Annex Bool upgrade :: Bool -> Annex UpgradeResult
upgrade automatic = do upgrade automatic = do
unless automatic $ -- Skip running when git-annex assistant (or watch) is running,
showAction "v8 to v9" -- because these are long-running daemons that could conceivably
-- run for an entire year, and so still be running when the v10
-- upgrade happens. If the assistant then tried to drop a file
-- after the v10 upgrade, it would use the wrong content file
-- locking, which could lead to data loss. The remotedaemon does
-- not drop content, so will not block the upgrade.
pidfile <- fromRepo gitAnnexPidFile
liftIO (checkDaemon (fromRawFilePath pidfile)) >>= \case
Just _pid
| automatic -> return UpgradeDeferred
| otherwise -> giveup "Cannot upgrade to v9 when git-annex assistant or watch daemon is running."
Nothing -> do
unless automatic $
showAction "v8 to v9"
{- When core.sharedRepository is set, object files return UpgradeSuccess
- used to have their write bits set. That can now be removed,
- if the user the upgrade is running as has permission to remove
- it. (Otherwise, a later fsck will fix up the permissions.) -}
withShared $ \sr -> case sr of
GroupShared -> removewrite sr
AllShared -> removewrite sr
_ -> return ()
return True
where
newver = Just (RepoVersion 9)
removewrite sr = do
ks <- listKeys InAnnex
forM_ ks $ \k -> do
obj <- calcRepo (gitAnnexLocation k)
keystatus <- getKeyStatus k
case keystatus of
KeyPresent -> void $ tryIO $
freezeContent'' sr obj newver
KeyUnlockedThin -> return ()
KeyLockedThin -> return ()
KeyMissing -> return ()

45
Upgrade/V9.hs Normal file
View file

@ -0,0 +1,45 @@
{- git-annex v9 -> v10 upgrade support
-
- Copyright 2022 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
module Upgrade.V9 where
import Annex.Common
import Types.Upgrade
import Annex.Content
import Annex.Perms
import Git.ConfigTypes
import Types.RepoVersion
upgrade :: Bool -> Annex UpgradeResult
upgrade automatic = do
unless automatic $
showAction "v9 to v10"
{- When core.sharedRepository is set, object files
- used to have their write bits set. That can now be removed,
- if the user the upgrade is running as has permission to remove
- it. (Otherwise, a later fsck will fix up the permissions.) -}
withShared $ \sr -> case sr of
GroupShared -> removewrite sr
AllShared -> removewrite sr
_ -> return ()
return UpgradeDeferred
where
newver = Just (RepoVersion 9)
removewrite sr = do
ks <- listKeys InAnnex
forM_ ks $ \k -> do
obj <- calcRepo (gitAnnexLocation k)
keystatus <- getKeyStatus k
case keystatus of
KeyPresent -> void $ tryIO $
freezeContent'' sr obj newver
KeyUnlockedThin -> return ()
KeyLockedThin -> return ()
KeyMissing -> return ()

View file

@ -49,6 +49,24 @@ the upgrade would need to be run in a copy of the repository.
The upgrade events, so far: The upgrade events, so far:
## v9 -> v10 (git-annex version 10.x)
v10 repositories change a fundamental part of how git-annex does locking.
v9 repositories are automatically upgraded to v10, but with a delay.
The upgrade happens one year after the repository was upgraded to v9.
This delay is because it would not be safe to upgrade to v10 if a
git-annex process version 8.x was still running. Waiting a year makes
it very unlikely that such a process is still running.
## v8 -> v9 (git-annex version 10.x)
v8 repositories are automatically upgraded to v9.
Starting in v9, upgrades to a repository are prevented while another
git-annex command is running. That is needed for the v10 repository
upgrade to be performed safely.
## v7 -> v8 (git-annex version 8.x) ## v7 -> v8 (git-annex version 8.x)
v7 repositories are automatically upgraded to v8. v7 repositories are automatically upgraded to v8.

View file

@ -1055,6 +1055,7 @@ Executable git-annex
Types.Transitions Types.Transitions
Types.TrustLevel Types.TrustLevel
Types.UUID Types.UUID
Types.Upgrade
Types.UrlContents Types.UrlContents
Types.VectorClock Types.VectorClock
Types.View Types.View
@ -1070,6 +1071,7 @@ Executable git-annex
Upgrade.V6 Upgrade.V6
Upgrade.V7 Upgrade.V7
Upgrade.V8 Upgrade.V8
Upgrade.V9
Utility.Aeson Utility.Aeson
Utility.Android Utility.Android
Utility.Applicative Utility.Applicative