git-annex 7.20190122 → 7.20190129
raw patch · 12 files changed
+170/−54 lines, 12 files
Files
- Annex/FileMatcher.hs +2/−14
- Annex/Magic.hs +45/−0
- Assistant/WebApp/Configurators/Ssh.hs +0/−3
- Build/Version.hs +7/−2
- CHANGELOG +15/−0
- Command/Export.hs +5/−0
- Config.hs +6/−1
- Limit.hs +3/−9
- Remote/Helper/Special.hs +2/−4
- Remote/S3.hs +82/−19
- doc/git-annex-export.mdwn +1/−1
- git-annex.cabal +2/−1
Annex/FileMatcher.hs view
@@ -30,13 +30,9 @@ import Git.FilePath import Types.Remote (RemoteConfig) import Annex.CheckAttr+import Annex.Magic import Git.CheckAttr (unspecifiedAttr) -#ifdef WITH_MAGICMIME-import Magic-import Utility.Env-#endif- import Data.Either import qualified Data.Set as S @@ -139,15 +135,7 @@ mkLargeFilesParser :: Annex (String -> [ParseResult]) mkLargeFilesParser = do-#ifdef WITH_MAGICMIME- magicmime <- liftIO $ catchMaybeIO $ do- m <- magicOpen [MagicMimeType]- liftIO $ getEnv "GIT_ANNEX_DIR" >>= \case- Nothing -> magicLoadDefault m- Just d -> magicLoad m- (d </> "magic" </> "magic.mgc")- return m-#endif+ magicmime <- liftIO initMagicMimeType let parse = parseToken $ commonTokens #ifdef WITH_MAGICMIME ++ [ ValueToken "mimetype" (usev $ matchMagic magicmime) ]
+ Annex/Magic.hs view
@@ -0,0 +1,45 @@+{- Interface to libmagic+ -+ - Copyright 2019 Joey Hess <id@joeyh.name>+ -+ - Licensed under the GNU GPL version 3 or higher.+ -}++{-# LANGUAGE CPP #-}++module Annex.Magic (+ Magic,+ MimeType,+ initMagicMimeType,+ getMagicMimeType,+) where++#ifdef WITH_MAGICMIME+import Magic+import Utility.Env+import Common+#else+type Magic = ()+#endif++initMagicMimeType :: IO (Maybe Magic)+#ifdef WITH_MAGICMIME+initMagicMimeType = catchMaybeIO $ do+ m <- magicOpen [MagicMimeType]+ liftIO $ getEnv "GIT_ANNEX_DIR" >>= \case+ Nothing -> magicLoadDefault m+ Just d -> magicLoad m+ (d </> "magic" </> "magic.mgc")+ return m+#else+initMagicMimeType = return Nothing+#endif++type MimeType = String++getMagicMimeType :: Magic -> FilePath -> IO (Maybe MimeType)+#ifdef WITH_MAGICMIME+getMagicMimeType m f = Just <$> magicFile m f+#else+getMagicMimeType = return Nothing+#endif
Assistant/WebApp/Configurators/Ssh.hs view
@@ -24,10 +24,7 @@ import Git.Types (RemoteName, fromRef) import qualified Remote.GCrypt as GCrypt import qualified Annex-import qualified Git.Construct-import qualified Git.Config import qualified Git.Command-import qualified Remote.Helper.Ssh import qualified Annex.Branch import Annex.UUID import Logs.UUID
Build/Version.hs view
@@ -1,5 +1,6 @@ {- Package version determination. -} +{-# LANGUAGE LambdaCase #-} {-# OPTIONS_GHC -fno-warn-tabs #-} module Build.Version where@@ -13,6 +14,7 @@ import Utility.Monad import Utility.Exception+import Utility.Misc type Version = String @@ -54,9 +56,11 @@ middle = drop 1 . init writeVersion :: Version -> IO ()-writeVersion = writeFile "Build/Version" . body+writeVersion ver = catchMaybeIO (readFileStrict f) >>= \case+ Just s | s == body -> return ()+ _ -> writeFile f body where- body ver = unlines $ concat+ body = unlines $ concat [ header , ["packageversion :: String"] , ["packageversion = \"" ++ ver ++ "\""]@@ -67,3 +71,4 @@ , "" ] footer = []+ f = "Build/Version"
CHANGELOG view
@@ -1,3 +1,18 @@+git-annex (7.20190129) upstream; urgency=medium++ * initremote S3: When configured with versioning=yes, either ask the user+ to enable bucket versioning, or auto-enable it when built with aws-0.22.+ * enableremote S3: Do not let versioning=yes be set on existing remote,+ because when git-annex lacks S3 version IDs for files stored in+ the bucket, deleting them would cause data loss.+ * S3: Detect when version=yes but an exported file lacks a S3 version ID,+ and refuse to delete it, to avoid data loss.+ * S3: Send a Content-Type header when storing objects in S3,+ so exports to public buckets can be linked to from web pages.+ (When git-annex is built with MagicMime support.)++ -- Joey Hess <id@joeyh.name> Tue, 29 Jan 2019 15:09:20 -0400+ git-annex (7.20190122) upstream; urgency=medium * sync --content: Fix dropping unwanted content from the local repository.
Command/Export.hs view
@@ -279,6 +279,11 @@ loc = mkExportLocation f' f' = getTopFilePath f +-- Unlike a usual drop from a repository, this does not check that+-- numcopies is satisfied before removing the content. Typically an export+-- remote is untrusted, so would not count as a copy anyway.+-- Or, an export may be appendonly, and removing a file from it does+-- not really remove the content, which must be accessible later on. performUnexport :: Remote -> ExportActions Annex -> ExportHandle -> [ExportKey] -> ExportLocation -> CommandPerform performUnexport r ea db eks loc = do ifM (allM (\ek -> removeExport ea (asKey ek) loc) eks)
Config.hs view
@@ -1,6 +1,6 @@ {- Git configuration -- - Copyright 2011-2017 Joey Hess <id@joeyh.name>+ - Copyright 2011-2019 Joey Hess <id@joeyh.name> - - Licensed under the GNU GPL version 3 or higher. -}@@ -20,6 +20,8 @@ import Git.Types import qualified Types.Remote as Remote +import qualified Data.Map as M+ type UnqualifiedConfigKey = String data ConfigKey = ConfigKey String @@ -58,6 +60,9 @@ instance RemoteNameable Remote where getRemoteName = Remote.name++instance RemoteNameable Remote.RemoteConfig where+ getRemoteName c = fromMaybe "" (M.lookup "name" c) {- A per-remote config setting in git config. -} remoteConfig :: RemoteNameable r => r -> UnqualifiedConfigKey -> ConfigKey
Limit.hs view
@@ -5,8 +5,6 @@ - Licensed under the GNU GPL version 3 or higher. -} -{-# LANGUAGE CPP #-}- module Limit where import Annex.Common@@ -17,6 +15,7 @@ import Annex.WorkTree import Annex.Action import Annex.UUID+import Annex.Magic import Logs.Trust import Annex.NumCopies import Types.Key@@ -34,10 +33,6 @@ import Utility.HumanTime import Utility.DataUnits -#ifdef WITH_MAGICMIME-import Magic-#endif- import Data.Time.Clock.POSIX import qualified Data.Set as S import qualified Data.Map as M@@ -99,17 +94,16 @@ go (MatchingKey _ (AssociatedFile Nothing)) = pure False go (MatchingKey _ (AssociatedFile (Just af))) = pure $ matchGlob cglob af -#ifdef WITH_MAGICMIME matchMagic :: Maybe Magic -> MkLimit Annex matchMagic (Just magic) glob = Right $ const go where cglob = compileGlob glob CaseSensative -- memoized go (MatchingKey _ _) = pure False go (MatchingFile fi) = liftIO $ catchBoolIO $- matchGlob cglob <$> magicFile magic (currFile fi)+ maybe False (matchGlob cglob)+ <$> getMagicMimeType magic (currFile fi) go (MatchingInfo _ _ _ mimeval) = matchGlob cglob <$> getInfo mimeval matchMagic Nothing _ = Left "unable to load magic database; \"mimetype\" cannot be used"-#endif {- Adds a limit to skip files not believed to be present - in a specfied repository. Optionally on a prior date. -}
Remote/Helper/Special.hs view
@@ -69,10 +69,8 @@ gitConfigSpecialRemote :: UUID -> RemoteConfig -> [(String, String)] -> Annex () gitConfigSpecialRemote u c cfgs = do forM_ cfgs $ \(k, v) -> - setConfig (remoteConfig remotename k) v- setConfig (remoteConfig remotename "uuid") (fromUUID u)- where- remotename = fromJust (M.lookup "name" c)+ setConfig (remoteConfig c k) v+ setConfig (remoteConfig c "uuid") (fromUUID u) -- RetrievalVerifiableKeysSecure unless overridden by git config. --
Remote/S3.hs view
@@ -1,6 +1,6 @@ {- S3 remotes -- - Copyright 2011-2018 Joey Hess <id@joeyh.name>+ - Copyright 2011-2019 Joey Hess <id@joeyh.name> - - Licensed under the GNU GPL version 3 or higher. -}@@ -24,6 +24,7 @@ import qualified Data.Set as S import qualified System.FilePath.Posix as Posix import Data.Char+import Data.String import Network.Socket (HostName) import Network.HTTP.Conduit (Manager) import Network.HTTP.Client (responseStatus, responseBody, RequestBody(..))@@ -47,6 +48,7 @@ import qualified Remote.Helper.AWS as AWS import Creds import Annex.UUID+import Annex.Magic import Logs.Web import Logs.MetaData import Types.MetaData@@ -74,10 +76,11 @@ gen r u c gc = do cst <- remoteCost gc expensiveRemoteCost info <- extractS3Info c- return $ new cst info+ magic <- liftIO initMagicMimeType+ return $ new cst info magic where- new cst info = Just $ specialRemote c- (prepareS3Handle this $ store this info)+ new cst info magic = Just $ specialRemote c+ (prepareS3Handle this $ store this info magic) (prepareS3HandleMaybe this $ retrieve this c info) (prepareS3Handle this $ remove info) (prepareS3HandleMaybe this $ checkKey this c info)@@ -99,7 +102,7 @@ , checkPresentCheap = False , exportActions = withS3HandleMaybe c gc u $ \mh -> return $ ExportActions- { storeExport = storeExportS3 u info mh+ { storeExport = storeExportS3 u info mh magic , retrieveExport = retrieveExportS3 u info mh , removeExport = removeExportS3 u info mh , checkPresentExport = checkPresentExportS3 u info mh@@ -145,6 +148,7 @@ ] use fullconfig = do+ enableBucketVersioning ss fullconfig gc u gitConfigSpecialRemote u fullconfig [("s3", "true")] return (fullconfig, u) @@ -190,16 +194,16 @@ prepareS3HandleMaybe r = resourcePrepare $ const $ withS3HandleMaybe (config r) (gitconfig r) (uuid r) -store :: Remote -> S3Info -> S3Handle -> Storer-store _r info h = fileStorer $ \k f p -> do- void $ storeHelper info h f (T.pack $ bucketObject info k) p+store :: Remote -> S3Info -> Maybe Magic -> S3Handle -> Storer+store _r info magic h = fileStorer $ \k f p -> do+ void $ storeHelper info h magic f (T.pack $ bucketObject info k) p -- Store public URL to item in Internet Archive. when (isIA info && not (isChunkKey k)) $ setUrlPresent k (iaPublicUrl info (bucketObject info k)) return True -storeHelper :: S3Info -> S3Handle -> FilePath -> S3.Object -> MeterUpdate -> Annex (Maybe S3VersionID)-storeHelper info h f object p = case partSize info of+storeHelper :: S3Info -> S3Handle -> Maybe Magic -> FilePath -> S3.Object -> MeterUpdate -> Annex (Maybe S3VersionID)+storeHelper info h magic f object p = case partSize info of Just partsz | partsz > 0 -> do fsz <- liftIO $ getFileSize f if fsz > partsz@@ -208,16 +212,20 @@ _ -> singlepartupload where singlepartupload = do+ contenttype <- getcontenttype rbody <- liftIO $ httpBodyStorer f p- r <- sendS3Handle h $ putObject info object rbody+ r <- sendS3Handle h $ (putObject info object rbody)+ { S3.poContentType = encodeBS <$> contenttype } return (mkS3VersionID object (S3.porVersionId r)) multipartupload fsz partsz = do #if MIN_VERSION_aws(0,16,0)+ contenttype <- getcontenttype let startreq = (S3.postInitiateMultipartUpload (bucket info) object) { S3.imuStorageClass = Just (storageClass info) , S3.imuMetadata = metaHeaders info , S3.imuAutoMakeBucket = isIA info , S3.imuExpires = Nothing -- TODO set some reasonable expiry+ , S3.imuContentType = fromString <$> contenttype } uploadid <- S3.imurUploadId <$> sendS3Handle h startreq @@ -254,6 +262,8 @@ warning $ "Cannot do multipart upload (partsize " ++ show partsz ++ ") of large file (" ++ show fsz ++ "); built with too old a version of the aws library." singlepartupload #endif+ getcontenttype = liftIO $+ maybe (pure Nothing) (flip getMagicMimeType f) magic {- Implemented as a fileRetriever, that uses conduit to stream the chunks - out to the file. Would be better to implement a byteRetriever, but@@ -343,16 +353,16 @@ | otherwise = Nothing #endif -storeExportS3 :: UUID -> S3Info -> Maybe S3Handle -> FilePath -> Key -> ExportLocation -> MeterUpdate -> Annex Bool-storeExportS3 u info (Just h) f k loc p = +storeExportS3 :: UUID -> S3Info -> Maybe S3Handle -> Maybe Magic -> FilePath -> Key -> ExportLocation -> MeterUpdate -> Annex Bool+storeExportS3 u info (Just h) magic f k loc p = catchNonAsync go (\e -> warning (show e) >> return False) where go = do let o = T.pack $ bucketExportLocation info loc- storeHelper info h f o p+ storeHelper info h magic f o p >>= setS3VersionID info u k return True-storeExportS3 u _ Nothing _ _ _ _ = do+storeExportS3 u _ Nothing _ _ _ _ _ = do warning $ needS3Creds u return False @@ -373,7 +383,7 @@ exporturl = bucketExportLocation info loc removeExportS3 :: UUID -> S3Info -> Maybe S3Handle -> Key -> ExportLocation -> Annex Bool-removeExportS3 _u info (Just h) _k loc = +removeExportS3 u info (Just h) k loc = checkVersioning info u k $ catchNonAsync go (\e -> warning (show e) >> return False) where go = do@@ -396,7 +406,8 @@ -- S3 has no move primitive; copy and delete. renameExportS3 :: UUID -> S3Info -> Maybe S3Handle -> Key -> ExportLocation -> ExportLocation -> Annex Bool-renameExportS3 _u info (Just h) _k src dest = catchNonAsync go (\_ -> return False)+renameExportS3 u info (Just h) k src dest = checkVersioning info u k $ + catchNonAsync go (\_ -> return False) where go = do let co = S3.copyObject (bucket info) dstobject@@ -416,8 +427,8 @@ - UUID file within the bucket. - - Some ACLs can allow read/write to buckets, but not querying them,- - so first check if the UUID file already exists and we can skip doing- - anything.+ - so first check if the UUID file already exists and we can skip creating+ - it. -} genBucket :: RemoteConfig -> RemoteGitConfig -> UUID -> Annex () genBucket c gc u = do@@ -848,3 +859,55 @@ getS3VersionIDPublicUrls :: (S3Info -> BucketObject -> URLString) -> S3Info -> UUID -> Key -> Annex [URLString] getS3VersionIDPublicUrls mk info u k = map (s3VersionIDPublicUrl mk info) <$> getS3VersionID u k++-- Enable versioning on the bucket can only be done at init time;+-- setting versioning in a bucket that git-annex has already exported+-- files to risks losing the content of those un-versioned files.+enableBucketVersioning :: SetupStage -> RemoteConfig -> RemoteGitConfig -> UUID -> Annex ()+#if MIN_VERSION_aws(0,22,0)+enableBucketVersioning ss c gc u = do+#else+enableBucketVersioning ss c _ _ = do+#endif+ info <- extractS3Info c+ case ss of+ Init -> when (versioning info) $+ enableversioning (bucket info)+ Enable oldc -> do+ oldinfo <- extractS3Info oldc+ when (versioning info /= versioning oldinfo) $+ giveup "Cannot change versioning= of existing S3 remote."+ where+ enableversioning b = do+#if MIN_VERSION_aws(0,22,0)+ showAction "enabling bucket versioning"+ withS3Handle c gc u $ \h ->+ void $ sendS3Handle h $ S3.putBucketVersioning b S3.VersioningEnabled+#else+ showLongNote $ unlines+ [ "This version of git-annex cannot auto-enable S3 bucket versioning."+ , "You need to manually enable versioning in the S3 console"+ , "for the bucket \"" ++ T.unpack b ++ "\""+ , "https://docs.aws.amazon.com/AmazonS3/latest/user-guide/enable-versioning.html"+ , "It's important you enable versioning before storing anything in the bucket!"+ ]+#endif++-- If the remote has versioning enabled, but the version ID is for some+-- reason not being recorded, it's not safe to perform an action that+-- will remove the unversioned file. The file may be the only copy of an+-- annex object.+--+-- This code could be removed eventually, since enableBucketVersioning+-- will avoid this situation. Before that was added, some remotes+-- were created without versioning, some unversioned files exported to+-- them, and then versioning enabled, and this is to avoid data loss in+-- those cases.+checkVersioning :: S3Info -> UUID -> Key -> Annex Bool -> Annex Bool+checkVersioning info u k a+ | versioning info = getS3VersionID u k >>= \case+ [] -> do+ warning $ "Remote is configured to use versioning, but no S3 version ID is recorded for this key, so it cannot safely be modified."+ return False+ _ -> a+ | otherwise = a
doc/git-annex-export.mdwn view
@@ -41,7 +41,7 @@ However, some special remotes, notably S3, support keeping track of old versions of files stored in them. If a special remote is set up to do that, it can be used as a key/value store and the limitations in the above-paragraph do not appy. Note that dropping content from such a remote is+paragraph do not apply. Note that dropping content from such a remote is not supported. See individual special remotes' documentation for details of how to enable such versioning.
git-annex.cabal view
@@ -1,5 +1,5 @@ Name: git-annex-Version: 7.20190122+Version: 7.20190129 Cabal-Version: >= 1.8 License: GPL-3 Maintainer: Joey Hess <id@joeyh.name>@@ -631,6 +631,7 @@ Annex.LockFile Annex.LockPool Annex.LockPool.PosixOrPid+ Annex.Magic Annex.MetaData Annex.MetaData.StandardFields Annex.Multicast