diff options
38 files changed, 271 insertions, 245 deletions
diff --git a/Annex/Content.hs b/Annex/Content.hs index a78cf674c..f91c1e72a 100644 --- a/Annex/Content.hs +++ b/Annex/Content.hs @@ -110,10 +110,10 @@ inAnnexSafe key = inAnnex' (fromMaybe False) (Just False) go key ( checkOr is_unlocked lockfile , return is_missing ) - checkOr def lockfile = do + checkOr d lockfile = do v <- checkLocked lockfile return $ case v of - Nothing -> def + Nothing -> d Just True -> is_locked Just False -> is_unlocked #else diff --git a/Annex/Difference.hs b/Annex/Difference.hs index cb363e80c..07789e7bb 100644 --- a/Annex/Difference.hs +++ b/Annex/Difference.hs @@ -31,9 +31,7 @@ import qualified Data.Map as M setDifferences :: Annex () setDifferences = do u <- getUUID - otherds <- either error return - =<< sanityCheckDifferences . allDifferences - <$> recordedDifferences + otherds <- allDifferences <$> recordedDifferences ds <- mappend otherds . annexDifferences <$> Annex.getGitConfig when (ds /= mempty) $ do ds'@(Differences l) <- ifM (isJust <$> getVersion) diff --git a/Annex/DirHashes.hs b/Annex/DirHashes.hs new file mode 100644 index 000000000..b58b5a215 --- /dev/null +++ b/Annex/DirHashes.hs @@ -0,0 +1,86 @@ +{- git-annex file locations + - + - Copyright 2010-2015 Joey Hess <id@joeyh.name> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Annex.DirHashes ( + Hasher, + HashLevels(..), + objectHashLevels, + branchHashLevels, + branchHashDir, + dirHashes, + hashDirMixed, + hashDirLower, +) where + +import Data.Bits +import Data.Word +import Data.Hash.MD5 +import Data.Default + +import Common +import Types.Key +import Types.GitConfig +import Types.Difference + +type Hasher = Key -> FilePath + +-- Number of hash levels to use. 2 is the default. +newtype HashLevels = HashLevels Int + +instance Default HashLevels where + def = HashLevels 2 + +objectHashLevels :: GitConfig -> HashLevels +objectHashLevels = configHashLevels OneLevelObjectHash + +branchHashLevels :: GitConfig -> HashLevels +branchHashLevels = configHashLevels OneLevelBranchHash + +configHashLevels :: Difference -> GitConfig -> HashLevels +configHashLevels d config + | hasDifference (== d) (annexDifferences config) = HashLevels 1 + | otherwise = def + +branchHashDir :: GitConfig -> Key -> String +branchHashDir config key = hashDirLower (branchHashLevels config) key + +{- Two different directory hashes may be used. The mixed case hash + - came first, and is fine, except for the problem of case-strict + - filesystems such as Linux VFAT (mounted with shortname=mixed), + - which do not allow using a directory "XX" when "xx" already exists. + - To support that, most repositories use the lower case hash for new data. -} +dirHashes :: [HashLevels -> Hasher] +dirHashes = [hashDirLower, hashDirMixed] + +hashDirs :: HashLevels -> Int -> String -> FilePath +hashDirs (HashLevels 1) sz s = addTrailingPathSeparator $ take sz s +hashDirs _ sz s = addTrailingPathSeparator $ take sz s </> drop sz s + +hashDirMixed :: HashLevels -> Hasher +hashDirMixed n k = hashDirs n 2 $ take 4 $ display_32bits_as_dir =<< [a,b,c,d] + where + ABCD (a,b,c,d) = md5 $ md5FilePath $ key2file $ nonChunkKey k + +hashDirLower :: HashLevels -> Hasher +hashDirLower n k = hashDirs n 3 $ take 6 $ md5s $ md5FilePath $ key2file $ nonChunkKey k + +{- modified version of display_32bits_as_hex from Data.Hash.MD5 + - Copyright (C) 2001 Ian Lynagh + - License: Either BSD or GPL + -} +display_32bits_as_dir :: Word32 -> String +display_32bits_as_dir w = trim $ swap_pairs cs + where + -- Need 32 characters to use. To avoid inaverdently making + -- a real word, use letters that appear less frequently. + chars = ['0'..'9'] ++ "zqjxkmvwgpfZQJXKMVWGPF" + cs = map (\x -> getc $ (shiftR w (6*x)) .&. 31) [0..7] + getc n = chars !! fromIntegral n + swap_pairs (x1:x2:xs) = x2:x1:swap_pairs xs + swap_pairs _ = [] + -- Last 2 will always be 00, so omit. + trim = take 6 diff --git a/Annex/FileMatcher.hs b/Annex/FileMatcher.hs index c6a729a9c..16ade922c 100644 --- a/Annex/FileMatcher.hs +++ b/Annex/FileMatcher.hs @@ -28,12 +28,12 @@ checkFileMatcher :: (FileMatcher Annex) -> FilePath -> Annex Bool checkFileMatcher matcher file = checkMatcher matcher Nothing (Just file) S.empty True checkMatcher :: (FileMatcher Annex) -> Maybe Key -> AssociatedFile -> AssumeNotPresent -> Bool -> Annex Bool -checkMatcher matcher mkey afile notpresent def - | isEmpty matcher = return def +checkMatcher matcher mkey afile notpresent d + | isEmpty matcher = return d | otherwise = case (mkey, afile) of (_, Just file) -> go =<< fileMatchInfo file (Just key, _) -> go (MatchingKey key) - _ -> return def + _ -> return d where go mi = matchMrun matcher $ \a -> a notpresent mi diff --git a/Annex/Wanted.hs b/Annex/Wanted.hs index 87b4377c2..ba7df0a9c 100644 --- a/Annex/Wanted.hs +++ b/Annex/Wanted.hs @@ -15,15 +15,15 @@ import qualified Data.Set as S {- Check if a file is preferred content for the local repository. -} wantGet :: Bool -> Maybe Key -> AssociatedFile -> Annex Bool -wantGet def key file = isPreferredContent Nothing S.empty key file def +wantGet d key file = isPreferredContent Nothing S.empty key file d {- Check if a file is preferred content for a remote. -} wantSend :: Bool -> Maybe Key -> AssociatedFile -> UUID -> Annex Bool -wantSend def key file to = isPreferredContent (Just to) S.empty key file def +wantSend d key file to = isPreferredContent (Just to) S.empty key file d {- Check if a file can be dropped, maybe from a remote. - Don't drop files that are preferred content. -} wantDrop :: Bool -> Maybe UUID -> Maybe Key -> AssociatedFile -> Annex Bool -wantDrop def from key file = do +wantDrop d from key file = do u <- maybe getUUID (return . id) from - not <$> isPreferredContent (Just u) (S.singleton u) key file def + not <$> isPreferredContent (Just u) (S.singleton u) key file d diff --git a/Command/Find.hs b/Command/Find.hs index a52d17384..55124d838 100644 --- a/Command/Find.hs +++ b/Command/Find.hs @@ -7,6 +7,7 @@ module Command.Find where +import Data.Default import qualified Data.Map as M import Common.Annex @@ -65,8 +66,8 @@ keyVars key = , ("bytesize", size show) , ("humansize", size $ roughSize storageUnits True) , ("keyname", keyName key) - , ("hashdirlower", hashDirLower key) - , ("hashdirmixed", hashDirMixed key) + , ("hashdirlower", hashDirLower def key) + , ("hashdirmixed", hashDirMixed def key) , ("mtime", whenavail show $ keyMtime key) ] where diff --git a/Command/Log.hs b/Command/Log.hs index 7eaa48f70..7c48388c2 100644 --- a/Command/Log.hs +++ b/Command/Log.hs @@ -141,7 +141,8 @@ getLog :: Key -> [CommandParam] -> Annex [String] getLog key os = do top <- fromRepo Git.repoPath p <- liftIO $ relPathCwdToFile top - let logfile = p </> locationLogFile key + config <- Annex.getGitConfig + let logfile = p </> locationLogFile config key inRepo $ pipeNullSplitZombie $ [ Params "log -z --pretty=format:%ct --raw --abbrev=40" , Param "--remove-empty" diff --git a/Command/Uninit.hs b/Command/Uninit.hs index 592b71a02..28c169919 100644 --- a/Command/Uninit.hs +++ b/Command/Uninit.hs @@ -18,6 +18,9 @@ import Annex.Content import Annex.Init import Utility.FileMode +import System.IO.HVFS +import System.IO.HVFS.Utils + cmd :: [Command] cmd = [addCheck check $ command "uninit" paramPaths seek SectionUtility "de-initialize git-annex and clean out repository"] @@ -88,7 +91,7 @@ finish = do - preparation for removal. -} prepareRemoveAnnexDir :: FilePath -> IO () prepareRemoveAnnexDir annexdir = - mapM_ (void . tryIO . allowWrite) =<< dirContentsRecursive annexdir + recurseDir SystemFS annexdir >>= mapM_ (void . tryIO . allowWrite) {- Keys that were moved out of the annex have a hard link still in the - annex, with > 1 link count, and those can be removed. @@ -11,6 +11,7 @@ import Data.Maybe as X import Data.List as X hiding (head, tail, init, last) import Data.String.Utils as X hiding (join) import Data.Monoid as X +import Data.Default as X import System.FilePath as X import System.Directory as X @@ -23,7 +23,7 @@ instance Show ConfigKey where {- Looks up a setting in git config. -} getConfig :: ConfigKey -> String -> Annex String -getConfig (ConfigKey key) def = fromRepo $ Git.Config.get key def +getConfig (ConfigKey key) d = fromRepo $ Git.Config.get key d getConfigMaybe :: ConfigKey -> Annex (Maybe String) getConfigMaybe (ConfigKey key) = fromRepo $ Git.Config.getMaybe key @@ -58,7 +58,7 @@ annexConfig key = ConfigKey $ "annex." ++ key - by remote.<name>.annex-cost, or if remote.<name>.annex-cost-command - is set and prints a number, that is used. -} remoteCost :: RemoteGitConfig -> Cost -> Annex Cost -remoteCost c def = fromMaybe def <$> remoteCost' c +remoteCost c d = fromMaybe d <$> remoteCost' c remoteCost' :: RemoteGitConfig -> Annex (Maybe Cost) remoteCost' c = case remoteAnnexCostCommand c of diff --git a/Locations.hs b/Locations.hs index dcbde4bd9..c5221d8bc 100644 --- a/Locations.hs +++ b/Locations.hs @@ -1,6 +1,6 @@ {- git-annex file locations - - - Copyright 2010-2013 Joey Hess <id@joeyh.name> + - Copyright 2010-2015 Joey Hess <id@joeyh.name> - - Licensed under the GNU GPL version 3 or higher. -} @@ -20,7 +20,6 @@ module Locations ( gitAnnexInodeSentinal, gitAnnexInodeSentinalCache, annexLocations, - annexLocation, gitAnnexDir, gitAnnexObjectDir, gitAnnexTmpMiscDir, @@ -59,7 +58,7 @@ module Locations ( gitAnnexRemotesDir, gitAnnexAssistantDefaultDir, isLinkToAnnex, - annexHashes, + HashLevels(..), hashDirMixed, hashDirLower, preSanitizeKeyName, @@ -67,17 +66,16 @@ module Locations ( prop_idempotent_fileKey ) where -import Data.Bits -import Data.Word -import Data.Hash.MD5 import Data.Char +import Data.Default import Common -import Types +import Types.GitConfig import Types.Key import Types.UUID import Types.Difference import qualified Git +import Annex.DirHashes {- Conventions: - @@ -103,11 +101,15 @@ objectDir :: FilePath objectDir = addTrailingPathSeparator $ annexDir </> "objects" {- Annexed file's possible locations relative to the .git directory. - - There are two different possibilities, using different hashes. -} -annexLocations :: Key -> [FilePath] -annexLocations key = map (annexLocation key) annexHashes -annexLocation :: Key -> Hasher -> FilePath -annexLocation key hasher = objectDir </> keyPath key hasher + - There are two different possibilities, using different hashes. + - + - Also, some repositories have a Difference in hash directory depth. + -} +annexLocations :: GitConfig -> Key -> [FilePath] +annexLocations config key = map (annexLocation config key) dirHashes + +annexLocation :: GitConfig -> Key -> (HashLevels -> Hasher) -> FilePath +annexLocation config key hasher = objectDir </> keyPath key (hasher $ objectHashLevels config) {- Annexed object's location in a repository. - @@ -135,12 +137,12 @@ gitAnnexLocation' key r config crippled -} | Git.repoIsLocalBare r || crippled - || hasDifference (== ObjectHashLower True) (annexDifferences config) = - check $ map inrepo $ annexLocations key + || hasDifference (== ObjectHashLower) (annexDifferences config) = + check $ map inrepo $ annexLocations config key {- Non-bare repositories only use hashDirMixed, so - don't need to do any work to check if the file is - present. -} - | otherwise = return $ inrepo $ annexLocation key hashDirMixed + | otherwise = return $ inrepo $ annexLocation config key hashDirMixed where inrepo d = Git.localGitDir r </> d check locs@(l:_) = fromMaybe l <$> firstM doesFileExist locs @@ -405,9 +407,9 @@ prop_idempotent_fileKey s where k = stubKey { keyName = s, keyBackendName = "test" } -{- A location to store a key on the filesystem. A directory hash is used, - - to protect against filesystems that dislike having many items in a - - single directory. +{- A location to store a key on a special remote that uses a filesystem. + - A directory hash is used, to protect against filesystems that dislike + - having many items in a single directory. - - The file is put in a directory with the same name, this allows - write-protecting the directory to avoid accidental deletion of the file. @@ -417,44 +419,11 @@ keyPath key hasher = hasher key </> f </> f where f = keyFile key -{- All possibile locations to store a key using different directory hashes. -} -keyPaths :: Key -> [FilePath] -keyPaths key = map (keyPath key) annexHashes - -{- Two different directory hashes may be used. The mixed case hash - - came first, and is fine, except for the problem of case-strict - - filesystems such as Linux VFAT (mounted with shortname=mixed), - - which do not allow using a directory "XX" when "xx" already exists. - - To support that, most repositories use the lower case hash for new data. -} -type Hasher = Key -> FilePath - -annexHashes :: [Hasher] -annexHashes = [hashDirLower, hashDirMixed] - -hashDirMixed :: Hasher -hashDirMixed k = addTrailingPathSeparator $ take 2 dir </> drop 2 dir - where - dir = take 4 $ display_32bits_as_dir =<< [a,b,c,d] - ABCD (a,b,c,d) = md5 $ md5FilePath $ key2file $ nonChunkKey k - -hashDirLower :: Hasher -hashDirLower k = addTrailingPathSeparator $ take 3 dir </> drop 3 dir - where - dir = take 6 $ md5s $ md5FilePath $ key2file $ nonChunkKey k - -{- modified version of display_32bits_as_hex from Data.Hash.MD5 - - Copyright (C) 2001 Ian Lynagh - - License: Either BSD or GPL +{- All possibile locations to store a key in a special remote + - using different directory hashes. + - + - This is compatible with the annexLocations, for interoperability between + - special remotes and git-annex repos. -} -display_32bits_as_dir :: Word32 -> String -display_32bits_as_dir w = trim $ swap_pairs cs - where - -- Need 32 characters to use. To avoid inaverdently making - -- a real word, use letters that appear less frequently. - chars = ['0'..'9'] ++ "zqjxkmvwgpfZQJXKMVWGPF" - cs = map (\x -> getc $ (shiftR w (6*x)) .&. 31) [0..7] - getc n = chars !! fromIntegral n - swap_pairs (x1:x2:xs) = x2:x1:swap_pairs xs - swap_pairs _ = [] - -- Last 2 will always be 00, so omit. - trim = take 6 +keyPaths :: Key -> [FilePath] +keyPaths key = map (\h -> keyPath key (h def)) dirHashes @@ -9,6 +9,7 @@ module Logs where import Common.Annex import Types.Key +import Annex.DirHashes {- There are several varieties of log file formats. -} data LogVariety @@ -87,8 +88,8 @@ differenceLog :: FilePath differenceLog = "difference.log" {- The pathname of the location log file for a given key. -} -locationLogFile :: Key -> String -locationLogFile key = hashDirLower key ++ keyFile key ++ ".log" +locationLogFile :: GitConfig -> Key -> String +locationLogFile config key = branchHashDir config key </> keyFile key ++ ".log" {- Converts a pathname into a key if it's a location log. -} locationLogFileKey :: FilePath -> Maybe Key @@ -101,15 +102,17 @@ locationLogFileKey path (base, ext) = splitAt (length file - 4) file {- The filename of the url log for a given key. -} -urlLogFile :: Key -> FilePath -urlLogFile key = hashDirLower key </> keyFile key ++ urlLogExt +urlLogFile :: GitConfig -> Key -> FilePath +urlLogFile config key = branchHashDir config key </> keyFile key ++ urlLogExt {- Old versions stored the urls elsewhere. -} -oldurlLogs :: Key -> [FilePath] -oldurlLogs key = - [ "remote/web" </> hashDirLower key </> key2file key ++ ".log" - , "remote/web" </> hashDirLower key </> keyFile key ++ ".log" +oldurlLogs :: GitConfig -> Key -> [FilePath] +oldurlLogs config key = + [ "remote/web" </> hdir </> key2file key ++ ".log" + , "remote/web" </> hdir </> keyFile key ++ ".log" ] + where + hdir = branchHashDir config key urlLogExt :: String urlLogExt = ".log.web" @@ -130,8 +133,9 @@ isUrlLog :: FilePath -> Bool isUrlLog file = urlLogExt `isSuffixOf` file {- The filename of the remote state log for a given key. -} -remoteStateLogFile :: Key -> FilePath -remoteStateLogFile key = hashDirLower key </> keyFile key ++ remoteStateLogExt +remoteStateLogFile :: GitConfig -> Key -> FilePath +remoteStateLogFile config key = branchHashDir config key + </> keyFile key ++ remoteStateLogExt remoteStateLogExt :: String remoteStateLogExt = ".log.rmt" @@ -140,8 +144,8 @@ isRemoteStateLog :: FilePath -> Bool isRemoteStateLog path = remoteStateLogExt `isSuffixOf` path {- The filename of the chunk log for a given key. -} -chunkLogFile :: Key -> FilePath -chunkLogFile key = hashDirLower key </> keyFile key ++ chunkLogExt +chunkLogFile :: GitConfig -> Key -> FilePath +chunkLogFile config key = branchHashDir config key </> keyFile key ++ chunkLogExt chunkLogFileKey :: FilePath -> Maybe Key chunkLogFileKey path @@ -159,35 +163,11 @@ isChunkLog :: FilePath -> Bool isChunkLog path = chunkLogExt `isSuffixOf` path {- The filename of the metadata log for a given key. -} -metaDataLogFile :: Key -> FilePath -metaDataLogFile key = hashDirLower key </> keyFile key ++ metaDataLogExt +metaDataLogFile :: GitConfig -> Key -> FilePath +metaDataLogFile config key = branchHashDir config key </> keyFile key ++ metaDataLogExt metaDataLogExt :: String metaDataLogExt = ".log.met" isMetaDataLog :: FilePath -> Bool isMetaDataLog path = metaDataLogExt `isSuffixOf` path - -prop_logs_sane :: Key -> Bool -prop_logs_sane dummykey = and - [ isNothing (getLogVariety "unknown") - , expect gotUUIDBasedLog (getLogVariety uuidLog) - , expect gotPresenceLog (getLogVariety $ locationLogFile dummykey) - , expect gotPresenceLog (getLogVariety $ urlLogFile dummykey) - , expect gotNewUUIDBasedLog (getLogVariety $ remoteStateLogFile dummykey) - , expect gotChunkLog (getLogVariety $ chunkLogFile dummykey) - , expect gotOtherLog (getLogVariety $ metaDataLogFile dummykey) - , expect gotOtherLog (getLogVariety numcopiesLog) - ] - where - expect = maybe False - gotUUIDBasedLog UUIDBasedLog = True - gotUUIDBasedLog _ = False - gotNewUUIDBasedLog NewUUIDBasedLog = True - gotNewUUIDBasedLog _ = False - gotChunkLog (ChunkLog k) = k == dummykey - gotChunkLog _ = False - gotPresenceLog (PresenceLog k) = k == dummykey - gotPresenceLog _ = False - gotOtherLog OtherLog = True - gotOtherLog _ = False diff --git a/Logs/Chunk.hs b/Logs/Chunk.hs index 8ca3ffed3..8f0e7cedb 100644 --- a/Logs/Chunk.hs +++ b/Logs/Chunk.hs @@ -29,6 +29,7 @@ import Logs import Logs.MapLog import qualified Annex.Branch import Logs.Chunk.Pure +import qualified Annex import qualified Data.Map as M import Data.Time.Clock.POSIX @@ -36,14 +37,17 @@ import Data.Time.Clock.POSIX chunksStored :: UUID -> Key -> ChunkMethod -> ChunkCount -> Annex () chunksStored u k chunkmethod chunkcount = do ts <- liftIO getPOSIXTime - Annex.Branch.change (chunkLogFile k) $ + config <- Annex.getGitConfig + Annex.Branch.change (chunkLogFile config k) $ showLog . changeMapLog ts (u, chunkmethod) chunkcount . parseLog chunksRemoved :: UUID -> Key -> ChunkMethod -> Annex () chunksRemoved u k chunkmethod = chunksStored u k chunkmethod 0 getCurrentChunks :: UUID -> Key -> Annex [(ChunkMethod, ChunkCount)] -getCurrentChunks u k = select . parseLog <$> Annex.Branch.get (chunkLogFile k) +getCurrentChunks u k = do + config <- Annex.getGitConfig + select . parseLog <$> Annex.Branch.get (chunkLogFile config k) where select = filter (\(_m, ct) -> ct > 0) . map (\((_ku, m), l) -> (m, value l)) diff --git a/Logs/Difference.hs b/Logs/Difference.hs index 68d624f99..fcebffee9 100644 --- a/Logs/Difference.hs +++ b/Logs/Difference.hs @@ -24,10 +24,11 @@ import Logs.UUIDBased import Logs.Difference.Pure recordDifferences :: Differences -> UUID -> Annex () -recordDifferences differences uuid = do +recordDifferences (Differences differences) uuid = do ts <- liftIO getPOSIXTime Annex.Branch.change differenceLog $ showLog id . changeLog ts uuid (show differences) . parseLog Just +recordDifferences UnknownDifferences _ = return () -- Map of UUIDs that have Differences recorded. -- If a new version of git-annex has added a Difference this version diff --git a/Logs/Difference/Pure.hs b/Logs/Difference/Pure.hs index 76d995a01..25f3844d6 100644 --- a/Logs/Difference/Pure.hs +++ b/Logs/Difference/Pure.hs @@ -18,8 +18,7 @@ import Types.Difference import Logs.UUIDBased parseDifferencesLog :: String -> (M.Map UUID Differences) -parseDifferencesLog = simpleMap - . parseLog (Just . fromMaybe UnknownDifferences . readish) +parseDifferencesLog = simpleMap . parseLog (Just . readDifferences) -- The sum of all recorded differences, across all UUIDs. allDifferences :: M.Map UUID Differences -> Differences diff --git a/Logs/Location.hs b/Logs/Location.hs index d0109b848..7c6888c0b 100644 --- a/Logs/Location.hs +++ b/Logs/Location.hs @@ -29,6 +29,7 @@ import Logs import Logs.Presence import Annex.UUID import Git.Types (RefDate) +import qualified Annex {- Log a change in the presence of a key's value in current repository. -} logStatus :: Key -> LogStatus -> Annex () @@ -38,7 +39,9 @@ logStatus key s = do {- Log a change in the presence of a key's value in a repository. -} logChange :: Key -> UUID -> LogStatus -> Annex () -logChange key (UUID u) s = addLog (locationLogFile key) =<< logNow s u +logChange key (UUID u) s = do + config <- Annex.getGitConfig + addLog (locationLogFile config key) =<< logNow s u logChange _ NoUUID _ = noop {- Returns a list of repository UUIDs that, according to the log, have @@ -51,7 +54,9 @@ loggedLocationsHistorical :: RefDate -> Key -> Annex [UUID] loggedLocationsHistorical = getLoggedLocations . historicalLog getLoggedLocations :: (FilePath -> Annex [String]) -> Key -> Annex [UUID] -getLoggedLocations getter key = map toUUID <$> (getter . locationLogFile) key +getLoggedLocations getter key = do + config <- Annex.getGitConfig + map toUUID <$> (getter . locationLogFile config) key {- Finds all keys that have location log information. - (There may be duplicate keys in the list.) -} diff --git a/Logs/MetaData.hs b/Logs/MetaData.hs index 3091935cf..ed4e2363e 100644 --- a/Logs/MetaData.hs +++ b/Logs/MetaData.hs @@ -38,6 +38,7 @@ import Common.Annex import Types.MetaData import Annex.MetaData.StandardFields import qualified Annex.Branch +import qualified Annex import Logs import Logs.SingleValue @@ -52,7 +53,9 @@ instance SingleValueSerializable MetaData where deserialize = Types.MetaData.deserialize getMetaDataLog :: Key -> Annex (Log MetaData) -getMetaDataLog = readLog . metaDataLogFile +getMetaDataLog key = do + config <- Annex.getGitConfig + readLog $ metaDataLogFile config key {- Go through the log from oldest to newest, and combine it all - into a single MetaData representing the current state. @@ -97,10 +100,12 @@ addMetaData k metadata = addMetaData' k metadata =<< liftIO getPOSIXTime addMetaData' :: Key -> MetaData -> POSIXTime -> Annex () addMetaData' k d@(MetaData m) now | d == emptyMetaData = noop - | otherwise = Annex.Branch.change (metaDataLogFile k) $ - showLog . simplifyLog - . S.insert (LogEntry now metadata) - . parseLog + | otherwise = do + config <- Annex.getGitConfig + Annex.Branch.change (metaDataLogFile config k) $ + showLog . simplifyLog + . S.insert (LogEntry now metadata) + . parseLog where metadata = MetaData $ M.filterWithKey (\f _ -> not (isLastChangedField f)) m @@ -181,6 +186,7 @@ copyMetaData oldkey newkey | oldkey == newkey = noop | otherwise = do l <- getMetaDataLog oldkey - unless (S.null l) $ - Annex.Branch.change (metaDataLogFile newkey) $ + unless (S.null l) $ do + config <- Annex.getGitConfig + Annex.Branch.change (metaDataLogFile config newkey) $ const $ showLog l diff --git a/Logs/PreferredContent.hs b/Logs/PreferredContent.hs index 83269e6d7..6c885041a 100644 --- a/Logs/PreferredContent.hs +++ b/Logs/PreferredContent.hs @@ -52,12 +52,12 @@ isRequiredContent :: Maybe UUID -> AssumeNotPresent -> Maybe Key -> AssociatedFi isRequiredContent = checkMap requiredContentMap checkMap :: Annex (FileMatcherMap Annex) -> Maybe UUID -> AssumeNotPresent -> Maybe Key -> AssociatedFile -> Bool -> Annex Bool -checkMap getmap mu notpresent mkey afile def = do +checkMap getmap mu notpresent mkey afile d = do u <- maybe getUUID return mu m <- getmap case M.lookup u m of - Nothing -> return def - Just matcher -> checkMatcher matcher mkey afile notpresent def + Nothing -> return d + Just matcher -> checkMatcher matcher mkey afile notpresent d preferredContentMap :: Annex (FileMatcherMap Annex) preferredContentMap = maybe (fst <$> preferredRequiredMapsLoad) return diff --git a/Logs/RemoteState.hs b/Logs/RemoteState.hs index 7b3859a35..b302b739a 100644 --- a/Logs/RemoteState.hs +++ b/Logs/RemoteState.hs @@ -14,6 +14,7 @@ import Common.Annex import Logs import Logs.UUIDBased import qualified Annex.Branch +import qualified Annex import qualified Data.Map as M import Data.Time.Clock.POSIX @@ -23,11 +24,14 @@ type RemoteState = String setRemoteState :: UUID -> Key -> RemoteState -> Annex () setRemoteState u k s = do ts <- liftIO getPOSIXTime - Annex.Branch.change (remoteStateLogFile k) $ + config <- Annex.getGitConfig + Annex.Branch.change (remoteStateLogFile config k) $ showLogNew id . changeLog ts u s . parseLogNew Just getRemoteState :: UUID -> Key -> Annex (Maybe RemoteState) -getRemoteState u k = extract . parseLogNew Just - <$> Annex.Branch.get (remoteStateLogFile k) +getRemoteState u k = do + config <- Annex.getGitConfig + extract . parseLogNew Just + <$> Annex.Branch.get (remoteStateLogFile config k) where extract m = value <$> M.lookup u m diff --git a/Logs/Web.hs b/Logs/Web.hs index 4729cead4..38993c33c 100644 --- a/Logs/Web.hs +++ b/Logs/Web.hs @@ -37,7 +37,8 @@ import Utility.Url {- Gets all urls that a key might be available from. -} getUrls :: Key -> Annex [URLString] getUrls key = do - l <- go $ urlLogFile key : oldurlLogs key + config <- Annex.getGitConfig + l <- go $ urlLogFile config key : oldurlLogs config key tmpl <- Annex.getState (maybeToList . M.lookup key . Annex.tempurls) return (tmpl ++ l) where @@ -54,13 +55,15 @@ getUrlsWithPrefix key prefix = filter (prefix `isPrefixOf`) <$> getUrls key setUrlPresent :: UUID -> Key -> URLString -> Annex () setUrlPresent uuid key url = do us <- getUrls key - unless (url `elem` us) $ - addLog (urlLogFile key) =<< logNow InfoPresent url + unless (url `elem` us) $ do + config <- Annex.getGitConfig + addLog (urlLogFile config key) =<< logNow InfoPresent url logChange key uuid InfoPresent setUrlMissing :: UUID -> Key -> URLString -> Annex () setUrlMissing uuid key url = do - addLog (urlLogFile key) =<< logNow InfoMissing url + config <- Annex.getGitConfig + addLog (urlLogFile config key) =<< logNow InfoMissing url whenM (null <$> getUrls key) $ logChange key uuid InfoMissing diff --git a/Remote/Directory.hs b/Remote/Directory.hs index 2b887a82f..2eeb79317 100644 --- a/Remote/Directory.hs +++ b/Remote/Directory.hs @@ -15,6 +15,7 @@ module Remote.Directory ( import qualified Data.ByteString.Lazy as L import qualified Data.Map as M +import Data.Default import Common.Annex import Types.Remote @@ -107,7 +108,7 @@ getLocation d k = do {- Directory where the file(s) for a key are stored. -} storeDir :: FilePath -> Key -> FilePath -storeDir d k = addTrailingPathSeparator $ d </> hashDirLower k </> keyFile k +storeDir d k = addTrailingPathSeparator $ d </> hashDirLower def k </> keyFile k {- Where we store temporary data for a key, in the directory, as it's being - written. -} diff --git a/Remote/External.hs b/Remote/External.hs index 0ec284ff4..0579400ed 100644 --- a/Remote/External.hs +++ b/Remote/External.hs @@ -184,7 +184,7 @@ handleRequest' lck external req mp responsehandler handleRemoteRequest (PROGRESS bytesprocessed) = maybe noop (\a -> liftIO $ a bytesprocessed) mp handleRemoteRequest (DIRHASH k) = - send $ VALUE $ hashDirMixed k + send $ VALUE $ hashDirMixed def k handleRemoteRequest (SETCONFIG setting value) = liftIO $ atomically $ do let v = externalConfig external diff --git a/Remote/GCrypt.hs b/Remote/GCrypt.hs index b977750c3..aed54d20f 100644 --- a/Remote/GCrypt.hs +++ b/Remote/GCrypt.hs @@ -16,6 +16,7 @@ module Remote.GCrypt ( import qualified Data.Map as M import qualified Data.ByteString.Lazy as L import Control.Exception +import Data.Default import Common.Annex import Types.Remote @@ -361,7 +362,7 @@ checkKey r rsyncopts k {- Annexed objects are hashed using lower-case directories for max - portability. -} gCryptLocation :: Remote -> Key -> FilePath -gCryptLocation r key = Git.repoLocation (repo r) </> objectDir </> keyPath key hashDirLower +gCryptLocation r key = Git.repoLocation (repo r) </> objectDir </> keyPath key (hashDirLower def) data AccessMethod = AccessDirect | AccessShell diff --git a/Remote/Git.hs b/Remote/Git.hs index b31a1d850..328e39111 100644 --- a/Remote/Git.hs +++ b/Remote/Git.hs @@ -327,13 +327,15 @@ keyUrls r key = map tourl locs' -- If the remote is known to not be bare, try the hash locations -- used for non-bare repos first, as an optimisation. locs - | remoteAnnexBare (gitconfig r) == Just False = reverse (annexLocations key) - | otherwise = annexLocations key + | remoteAnnexBare remoteconfig == Just False = reverse (annexLocations cfg key) + | otherwise = annexLocations cfg key #ifndef mingw32_HOST_OS locs' = locs #else locs' = map (replace "\\" "/") locs #endif + remoteconfig = gitconfig r + cfg = fromJust $ remoteGitConfig remoteconfig dropKey :: Remote -> Key -> Annex Bool dropKey r key diff --git a/Remote/Hook.hs b/Remote/Hook.hs index ce7781cfb..592564772 100644 --- a/Remote/Hook.hs +++ b/Remote/Hook.hs @@ -7,8 +7,6 @@ module Remote.Hook (remote) where -import qualified Data.Map as M - import Common.Annex import Types.Remote import Types.Key @@ -20,6 +18,8 @@ import Annex.UUID import Remote.Helper.Special import Utility.Env +import qualified Data.Map as M + type Action = String type HookName = String @@ -90,7 +90,7 @@ hookEnv action k f = Just <$> mergeenv (fileenv f ++ keyenv) ] fileenv Nothing = [] fileenv (Just file) = [envvar "FILE" file] - hashbits = map takeDirectory $ splitPath $ hashDirMixed k + hashbits = map takeDirectory $ splitPath $ hashDirMixed def k lookupHook :: HookName -> Action -> Annex (Maybe String) lookupHook hookname action = do diff --git a/Remote/Rsync.hs b/Remote/Rsync.hs index ae6f5450e..f5d4c85c4 100644 --- a/Remote/Rsync.hs +++ b/Remote/Rsync.hs @@ -37,6 +37,7 @@ import Annex.Perms import Logs.Transfer import Types.Creds import Types.Key (isChunkKey) +import Annex.DirHashes import qualified Data.Map as M @@ -212,8 +213,8 @@ remove o k = do - content could be. Note that the parent directories have - to also be explicitly included, due to how rsync - traverses directories. -} - includes = concatMap use annexHashes - use h = let dir = h k in + includes = concatMap use dirHashes + use h = let dir = h def k in [ parentDir dir , dir -- match content directory and anything in it diff --git a/Remote/Rsync/RsyncUrl.hs b/Remote/Rsync/RsyncUrl.hs index 7ebd2f68d..9a7319246 100644 --- a/Remote/Rsync/RsyncUrl.hs +++ b/Remote/Rsync/RsyncUrl.hs @@ -14,10 +14,12 @@ import Locations import Utility.Rsync import Utility.SafeCommand +import Data.Default import System.FilePath.Posix #ifdef mingw32_HOST_OS import Data.String.Utils #endif +import Annex.DirHashes type RsyncUrl = String @@ -35,12 +37,12 @@ rsyncEscape o u | otherwise = u rsyncUrls :: RsyncOpts -> Key -> [RsyncUrl] -rsyncUrls o k = map use annexHashes +rsyncUrls o k = map use dirHashes where use h = rsyncUrl o </> hash h </> rsyncEscape o (f </> f) f = keyFile k #ifndef mingw32_HOST_OS - hash h = h k + hash h = h def k #else hash h = replace "\\" "/" (h k) #endif diff --git a/Remote/WebDAV/DavLocation.hs b/Remote/WebDAV/DavLocation.hs index b5d374943..157e7151a 100644 --- a/Remote/WebDAV/DavLocation.hs +++ b/Remote/WebDAV/DavLocation.hs @@ -17,6 +17,7 @@ import Utility.Url (URLString) import System.FilePath.Posix -- for manipulating url paths import Network.Protocol.HTTP.DAV (inDAVLocation, DAVT) import Control.Monad.IO.Class (MonadIO) +import Data.Default #ifdef mingw32_HOST_OS import Data.String.Utils #endif @@ -33,9 +34,9 @@ keyDir :: Key -> DavLocation keyDir k = addTrailingPathSeparator $ hashdir </> keyFile k where #ifndef mingw32_HOST_OS - hashdir = hashDirLower k + hashdir = hashDirLower def k #else - hashdir = replace "\\" "/" (hashDirLower k) + hashdir = replace "\\" "/" (hashDirLower def k) #endif keyLocation :: Key -> DavLocation @@ -38,7 +38,6 @@ import qualified Types.KeySource import qualified Types.Backend import qualified Types.TrustLevel import qualified Types -import qualified Logs import qualified Logs.MapLog import qualified Logs.Trust import qualified Logs.Remote @@ -138,7 +137,6 @@ properties = localOption (QuickCheckTests 1000) $ testGroup "QuickCheck" , testProperty "prop_idempotent_key_decode" Types.Key.prop_idempotent_key_decode , testProperty "prop_idempotent_shellEscape" Utility.SafeCommand.prop_idempotent_shellEscape , testProperty "prop_idempotent_shellEscape_multiword" Utility.SafeCommand.prop_idempotent_shellEscape_multiword - , testProperty "prop_logs_sane" Logs.prop_logs_sane , testProperty "prop_idempotent_configEscape" Logs.Remote.prop_idempotent_configEscape , testProperty "prop_parse_show_Config" Logs.Remote.prop_parse_show_Config , testProperty "prop_upFrom_basics" Utility.Path.prop_upFrom_basics diff --git a/Types/Difference.hs b/Types/Difference.hs index cbfad0fce..e4562a0d6 100644 --- a/Types/Difference.hs +++ b/Types/Difference.hs @@ -8,8 +8,8 @@ module Types.Difference ( Difference(..), Differences(..), + readDifferences, getDifferences, - sanityCheckDifferences, differenceConfigKey, differenceConfigVal, hasDifference, @@ -22,114 +22,67 @@ import qualified Git.Config import Data.List import Data.Maybe import Data.Monoid -import Control.Applicative -- Describes differences from the v5 repository format. -- --- The serilization is stored in difference.log, so avoid changes that +-- The serialization is stored in difference.log, so avoid changes that -- would break compatability. -- --- Not breaking comparability is why a list of Differences is used, rather +-- Not breaking compatability is why a list of Differences is used, rather -- than a sum type. With a sum type, adding a new field for some future -- difference would serialize to a value that an older version could not -- parse, even if that new field was not used. With the Differences list, -- old versions can still parse it, unless the new Difference constructor -- is used. +-- +-- The constructors intentionally do not have parameters; this is to +-- ensure that any Difference that can be expressed is supported. +-- So, a new repository version would be Version6, rather than Version Int. data Difference - = Version Int - | ObjectHashLower Bool - | ObjectHashDirectories Int - | BranchHashDirectories Int - deriving (Show, Read, Ord) - -instance Eq Difference where - Version a == Version b = a == b - ObjectHashLower a == ObjectHashLower b = a == b - ObjectHashDirectories a == ObjectHashDirectories b = a == b - BranchHashDirectories a == BranchHashDirectories b = a == b - _ == _ = False + = ObjectHashLower + | OneLevelObjectHash + | OneLevelBranchHash + deriving (Show, Read, Ord, Eq, Enum, Bounded) data Differences = Differences [Difference] | UnknownDifferences - deriving (Show, Read, Ord) instance Eq Differences where - Differences a == Differences b = simplify (defver:a) == simplify (defver:b) - _ == _ = False + Differences a == Differences b = canon a == canon b + _ == _ = False -- UnknownDifferences cannot be equal instance Monoid Differences where mempty = Differences [] - mappend (Differences l1) (Differences l2) = Differences (simplify (l1 ++ l2)) + mappend (Differences l1) (Differences l2) = Differences (canon (l1 ++ l2)) mappend _ _ = UnknownDifferences --- This is the default repository version that is assumed when no other one --- is given. Note that [] == [Version 5] -defver :: Difference -defver = Version 5 - --- Larger values of the same Difference constructor dominate --- over smaller values, so given [Version 6, Version 5], returns [Version 6] -simplify :: [Difference] -> [Difference] -simplify = go . sort - where - go [] = [] - go (d:[]) = [d] - go (d1:d2:ds) - | like d1 d2 = go (d2:ds) - | otherwise = d1 : go (d2:ds) +-- Canonical form, allowing comparison. +canon :: [Difference] -> [Difference] +canon = nub . sort - like (Version _) (Version _) = True - like (ObjectHashLower _) (ObjectHashLower _) = True - like (ObjectHashDirectories _) (ObjectHashDirectories _) = True - like (BranchHashDirectories _) (BranchHashDirectories _) = True - like _ _ = False +readDifferences :: String -> Differences +readDifferences = maybe UnknownDifferences Differences . readish getDifferences :: Git.Repo -> Differences -getDifferences r = checksane $ Differences $ catMaybes - [ ObjectHashLower - <$> getmaybebool (differenceConfigKey (ObjectHashLower undefined)) - , ObjectHashDirectories - <$> getmayberead (differenceConfigKey (ObjectHashDirectories undefined)) - , BranchHashDirectories - <$> getmayberead (differenceConfigKey (BranchHashDirectories undefined)) - ] +getDifferences r = Differences $ catMaybes $ + map getmaybe [minBound .. maxBound] where - getmaybe k = Git.Config.getMaybe k r - getmayberead k = readish =<< getmaybe k - getmaybebool k = Git.Config.isTrue =<< getmaybe k - checksane = either error id . sanityCheckDifferences + getmaybe d = case Git.Config.isTrue =<< Git.Config.getMaybe (differenceConfigKey d) r of + Just True -> Just d + _ -> Nothing differenceConfigKey :: Difference -> String -differenceConfigKey (Version _) = "annex.version" -differenceConfigKey (ObjectHashLower _) = tunable "objecthashlower" -differenceConfigKey (ObjectHashDirectories _) = tunable "objecthashdirectories" -differenceConfigKey (BranchHashDirectories _) = tunable "branchhashdirectories" +differenceConfigKey ObjectHashLower = tunable "objecthashlower" +differenceConfigKey OneLevelObjectHash = tunable "objecthash1" +differenceConfigKey OneLevelBranchHash = tunable "branchhash1" differenceConfigVal :: Difference -> String -differenceConfigVal (Version v) = show v -differenceConfigVal (ObjectHashLower b) = Git.Config.boolConfig b -differenceConfigVal (ObjectHashDirectories n) = show n -differenceConfigVal (BranchHashDirectories n) = show n +differenceConfigVal _ = Git.Config.boolConfig True tunable :: String -> String tunable k = "annex.tune." ++ k -sanityCheckDifferences :: Differences -> Either String Differences -sanityCheckDifferences d@(Differences l) - | null problems = Right d - | otherwise = Left (intercalate "; " problems) - where - problems = catMaybes (map check l) - check (ObjectHashDirectories n) - | n == 1 || n == 2 = Nothing - | otherwise = Just $ "Bad value for objecthashdirectories -- should be 1 or 2, not " ++ show n - check (BranchHashDirectories n) - | n == 1 || n == 2 = Nothing - | otherwise = Just $ "Bad value for branhhashdirectories -- should be 1 or 2, not " ++ show n - check _ = Nothing -sanityCheckDifferences UnknownDifferences = Left "unknown differences detected; update git-annex" - hasDifference :: (Difference -> Bool) -> Differences -> Bool hasDifference f (Differences l) = any f l hasDifference _ UnknownDifferences = False diff --git a/Types/GitConfig.hs b/Types/GitConfig.hs index 5ac524f45..ef8f2f2bd 100644 --- a/Types/GitConfig.hs +++ b/Types/GitConfig.hs @@ -98,7 +98,7 @@ extractGitConfig r = GitConfig , annexDifferences = getDifferences r } where - getbool k def = fromMaybe def $ getmaybebool k + getbool k d = fromMaybe d $ getmaybebool k getmaybebool k = Git.Config.isTrue =<< getmaybe k getmayberead k = readish =<< getmaybe k getmaybe k = Git.Config.getMaybe k r @@ -178,7 +178,7 @@ extractRemoteGitConfig r remotename = RemoteGitConfig , remoteGitConfig = Nothing } where - getbool k def = fromMaybe def $ getmaybebool k + getbool k d = fromMaybe d $ getmaybebool k getmaybebool k = Git.Config.isTrue =<< getmaybe k getmayberead k = readish =<< getmaybe k getmaybe k = mplus (Git.Config.getMaybe (key k) r) diff --git a/Upgrade/V1.hs b/Upgrade/V1.hs index e8a7efe13..801cdafa0 100644 --- a/Upgrade/V1.hs +++ b/Upgrade/V1.hs @@ -9,6 +9,7 @@ module Upgrade.V1 where import System.Posix.Types import Data.Char +import Data.Default import Common.Annex import Types.Key @@ -228,7 +229,7 @@ logFile1 :: Git.Repo -> Key -> String logFile1 repo key = Upgrade.V2.gitStateDir repo ++ keyFile1 key ++ ".log" logFile2 :: Key -> Git.Repo -> String -logFile2 = logFile' hashDirLower +logFile2 = logFile' (hashDirLower def) logFile' :: (Key -> FilePath) -> Key -> Git.Repo -> String logFile' hasher key repo = diff --git a/Upgrade/V2.hs b/Upgrade/V2.hs index 2b0b277e8..0f09205ff 100644 --- a/Upgrade/V2.hs +++ b/Upgrade/V2.hs @@ -12,6 +12,7 @@ import qualified Git import qualified Git.Command import qualified Git.Ref import qualified Annex.Branch +import qualified Annex import Annex.Content import Utility.Tmp import Logs @@ -47,7 +48,8 @@ upgrade = do e <- liftIO $ doesDirectoryExist old when e $ do - mapM_ (\(k, f) -> inject f $ locationLogFile k) =<< locationLogs + config <- Annex.getGitConfig + mapM_ (\(k, f) -> inject f $ locationLogFile config k) =<< locationLogs mapM_ (\f -> inject f f) =<< logFiles old saveState False diff --git a/Utility/Url.hs b/Utility/Url.hs index cb4fc7d37..ddf5eea40 100644 --- a/Utility/Url.hs +++ b/Utility/Url.hs @@ -28,7 +28,6 @@ import Common import Network.URI import Network.HTTP.Conduit import Network.HTTP.Types -import Data.Default import qualified Data.CaseInsensitive as CI import qualified Data.ByteString as B import qualified Data.ByteString.UTF8 as B8 diff --git a/debian/changelog b/debian/changelog index 3caa7ddad..529d460c8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -26,6 +26,8 @@ git-annex (5.20150114) UNRELEASED; urgency=medium http://git-annex.branchable.com/tuning/ * merge: Refuse to merge changes from a git-annex branch of a repo that has been tuned in incompatable ways. + * Support annex.tune.objecthash1, annex.tune.objecthashlower, and + annex.tune.branchhash1. -- Joey Hess <id@joeyh.name> Tue, 13 Jan 2015 17:03:39 -0400 diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn index 06eb85e11..80dce0ddc 100644 --- a/doc/git-annex.mdwn +++ b/doc/git-annex.mdwn @@ -1839,7 +1839,7 @@ Here are all the supported configuration settings. Used by hook special remotes and external special remotes to record the type of the remote. -* `annex.tune.objecthashdirectories`, `annex.tune.objecthashlower`, `annex.tune.branchhashdirectories` +* `annex.tune.objecthash1`, `annex.tune.objecthashlower`, `annex.tune.branchhash1` These can be passed to `git annex init` to tune the repository. They cannot be safely changed in a running repository. diff --git a/doc/internals.mdwn b/doc/internals.mdwn index 4eb72ceac..a562d6067 100644 --- a/doc/internals.mdwn +++ b/doc/internals.mdwn @@ -265,4 +265,4 @@ that should prevent merging. Example: - e605dca6-446a-11e0-8b2a-002170d25c55 [Version 5] timestamp=1422387398.30395s + e605dca6-446a-11e0-8b2a-002170d25c55 [ObjectHashLower] timestamp=1422387398.30395s diff --git a/doc/tuning.mdwn b/doc/tuning.mdwn index 761071b6b..cbe0eca64 100644 --- a/doc/tuning.mdwn +++ b/doc/tuning.mdwn @@ -12,7 +12,7 @@ done by passing `-c name=value` parameters to `git annex init`. For example, this will make git-annex use only 1 level for hash directories in `.git/annex/objects`: - git -c annex.tune.objecthashdirectories=1 annex init + git -c annex.tune.objecthash1=true annex init It's very important to keep in mind that this makes a nonstandard format git-annex repository. In general, this cannot safely be used with @@ -29,16 +29,18 @@ Again, tuned repositories are an experimental feature; use with caution! The following tuning parameters are available: -* `annex.tune.objecthashdirectories` (default: 2) - Sets the number of hash directories to use in `.git/annex/objects/` +* `annex.tune.objecthash1=true` + Use just one level of hash directories in `.git/annex/objects/`, + instead of the default two levels. -* `annex.tune.objecthashlower` (default: false) - Set to true to make the hash directories in `.git/annex/objects/` use - all lower-case. +* `annex.tune.objecthashlower=true` + Make the hash directories in `.git/annex/objects/` use + all lower-case, instead of the default mixed-case. -* `annex.tune.branchhashdirectories` (default: 2) - Sets the number of hash directories to use in the git-annex branch. +* `annex.tune.branchhash1=true` + Use just one level of hash directories in the git-annex branch, + instead of the default two levels. Note that git-annex will automatically propigate these setting to -`.git/config` for tuned repsitories. You should never directly change +`.git/config` for tuned repositories. You should never directly change these settings in `.git/config` |