refactor git-annex branch log filename code into central location
Having one module that knows about all the filenames used on the branch allows working back from an arbitrary filename to enough information about it to implement dropping dead remotes and doing other log file compacting as part of a forget transition.
This commit is contained in:
parent
6147652cc6
commit
62beaa1a86
12 changed files with 136 additions and 74 deletions
|
@ -17,7 +17,7 @@ import Data.Char
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Command
|
import Command
|
||||||
import qualified Logs.Location
|
import Logs
|
||||||
import qualified Logs.Presence
|
import qualified Logs.Presence
|
||||||
import Annex.CatFile
|
import Annex.CatFile
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
|
@ -135,7 +135,7 @@ getLog :: Key -> [CommandParam] -> Annex [String]
|
||||||
getLog key os = do
|
getLog key os = do
|
||||||
top <- fromRepo Git.repoPath
|
top <- fromRepo Git.repoPath
|
||||||
p <- liftIO $ relPathCwdToFile top
|
p <- liftIO $ relPathCwdToFile top
|
||||||
let logfile = p </> Logs.Location.logFile key
|
let logfile = p </> locationLogFile key
|
||||||
inRepo $ pipeNullSplitZombie $
|
inRepo $ pipeNullSplitZombie $
|
||||||
[ Params "log -z --pretty=format:%ct --raw --abbrev=40"
|
[ Params "log -z --pretty=format:%ct --raw --abbrev=40"
|
||||||
, Param "--remove-empty"
|
, Param "--remove-empty"
|
||||||
|
|
110
Logs.hs
Normal file
110
Logs.hs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
{- git-annex log file names
|
||||||
|
-
|
||||||
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Logs where
|
||||||
|
|
||||||
|
import Common.Annex
|
||||||
|
import Types.Key
|
||||||
|
|
||||||
|
data LogVariety = UUIDBasedLog | PresenceLog Key
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
{- Converts a path from the git-annex branch into one of the varieties
|
||||||
|
- of logs used by git-annex, if it's a known path. -}
|
||||||
|
getLogVariety :: FilePath -> Maybe LogVariety
|
||||||
|
getLogVariety f
|
||||||
|
| f `elem` uuidBasedLogs = Just UUIDBasedLog
|
||||||
|
| otherwise = PresenceLog <$> firstJust (presenceLogs f)
|
||||||
|
|
||||||
|
{- All the uuid-based logs stored in the git-annex branch. -}
|
||||||
|
uuidBasedLogs :: [FilePath]
|
||||||
|
uuidBasedLogs =
|
||||||
|
[ uuidLog
|
||||||
|
, remoteLog
|
||||||
|
, trustLog
|
||||||
|
, groupLog
|
||||||
|
, preferredContentLog
|
||||||
|
]
|
||||||
|
|
||||||
|
{- All the ways to get a key from a presence log file -}
|
||||||
|
presenceLogs :: FilePath -> [Maybe Key]
|
||||||
|
presenceLogs f =
|
||||||
|
[ urlLogFileKey f
|
||||||
|
, locationLogFileKey f
|
||||||
|
]
|
||||||
|
|
||||||
|
uuidLog :: FilePath
|
||||||
|
uuidLog = "uuid.log"
|
||||||
|
|
||||||
|
remoteLog :: FilePath
|
||||||
|
remoteLog = "remote.log"
|
||||||
|
|
||||||
|
trustLog :: FilePath
|
||||||
|
trustLog = "trust.log"
|
||||||
|
|
||||||
|
groupLog :: FilePath
|
||||||
|
groupLog = "group.log"
|
||||||
|
|
||||||
|
preferredContentLog :: FilePath
|
||||||
|
preferredContentLog = "preferred-content.log"
|
||||||
|
|
||||||
|
{- The pathname of the location log file for a given key. -}
|
||||||
|
locationLogFile :: Key -> String
|
||||||
|
locationLogFile key = hashDirLower key ++ keyFile key ++ ".log"
|
||||||
|
|
||||||
|
{- Converts a pathname into a key if it's a location log. -}
|
||||||
|
locationLogFileKey :: FilePath -> Maybe Key
|
||||||
|
locationLogFileKey path
|
||||||
|
| ["remote", "web"] `isPrefixOf` splitDirectories dir = Nothing
|
||||||
|
| ext == ".log" = fileKey base
|
||||||
|
| otherwise = Nothing
|
||||||
|
where
|
||||||
|
(dir, file) = splitFileName 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
|
||||||
|
|
||||||
|
{- 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"
|
||||||
|
]
|
||||||
|
|
||||||
|
urlLogExt :: String
|
||||||
|
urlLogExt = ".log.web"
|
||||||
|
|
||||||
|
{- Converts a url log file into a key.
|
||||||
|
- (Does not work on oldurlLogs.) -}
|
||||||
|
urlLogFileKey :: FilePath -> Maybe Key
|
||||||
|
urlLogFileKey path
|
||||||
|
| ext == urlLogExt = fileKey base
|
||||||
|
| otherwise = Nothing
|
||||||
|
where
|
||||||
|
file = takeFileName path
|
||||||
|
(base, ext) = splitAt (length file - extlen) file
|
||||||
|
extlen = length urlLogExt
|
||||||
|
|
||||||
|
{- Does not work on oldurllogs. -}
|
||||||
|
isUrlLog :: FilePath -> Bool
|
||||||
|
isUrlLog file = urlLogExt `isSuffixOf` file
|
||||||
|
|
||||||
|
prop_logs_sane :: Key -> Bool
|
||||||
|
prop_logs_sane dummykey = all id
|
||||||
|
[ isNothing (getLogVariety "unknown")
|
||||||
|
, expect isUUIDBasedLog (getLogVariety uuidLog)
|
||||||
|
, expect isPresenceLog (getLogVariety $ locationLogFile dummykey)
|
||||||
|
, expect isPresenceLog (getLogVariety $ urlLogFile dummykey)
|
||||||
|
]
|
||||||
|
where
|
||||||
|
expect = maybe False
|
||||||
|
isUUIDBasedLog UUIDBasedLog = True
|
||||||
|
isUUIDBasedLog _ = False
|
||||||
|
isPresenceLog (PresenceLog k) = k == dummykey
|
||||||
|
isPresenceLog _ = False
|
|
@ -21,16 +21,13 @@ import qualified Data.Set as S
|
||||||
import Data.Time.Clock.POSIX
|
import Data.Time.Clock.POSIX
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
|
import Logs
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
import Logs.UUIDBased
|
import Logs.UUIDBased
|
||||||
import Types.Group
|
import Types.Group
|
||||||
import Types.StandardGroups
|
import Types.StandardGroups
|
||||||
|
|
||||||
{- Filename of group.log. -}
|
|
||||||
groupLog :: FilePath
|
|
||||||
groupLog = "group.log"
|
|
||||||
|
|
||||||
{- Returns the groups of a given repo UUID. -}
|
{- Returns the groups of a given repo UUID. -}
|
||||||
lookupGroups :: UUID -> Annex (S.Set Group)
|
lookupGroups :: UUID -> Annex (S.Set Group)
|
||||||
lookupGroups u = (fromMaybe S.empty . M.lookup u) . groupsByUUID <$> groupMap
|
lookupGroups u = (fromMaybe S.empty . M.lookup u) . groupsByUUID <$> groupMap
|
||||||
|
|
|
@ -20,12 +20,11 @@ module Logs.Location (
|
||||||
loggedLocations,
|
loggedLocations,
|
||||||
loggedKeys,
|
loggedKeys,
|
||||||
loggedKeysFor,
|
loggedKeysFor,
|
||||||
logFile,
|
|
||||||
logFileKey
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
|
import Logs
|
||||||
import Logs.Presence
|
import Logs.Presence
|
||||||
import Annex.UUID
|
import Annex.UUID
|
||||||
|
|
||||||
|
@ -37,19 +36,19 @@ logStatus key status = do
|
||||||
|
|
||||||
{- Log a change in the presence of a key's value in a repository. -}
|
{- Log a change in the presence of a key's value in a repository. -}
|
||||||
logChange :: Key -> UUID -> LogStatus -> Annex ()
|
logChange :: Key -> UUID -> LogStatus -> Annex ()
|
||||||
logChange key (UUID u) s = addLog (logFile key) =<< logNow s u
|
logChange key (UUID u) s = addLog (locationLogFile key) =<< logNow s u
|
||||||
logChange _ NoUUID _ = noop
|
logChange _ NoUUID _ = noop
|
||||||
|
|
||||||
{- Returns a list of repository UUIDs that, according to the log, have
|
{- Returns a list of repository UUIDs that, according to the log, have
|
||||||
- the value of a key.
|
- the value of a key.
|
||||||
-}
|
-}
|
||||||
loggedLocations :: Key -> Annex [UUID]
|
loggedLocations :: Key -> Annex [UUID]
|
||||||
loggedLocations key = map toUUID <$> (currentLog . logFile) key
|
loggedLocations key = map toUUID <$> (currentLog . locationLogFile) key
|
||||||
|
|
||||||
{- Finds all keys that have location log information.
|
{- Finds all keys that have location log information.
|
||||||
- (There may be duplicate keys in the list.) -}
|
- (There may be duplicate keys in the list.) -}
|
||||||
loggedKeys :: Annex [Key]
|
loggedKeys :: Annex [Key]
|
||||||
loggedKeys = mapMaybe (logFileKey . takeFileName) <$> Annex.Branch.files
|
loggedKeys = mapMaybe locationLogFileKey <$> Annex.Branch.files
|
||||||
|
|
||||||
{- Finds all keys that have location log information indicating
|
{- Finds all keys that have location log information indicating
|
||||||
- they are present for the specified repository. -}
|
- they are present for the specified repository. -}
|
||||||
|
@ -62,15 +61,3 @@ loggedKeysFor u = filterM isthere =<< loggedKeys
|
||||||
us <- loggedLocations k
|
us <- loggedLocations k
|
||||||
let !there = u `elem` us
|
let !there = u `elem` us
|
||||||
return there
|
return there
|
||||||
|
|
||||||
{- The filename of the log file for a given key. -}
|
|
||||||
logFile :: Key -> String
|
|
||||||
logFile key = hashDirLower key ++ keyFile key ++ ".log"
|
|
||||||
|
|
||||||
{- Converts a log filename into a key. -}
|
|
||||||
logFileKey :: FilePath -> Maybe Key
|
|
||||||
logFileKey file
|
|
||||||
| ext == ".log" = fileKey base
|
|
||||||
| otherwise = Nothing
|
|
||||||
where
|
|
||||||
(base, ext) = splitAt (length file - 4) file
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import Data.Time.Clock.POSIX
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
|
import Logs
|
||||||
import Logs.UUIDBased
|
import Logs.UUIDBased
|
||||||
import Limit
|
import Limit
|
||||||
import qualified Utility.Matcher
|
import qualified Utility.Matcher
|
||||||
|
@ -35,10 +36,6 @@ import Logs.Group
|
||||||
import Logs.Remote
|
import Logs.Remote
|
||||||
import Types.StandardGroups
|
import Types.StandardGroups
|
||||||
|
|
||||||
{- Filename of preferred-content.log. -}
|
|
||||||
preferredContentLog :: FilePath
|
|
||||||
preferredContentLog = "preferred-content.log"
|
|
||||||
|
|
||||||
{- Changes the preferred content configuration of a remote. -}
|
{- Changes the preferred content configuration of a remote. -}
|
||||||
preferredContentSet :: UUID -> String -> Annex ()
|
preferredContentSet :: UUID -> String -> Annex ()
|
||||||
preferredContentSet uuid@(UUID _) val = do
|
preferredContentSet uuid@(UUID _) val = do
|
||||||
|
|
|
@ -25,12 +25,9 @@ import Data.Char
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
import Types.Remote
|
import Types.Remote
|
||||||
|
import Logs
|
||||||
import Logs.UUIDBased
|
import Logs.UUIDBased
|
||||||
|
|
||||||
{- Filename of remote.log. -}
|
|
||||||
remoteLog :: FilePath
|
|
||||||
remoteLog = "remote.log"
|
|
||||||
|
|
||||||
{- Adds or updates a remote's config in the log. -}
|
{- Adds or updates a remote's config in the log. -}
|
||||||
configSet :: UUID -> RemoteConfig -> Annex ()
|
configSet :: UUID -> RemoteConfig -> Annex ()
|
||||||
configSet u c = do
|
configSet u c = do
|
||||||
|
|
|
@ -27,14 +27,11 @@ import Common.Annex
|
||||||
import Types.TrustLevel
|
import Types.TrustLevel
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
|
import Logs
|
||||||
import Logs.UUIDBased
|
import Logs.UUIDBased
|
||||||
import Remote.List
|
import Remote.List
|
||||||
import qualified Types.Remote
|
import qualified Types.Remote
|
||||||
|
|
||||||
{- Filename of trust.log. -}
|
|
||||||
trustLog :: FilePath
|
|
||||||
trustLog = "trust.log"
|
|
||||||
|
|
||||||
{- Returns a list of UUIDs that the trustLog indicates have the
|
{- Returns a list of UUIDs that the trustLog indicates have the
|
||||||
- specified trust level.
|
- specified trust level.
|
||||||
- Note that the list can be incomplete for SemiTrusted, since that's
|
- Note that the list can be incomplete for SemiTrusted, since that's
|
||||||
|
|
|
@ -28,13 +28,10 @@ import Types.UUID
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
|
import Logs
|
||||||
import Logs.UUIDBased
|
import Logs.UUIDBased
|
||||||
import qualified Annex.UUID
|
import qualified Annex.UUID
|
||||||
|
|
||||||
{- Filename of uuid.log. -}
|
|
||||||
uuidLog :: FilePath
|
|
||||||
uuidLog = "uuid.log"
|
|
||||||
|
|
||||||
{- Records a description for a uuid in the log. -}
|
{- Records a description for a uuid in the log. -}
|
||||||
describeUUID :: UUID -> String -> Annex ()
|
describeUUID :: UUID -> String -> Annex ()
|
||||||
describeUUID uuid desc = do
|
describeUUID uuid desc = do
|
||||||
|
|
36
Logs/Web.hs
36
Logs/Web.hs
|
@ -11,8 +11,6 @@ module Logs.Web (
|
||||||
getUrls,
|
getUrls,
|
||||||
setUrlPresent,
|
setUrlPresent,
|
||||||
setUrlMissing,
|
setUrlMissing,
|
||||||
urlLog,
|
|
||||||
urlLogKey,
|
|
||||||
knownUrls,
|
knownUrls,
|
||||||
Downloader(..),
|
Downloader(..),
|
||||||
getDownloader,
|
getDownloader,
|
||||||
|
@ -22,9 +20,9 @@ module Logs.Web (
|
||||||
import qualified Data.ByteString.Lazy.Char8 as L
|
import qualified Data.ByteString.Lazy.Char8 as L
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
|
import Logs
|
||||||
import Logs.Presence
|
import Logs.Presence
|
||||||
import Logs.Location
|
import Logs.Location
|
||||||
import Types.Key
|
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
import Annex.CatFile
|
import Annex.CatFile
|
||||||
import qualified Git
|
import qualified Git
|
||||||
|
@ -36,35 +34,9 @@ type URLString = String
|
||||||
webUUID :: UUID
|
webUUID :: UUID
|
||||||
webUUID = UUID "00000000-0000-0000-0000-000000000001"
|
webUUID = UUID "00000000-0000-0000-0000-000000000001"
|
||||||
|
|
||||||
urlLogExt :: String
|
|
||||||
urlLogExt = ".log.web"
|
|
||||||
|
|
||||||
urlLog :: Key -> FilePath
|
|
||||||
urlLog key = hashDirLower key </> keyFile key ++ urlLogExt
|
|
||||||
|
|
||||||
{- Converts a url log file into a key.
|
|
||||||
- (Does not work on oldurlLogs.) -}
|
|
||||||
urlLogKey :: FilePath -> Maybe Key
|
|
||||||
urlLogKey file
|
|
||||||
| ext == urlLogExt = fileKey base
|
|
||||||
| otherwise = Nothing
|
|
||||||
where
|
|
||||||
(base, ext) = splitAt (length file - extlen) file
|
|
||||||
extlen = length urlLogExt
|
|
||||||
|
|
||||||
isUrlLog :: FilePath -> Bool
|
|
||||||
isUrlLog file = urlLogExt `isSuffixOf` file
|
|
||||||
|
|
||||||
{- Used to store the urls elsewhere. -}
|
|
||||||
oldurlLogs :: Key -> [FilePath]
|
|
||||||
oldurlLogs key =
|
|
||||||
[ "remote/web" </> hashDirLower key </> key2file key ++ ".log"
|
|
||||||
, "remote/web" </> hashDirLower key </> keyFile key ++ ".log"
|
|
||||||
]
|
|
||||||
|
|
||||||
{- Gets all urls that a key might be available from. -}
|
{- Gets all urls that a key might be available from. -}
|
||||||
getUrls :: Key -> Annex [URLString]
|
getUrls :: Key -> Annex [URLString]
|
||||||
getUrls key = go $ urlLog key : oldurlLogs key
|
getUrls key = go $ urlLogFile key : oldurlLogs key
|
||||||
where
|
where
|
||||||
go [] = return []
|
go [] = return []
|
||||||
go (l:ls) = do
|
go (l:ls) = do
|
||||||
|
@ -77,13 +49,13 @@ setUrlPresent :: Key -> URLString -> Annex ()
|
||||||
setUrlPresent key url = do
|
setUrlPresent key url = do
|
||||||
us <- getUrls key
|
us <- getUrls key
|
||||||
unless (url `elem` us) $ do
|
unless (url `elem` us) $ do
|
||||||
addLog (urlLog key) =<< logNow InfoPresent url
|
addLog (urlLogFile key) =<< logNow InfoPresent url
|
||||||
-- update location log to indicate that the web has the key
|
-- update location log to indicate that the web has the key
|
||||||
logChange key webUUID InfoPresent
|
logChange key webUUID InfoPresent
|
||||||
|
|
||||||
setUrlMissing :: Key -> URLString -> Annex ()
|
setUrlMissing :: Key -> URLString -> Annex ()
|
||||||
setUrlMissing key url = do
|
setUrlMissing key url = do
|
||||||
addLog (urlLog key) =<< logNow InfoMissing url
|
addLog (urlLogFile key) =<< logNow InfoMissing url
|
||||||
whenM (null <$> getUrls key) $
|
whenM (null <$> getUrls key) $
|
||||||
logChange key webUUID InfoMissing
|
logChange key webUUID InfoMissing
|
||||||
|
|
||||||
|
|
2
Test.hs
2
Test.hs
|
@ -33,6 +33,7 @@ import qualified Types.KeySource
|
||||||
import qualified Types.Backend
|
import qualified Types.Backend
|
||||||
import qualified Types.TrustLevel
|
import qualified Types.TrustLevel
|
||||||
import qualified Types
|
import qualified Types
|
||||||
|
import qualified Logs
|
||||||
import qualified Logs.UUIDBased
|
import qualified Logs.UUIDBased
|
||||||
import qualified Logs.Trust
|
import qualified Logs.Trust
|
||||||
import qualified Logs.Remote
|
import qualified Logs.Remote
|
||||||
|
@ -115,6 +116,7 @@ quickcheck =
|
||||||
, check "prop_idempotent_key_encode" Types.Key.prop_idempotent_key_encode
|
, check "prop_idempotent_key_encode" Types.Key.prop_idempotent_key_encode
|
||||||
, check "prop_idempotent_shellEscape" Utility.SafeCommand.prop_idempotent_shellEscape
|
, check "prop_idempotent_shellEscape" Utility.SafeCommand.prop_idempotent_shellEscape
|
||||||
, check "prop_idempotent_shellEscape_multiword" Utility.SafeCommand.prop_idempotent_shellEscape_multiword
|
, check "prop_idempotent_shellEscape_multiword" Utility.SafeCommand.prop_idempotent_shellEscape_multiword
|
||||||
|
, check "prop_logs_sane" Logs.prop_logs_sane
|
||||||
, check "prop_idempotent_configEscape" Logs.Remote.prop_idempotent_configEscape
|
, check "prop_idempotent_configEscape" Logs.Remote.prop_idempotent_configEscape
|
||||||
, check "prop_parse_show_Config" Logs.Remote.prop_parse_show_Config
|
, check "prop_parse_show_Config" Logs.Remote.prop_parse_show_Config
|
||||||
, check "prop_parentDir_basics" Utility.Path.prop_parentDir_basics
|
, check "prop_parentDir_basics" Utility.Path.prop_parentDir_basics
|
||||||
|
|
|
@ -12,9 +12,9 @@ import qualified Git
|
||||||
import qualified Git.Command
|
import qualified Git.Command
|
||||||
import qualified Git.Ref
|
import qualified Git.Ref
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
import Logs.Location
|
|
||||||
import Annex.Content
|
import Annex.Content
|
||||||
import Utility.Tmp
|
import Utility.Tmp
|
||||||
|
import Logs
|
||||||
|
|
||||||
olddir :: Git.Repo -> FilePath
|
olddir :: Git.Repo -> FilePath
|
||||||
olddir g
|
olddir g
|
||||||
|
@ -47,7 +47,7 @@ upgrade = do
|
||||||
|
|
||||||
e <- liftIO $ doesDirectoryExist old
|
e <- liftIO $ doesDirectoryExist old
|
||||||
when e $ do
|
when e $ do
|
||||||
mapM_ (\(k, f) -> inject f $ logFile k) =<< locationLogs
|
mapM_ (\(k, f) -> inject f $ locationLogFile k) =<< locationLogs
|
||||||
mapM_ (\f -> inject f f) =<< logFiles old
|
mapM_ (\f -> inject f f) =<< logFiles old
|
||||||
|
|
||||||
saveState False
|
saveState False
|
||||||
|
@ -73,7 +73,7 @@ locationLogs = do
|
||||||
where
|
where
|
||||||
tryDirContents d = catchDefaultIO [] $ dirContents d
|
tryDirContents d = catchDefaultIO [] $ dirContents d
|
||||||
islogfile f = maybe Nothing (\k -> Just (k, f)) $
|
islogfile f = maybe Nothing (\k -> Just (k, f)) $
|
||||||
logFileKey $ takeFileName f
|
locationLogFileKey f
|
||||||
|
|
||||||
inject :: FilePath -> FilePath -> Annex ()
|
inject :: FilePath -> FilePath -> Annex ()
|
||||||
inject source dest = do
|
inject source dest = do
|
||||||
|
|
|
@ -91,6 +91,12 @@ massReplace vs = go [] vs
|
||||||
go (replacement:acc) vs (drop (length val) s)
|
go (replacement:acc) vs (drop (length val) s)
|
||||||
| otherwise = go acc rest s
|
| otherwise = go acc rest s
|
||||||
|
|
||||||
|
{- First item in the list that is not Nothing. -}
|
||||||
|
firstJust :: Eq a => [Maybe a] -> Maybe a
|
||||||
|
firstJust ms = case dropWhile (== Nothing) ms of
|
||||||
|
[] -> Nothing
|
||||||
|
(md:_) -> md
|
||||||
|
|
||||||
{- Given two orderings, returns the second if the first is EQ and returns
|
{- Given two orderings, returns the second if the first is EQ and returns
|
||||||
- the first otherwise.
|
- the first otherwise.
|
||||||
-
|
-
|
||||||
|
|
Loading…
Reference in a new issue