tagging package git-annex version 5.20140320

-----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIVAwUAUytJN8kQ2SIlEuPHAQgcEA//X5MJTU5DXbnTV9Yrqg9zf3WUF9dTeKnb
 fxpUzHd20pjVhH8oqAmwTPpMHViNQax3CBhR2qd/T41eHZA0jljxefihWYd7e+2m
 3KFxTc//9/5BI2cNjr+3cOikairVRK1jjCts0+rWF6YBjK0IRjxCN56ij9hg4B0a
 11J/qu5GelB3VvyMUzyZqheapmP0MKAtIlcIXL0tr695dR2Ar4etvrkX/VFgtuSQ
 2Nir6emXsoxrhl4Ph1WtwC9+mdJi85n2IbZ4l14l5n+sAc1Lmkp+r6RZJp8Efkef
 LN6OsYHV2pqyKUZiZ9y3RgBzd838R7ocpOggsA14RtMslxAYxP5jZP9QwUvSWAqk
 t2gibt6p3sJms1cvASHpOtegnz6d7+LK6pNqIl5XgjugofHlT+XHUEvrzyP/Lan4
 RZK8wUIfJRX+VBju4tCZ+Q/5Br9VM/ZF80DIps0WIYOzLR5b1po8YTTWMskwkiii
 g5UjNw4NVfi9X7N+SjArwzmtpijctAeQj2QkBlb+kaPL+dxx33EDcRegw7KZ+/4Q
 ViyOhwl21Lb5Nzj8n6t/8g5GKRNJBmlOsa/K80vTOraRgukt4QejliFPswK7Z/6b
 DjfItpny+qqwuufa0hWW/U9VgTQfbewEsCt8QdpQHIUpjO+KvGHjOpal/zjnGemy
 qpFXO5EiwOo=
 =AwJK
 -----END PGP SIGNATURE-----

Merge tag '5.20140320' into debian-wheezy-backport

tagging package git-annex version 5.20140320

Conflicts:
	Assistant/Threads/WebApp.hs
	Utility/WebApp.hs
	git-annex.cabal
This commit is contained in:
Joey Hess 2014-03-26 15:14:16 -04:00
commit a20b9d78dc
306 changed files with 4068 additions and 1379 deletions

View file

@ -60,6 +60,7 @@ import Types.FileMatcher
import Types.NumCopies import Types.NumCopies
import Types.LockPool import Types.LockPool
import Types.MetaData import Types.MetaData
import Types.CleanupActions
import qualified Utility.Matcher import qualified Utility.Matcher
import qualified Data.Map as M import qualified Data.Map as M
import qualified Data.Set as S import qualified Data.Set as S
@ -88,6 +89,7 @@ data AnnexState = AnnexState
, gitconfig :: GitConfig , gitconfig :: GitConfig
, backends :: [BackendA Annex] , backends :: [BackendA Annex]
, remotes :: [Types.Remote.RemoteA Annex] , remotes :: [Types.Remote.RemoteA Annex]
, remoteannexstate :: M.Map UUID AnnexState
, output :: MessageState , output :: MessageState
, force :: Bool , force :: Bool
, fast :: Bool , fast :: Bool
@ -113,7 +115,7 @@ data AnnexState = AnnexState
, flags :: M.Map String Bool , flags :: M.Map String Bool
, fields :: M.Map String String , fields :: M.Map String String
, modmeta :: [ModMeta] , modmeta :: [ModMeta]
, cleanup :: M.Map String (Annex ()) , cleanup :: M.Map CleanupAction (Annex ())
, inodeschanged :: Maybe Bool , inodeschanged :: Maybe Bool
, useragent :: Maybe String , useragent :: Maybe String
, errcounter :: Integer , errcounter :: Integer
@ -128,6 +130,7 @@ newState c r = AnnexState
, gitconfig = c , gitconfig = c
, backends = [] , backends = []
, remotes = [] , remotes = []
, remoteannexstate = M.empty
, output = defaultMessageState , output = defaultMessageState
, force = False , force = False
, fast = False , fast = False
@ -208,9 +211,9 @@ setField field value = changeState $ \s ->
s { fields = M.insertWith' const field value $ fields s } s { fields = M.insertWith' const field value $ fields s }
{- Adds a cleanup action to perform. -} {- Adds a cleanup action to perform. -}
addCleanup :: String -> Annex () -> Annex () addCleanup :: CleanupAction -> Annex () -> Annex ()
addCleanup uid a = changeState $ \s -> addCleanup k a = changeState $ \s ->
s { cleanup = M.insertWith' const uid a $ cleanup s } s { cleanup = M.insertWith' const k a $ cleanup s }
{- Sets the type of output to emit. -} {- Sets the type of output to emit. -}
setOutput :: OutputType -> Annex () setOutput :: OutputType -> Annex ()

View file

@ -80,7 +80,7 @@ catKey = catKey' True
catKey' :: Bool -> Ref -> FileMode -> Annex (Maybe Key) catKey' :: Bool -> Ref -> FileMode -> Annex (Maybe Key)
catKey' modeguaranteed ref mode catKey' modeguaranteed ref mode
| isSymLink mode = do | isSymLink mode = do
l <- fromInternalGitPath . encodeW8 . L.unpack <$> get l <- fromInternalGitPath . decodeBS <$> get
return $ if isLinkToAnnex l return $ if isLinkToAnnex l
then fileKey $ takeFileName l then fileKey $ takeFileName l
else Nothing else Nothing

View file

@ -24,6 +24,7 @@ module Annex.Content (
removeAnnex, removeAnnex,
fromAnnex, fromAnnex,
moveBad, moveBad,
KeyLocation(..),
getKeysPresent, getKeysPresent,
saveState, saveState,
downloadUrl, downloadUrl,
@ -466,22 +467,33 @@ moveBad key = do
logStatus key InfoMissing logStatus key InfoMissing
return dest return dest
{- List of keys whose content exists in the annex. -} data KeyLocation = InAnnex | InRepository
getKeysPresent :: Annex [Key]
getKeysPresent = do {- List of keys whose content exists in the specified location.
- InAnnex only lists keys under .git/annex/objects,
- while InRepository, in direct mode, also finds keys located in the
- work tree.
-
- Note that InRepository has to check whether direct mode files
- have goodContent.
-}
getKeysPresent :: KeyLocation -> Annex [Key]
getKeysPresent keyloc = do
direct <- isDirect direct <- isDirect
dir <- fromRepo gitAnnexObjectDir dir <- fromRepo gitAnnexObjectDir
liftIO $ traverse direct (2 :: Int) dir s <- getstate direct
liftIO $ traverse s direct (2 :: Int) dir
where where
traverse direct depth dir = do traverse s direct depth dir = do
contents <- catchDefaultIO [] (dirContents dir) contents <- catchDefaultIO [] (dirContents dir)
if depth == 0 if depth == 0
then do then do
contents' <- filterM (present direct) contents contents' <- filterM (present s direct) contents
let keys = mapMaybe (fileKey . takeFileName) contents' let keys = mapMaybe (fileKey . takeFileName) contents'
continue keys [] continue keys []
else do else do
let deeper = traverse direct (depth - 1) let deeper = traverse s direct (depth - 1)
continue [] (map deeper contents) continue [] (map deeper contents)
continue keys [] = return keys continue keys [] = return keys
continue keys (a:as) = do continue keys (a:as) = do
@ -489,15 +501,31 @@ getKeysPresent = do
morekeys <- unsafeInterleaveIO a morekeys <- unsafeInterleaveIO a
continue (morekeys++keys) as continue (morekeys++keys) as
{- In indirect mode, look for the key. In direct mode, present _ False d = presentInAnnex d
- the inode cache file is only present when a key's content present s True d = presentDirect s d <||> presentInAnnex d
- is present, so can be used as a surrogate if the content
- is not located in the annex directory. -} presentInAnnex = doesFileExist . contentfile
present False d = doesFileExist $ contentfile d
present True d = doesFileExist (contentfile d ++ ".cache")
<||> present False d
contentfile d = d </> takeFileName d contentfile d = d </> takeFileName d
presentDirect s d = case keyloc of
InAnnex -> return False
InRepository -> case fileKey (takeFileName d) of
Nothing -> return False
Just k -> Annex.eval s $
anyM (goodContent k) =<< associatedFiles k
{- In order to run Annex monad actions within unsafeInterleaveIO,
- the current state is taken and reused. No changes made to this
- state will be preserved.
-
- As an optimsation, call inodesChanged to prime the state with
- a cached value that will be used in the call to goodContent.
-}
getstate direct = do
when direct $
void $ inodesChanged
Annex.getState id
{- Things to do to record changes to content when shutting down. {- Things to do to record changes to content when shutting down.
- -
- It's acceptable to avoid committing changes to the branch, - It's acceptable to avoid committing changes to the branch,

View file

@ -56,23 +56,27 @@ parsedToMatcher parsed = case partitionEithers parsed of
([], vs) -> Right $ generate vs ([], vs) -> Right $ generate vs
(es, _) -> Left $ unwords $ map ("Parse failure: " ++) es (es, _) -> Left $ unwords $ map ("Parse failure: " ++) es
exprParser :: GroupMap -> M.Map UUID RemoteConfig -> Maybe UUID -> String -> [Either String (Token MatchFiles)] exprParser :: FileMatcher -> FileMatcher -> GroupMap -> M.Map UUID RemoteConfig -> Maybe UUID -> String -> [Either String (Token MatchFiles)]
exprParser groupmap configmap mu expr = exprParser matchstandard matchgroupwanted groupmap configmap mu expr =
map parse $ tokenizeMatcher expr map parse $ tokenizeMatcher expr
where where
parse = parseToken parse = parseToken
matchstandard
matchgroupwanted
(limitPresent mu) (limitPresent mu)
(limitInDir preferreddir) (limitInDir preferreddir)
groupmap groupmap
preferreddir = fromMaybe "public" $ preferreddir = fromMaybe "public" $
M.lookup "preferreddir" =<< (`M.lookup` configmap) =<< mu M.lookup "preferreddir" =<< (`M.lookup` configmap) =<< mu
parseToken :: MkLimit -> MkLimit -> GroupMap -> String -> Either String (Token MatchFiles) parseToken :: FileMatcher -> FileMatcher -> MkLimit -> MkLimit -> GroupMap -> String -> Either String (Token MatchFiles)
parseToken checkpresent checkpreferreddir groupmap t parseToken matchstandard matchgroupwanted checkpresent checkpreferreddir groupmap t
| t `elem` tokens = Right $ token t | t `elem` tokens = Right $ token t
| t == "standard" = call matchstandard
| t == "groupwanted" = call matchgroupwanted
| t == "present" = use checkpresent | t == "present" = use checkpresent
| t == "inpreferreddir" = use checkpreferreddir | t == "inpreferreddir" = use checkpreferreddir
| t == "unused" = Right (Operation limitUnused) | t == "unused" = Right $ Operation limitUnused
| otherwise = maybe (Left $ "near " ++ show t) use $ M.lookup k $ | otherwise = maybe (Left $ "near " ++ show t) use $ M.lookup k $
M.fromList M.fromList
[ ("include", limitInclude) [ ("include", limitInclude)
@ -89,6 +93,8 @@ parseToken checkpresent checkpreferreddir groupmap t
where where
(k, v) = separate (== '=') t (k, v) = separate (== '=') t
use a = Operation <$> a v use a = Operation <$> a v
call sub = Right $ Operation $ \notpresent mi ->
matchMrun sub $ \a -> a notpresent mi
{- This is really dumb tokenization; there's no support for quoted values. {- This is really dumb tokenization; there's no support for quoted values.
- Open and close parens are always treated as standalone tokens; - Open and close parens are always treated as standalone tokens;
@ -109,5 +115,5 @@ largeFilesMatcher = go =<< annexLargeFiles <$> Annex.getGitConfig
rc <- readRemoteLog rc <- readRemoteLog
u <- getUUID u <- getUUID
either badexpr return $ either badexpr return $
parsedToMatcher $ exprParser gm rc (Just u) expr parsedToMatcher $ exprParser matchAll matchAll gm rc (Just u) expr
badexpr e = error $ "bad annex.largefiles configuration: " ++ e badexpr e = error $ "bad annex.largefiles configuration: " ++ e

View file

@ -198,7 +198,7 @@ enableDirectMode = unlessM isDirect $ do
-} -}
fixBadBare :: Annex () fixBadBare :: Annex ()
fixBadBare = whenM checkBadBare $ do fixBadBare = whenM checkBadBare $ do
ks <- getKeysPresent ks <- getKeysPresent InAnnex
liftIO $ debugM "Init" $ unwords liftIO $ debugM "Init" $ unwords
[ "Detected bad bare repository with" [ "Detected bad bare repository with"
, show (length ks) , show (length ks)

View file

@ -5,11 +5,15 @@
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
module Annex.MetaData where module Annex.MetaData (
genMetaData,
module X
) where
import Common.Annex import Common.Annex
import qualified Annex import qualified Annex
import Types.MetaData import Types.MetaData as X
import Annex.MetaData.StandardFields as X
import Logs.MetaData import Logs.MetaData
import Annex.CatFile import Annex.CatFile
@ -19,15 +23,6 @@ import Data.Time.Calendar
import Data.Time.Clock import Data.Time.Clock
import Data.Time.Clock.POSIX import Data.Time.Clock.POSIX
tagMetaField :: MetaField
tagMetaField = mkMetaFieldUnchecked "tag"
yearMetaField :: MetaField
yearMetaField = mkMetaFieldUnchecked "year"
monthMetaField :: MetaField
monthMetaField = mkMetaFieldUnchecked "month"
{- Adds metadata for a file that has just been ingested into the {- Adds metadata for a file that has just been ingested into the
- annex, but has not yet been committed to git. - annex, but has not yet been committed to git.
- -

View file

@ -0,0 +1,47 @@
{- git-annex metadata, standard fields
-
- Copyright 2014 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Annex.MetaData.StandardFields (
tagMetaField,
yearMetaField,
monthMetaField,
lastChangedField,
mkLastChangedField,
isLastChangedField
) where
import Types.MetaData
import Data.List
tagMetaField :: MetaField
tagMetaField = mkMetaFieldUnchecked "tag"
yearMetaField :: MetaField
yearMetaField = mkMetaFieldUnchecked "year"
monthMetaField :: MetaField
monthMetaField = mkMetaFieldUnchecked "month"
lastChangedField :: MetaField
lastChangedField = mkMetaFieldUnchecked lastchanged
mkLastChangedField :: MetaField -> MetaField
mkLastChangedField f = mkMetaFieldUnchecked (fromMetaField f ++ lastchangedSuffix)
isLastChangedField :: MetaField -> Bool
isLastChangedField f
| f == lastChangedField = True
| otherwise = lastchanged `isSuffixOf` s && s /= lastchangedSuffix
where
s = fromMetaField f
lastchanged :: String
lastchanged = "lastchanged"
lastchangedSuffix :: String
lastchangedSuffix = "-lastchanged"

View file

@ -9,7 +9,6 @@
module Annex.Ssh ( module Annex.Ssh (
sshCachingOptions, sshCachingOptions,
sshCleanup,
sshCacheDir, sshCacheDir,
sshReadPort, sshReadPort,
) where ) where
@ -24,6 +23,7 @@ import qualified Build.SysConfig as SysConfig
import qualified Annex import qualified Annex
import Config import Config
import Utility.Env import Utility.Env
import Types.CleanupActions
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
import Annex.Perms import Annex.Perms
#endif #endif
@ -31,7 +31,9 @@ import Annex.Perms
{- Generates parameters to ssh to a given host (or user@host) on a given {- Generates parameters to ssh to a given host (or user@host) on a given
- port, with connection caching. -} - port, with connection caching. -}
sshCachingOptions :: (String, Maybe Integer) -> [CommandParam] -> Annex [CommandParam] sshCachingOptions :: (String, Maybe Integer) -> [CommandParam] -> Annex [CommandParam]
sshCachingOptions (host, port) opts = go =<< sshInfo (host, port) sshCachingOptions (host, port) opts = do
Annex.addCleanup SshCachingCleanup sshCleanup
go =<< sshInfo (host, port)
where where
go (Nothing, params) = ret params go (Nothing, params) = ret params
go (Just socketfile, params) = do go (Just socketfile, params) = do
@ -144,8 +146,9 @@ sshCleanup = go =<< sshCacheDir
withQuietOutput createProcessSuccess $ withQuietOutput createProcessSuccess $
(proc "ssh" $ toCommand $ (proc "ssh" $ toCommand $
[ Params "-O stop" [ Params "-O stop"
] ++ params ++ [Param "any"]) ] ++ params ++ [Param "localhost"])
{ cwd = Just dir } { cwd = Just dir }
liftIO $ nukeFile socketfile
-- Cannot remove the lock file; other processes may -- Cannot remove the lock file; other processes may
-- be waiting on our exclusive lock to use it. -- be waiting on our exclusive lock to use it.

View file

@ -45,7 +45,6 @@ import Assistant.Threads.XMPPClient
import Assistant.Threads.XMPPPusher import Assistant.Threads.XMPPPusher
#endif #endif
#else #else
#warning Building without the webapp. You probably need to install Yesod..
import Assistant.Types.UrlRenderer import Assistant.Types.UrlRenderer
#endif #endif
import qualified Utility.Daemon import qualified Utility.Daemon

View file

@ -14,6 +14,7 @@ import Utility.Tense
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Map as M import qualified Data.Map as M
import Data.Monoid
{- This is as many alerts as it makes sense to display at a time. {- This is as many alerts as it makes sense to display at a time.
- A display might be smaller, or larger, the point is to not overwhelm the - A display might be smaller, or larger, the point is to not overwhelm the
@ -43,8 +44,8 @@ compareAlertPairs
(aid, Alert { alertClass = aclass, alertPriority = aprio }) (aid, Alert { alertClass = aclass, alertPriority = aprio })
(bid, Alert { alertClass = bclass, alertPriority = bprio }) (bid, Alert { alertClass = bclass, alertPriority = bprio })
= compare aprio bprio = compare aprio bprio
`thenOrd` compare aid bid `mappend` compare aid bid
`thenOrd` compare aclass bclass `mappend` compare aclass bclass
sortAlertPairs :: [AlertPair] -> [AlertPair] sortAlertPairs :: [AlertPair] -> [AlertPair]
sortAlertPairs = sortBy compareAlertPairs sortAlertPairs = sortBy compareAlertPairs

View file

@ -21,7 +21,7 @@ installMenu command menufile iconsrcdir icondir = do
writeDesktopMenuFile (fdoDesktopMenu command) menufile writeDesktopMenuFile (fdoDesktopMenu command) menufile
installIcon (iconsrcdir </> "logo.svg") $ installIcon (iconsrcdir </> "logo.svg") $
iconFilePath (iconBaseName ++ ".svg") "scalable" icondir iconFilePath (iconBaseName ++ ".svg") "scalable" icondir
installIcon (iconsrcdir </> "favicon.png") $ installIcon (iconsrcdir </> "logo_16x16.png") $
iconFilePath (iconBaseName ++ ".png") "16x16" icondir iconFilePath (iconBaseName ++ ".png") "16x16" icondir
#endif #endif

View file

@ -73,7 +73,7 @@ webAppThread assistantdata urlrenderer noannex cannotrun postfirstrun listenhost
#endif #endif
webapp <- WebApp webapp <- WebApp
<$> pure assistantdata <$> pure assistantdata
<*> (pack <$> genRandomToken) <*> genAuthToken
<*> getreldir <*> getreldir
<*> pure staticRoutes <*> pure staticRoutes
<*> pure postfirstrun <*> pure postfirstrun
@ -125,7 +125,7 @@ myUrl tlssettings webapp addr = unpack $ yesodRender webapp urlbase DashboardR [
getTlsSettings :: Annex (Maybe TLS.TLSSettings) getTlsSettings :: Annex (Maybe TLS.TLSSettings)
getTlsSettings = do getTlsSettings = do
#ifdef WITH_WEBAPP_HTTPS #ifdef WITH_WEBAPP_SECURE
cert <- fromRepo gitAnnexWebCertificate cert <- fromRepo gitAnnexWebCertificate
privkey <- fromRepo gitAnnexWebPrivKey privkey <- fromRepo gitAnnexWebPrivKey
ifM (liftIO $ allM doesFileExist [cert, privkey]) ifM (liftIO $ allM doesFileExist [cert, privkey])

View file

@ -14,6 +14,7 @@ import Assistant.WebApp.Types
import Assistant.Common import Assistant.Common
import Utility.NotificationBroadcaster import Utility.NotificationBroadcaster
import Utility.Yesod import Utility.Yesod
import Utility.WebApp
import Data.Text (Text) import Data.Text (Text)
import Control.Concurrent import Control.Concurrent
@ -36,7 +37,7 @@ newNotifier getbroadcaster = liftAssistant $ do
webAppFormAuthToken :: Widget webAppFormAuthToken :: Widget
webAppFormAuthToken = do webAppFormAuthToken = do
webapp <- liftH getYesod webapp <- liftH getYesod
[whamlet|<input type="hidden" name="auth" value="#{secretToken webapp}">|] [whamlet|<input type="hidden" name="auth" value="#{fromAuthToken (authToken webapp)}">|]
{- A button with an icon, and maybe label or tooltip, that can be {- A button with an icon, and maybe label or tooltip, that can be
- clicked to perform some action. - clicked to perform some action.

View file

@ -22,6 +22,7 @@ import Assistant.DaemonStatus
import Assistant.Types.Buddies import Assistant.Types.Buddies
import Utility.NotificationBroadcaster import Utility.NotificationBroadcaster
import Utility.Yesod import Utility.Yesod
import Utility.WebApp
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
@ -64,7 +65,7 @@ notifierUrl route broadcaster = do
[ "/" [ "/"
, T.intercalate "/" urlbits , T.intercalate "/" urlbits
, "?auth=" , "?auth="
, secretToken webapp , fromAuthToken (authToken webapp)
] ]
getNotifierTransfersR :: Handler RepPlain getNotifierTransfersR :: Handler RepPlain

View file

@ -31,6 +31,7 @@ import qualified Data.Map as M
import qualified Data.Set as S import qualified Data.Set as S
import qualified Data.Text as T import qualified Data.Text as T
import Data.Function import Data.Function
import Control.Concurrent
type RepoList = [(RepoDesc, RepoId, Actions)] type RepoList = [(RepoDesc, RepoId, Actions)]
@ -238,3 +239,15 @@ reorderCosts remote rs = zip rs'' (insertCostAfter costs i)
costs = map Remote.cost rs' costs = map Remote.cost rs'
rs'' = (\(x, y) -> x ++ [remote] ++ y) $ splitAt (i + 1) rs' rs'' = (\(x, y) -> x ++ [remote] ++ y) $ splitAt (i + 1) rs'
getSyncNowRepositoryR :: UUID -> Handler ()
getSyncNowRepositoryR uuid = do
u <- liftAnnex getUUID
if u == uuid
then do
thread <- liftAssistant $ asIO $
reconnectRemotes True
=<< (syncRemotes <$> getDaemonStatus)
void $ liftIO $ forkIO thread
else maybe noop (liftAssistant . syncRemote)
=<< liftAnnex (Remote.remoteFromUUID uuid)
redirectBack

View file

@ -41,7 +41,7 @@ mkYesodData "WebApp" $(parseRoutesFile "Assistant/WebApp/routes")
data WebApp = WebApp data WebApp = WebApp
{ assistantData :: AssistantData { assistantData :: AssistantData
, secretToken :: Text , authToken :: AuthToken
, relDir :: Maybe FilePath , relDir :: Maybe FilePath
, getStatic :: Static , getStatic :: Static
, postFirstRun :: Maybe (IO String) , postFirstRun :: Maybe (IO String)
@ -52,11 +52,11 @@ data WebApp = WebApp
instance Yesod WebApp where instance Yesod WebApp where
{- Require an auth token be set when accessing any (non-static) route -} {- Require an auth token be set when accessing any (non-static) route -}
isAuthorized _ _ = checkAuthToken secretToken isAuthorized _ _ = checkAuthToken authToken
{- Add the auth token to every url generated, except static subsite {- Add the auth token to every url generated, except static subsite
- urls (which can show up in Permission Denied pages). -} - urls (which can show up in Permission Denied pages). -}
joinPath = insertAuthToken secretToken excludeStatic joinPath = insertAuthToken authToken excludeStatic
where where
excludeStatic [] = True excludeStatic [] = True
excludeStatic (p:_) = p /= "static" excludeStatic (p:_) = p /= "static"

View file

@ -82,6 +82,7 @@
/config/repository/reorder RepositoriesReorderR GET /config/repository/reorder RepositoriesReorderR GET
/config/repository/syncnow/#UUID SyncNowRepositoryR GET
/config/repository/disable/#UUID DisableRepositoryR GET /config/repository/disable/#UUID DisableRepositoryR GET
/config/repository/delete/confirm/#UUID DeleteRepositoryR GET /config/repository/delete/confirm/#UUID DeleteRepositoryR GET

View file

@ -3,20 +3,14 @@
module Build.Configure where module Build.Configure where
import System.Directory import System.Directory
import Data.List
import System.Process
import Control.Applicative import Control.Applicative
import System.FilePath
import System.Environment (getArgs) import System.Environment (getArgs)
import Data.Maybe
import Control.Monad.IfElse import Control.Monad.IfElse
import Control.Monad import Control.Monad
import Data.Char
import Build.TestConfig import Build.TestConfig
import Build.Version import Build.Version
import Utility.SafeCommand import Utility.SafeCommand
import Utility.Monad
import Utility.ExternalSHA import Utility.ExternalSHA
import Utility.Env import Utility.Env
import qualified Git.Version import qualified Git.Version

View file

@ -24,9 +24,7 @@ import System.Directory
import System.Environment import System.Environment
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
import System.Posix.User import System.Posix.User
import System.Posix.Files
#endif #endif
import System.FilePath
import Data.Maybe import Data.Maybe
systemwideInstall :: IO Bool systemwideInstall :: IO Bool

View file

@ -67,7 +67,7 @@ uninstaller :: FilePath
uninstaller = "git-annex-uninstall.exe" uninstaller = "git-annex-uninstall.exe"
gitInstallDir :: Exp FilePath gitInstallDir :: Exp FilePath
gitInstallDir = fromString "$PROGRAMFILES\\Git\\cmd" gitInstallDir = fromString "$PROGRAMFILES\\Git\\bin"
startMenuItem :: Exp FilePath startMenuItem :: Exp FilePath
startMenuItem = "$SMPROGRAMS/git-annex.lnk" startMenuItem = "$SMPROGRAMS/git-annex.lnk"

View file

@ -7,8 +7,6 @@ import Utility.Monad
import Utility.SafeCommand import Utility.SafeCommand
import System.IO import System.IO
import System.Cmd
import System.Exit
import System.FilePath import System.FilePath
import System.Directory import System.Directory

View file

@ -14,24 +14,36 @@ buildFlags = filter (not . null)
[ "" [ ""
#ifdef WITH_ASSISTANT #ifdef WITH_ASSISTANT
, "Assistant" , "Assistant"
#else
#warning Building without the assistant.
#endif #endif
#ifdef WITH_WEBAPP #ifdef WITH_WEBAPP
, "Webapp" , "Webapp"
#else
#warning Building without the webapp. You probably need to install Yesod..
#endif #endif
#ifdef WITH_WEBAPP_HTTPS #ifdef WITH_WEBAPP_SECURE
, "Webapp-https" , "Webapp-secure"
#endif #endif
#ifdef WITH_PAIRING #ifdef WITH_PAIRING
, "Pairing" , "Pairing"
#else
#warning Building without local pairing.
#endif #endif
#ifdef WITH_TESTSUITE #ifdef WITH_TESTSUITE
, "Testsuite" , "Testsuite"
#else
#warning Building without the testsuite.
#endif #endif
#ifdef WITH_S3 #ifdef WITH_S3
, "S3" , "S3"
#else
#warning Building without S3.
#endif #endif
#ifdef WITH_WEBDAV #ifdef WITH_WEBDAV
, "WebDAV" , "WebDAV"
#else
#warning Building without WebDAV.
#endif #endif
#ifdef WITH_INOTIFY #ifdef WITH_INOTIFY
, "Inotify" , "Inotify"
@ -47,21 +59,29 @@ buildFlags = filter (not . null)
#endif #endif
#ifdef WITH_XMPP #ifdef WITH_XMPP
, "XMPP" , "XMPP"
#else
#warning Building without XMPP.
#endif #endif
#ifdef WITH_DNS #ifdef WITH_DNS
, "DNS" , "DNS"
#endif #endif
#ifdef WITH_FEED #ifdef WITH_FEED
, "Feeds" , "Feeds"
#else
#warning Building without Feeds.
#endif #endif
#ifdef WITH_QUVI #ifdef WITH_QUVI
, "Quvi" , "Quvi"
#else
#warning Building without quvi.
#endif #endif
#ifdef WITH_TDFA #ifdef WITH_TDFA
, "TDFA" , "TDFA"
#endif #endif
#ifdef WITH_CRYPTOHASH #ifdef WITH_CRYPTOHASH
, "CryptoHash" , "CryptoHash"
#else
#warning Building without CryptoHash.
#endif #endif
#ifdef WITH_EKG #ifdef WITH_EKG
, "EKG" , "EKG"

View file

@ -26,7 +26,6 @@ import qualified Annex
import qualified Git import qualified Git
import qualified Git.AutoCorrect import qualified Git.AutoCorrect
import Annex.Content import Annex.Content
import Annex.Ssh
import Annex.Environment import Annex.Environment
import Command import Command
import Types.Messages import Types.Messages
@ -107,4 +106,3 @@ shutdown nocommit = do
saveState nocommit saveState nocommit
sequence_ =<< M.elems <$> Annex.getState Annex.cleanup sequence_ =<< M.elems <$> Annex.getState Annex.cleanup
liftIO reapZombies -- zombies from long-running git processes liftIO reapZombies -- zombies from long-running git processes
sshCleanup -- ssh connection caching

View file

@ -73,6 +73,8 @@ paramNumRange :: String
paramNumRange = "NUM|RANGE" paramNumRange = "NUM|RANGE"
paramRemote :: String paramRemote :: String
paramRemote = "REMOTE" paramRemote = "REMOTE"
paramField :: String
paramField = "FIELD"
paramGlob :: String paramGlob :: String
paramGlob = "GLOB" paramGlob = "GLOB"
paramName :: String paramName :: String

View file

@ -93,12 +93,15 @@ start file = ifAnnexed file addpresent add
- Lockdown can fail if a file gets deleted, and Nothing will be returned. - Lockdown can fail if a file gets deleted, and Nothing will be returned.
-} -}
lockDown :: FilePath -> Annex (Maybe KeySource) lockDown :: FilePath -> Annex (Maybe KeySource)
lockDown file = ifM crippledFileSystem lockDown = either (\e -> showErr e >> return Nothing) (return . Just) <=< lockDown'
( liftIO $ catchMaybeIO nohardlink
, do lockDown' :: FilePath -> Annex (Either IOException KeySource)
lockDown' file = ifM crippledFileSystem
( liftIO $ tryIO nohardlink
, tryAnnexIO $ do
tmp <- fromRepo gitAnnexTmpMiscDir tmp <- fromRepo gitAnnexTmpMiscDir
createAnnexDirectory tmp createAnnexDirectory tmp
eitherToMaybe <$> tryAnnexIO (go tmp) go tmp
) )
where where
{- In indirect mode, the write bit is removed from the file as part {- In indirect mode, the write bit is removed from the file as part

View file

@ -29,6 +29,7 @@ import Utility.DataUnits
import Utility.FileMode import Utility.FileMode
import Config import Config
import Types.Key import Types.Key
import Types.CleanupActions
import Utility.HumanTime import Utility.HumanTime
import Git.FilePath import Git.FilePath
import Utility.PID import Utility.PID
@ -93,7 +94,7 @@ getIncremental = do
checkschedule Nothing = error "bad --incremental-schedule value" checkschedule Nothing = error "bad --incremental-schedule value"
checkschedule (Just delta) = do checkschedule (Just delta) = do
Annex.addCleanup "" $ do Annex.addCleanup FsckCleanup $ do
v <- getStartTime v <- getStartTime
case v of case v of
Nothing -> noop Nothing -> noop

View file

@ -281,7 +281,7 @@ cachedPresentData = do
case presentData s of case presentData s of
Just v -> return v Just v -> return v
Nothing -> do Nothing -> do
v <- foldKeys <$> lift getKeysPresent v <- foldKeys <$> lift (getKeysPresent InRepository)
put s { presentData = Just v } put s { presentData = Just v }
return v return v

View file

@ -158,7 +158,8 @@ absRepo reference r
| Git.repoIsUrl r = return r | Git.repoIsUrl r = return r
| otherwise = liftIO $ do | otherwise = liftIO $ do
r' <- Git.Construct.fromAbsPath =<< absPath (Git.repoPath r) r' <- Git.Construct.fromAbsPath =<< absPath (Git.repoPath r)
flip Annex.eval Annex.gitRepo =<< Annex.new r' r'' <- safely $ flip Annex.eval Annex.gitRepo =<< Annex.new r'
return (fromMaybe r' r'')
{- Checks if two repos are the same. -} {- Checks if two repos are the same. -}
same :: Git.Repo -> Git.Repo -> Bool same :: Git.Repo -> Git.Repo -> Bool
@ -192,14 +193,9 @@ tryScan :: Git.Repo -> Annex (Maybe Git.Repo)
tryScan r tryScan r
| Git.repoIsSsh r = sshscan | Git.repoIsSsh r = sshscan
| Git.repoIsUrl r = return Nothing | Git.repoIsUrl r = return Nothing
| otherwise = safely $ Git.Config.read r | otherwise = liftIO $ safely $ Git.Config.read r
where where
safely a = do pipedconfig cmd params = liftIO $ safely $
result <- liftIO (try a :: IO (Either SomeException Git.Repo))
case result of
Left _ -> return Nothing
Right r' -> return $ Just r'
pipedconfig cmd params = safely $
withHandle StdoutHandle createProcessSuccess p $ withHandle StdoutHandle createProcessSuccess p $
Git.Config.hRead r Git.Config.hRead r
where where
@ -247,3 +243,10 @@ combineSame = map snd . nubBy sameuuid . map pair
where where
sameuuid (u1, _) (u2, _) = u1 == u2 && u1 /= NoUUID sameuuid (u1, _) (u2, _) = u1 == u2 && u1 /= NoUUID
pair r = (getUncachedUUID r, r) pair r = (getUncachedUUID r, r)
safely :: IO Git.Repo -> IO (Maybe Git.Repo)
safely a = do
result <- try a :: IO (Either SomeException Git.Repo)
case result of
Left _ -> return Nothing
Right r' -> return $ Just r'

View file

@ -12,16 +12,24 @@ import qualified Annex
import Command import Command
import Annex.MetaData import Annex.MetaData
import Logs.MetaData import Logs.MetaData
import Types.MetaData
import qualified Data.Set as S import qualified Data.Set as S
import Data.Time.Clock.POSIX import Data.Time.Clock.POSIX
def :: [Command] def :: [Command]
def = [withOptions [setOption, tagOption, untagOption, jsonOption] $ def = [withOptions metaDataOptions $
command "metadata" paramPaths seek command "metadata" paramPaths seek
SectionMetaData "sets metadata of a file"] SectionMetaData "sets metadata of a file"]
metaDataOptions :: [Option]
metaDataOptions =
[ setOption
, tagOption
, untagOption
, getOption
, jsonOption
] ++ keyOptions
storeModMeta :: ModMeta -> Annex () storeModMeta :: ModMeta -> Annex ()
storeModMeta modmeta = Annex.changeState $ storeModMeta modmeta = Annex.changeState $
\s -> s { Annex.modmeta = modmeta:Annex.modmeta s } \s -> s { Annex.modmeta = modmeta:Annex.modmeta s }
@ -31,6 +39,9 @@ setOption = Option ['s'] ["set"] (ReqArg mkmod "FIELD[+-]=VALUE") "set metadata"
where where
mkmod = either error storeModMeta . parseModMeta mkmod = either error storeModMeta . parseModMeta
getOption :: Option
getOption = fieldOption ['g'] "get" paramField "get single metadata field"
tagOption :: Option tagOption :: Option
tagOption = Option ['t'] ["tag"] (ReqArg mkmod "TAG") "set a tag" tagOption = Option ['t'] ["tag"] (ReqArg mkmod "TAG") "set a tag"
where where
@ -44,19 +55,35 @@ untagOption = Option ['u'] ["untag"] (ReqArg mkmod "TAG") "remove a tag"
seek :: CommandSeek seek :: CommandSeek
seek ps = do seek ps = do
modmeta <- Annex.getState Annex.modmeta modmeta <- Annex.getState Annex.modmeta
getfield <- getOptionField getOption $ \ms ->
return $ either error id . mkMetaField <$> ms
now <- liftIO getPOSIXTime now <- liftIO getPOSIXTime
withFilesInGit (whenAnnexed $ start now modmeta) ps withKeyOptions
(startKeys now getfield modmeta)
(withFilesInGit (whenAnnexed $ start now getfield modmeta))
ps
start :: POSIXTime -> [ModMeta] -> FilePath -> (Key, Backend) -> CommandStart start :: POSIXTime -> Maybe MetaField -> [ModMeta] -> FilePath -> (Key, Backend) -> CommandStart
start now ms file (k, _) = do start now f ms file (k, _) = start' (Just file) now f ms k
showStart "metadata" file
startKeys :: POSIXTime -> Maybe MetaField -> [ModMeta] -> Key -> CommandStart
startKeys = start' Nothing
start' :: AssociatedFile -> POSIXTime -> Maybe MetaField -> [ModMeta] -> Key -> CommandStart
start' afile now Nothing ms k = do
showStart' "metadata" k afile
next $ perform now ms k next $ perform now ms k
start' _ _ (Just f) _ k = do
l <- S.toList . currentMetaDataValues f <$> getCurrentMetaData k
liftIO $ forM_ l $
putStrLn . fromMetaValue
stop
perform :: POSIXTime -> [ModMeta] -> Key -> CommandPerform perform :: POSIXTime -> [ModMeta] -> Key -> CommandPerform
perform _ [] k = next $ cleanup k perform _ [] k = next $ cleanup k
perform now ms k = do perform now ms k = do
oldm <- getCurrentMetaData k oldm <- getCurrentMetaData k
let m = foldl' unionMetaData emptyMetaData $ map (modMeta oldm) ms let m = combineMetaData $ map (modMeta oldm) ms
addMetaData' k m now addMetaData' k m now
next $ cleanup k next $ cleanup k

View file

@ -69,20 +69,29 @@ toStart dest move afile key = do
ishere <- inAnnex key ishere <- inAnnex key
if not ishere || u == Remote.uuid dest if not ishere || u == Remote.uuid dest
then stop -- not here, so nothing to do then stop -- not here, so nothing to do
else do else toStart' dest move afile key
showMoveAction move key afile
next $ toPerform dest move key afile toStart' :: Remote -> Bool -> AssociatedFile -> Key -> CommandStart
toPerform :: Remote -> Bool -> Key -> AssociatedFile -> CommandPerform toStart' dest move afile key = do
toPerform dest move key afile = moveLock move key $ do
-- Checking the remote is expensive, so not done in the start step.
-- In fast mode, location tracking is assumed to be correct,
-- and an explicit check is not done, when copying. When moving,
-- it has to be done, to avoid inaverdent data loss.
fast <- Annex.getState Annex.fast fast <- Annex.getState Annex.fast
let fastcheck = fast && not move && not (Remote.hasKeyCheap dest) if fast && not move && not (Remote.hasKeyCheap dest)
isthere <- if fastcheck then ifM (expectedPresent dest key)
then Right <$> expectedpresent ( stop
else Remote.hasKey dest key , go True (pure $ Right False)
)
else go False (Remote.hasKey dest key)
where
go fastcheck isthere = do
showMoveAction move key afile
next $ toPerform dest move key afile fastcheck =<< isthere
expectedPresent :: Remote -> Key -> Annex Bool
expectedPresent dest key = do
remotes <- Remote.keyPossibilities key
return $ dest `elem` remotes
toPerform :: Remote -> Bool -> Key -> AssociatedFile -> Bool -> Either String Bool -> CommandPerform
toPerform dest move key afile fastcheck isthere = moveLock move key $
case isthere of case isthere of
Left err -> do Left err -> do
showNote err showNote err
@ -100,7 +109,7 @@ toPerform dest move key afile = moveLock move key $ do
warning "This could have failed because --fast is enabled." warning "This could have failed because --fast is enabled."
stop stop
Right True -> do Right True -> do
unlessM expectedpresent $ unlessM (expectedPresent dest key) $
Remote.logStatus dest key InfoPresent Remote.logStatus dest key InfoPresent
finish finish
where where
@ -109,9 +118,6 @@ toPerform dest move key afile = moveLock move key $ do
removeAnnex key removeAnnex key
next $ Command.Drop.cleanupLocal key next $ Command.Drop.cleanupLocal key
| otherwise = next $ return True | otherwise = next $ return True
expectedpresent = do
remotes <- Remote.keyPossibilities key
return $ dest `elem` remotes
{- Moves (or copies) the content of an annexed file from a remote {- Moves (or copies) the content of an annexed file from a remote
- to the current repository. - to the current repository.

View file

@ -376,5 +376,5 @@ syncFile rs f (k, _) = do
put dest = do put dest = do
ok <- commandAction $ do ok <- commandAction $ do
showStart "copy" f showStart "copy" f
next $ Command.Move.toPerform dest False k (Just f) Command.Move.toStart' dest False (Just f) k
return (ok, if ok then Just (Remote.uuid dest) else Nothing) return (ok, if ok then Just (Remote.uuid dest) else Nothing)

View file

@ -53,7 +53,7 @@ finish :: Annex ()
finish = do finish = do
annexdir <- fromRepo gitAnnexDir annexdir <- fromRepo gitAnnexDir
annexobjectdir <- fromRepo gitAnnexObjectDir annexobjectdir <- fromRepo gitAnnexObjectDir
leftovers <- removeUnannexed =<< getKeysPresent leftovers <- removeUnannexed =<< getKeysPresent InAnnex
if null leftovers if null leftovers
then liftIO $ removeDirectoryRecursive annexdir then liftIO $ removeDirectoryRecursive annexdir
else error $ unlines else error $ unlines

View file

@ -10,7 +10,6 @@
module Command.Unused where module Command.Unused where
import qualified Data.Set as S import qualified Data.Set as S
import qualified Data.ByteString.Lazy as L
import Data.BloomFilter import Data.BloomFilter
import Data.BloomFilter.Easy import Data.BloomFilter.Easy
import Data.BloomFilter.Hash import Data.BloomFilter.Hash
@ -71,7 +70,9 @@ checkUnused = chain 0
return [] return []
findunused False = do findunused False = do
showAction "checking for unused data" showAction "checking for unused data"
excludeReferenced =<< getKeysPresent -- InAnnex, not InRepository because if a direct mode
-- file exists, it is obviously not unused.
excludeReferenced =<< getKeysPresent InAnnex
chain _ [] = next $ return True chain _ [] = next $ return True
chain v (a:as) = do chain v (a:as) = do
v' <- a v v' <- a v
@ -294,7 +295,7 @@ withKeysReferencedInGitRef a ref = do
liftIO $ void clean liftIO $ void clean
where where
tKey True = fmap fst <$$> Backend.lookupFile . getTopFilePath . DiffTree.file tKey True = fmap fst <$$> Backend.lookupFile . getTopFilePath . DiffTree.file
tKey False = fileKey . takeFileName . encodeW8 . L.unpack <$$> tKey False = fileKey . takeFileName . decodeBS <$$>
catFile ref . getTopFilePath . DiffTree.file catFile ref . getTopFilePath . DiffTree.file
{- Looks in the specified directory for bad/tmp keys, and returns a list {- Looks in the specified directory for bad/tmp keys, and returns a list

View file

@ -1,6 +1,6 @@
{- git-annex command {- git-annex command
- -
- Copyright 2012 Joey Hess <joey@kitenet.net> - Copyright 2012-2014 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -60,7 +60,8 @@ vicfg curcfg f = do
data Cfg = Cfg data Cfg = Cfg
{ cfgTrustMap :: TrustMap { cfgTrustMap :: TrustMap
, cfgGroupMap :: M.Map UUID (S.Set Group) , cfgGroupMap :: M.Map UUID (S.Set Group)
, cfgPreferredContentMap :: M.Map UUID String , cfgPreferredContentMap :: M.Map UUID PreferredContentExpression
, cfgGroupPreferredContentMap :: M.Map Group PreferredContentExpression
, cfgScheduleMap :: M.Map UUID [ScheduledActivity] , cfgScheduleMap :: M.Map UUID [ScheduledActivity]
} }
@ -69,25 +70,40 @@ getCfg = Cfg
<$> trustMapRaw -- without local trust overrides <$> trustMapRaw -- without local trust overrides
<*> (groupsByUUID <$> groupMap) <*> (groupsByUUID <$> groupMap)
<*> preferredContentMapRaw <*> preferredContentMapRaw
<*> groupPreferredContentMapRaw
<*> scheduleMap <*> scheduleMap
setCfg :: Cfg -> Cfg -> Annex () setCfg :: Cfg -> Cfg -> Annex ()
setCfg curcfg newcfg = do setCfg curcfg newcfg = do
let (trustchanges, groupchanges, preferredcontentchanges, schedulechanges) = diffCfg curcfg newcfg let diff = diffCfg curcfg newcfg
mapM_ (uncurry trustSet) $ M.toList trustchanges mapM_ (uncurry trustSet) $ M.toList $ cfgTrustMap diff
mapM_ (uncurry groupSet) $ M.toList groupchanges mapM_ (uncurry groupSet) $ M.toList $ cfgGroupMap diff
mapM_ (uncurry preferredContentSet) $ M.toList preferredcontentchanges mapM_ (uncurry preferredContentSet) $ M.toList $ cfgPreferredContentMap diff
mapM_ (uncurry scheduleSet) $ M.toList schedulechanges mapM_ (uncurry groupPreferredContentSet) $ M.toList $ cfgGroupPreferredContentMap diff
mapM_ (uncurry scheduleSet) $ M.toList $ cfgScheduleMap diff
diffCfg :: Cfg -> Cfg -> (TrustMap, M.Map UUID (S.Set Group), M.Map UUID String, M.Map UUID [ScheduledActivity]) diffCfg :: Cfg -> Cfg -> Cfg
diffCfg curcfg newcfg = (diff cfgTrustMap, diff cfgGroupMap, diff cfgPreferredContentMap, diff cfgScheduleMap) diffCfg curcfg newcfg = Cfg
{ cfgTrustMap = diff cfgTrustMap
, cfgGroupMap = diff cfgGroupMap
, cfgPreferredContentMap = diff cfgPreferredContentMap
, cfgGroupPreferredContentMap = diff cfgGroupPreferredContentMap
, cfgScheduleMap = diff cfgScheduleMap
}
where where
diff f = M.differenceWith (\x y -> if x == y then Nothing else Just x) diff f = M.differenceWith (\x y -> if x == y then Nothing else Just x)
(f newcfg) (f curcfg) (f newcfg) (f curcfg)
genCfg :: Cfg -> M.Map UUID String -> String genCfg :: Cfg -> M.Map UUID String -> String
genCfg cfg descs = unlines $ concat genCfg cfg descs = unlines $ intercalate [""]
[intro, trust, groups, preferredcontent, schedule] [ intro
, trust
, groups
, preferredcontent
, grouppreferredcontent
, standardgroups
, schedule
]
where where
intro = intro =
[ com "git-annex configuration" [ com "git-annex configuration"
@ -95,22 +111,20 @@ genCfg cfg descs = unlines $ concat
, com "Changes saved to this file will be recorded in the git-annex branch." , com "Changes saved to this file will be recorded in the git-annex branch."
, com "" , com ""
, com "Lines in this file have the format:" , com "Lines in this file have the format:"
, com " setting uuid = value" , com " setting field = value"
] ]
trust = settings cfgTrustMap trust = settings cfg descs cfgTrustMap
[ "" [ com "Repository trust configuration"
, com "Repository trust configuration"
, com "(Valid trust levels: " ++ trustlevels ++ ")" , com "(Valid trust levels: " ++ trustlevels ++ ")"
] ]
(\(t, u) -> line "trust" u $ showTrustLevel t) (\(t, u) -> line "trust" u $ showTrustLevel t)
(\u -> lcom $ line "trust" u $ showTrustLevel SemiTrusted) (\u -> lcom $ line "trust" u $ showTrustLevel SemiTrusted)
where where
trustlevels = unwords $ map showTrustLevel [Trusted .. DeadTrusted] trustlevels = unwords $ map showTrustLevel [Trusted .. DeadTrusted]
groups = settings cfgGroupMap groups = settings cfg descs cfgGroupMap
[ "" [ com "Repository groups"
, com "Repository groups"
, com $ "(Standard groups: " ++ grouplist ++ ")" , com $ "(Standard groups: " ++ grouplist ++ ")"
, com "(Separate group names with spaces)" , com "(Separate group names with spaces)"
] ]
@ -119,33 +133,60 @@ genCfg cfg descs = unlines $ concat
where where
grouplist = unwords $ map fromStandardGroup [minBound..] grouplist = unwords $ map fromStandardGroup [minBound..]
preferredcontent = settings cfgPreferredContentMap preferredcontent = settings cfg descs cfgPreferredContentMap
[ "" [ com "Repository preferred contents" ]
, com "Repository preferred contents" (\(s, u) -> line "wanted" u s)
] (\u -> line "wanted" u "standard")
(\(s, u) -> line "content" u s)
(\u -> line "content" u "")
schedule = settings cfgScheduleMap grouppreferredcontent = settings' cfg allgroups cfgGroupPreferredContentMap
[ "" [ com "Group preferred contents"
, com "Scheduled activities" , com "(Used by repositories with \"groupwanted\" in their preferred contents)"
]
(\(s, g) -> gline g s)
(\g -> gline g "standard")
where
gline g value = [ unwords ["groupwanted", g, "=", value] ]
allgroups = S.unions $ stdgroups : M.elems (cfgGroupMap cfg)
stdgroups = S.fromList $ map fromStandardGroup [minBound..maxBound]
standardgroups =
[ com "Standard preferred contents"
, com "(Used by wanted or groupwanted expressions containing \"standard\")"
, com "(For reference only; built-in and cannot be changed!)"
]
++ map gline [minBound..maxBound]
where
gline g = com $ unwords
[ "standard"
, fromStandardGroup g, "=", standardPreferredContent g
]
schedule = settings cfg descs cfgScheduleMap
[ com "Scheduled activities"
, com "(Separate multiple activities with \"; \")" , com "(Separate multiple activities with \"; \")"
] ]
(\(l, u) -> line "schedule" u $ fromScheduledActivities l) (\(l, u) -> line "schedule" u $ fromScheduledActivities l)
(\u -> line "schedule" u "") (\u -> line "schedule" u "")
settings field desc showvals showdefaults = concat
[ desc
, concatMap showvals $ sort $ map swap $ M.toList $ field cfg
, concatMap (lcom . showdefaults) $ missing field
]
line setting u value = line setting u value =
[ com $ "(for " ++ fromMaybe "" (M.lookup u descs) ++ ")" [ com $ "(for " ++ fromMaybe "" (M.lookup u descs) ++ ")"
, unwords [setting, fromUUID u, "=", value] , unwords [setting, fromUUID u, "=", value]
] ]
lcom = map (\l -> if "#" `isPrefixOf` l then l else '#' : l)
missing field = S.toList $ M.keysSet descs `S.difference` M.keysSet (field cfg) settings :: Ord v => Cfg -> M.Map UUID String -> (Cfg -> M.Map UUID v) -> [String] -> ((v, UUID) -> [String]) -> (UUID -> [String]) -> [String]
settings cfg descs = settings' cfg (M.keysSet descs)
settings' :: (Ord v, Ord f) => Cfg -> S.Set f -> (Cfg -> M.Map f v) -> [String] -> ((v, f) -> [String]) -> (f -> [String]) -> [String]
settings' cfg s field desc showvals showdefaults = concat
[ desc
, concatMap showvals $ sort $ map swap $ M.toList $ field cfg
, concatMap (lcom . showdefaults) missing
]
where
missing = S.toList $ s `S.difference` M.keysSet (field cfg)
lcom :: [String] -> [String]
lcom = map (\l -> if "#" `isPrefixOf` l then l else '#' : l)
{- If there's a parse error, returns a new version of the file, {- If there's a parse error, returns a new version of the file,
- with the problem lines noted. -} - with the problem lines noted. -}
@ -163,16 +204,16 @@ parseCfg curcfg = go [] curcfg . lines
parse l cfg parse l cfg
| null l = Right cfg | null l = Right cfg
| "#" `isPrefixOf` l = Right cfg | "#" `isPrefixOf` l = Right cfg
| null setting || null u = Left "missing repository uuid" | null setting || null f = Left "missing field"
| otherwise = handle cfg (toUUID u) setting value' | otherwise = handle cfg f setting value'
where where
(setting, rest) = separate isSpace l (setting, rest) = separate isSpace l
(r, value) = separate (== '=') rest (r, value) = separate (== '=') rest
value' = trimspace value value' = trimspace value
u = reverse $ trimspace $ reverse $ trimspace r f = reverse $ trimspace $ reverse $ trimspace r
trimspace = dropWhile isSpace trimspace = dropWhile isSpace
handle cfg u setting value handle cfg f setting value
| setting == "trust" = case readTrustLevel value of | setting == "trust" = case readTrustLevel value of
Nothing -> badval "trust value" value Nothing -> badval "trust value" value
Just t -> Just t ->
@ -181,18 +222,26 @@ parseCfg curcfg = go [] curcfg . lines
| setting == "group" = | setting == "group" =
let m = M.insert u (S.fromList $ words value) (cfgGroupMap cfg) let m = M.insert u (S.fromList $ words value) (cfgGroupMap cfg)
in Right $ cfg { cfgGroupMap = m } in Right $ cfg { cfgGroupMap = m }
| setting == "content" = | setting == "wanted" =
case checkPreferredContentExpression value of case checkPreferredContentExpression value of
Just e -> Left e Just e -> Left e
Nothing -> Nothing ->
let m = M.insert u value (cfgPreferredContentMap cfg) let m = M.insert u value (cfgPreferredContentMap cfg)
in Right $ cfg { cfgPreferredContentMap = m } in Right $ cfg { cfgPreferredContentMap = m }
| setting == "groupwanted" =
case checkPreferredContentExpression value of
Just e -> Left e
Nothing ->
let m = M.insert f value (cfgGroupPreferredContentMap cfg)
in Right $ cfg { cfgGroupPreferredContentMap = m }
| setting == "schedule" = case parseScheduledActivities value of | setting == "schedule" = case parseScheduledActivities value of
Left e -> Left e Left e -> Left e
Right l -> Right l ->
let m = M.insert u l (cfgScheduleMap cfg) let m = M.insert u l (cfgScheduleMap cfg)
in Right $ cfg { cfgScheduleMap = m } in Right $ cfg { cfgScheduleMap = m }
| otherwise = badval "setting" setting | otherwise = badval "setting" setting
where
u = toUUID f
showerr (Just msg, l) = [parseerr ++ msg, l] showerr (Just msg, l) = [parseerr ++ msg, l]
showerr (Nothing, l) showerr (Nothing, l)
@ -203,11 +252,12 @@ parseCfg curcfg = go [] curcfg . lines
badval desc val = Left $ "unknown " ++ desc ++ " \"" ++ val ++ "\"" badval desc val = Left $ "unknown " ++ desc ++ " \"" ++ val ++ "\""
badheader = badheader =
[ com "There was a problem parsing your input." [ com "** There was a problem parsing your input!"
, com "Search for \"Parse error\" to find the bad lines." , com "** Search for \"Parse error\" to find the bad lines."
, com "Either fix the bad lines, or delete them (to discard your changes)." , com "** Either fix the bad lines, or delete them (to discard your changes)."
, ""
] ]
parseerr = com "Parse error in next line: " parseerr = com "** Parse error in next line: "
com :: String -> String com :: String -> String
com s = "# " ++ s com s = "# " ++ s

View file

@ -108,6 +108,6 @@ catTree h treeref = go <$> catObjectDetails h treeref
dropsha = L.drop 21 dropsha = L.drop 21
parsemodefile b = parsemodefile b =
let (modestr, file) = separate (== ' ') (encodeW8 $ L.unpack b) let (modestr, file) = separate (== ' ') (decodeBS b)
in (file, readmode modestr) in (file, readmode modestr)
readmode = fst . fromMaybe (0, undefined) . headMaybe . readOct readmode = fst . fromMaybe (0, undefined) . headMaybe . readOct

View file

@ -23,10 +23,17 @@ import Utility.Batch
import qualified Git.Version import qualified Git.Version
import qualified Data.Set as S import qualified Data.Set as S
import System.Process (std_out, std_err)
import Control.Concurrent.Async
type MissingObjects = S.Set Sha type MissingObjects = S.Set Sha
data FsckResults = FsckFoundMissing MissingObjects | FsckFailed data FsckResults
= FsckFoundMissing
{ missingObjects :: MissingObjects
, missingObjectsTruncated :: Bool
}
| FsckFailed
deriving (Show) deriving (Show)
{- Runs fsck to find some of the broken objects in the repository. {- Runs fsck to find some of the broken objects in the repository.
@ -46,20 +53,32 @@ findBroken batchmode r = do
(command', params') <- if batchmode (command', params') <- if batchmode
then toBatchCommand (command, params) then toBatchCommand (command, params)
else return (command, params) else return (command, params)
(output, fsckok) <- processTranscript command' (toCommand params') Nothing
let objs = findShas supportsNoDangling output p@(_, _, _, pid) <- createProcess $
badobjs <- findMissing objs r (proc command' (toCommand params'))
{ std_out = CreatePipe
, std_err = CreatePipe
}
(bad1, bad2) <- concurrently
(readMissingObjs maxobjs r supportsNoDangling (stdoutHandle p))
(readMissingObjs maxobjs r supportsNoDangling (stderrHandle p))
fsckok <- checkSuccessProcess pid
let truncated = S.size bad1 == maxobjs || S.size bad1 == maxobjs
let badobjs = S.union bad1 bad2
if S.null badobjs && not fsckok if S.null badobjs && not fsckok
then return FsckFailed then return FsckFailed
else return $ FsckFoundMissing badobjs else return $ FsckFoundMissing badobjs truncated
where
maxobjs = 10000
foundBroken :: FsckResults -> Bool foundBroken :: FsckResults -> Bool
foundBroken FsckFailed = True foundBroken FsckFailed = True
foundBroken (FsckFoundMissing s) = not (S.null s) foundBroken (FsckFoundMissing s _) = not (S.null s)
knownMissing :: FsckResults -> MissingObjects knownMissing :: FsckResults -> MissingObjects
knownMissing FsckFailed = S.empty knownMissing FsckFailed = S.empty
knownMissing (FsckFoundMissing s) = s knownMissing (FsckFoundMissing s _) = s
{- Finds objects that are missing from the git repsitory, or are corrupt. {- Finds objects that are missing from the git repsitory, or are corrupt.
- -
@ -69,6 +88,11 @@ knownMissing (FsckFoundMissing s) = s
findMissing :: [Sha] -> Repo -> IO MissingObjects findMissing :: [Sha] -> Repo -> IO MissingObjects
findMissing objs r = S.fromList <$> filterM (`isMissing` r) objs findMissing objs r = S.fromList <$> filterM (`isMissing` r) objs
readMissingObjs :: Int -> Repo -> Bool -> Handle -> IO MissingObjects
readMissingObjs maxobjs r supportsNoDangling h = do
objs <- take maxobjs . findShas supportsNoDangling <$> hGetContents h
findMissing objs r
isMissing :: Sha -> Repo -> IO Bool isMissing :: Sha -> Repo -> IO Bool
isMissing s r = either (const True) (const False) <$> tryIO dump isMissing s r = either (const True) (const False) <$> tryIO dump
where where

View file

@ -1,7 +1,6 @@
{- git repository recovery {- git repository recovery
import qualified Data.Set as S
- -
- Copyright 2013 Joey Hess <joey@kitenet.net> - Copyright 2013-2014 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -45,35 +44,18 @@ import qualified Data.ByteString.Lazy as L
import Data.Tuple.Utils import Data.Tuple.Utils
{- Given a set of bad objects found by git fsck, which may not {- Given a set of bad objects found by git fsck, which may not
- be complete, finds and removes all corrupt objects, - be complete, finds and removes all corrupt objects. -}
- and returns missing objects. cleanCorruptObjects :: FsckResults -> Repo -> IO ()
-}
cleanCorruptObjects :: FsckResults -> Repo -> IO FsckResults
cleanCorruptObjects fsckresults r = do cleanCorruptObjects fsckresults r = do
void $ explodePacks r void $ explodePacks r
objs <- listLooseObjectShas r mapM_ removeLoose (S.toList $ knownMissing fsckresults)
mapM_ (tryIO . allowRead . looseObjectFile r) objs mapM_ removeBad =<< listLooseObjectShas r
bad <- findMissing objs r where
void $ removeLoose r $ S.union bad (knownMissing fsckresults) removeLoose s = nukeFile (looseObjectFile r s)
-- Rather than returning the loose objects that were removed, re-run removeBad s = do
-- fsck. Other missing objects may have been in the packs, void $ tryIO $ allowRead $ looseObjectFile r s
-- and this way fsck will find them. whenM (isMissing s r) $
findBroken False r removeLoose s
removeLoose :: Repo -> MissingObjects -> IO Bool
removeLoose r s = do
fs <- filterM doesFileExist (map (looseObjectFile r) (S.toList s))
let count = length fs
if count > 0
then do
putStrLn $ unwords
[ "Removing"
, show count
, "corrupt loose objects."
]
mapM_ nukeFile fs
return True
else return False
{- Explodes all pack files, and deletes them. {- Explodes all pack files, and deletes them.
- -
@ -132,7 +114,9 @@ retrieveMissingObjects missing referencerepo r
void $ copyObjects tmpr r void $ copyObjects tmpr r
case stillmissing of case stillmissing of
FsckFailed -> return $ FsckFailed FsckFailed -> return $ FsckFailed
FsckFoundMissing s -> FsckFoundMissing <$> findMissing (S.toList s) r FsckFoundMissing s t -> FsckFoundMissing
<$> findMissing (S.toList s) r
<*> pure t
, return stillmissing , return stillmissing
) )
pullremotes tmpr (rmt:rmts) fetchrefs ms pullremotes tmpr (rmt:rmts) fetchrefs ms
@ -145,9 +129,9 @@ retrieveMissingObjects missing referencerepo r
void $ copyObjects tmpr r void $ copyObjects tmpr r
case ms of case ms of
FsckFailed -> pullremotes tmpr rmts fetchrefs ms FsckFailed -> pullremotes tmpr rmts fetchrefs ms
FsckFoundMissing s -> do FsckFoundMissing s t -> do
stillmissing <- findMissing (S.toList s) r stillmissing <- findMissing (S.toList s) r
pullremotes tmpr rmts fetchrefs (FsckFoundMissing stillmissing) pullremotes tmpr rmts fetchrefs (FsckFoundMissing stillmissing t)
, pullremotes tmpr rmts fetchrefs ms , pullremotes tmpr rmts fetchrefs ms
) )
fetchfrom fetchurl ps = runBool $ fetchfrom fetchurl ps = runBool $
@ -295,7 +279,7 @@ findUncorruptedCommit missing goodcommits branch r = do
then return (Just c, gcs') then return (Just c, gcs')
else findfirst gcs' cs else findfirst gcs' cs
{- Verifies tha none of the missing objects in the set are used by {- Verifies that none of the missing objects in the set are used by
- the commit. Also adds to a set of commit shas that have been verified to - the commit. Also adds to a set of commit shas that have been verified to
- be good, which can be passed into subsequent calls to avoid - be good, which can be passed into subsequent calls to avoid
- redundant work when eg, chasing down branches to find the first - redundant work when eg, chasing down branches to find the first
@ -465,10 +449,11 @@ runRepairOf fsckresult removablebranch forced referencerepo g = do
runRepair' :: (Ref -> Bool) -> FsckResults -> Bool -> Maybe FilePath -> Repo -> IO (Bool, [Branch]) runRepair' :: (Ref -> Bool) -> FsckResults -> Bool -> Maybe FilePath -> Repo -> IO (Bool, [Branch])
runRepair' removablebranch fsckresult forced referencerepo g = do runRepair' removablebranch fsckresult forced referencerepo g = do
missing <- cleanCorruptObjects fsckresult g cleanCorruptObjects fsckresult g
missing <- findBroken False g
stillmissing <- retrieveMissingObjects missing referencerepo g stillmissing <- retrieveMissingObjects missing referencerepo g
case stillmissing of case stillmissing of
FsckFoundMissing s FsckFoundMissing s t
| S.null s -> if repoIsLocalBare g | S.null s -> if repoIsLocalBare g
then successfulfinish [] then successfulfinish []
else ifM (checkIndex g) else ifM (checkIndex g)
@ -481,7 +466,7 @@ runRepair' removablebranch fsckresult forced referencerepo g = do
) )
| otherwise -> if forced | otherwise -> if forced
then ifM (checkIndex g) then ifM (checkIndex g)
( continuerepairs s ( forcerepair s t
, corruptedindex , corruptedindex
) )
else do else do
@ -493,17 +478,17 @@ runRepair' removablebranch fsckresult forced referencerepo g = do
FsckFailed FsckFailed
| forced -> ifM (pure (repoIsLocalBare g) <||> checkIndex g) | forced -> ifM (pure (repoIsLocalBare g) <||> checkIndex g)
( do ( do
missing' <- cleanCorruptObjects FsckFailed g cleanCorruptObjects FsckFailed g
case missing' of stillmissing' <- findBroken False g
case stillmissing' of
FsckFailed -> return (False, []) FsckFailed -> return (False, [])
FsckFoundMissing stillmissing' -> FsckFoundMissing s t -> forcerepair s t
continuerepairs stillmissing'
, corruptedindex , corruptedindex
) )
| otherwise -> unsuccessfulfinish | otherwise -> unsuccessfulfinish
where where
continuerepairs stillmissing = do repairbranches missing = do
(removedbranches, goodcommits) <- removeBadBranches removablebranch stillmissing emptyGoodCommits g (removedbranches, goodcommits) <- removeBadBranches removablebranch missing emptyGoodCommits g
let remotebranches = filter isTrackingBranch removedbranches let remotebranches = filter isTrackingBranch removedbranches
unless (null remotebranches) $ unless (null remotebranches) $
putStrLn $ unwords putStrLn $ unwords
@ -511,32 +496,43 @@ runRepair' removablebranch fsckresult forced referencerepo g = do
, show (length remotebranches) , show (length remotebranches)
, "remote tracking branches that referred to missing objects." , "remote tracking branches that referred to missing objects."
] ]
(resetbranches, deletedbranches, _) <- resetLocalBranches stillmissing goodcommits g (resetbranches, deletedbranches, _) <- resetLocalBranches missing goodcommits g
displayList (map fromRef resetbranches) displayList (map fromRef resetbranches)
"Reset these local branches to old versions before the missing objects were committed:" "Reset these local branches to old versions before the missing objects were committed:"
displayList (map fromRef deletedbranches) displayList (map fromRef deletedbranches)
"Deleted these local branches, which could not be recovered due to missing objects:" "Deleted these local branches, which could not be recovered due to missing objects:"
return (resetbranches ++ deletedbranches)
forcerepair missing fscktruncated = do
modifiedbranches <- repairbranches missing
deindexedfiles <- rewriteIndex g deindexedfiles <- rewriteIndex g
displayList deindexedfiles displayList deindexedfiles
"Removed these missing files from the index. You should look at what files are present in your working tree and git add them back to the index when appropriate." "Removed these missing files from the index. You should look at what files are present in your working tree and git add them back to the index when appropriate."
let modifiedbranches = resetbranches ++ deletedbranches
if null resetbranches && null deletedbranches -- When the fsck results were truncated, try
then successfulfinish modifiedbranches -- fscking again, and as long as different
else do -- missing objects are found, continue
unless (repoIsLocalBare g) $ do -- the repair process.
mcurr <- Branch.currentUnsafe g if fscktruncated
case mcurr of then do
Nothing -> return () fsckresult' <- findBroken False g
Just curr -> when (any (== curr) modifiedbranches) $ do case fsckresult' of
FsckFailed -> do
putStrLn "git fsck is failing"
return (False, modifiedbranches)
FsckFoundMissing s _
| S.null s -> successfulfinish modifiedbranches
| S.null (s `S.difference` missing) -> do
putStrLn $ unwords putStrLn $ unwords
[ "You currently have" [ show (S.size s)
, fromRef curr , "missing objects could not be recovered!"
, "checked out. You may have staged changes in the index that can be committed to recover the lost state of this branch!"
] ]
putStrLn "Successfully recovered repository!" return (False, modifiedbranches)
putStrLn "Please carefully check that the changes mentioned above are ok.." | otherwise -> do
return (True, modifiedbranches) (ok, modifiedbranches') <- runRepairOf fsckresult' removablebranch forced referencerepo g
return (ok, modifiedbranches++modifiedbranches')
else successfulfinish modifiedbranches
corruptedindex = do corruptedindex = do
nukeFile (indexFile g) nukeFile (indexFile g)
-- The corrupted index can prevent fsck from finding other -- The corrupted index can prevent fsck from finding other
@ -546,12 +542,28 @@ runRepair' removablebranch fsckresult forced referencerepo g = do
putStrLn "Removed the corrupted index file. You should look at what files are present in your working tree and git add them back to the index when appropriate." putStrLn "Removed the corrupted index file. You should look at what files are present in your working tree and git add them back to the index when appropriate."
return result return result
successfulfinish modifiedbranches = do successfulfinish modifiedbranches
mapM_ putStrLn | null modifiedbranches = do
[ "Successfully recovered repository!" mapM_ putStrLn
, "You should run \"git fsck\" to make sure, but it looks like everything was recovered ok." [ "Successfully recovered repository!"
] , "You should run \"git fsck\" to make sure, but it looks like everything was recovered ok."
return (True, modifiedbranches) ]
return (True, modifiedbranches)
| otherwise = do
unless (repoIsLocalBare g) $ do
mcurr <- Branch.currentUnsafe g
case mcurr of
Nothing -> return ()
Just curr -> when (any (== curr) modifiedbranches) $ do
putStrLn $ unwords
[ "You currently have"
, fromRef curr
, "checked out. You may have staged changes in the index that can be committed to recover the lost state of this branch!"
]
putStrLn "Successfully recovered repository!"
putStrLn "Please carefully check that the changes mentioned above are ok.."
return (True, modifiedbranches)
unsuccessfulfinish = do unsuccessfulfinish = do
if repoIsLocalBare g if repoIsLocalBare g
then do then do

View file

@ -94,18 +94,16 @@ matchGlobFile glob = go
{- Adds a limit to skip files not believed to be present {- Adds a limit to skip files not believed to be present
- in a specfied repository. Optionally on a prior date. -} - in a specfied repository. Optionally on a prior date. -}
addIn :: String -> Annex () addIn :: String -> Annex ()
addIn = addLimit . limitIn addIn s = addLimit =<< mk
limitIn :: MkLimit
limitIn s = Right $ \notpresent -> checkKey $ \key ->
if name == "."
then if null date
then inhere notpresent key
else inuuid notpresent key =<< getUUID
else inuuid notpresent key =<< Remote.nameToUUID name
where where
(name, date) = separate (== '@') s (name, date) = separate (== '@') s
inuuid notpresent key u mk
| name == "." = if null date
then use inhere
else use . inuuid =<< getUUID
| otherwise = use . inuuid =<< Remote.nameToUUID name
use a = return $ Right $ \notpresent -> checkKey (a notpresent)
inuuid u notpresent key
| null date = do | null date = do
us <- Remote.keyLocations key us <- Remote.keyLocations key
return $ u `elem` us && u `S.notMember` notpresent return $ u `elem` us && u `S.notMember` notpresent
@ -122,7 +120,10 @@ limitIn s = Right $ \notpresent -> checkKey $ \key ->
{- Limit to content that is currently present on a uuid. -} {- Limit to content that is currently present on a uuid. -}
limitPresent :: Maybe UUID -> MkLimit limitPresent :: Maybe UUID -> MkLimit
limitPresent u _ = Right $ const $ checkKey $ \key -> do limitPresent u _ = Right $ matchPresent u
matchPresent :: Maybe UUID -> MatchFiles
matchPresent u _ = checkKey $ \key -> do
hereu <- getUUID hereu <- getUUID
if u == Just hereu || isNothing u if u == Just hereu || isNothing u
then inAnnex key then inAnnex key

12
Logs.hs
View file

@ -24,7 +24,7 @@ getLogVariety :: FilePath -> Maybe LogVariety
getLogVariety f getLogVariety f
| f `elem` topLevelUUIDBasedLogs = Just UUIDBasedLog | f `elem` topLevelUUIDBasedLogs = Just UUIDBasedLog
| isRemoteStateLog f = Just NewUUIDBasedLog | isRemoteStateLog f = Just NewUUIDBasedLog
| isMetaDataLog f || f == numcopiesLog = Just OtherLog | isMetaDataLog f || f `elem` otherLogs = Just OtherLog
| otherwise = PresenceLog <$> firstJust (presenceLogs f) | otherwise = PresenceLog <$> firstJust (presenceLogs f)
{- All the uuid-based logs stored in the top of the git-annex branch. -} {- All the uuid-based logs stored in the top of the git-annex branch. -}
@ -45,6 +45,13 @@ presenceLogs f =
, locationLogFileKey f , locationLogFileKey f
] ]
{- Logs that are neither UUID based nor presence logs. -}
otherLogs :: [FilePath]
otherLogs =
[ numcopiesLog
, groupPreferredContentLog
]
uuidLog :: FilePath uuidLog :: FilePath
uuidLog = "uuid.log" uuidLog = "uuid.log"
@ -63,6 +70,9 @@ groupLog = "group.log"
preferredContentLog :: FilePath preferredContentLog :: FilePath
preferredContentLog = "preferred-content.log" preferredContentLog = "preferred-content.log"
groupPreferredContentLog :: FilePath
groupPreferredContentLog = "group-preferred-content.log"
scheduleLog :: FilePath scheduleLog :: FilePath
scheduleLog = "schedule.log" scheduleLog = "schedule.log"

View file

@ -23,25 +23,31 @@ writeFsckResults u fsckresults = do
logfile <- fromRepo $ gitAnnexFsckResultsLog u logfile <- fromRepo $ gitAnnexFsckResultsLog u
liftIO $ liftIO $
case fsckresults of case fsckresults of
FsckFailed -> store S.empty logfile FsckFailed -> store S.empty False logfile
FsckFoundMissing s FsckFoundMissing s t
| S.null s -> nukeFile logfile | S.null s -> nukeFile logfile
| otherwise -> store s logfile | otherwise -> store s t logfile
where where
store s logfile = do store s t logfile = do
createDirectoryIfMissing True (parentDir logfile) createDirectoryIfMissing True (parentDir logfile)
liftIO $ viaTmp writeFile logfile $ serialize s liftIO $ viaTmp writeFile logfile $ serialize s t
serialize = unlines . map fromRef . S.toList serialize s t =
let ls = map fromRef (S.toList s)
in if t
then unlines ("truncated":ls)
else unlines ls
readFsckResults :: UUID -> Annex FsckResults readFsckResults :: UUID -> Annex FsckResults
readFsckResults u = do readFsckResults u = do
logfile <- fromRepo $ gitAnnexFsckResultsLog u logfile <- fromRepo $ gitAnnexFsckResultsLog u
liftIO $ catchDefaultIO (FsckFoundMissing S.empty) $ liftIO $ catchDefaultIO (FsckFoundMissing S.empty False) $
deserialize <$> readFile logfile deserialize . lines <$> readFile logfile
where where
deserialize l = deserialize ("truncated":ls) = deserialize' ls True
let s = S.fromList $ map Ref $ lines l deserialize ls = deserialize' ls False
in if S.null s then FsckFailed else FsckFoundMissing s deserialize' ls t =
let s = S.fromList $ map Ref ls
in if S.null s then FsckFailed else FsckFoundMissing s t
clearFsckResults :: UUID -> Annex () clearFsckResults :: UUID -> Annex ()
clearFsckResults = liftIO . nukeFile <=< fromRepo . gitAnnexFsckResultsLog clearFsckResults = liftIO . nukeFile <=< fromRepo . gitAnnexFsckResultsLog

81
Logs/MapLog.hs Normal file
View file

@ -0,0 +1,81 @@
{- git-annex Map log
-
- This is used to store a Map, in a way that can be union merged.
-
- A line of the log will look like: "timestamp field value"
-
- Copyright 2014 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Logs.MapLog where
import qualified Data.Map as M
import Data.Time.Clock.POSIX
import Data.Time
import System.Locale
import Common
data TimeStamp = Unknown | Date POSIXTime
deriving (Eq, Ord, Show)
data LogEntry v = LogEntry
{ changed :: TimeStamp
, value :: v
} deriving (Eq, Show)
type MapLog f v = M.Map f (LogEntry v)
showMapLog :: (f -> String) -> (v -> String) -> MapLog f v -> String
showMapLog fieldshower valueshower = unlines . map showpair . M.toList
where
showpair (f, LogEntry (Date p) v) =
unwords [show p, fieldshower f, valueshower v]
showpair (f, LogEntry Unknown v) =
unwords ["0", fieldshower f, valueshower v]
parseMapLog :: Ord f => (String -> Maybe f) -> (String -> Maybe v) -> String -> MapLog f v
parseMapLog fieldparser valueparser = M.fromListWith best . mapMaybe parse . lines
where
parse line = do
let (ts, rest) = splitword line
(sf, sv) = splitword rest
date <- Date . utcTimeToPOSIXSeconds <$> parseTime defaultTimeLocale "%s%Qs" ts
f <- fieldparser sf
v <- valueparser sv
Just (f, LogEntry date v)
splitword = separate (== ' ')
changeMapLog :: Ord f => POSIXTime -> f -> v -> MapLog f v -> MapLog f v
changeMapLog t f v = M.insert f $ LogEntry (Date t) v
{- Only add an LogEntry if it's newer (or at least as new as) than any
- existing LogEntry for a field. -}
addMapLog :: Ord f => f -> LogEntry v -> MapLog f v -> MapLog f v
addMapLog = M.insertWith' best
{- Converts a MapLog into a simple Map without the timestamp information.
- This is a one-way trip, but useful for code that never needs to change
- the log. -}
simpleMap :: MapLog f v -> M.Map f v
simpleMap = M.map value
best :: LogEntry v -> LogEntry v -> LogEntry v
best new old
| changed old > changed new = old
| otherwise = new
-- Unknown is oldest.
prop_TimeStamp_sane :: Bool
prop_TimeStamp_sane = Unknown < Date 1
prop_addMapLog_sane :: Bool
prop_addMapLog_sane = newWins && newestWins
where
newWins = addMapLog ("foo") (LogEntry (Date 1) "new") l == l2
newestWins = addMapLog ("foo") (LogEntry (Date 1) "newest") l2 /= l2
l = M.fromList [("foo", LogEntry (Date 0) "old")]
l2 = M.fromList [("foo", LogEntry (Date 1) "new")]

View file

@ -36,26 +36,54 @@ module Logs.MetaData (
import Common.Annex import Common.Annex
import Types.MetaData import Types.MetaData
import Annex.MetaData.StandardFields
import qualified Annex.Branch import qualified Annex.Branch
import Logs import Logs
import Logs.SingleValue import Logs.SingleValue
import qualified Data.Set as S import qualified Data.Set as S
import qualified Data.Map as M
import Data.Time.Clock.POSIX import Data.Time.Clock.POSIX
import Data.Time.Format
import System.Locale
instance SingleValueSerializable MetaData where instance SingleValueSerializable MetaData where
serialize = Types.MetaData.serialize serialize = Types.MetaData.serialize
deserialize = Types.MetaData.deserialize deserialize = Types.MetaData.deserialize
getMetaData :: Key -> Annex (Log MetaData) getMetaDataLog :: Key -> Annex (Log MetaData)
getMetaData = readLog . metaDataLogFile getMetaDataLog = readLog . metaDataLogFile
{- Go through the log from oldest to newest, and combine it all {- Go through the log from oldest to newest, and combine it all
- into a single MetaData representing the current state. -} - into a single MetaData representing the current state.
-
- Automatically generates a lastchanged metadata for each field that's
- currently set, based on timestamps in the log.
-}
getCurrentMetaData :: Key -> Annex MetaData getCurrentMetaData :: Key -> Annex MetaData
getCurrentMetaData = currentMetaData . collect <$$> getMetaData getCurrentMetaData k = do
ls <- S.toAscList <$> getMetaDataLog k
let loggedmeta = currentMetaData $ combineMetaData $ map value ls
return $ currentMetaData $ unionMetaData loggedmeta
(lastchanged ls loggedmeta)
where where
collect = foldl' unionMetaData emptyMetaData . map value . S.toAscList lastchanged [] _ = emptyMetaData
lastchanged ls (MetaData currentlyset) =
let m = foldl' (flip M.union) M.empty (map genlastchanged ls)
in MetaData $
-- Add a overall lastchanged using the oldest log
-- item (log is in ascending order).
M.insert lastChangedField (lastchangedval $ Prelude.last ls) $
M.mapKeys mkLastChangedField $
-- Only include fields that are currently set.
m `M.intersection` currentlyset
-- Makes each field have the timestamp as its value.
genlastchanged l =
let MetaData m = value l
ts = lastchangedval l
in M.map (const ts) m
lastchangedval l = S.singleton $ toMetaValue $ showts $ changed l
showts = formatTime defaultTimeLocale "%F@%H-%M-%S" . posixSecondsToUTCTime
{- Adds in some metadata, which can override existing values, or unset {- Adds in some metadata, which can override existing values, or unset
- them, but otherwise leaves any existing metadata as-is. -} - them, but otherwise leaves any existing metadata as-is. -}
@ -67,10 +95,12 @@ addMetaData k metadata = addMetaData' k metadata =<< liftIO getPOSIXTime
- will tend to be generated across the different log files, and so - will tend to be generated across the different log files, and so
- git will be able to pack the data more efficiently. -} - git will be able to pack the data more efficiently. -}
addMetaData' :: Key -> MetaData -> POSIXTime -> Annex () addMetaData' :: Key -> MetaData -> POSIXTime -> Annex ()
addMetaData' k metadata now = Annex.Branch.change (metaDataLogFile k) $ addMetaData' k (MetaData m) now = Annex.Branch.change (metaDataLogFile k) $
showLog . simplifyLog showLog . simplifyLog
. S.insert (LogEntry now metadata) . S.insert (LogEntry now metadata)
. parseLog . parseLog
where
metadata = MetaData $ M.filterWithKey (\f _ -> not (isLastChangedField f)) m
{- Simplify a log, removing historical values that are no longer {- Simplify a log, removing historical values that are no longer
- needed. - needed.
@ -148,7 +178,7 @@ copyMetaData :: Key -> Key -> Annex ()
copyMetaData oldkey newkey copyMetaData oldkey newkey
| oldkey == newkey = noop | oldkey == newkey = noop
| otherwise = do | otherwise = do
l <- getMetaData oldkey l <- getMetaDataLog oldkey
unless (S.null l) $ unless (S.null l) $
Annex.Branch.change (metaDataLogFile newkey) $ Annex.Branch.change (metaDataLogFile newkey) $
const $ showLog l const $ showLog l

View file

@ -1,6 +1,6 @@
{- git-annex preferred content matcher configuration {- git-annex preferred content matcher configuration
- -
- Copyright 2012 Joey Hess <joey@kitenet.net> - Copyright 2012-2014 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -8,10 +8,12 @@
module Logs.PreferredContent ( module Logs.PreferredContent (
preferredContentLog, preferredContentLog,
preferredContentSet, preferredContentSet,
groupPreferredContentSet,
isPreferredContent, isPreferredContent,
preferredContentMap, preferredContentMap,
preferredContentMapLoad, preferredContentMapLoad,
preferredContentMapRaw, preferredContentMapRaw,
groupPreferredContentMapRaw,
checkPreferredContentExpression, checkPreferredContentExpression,
setStandardGroup, setStandardGroup,
) where ) where
@ -35,6 +37,7 @@ import Types.Remote (RemoteConfig)
import Logs.Group import Logs.Group
import Logs.Remote import Logs.Remote
import Types.StandardGroups import Types.StandardGroups
import Limit
{- Checks if a file is preferred content for the specified repository {- Checks if a file is preferred content for the specified repository
- (or the current repository if none is specified). -} - (or the current repository if none is specified). -}
@ -56,40 +59,61 @@ preferredContentMapLoad :: Annex Annex.PreferredContentMap
preferredContentMapLoad = do preferredContentMapLoad = do
groupmap <- groupMap groupmap <- groupMap
configmap <- readRemoteLog configmap <- readRemoteLog
groupwantedmap <- groupPreferredContentMapRaw
m <- simpleMap m <- simpleMap
. parseLogWithUUID ((Just .) . makeMatcher groupmap configmap) . parseLogWithUUID ((Just .) . makeMatcher groupmap configmap groupwantedmap)
<$> Annex.Branch.get preferredContentLog <$> Annex.Branch.get preferredContentLog
Annex.changeState $ \s -> s { Annex.preferredcontentmap = Just m } Annex.changeState $ \s -> s { Annex.preferredcontentmap = Just m }
return m return m
{- This intentionally never fails, even on unparsable expressions, {- This intentionally never fails, even on unparsable expressions,
- because the configuration is shared among repositories and newer - because the configuration is shared among repositories and newer
- versions of git-annex may add new features. Instead, parse errors - versions of git-annex may add new features. -}
- result in a Matcher that will always succeed. -} makeMatcher
makeMatcher :: GroupMap -> M.Map UUID RemoteConfig -> UUID -> PreferredContentExpression -> FileMatcher :: GroupMap
makeMatcher groupmap configmap u expr -> M.Map UUID RemoteConfig
| expr == "standard" = standardMatcher groupmap configmap u -> M.Map Group PreferredContentExpression
| null (lefts tokens) = Utility.Matcher.generate $ rights tokens -> UUID
| otherwise = matchAll -> PreferredContentExpression
-> FileMatcher
makeMatcher groupmap configmap groupwantedmap u = go True True
where where
tokens = exprParser groupmap configmap (Just u) expr go expandstandard expandgroupwanted expr
| null (lefts tokens) = Utility.Matcher.generate $ rights tokens
| otherwise = unknownMatcher u
where
tokens = exprParser matchstandard matchgroupwanted groupmap configmap (Just u) expr
matchstandard
| expandstandard = maybe (unknownMatcher u) (go False False)
(standardPreferredContent <$> getStandardGroup mygroups)
| otherwise = unknownMatcher u
matchgroupwanted
| expandgroupwanted = maybe (unknownMatcher u) (go True False)
(groupwanted mygroups)
| otherwise = unknownMatcher u
mygroups = fromMaybe S.empty (u `M.lookup` groupsByUUID groupmap)
groupwanted s = case M.elems $ M.filterWithKey (\k _ -> S.member k s) groupwantedmap of
[pc] -> Just pc
_ -> Nothing
{- Standard matchers are pre-defined for some groups. If none is defined, {- When a preferred content expression cannot be parsed, but is already
- or a repository is in multiple groups with standard matchers, match all. -} - in the log (eg, put there by a newer version of git-annex),
standardMatcher :: GroupMap -> M.Map UUID RemoteConfig -> UUID -> FileMatcher - the fallback behavior is to match only files that are currently present.
standardMatcher groupmap configmap u = -
maybe matchAll (makeMatcher groupmap configmap u . preferredContent) $ - This avoid unwanted/expensive changes to the content, until the problem
getStandardGroup =<< u `M.lookup` groupsByUUID groupmap - is resolved. -}
unknownMatcher :: UUID -> FileMatcher
unknownMatcher u = Utility.Matcher.generate [present]
where
present = Utility.Matcher.Operation $ matchPresent (Just u)
{- Checks if an expression can be parsed, if not returns Just error -} {- Checks if an expression can be parsed, if not returns Just error -}
checkPreferredContentExpression :: PreferredContentExpression -> Maybe String checkPreferredContentExpression :: PreferredContentExpression -> Maybe String
checkPreferredContentExpression expr checkPreferredContentExpression expr = case parsedToMatcher tokens of
| expr == "standard" = Nothing Left e -> Just e
| otherwise = case parsedToMatcher tokens of Right _ -> Nothing
Left e -> Just e
Right _ -> Nothing
where where
tokens = exprParser emptyGroupMap M.empty Nothing expr tokens = exprParser matchAll matchAll emptyGroupMap M.empty Nothing expr
{- Puts a UUID in a standard group, and sets its preferred content to use {- Puts a UUID in a standard group, and sets its preferred content to use
- the standard expression for that group, unless something is already set. -} - the standard expression for that group, unless something is already set. -}

View file

@ -1,6 +1,6 @@
{- unparsed preferred content expressions {- unparsed preferred content expressions
- -
- Copyright 2012 Joey Hess <joey@kitenet.net> - Copyright 2012-2014 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -15,17 +15,35 @@ import qualified Annex.Branch
import qualified Annex import qualified Annex
import Logs import Logs
import Logs.UUIDBased import Logs.UUIDBased
import Logs.MapLog
import Types.StandardGroups import Types.StandardGroups
import Types.Group
{- Changes the preferred content configuration of a remote. -} {- Changes the preferred content configuration of a remote. -}
preferredContentSet :: UUID -> PreferredContentExpression -> Annex () preferredContentSet :: UUID -> PreferredContentExpression -> Annex ()
preferredContentSet uuid@(UUID _) val = do preferredContentSet uuid@(UUID _) val = do
ts <- liftIO getPOSIXTime ts <- liftIO getPOSIXTime
Annex.Branch.change preferredContentLog $ Annex.Branch.change preferredContentLog $
showLog id . changeLog ts uuid val . parseLog Just showLog id
. changeLog ts uuid val
. parseLog Just
Annex.changeState $ \s -> s { Annex.preferredcontentmap = Nothing } Annex.changeState $ \s -> s { Annex.preferredcontentmap = Nothing }
preferredContentSet NoUUID _ = error "unknown UUID; cannot modify" preferredContentSet NoUUID _ = error "unknown UUID; cannot modify"
{- Changes the preferred content configuration of a group. -}
groupPreferredContentSet :: Group -> PreferredContentExpression -> Annex ()
groupPreferredContentSet g val = do
ts <- liftIO getPOSIXTime
Annex.Branch.change groupPreferredContentLog $
showMapLog id id
. changeMapLog ts g val
. parseMapLog Just Just
Annex.changeState $ \s -> s { Annex.preferredcontentmap = Nothing }
preferredContentMapRaw :: Annex (M.Map UUID PreferredContentExpression) preferredContentMapRaw :: Annex (M.Map UUID PreferredContentExpression)
preferredContentMapRaw = simpleMap . parseLog Just preferredContentMapRaw = simpleMap . parseLog Just
<$> Annex.Branch.get preferredContentLog <$> Annex.Branch.get preferredContentLog
groupPreferredContentMapRaw :: Annex (M.Map Group PreferredContentExpression)
groupPreferredContentMapRaw = simpleMap . parseMapLog Just Just
<$> Annex.Branch.get groupPreferredContentLog

View file

@ -26,9 +26,6 @@ module Logs.UUIDBased (
changeLog, changeLog,
addLog, addLog,
simpleMap, simpleMap,
prop_TimeStamp_sane,
prop_addLog_sane,
) where ) where
import qualified Data.Map as M import qualified Data.Map as M
@ -38,21 +35,11 @@ import System.Locale
import Common import Common
import Types.UUID import Types.UUID
import Logs.MapLog
data TimeStamp = Unknown | Date POSIXTime type Log v = MapLog UUID v
deriving (Eq, Ord, Show)
data LogEntry a = LogEntry showLog :: (v -> String) -> Log v -> String
{ changed :: TimeStamp
, value :: a
} deriving (Eq, Show)
type Log a = M.Map UUID (LogEntry a)
tskey :: String
tskey = "timestamp="
showLog :: (a -> String) -> Log a -> String
showLog shower = unlines . map showpair . M.toList showLog shower = unlines . map showpair . M.toList
where where
showpair (k, LogEntry (Date p) v) = showpair (k, LogEntry (Date p) v) =
@ -60,14 +47,6 @@ showLog shower = unlines . map showpair . M.toList
showpair (k, LogEntry Unknown v) = showpair (k, LogEntry Unknown v) =
unwords [fromUUID k, shower v] unwords [fromUUID k, shower v]
showLogNew :: (a -> String) -> Log a -> String
showLogNew shower = unlines . map showpair . M.toList
where
showpair (k, LogEntry (Date p) v) =
unwords [show p, fromUUID k, shower v]
showpair (k, LogEntry Unknown v) =
unwords ["0", fromUUID k, shower v]
parseLog :: (String -> Maybe a) -> String -> Log a parseLog :: (String -> Maybe a) -> String -> Log a
parseLog = parseLogWithUUID . const parseLog = parseLogWithUUID . const
@ -98,45 +77,17 @@ parseLogWithUUID parser = M.fromListWith best . mapMaybe parse . lines
Nothing -> Unknown Nothing -> Unknown
Just d -> Date $ utcTimeToPOSIXSeconds d Just d -> Date $ utcTimeToPOSIXSeconds d
parseLogNew :: (String -> Maybe a) -> String -> Log a showLogNew :: (v -> String) -> Log v -> String
parseLogNew parser = M.fromListWith best . mapMaybe parse . lines showLogNew = showMapLog fromUUID
where
parse line = do
let (ts, rest) = splitword line
(u, v) = splitword rest
date <- Date . utcTimeToPOSIXSeconds <$> parseTime defaultTimeLocale "%s%Qs" ts
val <- parser v
Just (toUUID u, LogEntry date val)
splitword = separate (== ' ')
changeLog :: POSIXTime -> UUID -> a -> Log a -> Log a parseLogNew :: (String -> Maybe v) -> String -> Log v
changeLog t u v = M.insert u $ LogEntry (Date t) v parseLogNew = parseMapLog (Just . toUUID)
{- Only add an LogEntry if it's newer (or at least as new as) than any changeLog :: POSIXTime -> UUID -> v -> Log v -> Log v
- existing LogEntry for a UUID. -} changeLog = changeMapLog
addLog :: UUID -> LogEntry a -> Log a -> Log a
addLog = M.insertWith' best
{- Converts a Log into a simple Map without the timestamp information. addLog :: UUID -> LogEntry v -> Log v -> Log v
- This is a one-way trip, but useful for code that never needs to change addLog = addMapLog
- the log. -}
simpleMap :: Log a -> M.Map UUID a
simpleMap = M.map value
best :: LogEntry a -> LogEntry a -> LogEntry a tskey :: String
best new old tskey = "timestamp="
| changed old > changed new = old
| otherwise = new
-- Unknown is oldest.
prop_TimeStamp_sane :: Bool
prop_TimeStamp_sane = Unknown < Date 1
prop_addLog_sane :: Bool
prop_addLog_sane = newWins && newestWins
where
newWins = addLog (UUID "foo") (LogEntry (Date 1) "new") l == l2
newestWins = addLog (UUID "foo") (LogEntry (Date 1) "newest") l2 /= l2
l = M.fromList [(UUID "foo", LogEntry (Date 0) "old")]
l2 = M.fromList [(UUID "foo", LogEntry (Date 1) "new")]

View file

@ -67,7 +67,7 @@ updateUnusedLog prefix m = do
writeUnusedLog :: FilePath -> UnusedLog -> Annex () writeUnusedLog :: FilePath -> UnusedLog -> Annex ()
writeUnusedLog prefix l = do writeUnusedLog prefix l = do
logfile <- fromRepo $ gitAnnexUnusedLog prefix logfile <- fromRepo $ gitAnnexUnusedLog prefix
liftIO $ viaTmp writeFile logfile $ unlines $ map format $ M.toList l liftIO $ viaTmp writeFileAnyEncoding logfile $ unlines $ map format $ M.toList l
where where
format (k, (i, Just t)) = show i ++ " " ++ key2file k ++ " " ++ show t format (k, (i, Just t)) = show i ++ " " ++ key2file k ++ " " ++ show t
format (k, (i, Nothing)) = show i ++ " " ++ key2file k format (k, (i, Nothing)) = show i ++ " " ++ key2file k
@ -77,7 +77,7 @@ readUnusedLog prefix = do
f <- fromRepo $ gitAnnexUnusedLog prefix f <- fromRepo $ gitAnnexUnusedLog prefix
ifM (liftIO $ doesFileExist f) ifM (liftIO $ doesFileExist f)
( M.fromList . mapMaybe parse . lines ( M.fromList . mapMaybe parse . lines
<$> liftIO (readFile f) <$> liftIO (readFileStrictAnyEncoding f)
, return M.empty , return M.empty
) )
where where
@ -99,7 +99,6 @@ dateUnusedLog prefix = do
f <- fromRepo $ gitAnnexUnusedLog prefix f <- fromRepo $ gitAnnexUnusedLog prefix
liftIO $ catchMaybeIO $ getModificationTime f liftIO $ catchMaybeIO $ getModificationTime f
#else #else
#warning foo
-- old ghc's getModificationTime returned a ClockTime -- old ghc's getModificationTime returned a ClockTime
dateUnusedLog _prefix = return Nothing dateUnusedLog _prefix = return Nothing
#endif #endif

View file

@ -119,7 +119,7 @@ linuxstandalone-nobuild: Build/Standalone Build/LinuxMkLibs
strip "$(LINUXSTANDALONE_DEST)/bin/git-annex" strip "$(LINUXSTANDALONE_DEST)/bin/git-annex"
ln -sf git-annex "$(LINUXSTANDALONE_DEST)/bin/git-annex-shell" ln -sf git-annex "$(LINUXSTANDALONE_DEST)/bin/git-annex-shell"
zcat standalone/licences.gz > $(LINUXSTANDALONE_DEST)/LICENSE zcat standalone/licences.gz > $(LINUXSTANDALONE_DEST)/LICENSE
cp doc/favicon.png doc/logo.svg $(LINUXSTANDALONE_DEST) cp doc/logo_16x16.png doc/logo.svg $(LINUXSTANDALONE_DEST)
./Build/Standalone "$(LINUXSTANDALONE_DEST)" ./Build/Standalone "$(LINUXSTANDALONE_DEST)"

View file

@ -37,6 +37,7 @@ module Remote (
keyPossibilities, keyPossibilities,
keyPossibilitiesTrusted, keyPossibilitiesTrusted,
nameToUUID, nameToUUID,
nameToUUID',
showTriedRemotes, showTriedRemotes,
showLocations, showLocations,
forceTrust, forceTrust,
@ -48,7 +49,6 @@ module Remote (
import qualified Data.Map as M import qualified Data.Map as M
import Text.JSON import Text.JSON
import Text.JSON.Generic import Text.JSON.Generic
import Data.Tuple
import Data.Ord import Data.Ord
import Common.Annex import Common.Annex
@ -121,23 +121,25 @@ noRemoteUUIDMsg r = "cannot determine uuid for " ++ name r
- and returns its UUID. Finds even repositories that are not - and returns its UUID. Finds even repositories that are not
- configured in .git/config. -} - configured in .git/config. -}
nameToUUID :: RemoteName -> Annex UUID nameToUUID :: RemoteName -> Annex UUID
nameToUUID "." = getUUID -- special case for current repo nameToUUID = either error return <=< nameToUUID'
nameToUUID "here" = getUUID
nameToUUID "" = error "no remote specified" nameToUUID' :: RemoteName -> Annex (Either String UUID)
nameToUUID n = byName' n >>= go nameToUUID' "." = Right <$> getUUID -- special case for current repo
nameToUUID' "here" = Right <$> getUUID
nameToUUID' n = byName' n >>= go
where where
go (Right r) = case uuid r of go (Right r) = return $ case uuid r of
NoUUID -> error $ noRemoteUUIDMsg r NoUUID -> Left $ noRemoteUUIDMsg r
u -> return u u -> Right u
go (Left e) = fromMaybe (error e) <$> bydescription go (Left e) = do
bydescription = do
m <- uuidMap m <- uuidMap
case M.lookup n $ transform swap m of return $ case M.keys (M.filter (== n) m) of
Just u -> return $ Just u [u] -> Right u
Nothing -> return $ byuuid m [] -> let u = toUUID n
byuuid m = M.lookup (toUUID n) $ transform double m in case M.keys (M.filterWithKey (\k _ -> k == u) m) of
transform a = M.fromList . map a . M.toList [] -> Left e
double (a, _) = (a, a) _ -> Right u
_us -> Left "Found multiple repositories with that description"
{- Pretty-prints a list of UUIDs of remotes, for human display. {- Pretty-prints a list of UUIDs of remotes, for human display.
- -

View file

@ -11,6 +11,7 @@ import Remote.External.Types
import qualified Annex import qualified Annex
import Common.Annex import Common.Annex
import Types.Remote import Types.Remote
import Types.CleanupActions
import qualified Git import qualified Git
import Config import Config
import Remote.Helper.Special import Remote.Helper.Special
@ -43,7 +44,7 @@ remote = RemoteType {
gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex (Maybe Remote) gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex (Maybe Remote)
gen r u c gc = do gen r u c gc = do
external <- newExternal externaltype u c external <- newExternal externaltype u c
Annex.addCleanup (fromUUID u) $ stopExternal external Annex.addCleanup (RemoteCleanup u) $ stopExternal external
cst <- getCost external r gc cst <- getCost external r gc
avail <- getAvailability external r gc avail <- getAvailability external r gc
return $ Just $ encryptableRemote c return $ Just $ encryptableRemote c

View file

@ -36,6 +36,7 @@ import Config
import Config.Cost import Config.Cost
import Annex.Init import Annex.Init
import Types.Key import Types.Key
import Types.CleanupActions
import qualified CmdLine.GitAnnexShell.Fields as Fields import qualified CmdLine.GitAnnexShell.Fields as Fields
import Logs.Location import Logs.Location
import Utility.Metered import Utility.Metered
@ -144,7 +145,7 @@ repoAvail r
else return True else return True
| Git.repoIsUrl r = return True | Git.repoIsUrl r = return True
| Git.repoIsLocalUnknown r = return False | Git.repoIsLocalUnknown r = return False
| otherwise = liftIO $ catchBoolIO $ onLocal r $ return True | otherwise = liftIO $ isJust <$> catchMaybeIO (Git.Config.read r)
{- Tries to read the config for a specified remote, updates state, and {- Tries to read the config for a specified remote, updates state, and
- returns the updated repo. -} - returns the updated repo. -}
@ -161,9 +162,12 @@ tryGitConfigRead r
| Git.repoIsHttp r = store geturlconfig | Git.repoIsHttp r = store geturlconfig
| Git.GCrypt.isEncrypted r = handlegcrypt =<< getConfigMaybe (remoteConfig r "uuid") | Git.GCrypt.isEncrypted r = handlegcrypt =<< getConfigMaybe (remoteConfig r "uuid")
| Git.repoIsUrl r = return r | Git.repoIsUrl r = return r
| otherwise = store $ safely $ onLocal r $ do | otherwise = store $ safely $ do
ensureInitialized s <- Annex.new r
Annex.getState Annex.repo Annex.eval s $ do
Annex.BranchState.disableUpdate
ensureInitialized
Annex.getState Annex.repo
where where
haveconfig = not . M.null . Git.config haveconfig = not . M.null . Git.config
@ -267,8 +271,8 @@ inAnnex rmt key
checkremote = Ssh.inAnnex r key checkremote = Ssh.inAnnex r key
checklocal = guardUsable r (cantCheck r) $ dispatch <$> check checklocal = guardUsable r (cantCheck r) $ dispatch <$> check
where where
check = liftIO $ catchMsgIO $ onLocal r $ check = either (Left . show) Right
Annex.Content.inAnnexSafe key <$> tryAnnex (onLocal rmt $ Annex.Content.inAnnexSafe key)
dispatch (Left e) = Left e dispatch (Left e) = Left e
dispatch (Right (Just b)) = Right b dispatch (Right (Just b)) = Right b
dispatch (Right Nothing) = cantCheck r dispatch (Right Nothing) = cantCheck r
@ -291,7 +295,7 @@ keyUrls r key = map tourl locs'
dropKey :: Remote -> Key -> Annex Bool dropKey :: Remote -> Key -> Annex Bool
dropKey r key dropKey r key
| not $ Git.repoIsUrl (repo r) = | not $ Git.repoIsUrl (repo r) =
guardUsable (repo r) False $ commitOnCleanup r $ liftIO $ onLocal (repo r) $ do guardUsable (repo r) False $ commitOnCleanup r $ onLocal r $ do
ensureInitialized ensureInitialized
whenM (Annex.Content.inAnnex key) $ do whenM (Annex.Content.inAnnex key) $ do
Annex.Content.lockContent key $ Annex.Content.lockContent key $
@ -311,7 +315,7 @@ copyFromRemote' r key file dest
let params = Ssh.rsyncParams r Download let params = Ssh.rsyncParams r Download
u <- getUUID u <- getUUID
-- run copy from perspective of remote -- run copy from perspective of remote
liftIO $ onLocal (repo r) $ do onLocal r $ do
ensureInitialized ensureInitialized
v <- Annex.Content.prepSendAnnex key v <- Annex.Content.prepSendAnnex key
case v of case v of
@ -410,7 +414,7 @@ copyToRemote r key file p
let params = Ssh.rsyncParams r Upload let params = Ssh.rsyncParams r Upload
u <- getUUID u <- getUUID
-- run copy from perspective of remote -- run copy from perspective of remote
liftIO $ onLocal (repo r) $ ifM (Annex.Content.inAnnex key) onLocal r $ ifM (Annex.Content.inAnnex key)
( return True ( return True
, do , do
ensureInitialized ensureInitialized
@ -439,19 +443,40 @@ fsckOnRemote r params
{- The passed repair action is run in the Annex monad of the remote. -} {- The passed repair action is run in the Annex monad of the remote. -}
repairRemote :: Git.Repo -> Annex Bool -> Annex (IO Bool) repairRemote :: Git.Repo -> Annex Bool -> Annex (IO Bool)
repairRemote r a = return $ Remote.Git.onLocal r a repairRemote r a = return $ do
{- Runs an action on a local repository inexpensively, by making an annex
- monad using that repository. -}
onLocal :: Git.Repo -> Annex a -> IO a
onLocal r a = do
s <- Annex.new r s <- Annex.new r
Annex.eval s $ do Annex.eval s $ do
-- No need to update the branch; its data is not used
-- for anything onLocal is used to do.
Annex.BranchState.disableUpdate Annex.BranchState.disableUpdate
ensureInitialized
a a
{- Runs an action from the perspective of a local remote.
-
- The AnnexState is cached for speed and to avoid resource leaks.
-
- The repository's git-annex branch is not updated, as an optimisation.
- No caller of onLocal can query data from the branch and be ensured
- it gets a current value. Caller of onLocal can make changes to
- the branch, however.
-}
onLocal :: Remote -> Annex a -> Annex a
onLocal r a = do
m <- Annex.getState Annex.remoteannexstate
case M.lookup (uuid r) m of
Nothing -> do
st <- liftIO $ Annex.new (repo r)
go st $ do
Annex.BranchState.disableUpdate
a
Just st -> go st a
where
cache st = Annex.changeState $ \s -> s
{ Annex.remoteannexstate = M.insert (uuid r) st (Annex.remoteannexstate s) }
go st a' = do
(ret, st') <- liftIO $ Annex.run st a'
cache st'
return ret
{- Copys a file with rsync unless both locations are on the same {- Copys a file with rsync unless both locations are on the same
- filesystem. Then cp could be faster. -} - filesystem. Then cp could be faster. -}
rsyncOrCopyFile :: [CommandParam] -> FilePath -> FilePath -> MeterUpdate -> Annex Bool rsyncOrCopyFile :: [CommandParam] -> FilePath -> FilePath -> MeterUpdate -> Annex Bool
@ -486,9 +511,9 @@ rsyncOrCopyFile rsyncparams src dest p =
commitOnCleanup :: Remote -> Annex a -> Annex a commitOnCleanup :: Remote -> Annex a -> Annex a
commitOnCleanup r a = go `after` a commitOnCleanup r a = go `after` a
where where
go = Annex.addCleanup (Git.repoLocation $ repo r) cleanup go = Annex.addCleanup (RemoteCleanup $ uuid r) cleanup
cleanup cleanup
| not $ Git.repoIsUrl (repo r) = liftIO $ onLocal (repo r) $ | not $ Git.repoIsUrl (repo r) = onLocal r $
doQuietSideAction $ doQuietSideAction $
Annex.Branch.commit "update" Annex.Branch.commit "update"
| otherwise = void $ do | otherwise = void $ do

View file

@ -13,6 +13,7 @@ import qualified Data.Map as M
import Common.Annex import Common.Annex
import Types.Remote import Types.Remote
import Types.CleanupActions
import qualified Annex import qualified Annex
import Annex.LockPool import Annex.LockPool
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
@ -74,7 +75,7 @@ runHooks r starthook stophook a = do
-- So, requiring idempotency is the right approach. -- So, requiring idempotency is the right approach.
run starthook run starthook
Annex.addCleanup (remoteid ++ "-stop-command") $ runstop lck Annex.addCleanup (StopHook $ uuid r) $ runstop lck
runstop lck = do runstop lck = do
-- Drop any shared lock we have, and take an -- Drop any shared lock we have, and take an
-- exclusive lock, without blocking. If the lock -- exclusive lock, without blocking. If the lock

View file

@ -28,6 +28,7 @@ import Annex.UUID
import Annex.Ssh import Annex.Ssh
import Remote.Helper.Special import Remote.Helper.Special
import Remote.Helper.Encryptable import Remote.Helper.Encryptable
import Remote.Rsync.RsyncUrl
import Crypto import Crypto
import Utility.Rsync import Utility.Rsync
import Utility.CopyFile import Utility.CopyFile
@ -40,16 +41,6 @@ import Types.Creds
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import qualified Data.Map as M import qualified Data.Map as M
type RsyncUrl = String
data RsyncOpts = RsyncOpts
{ rsyncUrl :: RsyncUrl
, rsyncOptions :: [CommandParam]
, rsyncUploadOptions :: [CommandParam]
, rsyncDownloadOptions :: [CommandParam]
, rsyncShellEscape :: Bool
}
remote :: RemoteType remote :: RemoteType
remote = RemoteType { remote = RemoteType {
typename = "rsync", typename = "rsync",
@ -148,17 +139,6 @@ rsyncSetup mu _ c = do
gitConfigSpecialRemote u c' "rsyncurl" url gitConfigSpecialRemote u c' "rsyncurl" url
return (c', u) return (c', u)
rsyncEscape :: RsyncOpts -> String -> String
rsyncEscape o s
| rsyncShellEscape o && rsyncUrlIsShell (rsyncUrl o) = shellEscape s
| otherwise = s
rsyncUrls :: RsyncOpts -> Key -> [String]
rsyncUrls o k = map use annexHashes
where
use h = rsyncUrl o </> h k </> rsyncEscape o (f </> f)
f = keyFile k
store :: RsyncOpts -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool store :: RsyncOpts -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool
store o k _f p = sendAnnex k (void $ remove o k) $ rsyncSend o p k False store o k _f p = sendAnnex k (void $ remove o k) $ rsyncSend o p k False

46
Remote/Rsync/RsyncUrl.hs Normal file
View file

@ -0,0 +1,46 @@
{- Rsync urls.
-
- Copyright 2014 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
{-# LANGUAGE CPP #-}
module Remote.Rsync.RsyncUrl where
import Types
import Locations
import Utility.Rsync
import Utility.SafeCommand
import System.FilePath.Posix
#ifdef mingw32_HOST_OS
import Data.String.Utils
#endif
type RsyncUrl = String
data RsyncOpts = RsyncOpts
{ rsyncUrl :: RsyncUrl
, rsyncOptions :: [CommandParam]
, rsyncUploadOptions :: [CommandParam]
, rsyncDownloadOptions :: [CommandParam]
, rsyncShellEscape :: Bool
}
rsyncEscape :: RsyncOpts -> RsyncUrl -> RsyncUrl
rsyncEscape o u
| rsyncShellEscape o && rsyncUrlIsShell (rsyncUrl o) = shellEscape u
| otherwise = u
rsyncUrls :: RsyncOpts -> Key -> [RsyncUrl]
rsyncUrls o k = map use annexHashes
where
use h = rsyncUrl o </> hash h </> rsyncEscape o (f </> f)
f = keyFile k
#ifndef mingw32_HOST_OS
hash h = h k
#else
hash h = replace "\\" "/" (h k)
#endif

View file

@ -16,15 +16,14 @@ import System.Directory
import qualified Build.DesktopFile as DesktopFile import qualified Build.DesktopFile as DesktopFile
import qualified Build.Configure as Configure import qualified Build.Configure as Configure
main :: IO ()
main = defaultMainWithHooks simpleUserHooks main = defaultMainWithHooks simpleUserHooks
{ preConf = configure { preConf = \_ _ -> do
Configure.run Configure.tests
return (Nothing, [])
, postInst = myPostInst , postInst = myPostInst
} }
configure _ _ = do
Configure.run Configure.tests
return (Nothing, [])
myPostInst :: Args -> InstallFlags -> PackageDescription -> LocalBuildInfo -> IO () myPostInst :: Args -> InstallFlags -> PackageDescription -> LocalBuildInfo -> IO ()
myPostInst _ (InstallFlags { installVerbosity }) pkg lbi = do myPostInst _ (InstallFlags { installVerbosity }) pkg lbi = do
installGitAnnexShell dest verbosity pkg lbi installGitAnnexShell dest verbosity pkg lbi
@ -57,7 +56,7 @@ installManpages copyDest verbosity pkg lbi =
manpages = ["git-annex.1", "git-annex-shell.1"] manpages = ["git-annex.1", "git-annex-shell.1"]
installDesktopFile :: CopyDest -> Verbosity -> PackageDescription -> LocalBuildInfo -> IO () installDesktopFile :: CopyDest -> Verbosity -> PackageDescription -> LocalBuildInfo -> IO ()
installDesktopFile copyDest verbosity pkg lbi = installDesktopFile copyDest _verbosity pkg lbi =
DesktopFile.install $ dstBinDir </> "git-annex" DesktopFile.install $ dstBinDir </> "git-annex"
where where
dstBinDir = bindir $ absoluteInstallDirs pkg lbi copyDest dstBinDir = bindir $ absoluteInstallDirs pkg lbi copyDest

27
Test.hs
View file

@ -17,12 +17,14 @@ import Test.Tasty.Ingredients.Rerun
import Data.Monoid import Data.Monoid
import Options.Applicative hiding (command) import Options.Applicative hiding (command)
#if MIN_VERSION_optparse_applicative(0,8,0)
import qualified Options.Applicative.Types as Opt
#endif
import Control.Exception.Extensible import Control.Exception.Extensible
import qualified Data.Map as M import qualified Data.Map as M
import System.IO.HVFS (SystemFS(..)) import System.IO.HVFS (SystemFS(..))
import qualified Text.JSON import qualified Text.JSON
import System.Path import System.Path
import qualified Data.ByteString.Lazy as L
import Common import Common
@ -43,7 +45,7 @@ import qualified Types.Backend
import qualified Types.TrustLevel import qualified Types.TrustLevel
import qualified Types import qualified Types
import qualified Logs import qualified Logs
import qualified Logs.UUIDBased import qualified Logs.MapLog
import qualified Logs.Trust import qualified Logs.Trust
import qualified Logs.Remote import qualified Logs.Remote
import qualified Logs.Unused import qualified Logs.Unused
@ -104,8 +106,7 @@ main ps = do
-- parameters is "test". -- parameters is "test".
let pinfo = info (helper <*> suiteOptionParser ingredients tests) let pinfo = info (helper <*> suiteOptionParser ingredients tests)
( fullDesc <> header "Builtin test suite" ) ( fullDesc <> header "Builtin test suite" )
opts <- either (\f -> error =<< errMessage f "git-annex test") return $ opts <- parseOpts (prefs idm) pinfo ps
execParserPure (prefs idm) pinfo ps
case tryIngredients ingredients opts tests of case tryIngredients ingredients opts tests of
Nothing -> error "No tests found!?" Nothing -> error "No tests found!?"
Just act -> ifM act Just act -> ifM act
@ -115,6 +116,18 @@ main ps = do
putStrLn " with utilities, such as git, installed on this system.)" putStrLn " with utilities, such as git, installed on this system.)"
exitFailure exitFailure
) )
where
progdesc = "git-annex test"
parseOpts pprefs pinfo args =
#if MIN_VERSION_optparse_applicative(0,8,0)
pure $ case execParserPure pprefs pinfo args of
Opt.Success v -> v
Opt.Failure f -> error $ fst $ Opt.execFailure f progdesc
Opt.CompletionInvoked _ -> error "completion not supported"
#else
either (error <=< flip errMessage progdesc) return $
execParserPure pprefs pinfo args
#endif
ingredients :: [Ingredient] ingredients :: [Ingredient]
ingredients = ingredients =
@ -140,8 +153,8 @@ properties = localOption (QuickCheckTests 1000) $ testGroup "QuickCheck"
, testProperty "prop_cost_sane" Config.Cost.prop_cost_sane , testProperty "prop_cost_sane" Config.Cost.prop_cost_sane
, testProperty "prop_matcher_sane" Utility.Matcher.prop_matcher_sane , testProperty "prop_matcher_sane" Utility.Matcher.prop_matcher_sane
, testProperty "prop_HmacSha1WithCipher_sane" Crypto.prop_HmacSha1WithCipher_sane , testProperty "prop_HmacSha1WithCipher_sane" Crypto.prop_HmacSha1WithCipher_sane
, testProperty "prop_TimeStamp_sane" Logs.UUIDBased.prop_TimeStamp_sane , testProperty "prop_TimeStamp_sane" Logs.MapLog.prop_TimeStamp_sane
, testProperty "prop_addLog_sane" Logs.UUIDBased.prop_addLog_sane , testProperty "prop_addMapLog_sane" Logs.MapLog.prop_addMapLog_sane
, testProperty "prop_verifiable_sane" Utility.Verifiable.prop_verifiable_sane , testProperty "prop_verifiable_sane" Utility.Verifiable.prop_verifiable_sane
, testProperty "prop_segment_regressionTest" Utility.Misc.prop_segment_regressionTest , testProperty "prop_segment_regressionTest" Utility.Misc.prop_segment_regressionTest
, testProperty "prop_read_write_transferinfo" Logs.Transfer.prop_read_write_transferinfo , testProperty "prop_read_write_transferinfo" Logs.Transfer.prop_read_write_transferinfo
@ -1272,7 +1285,7 @@ test_add_subdirs env = intmpclonerepo env $ do
{- Regression test for Windows bug where symlinks were not {- Regression test for Windows bug where symlinks were not
- calculated correctly for files in subdirs. -} - calculated correctly for files in subdirs. -}
git_annex env "sync" [] @? "sync failed" git_annex env "sync" [] @? "sync failed"
l <- annexeval $ encodeW8 . L.unpack <$> Annex.CatFile.catObject (Git.Types.Ref "HEAD:dir/foo") l <- annexeval $ decodeBS <$> Annex.CatFile.catObject (Git.Types.Ref "HEAD:dir/foo")
"../.git/annex/" `isPrefixOf` l @? ("symlink from subdir to .git/annex is wrong: " ++ l) "../.git/annex/" `isPrefixOf` l @? ("symlink from subdir to .git/annex is wrong: " ++ l)
createDirectory "dir2" createDirectory "dir2"

17
Types/CleanupActions.hs Normal file
View file

@ -0,0 +1,17 @@
{- Enumeration of cleanup actions
-
- Copyright 2014 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Types.CleanupActions where
import Types.UUID
data CleanupAction
= RemoteCleanup UUID
| StopHook UUID
| FsckCleanup
| SshCachingCleanup
deriving (Eq, Ord)

View file

@ -28,6 +28,7 @@ module Types.MetaData (
emptyMetaData, emptyMetaData,
updateMetaData, updateMetaData,
unionMetaData, unionMetaData,
combineMetaData,
differenceMetaData, differenceMetaData,
isSet, isSet,
currentMetaData, currentMetaData,
@ -140,7 +141,7 @@ toMetaField f
- that would break views. - that would break views.
- -
- So, require they have an alphanumeric first letter, with the remainder - So, require they have an alphanumeric first letter, with the remainder
- being either alphanumeric or a small set of shitelisted common punctuation. - being either alphanumeric or a small set of whitelisted common punctuation.
-} -}
legalField :: String -> Bool legalField :: String -> Bool
legalField [] = False legalField [] = False
@ -188,6 +189,9 @@ unionMetaData :: MetaData -> MetaData -> MetaData
unionMetaData (MetaData old) (MetaData new) = MetaData $ unionMetaData (MetaData old) (MetaData new) = MetaData $
M.unionWith S.union new old M.unionWith S.union new old
combineMetaData :: [MetaData] -> MetaData
combineMetaData = foldl' unionMetaData emptyMetaData
differenceMetaData :: MetaData -> MetaData -> MetaData differenceMetaData :: MetaData -> MetaData -> MetaData
differenceMetaData (MetaData m) (MetaData excludem) = MetaData $ differenceMetaData (MetaData m) (MetaData excludem) = MetaData $
M.differenceWith diff m excludem M.differenceWith diff m excludem

View file

@ -8,6 +8,7 @@
module Types.StandardGroups where module Types.StandardGroups where
import Types.Remote (RemoteConfig) import Types.Remote (RemoteConfig)
import Types.Group
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe import Data.Maybe
@ -27,7 +28,7 @@ data StandardGroup
| UnwantedGroup | UnwantedGroup
deriving (Eq, Ord, Enum, Bounded, Show) deriving (Eq, Ord, Enum, Bounded, Show)
fromStandardGroup :: StandardGroup -> String fromStandardGroup :: StandardGroup -> Group
fromStandardGroup ClientGroup = "client" fromStandardGroup ClientGroup = "client"
fromStandardGroup TransferGroup = "transfer" fromStandardGroup TransferGroup = "transfer"
fromStandardGroup BackupGroup = "backup" fromStandardGroup BackupGroup = "backup"
@ -39,7 +40,7 @@ fromStandardGroup ManualGroup = "manual"
fromStandardGroup PublicGroup = "public" fromStandardGroup PublicGroup = "public"
fromStandardGroup UnwantedGroup = "unwanted" fromStandardGroup UnwantedGroup = "unwanted"
toStandardGroup :: String -> Maybe StandardGroup toStandardGroup :: Group -> Maybe StandardGroup
toStandardGroup "client" = Just ClientGroup toStandardGroup "client" = Just ClientGroup
toStandardGroup "transfer" = Just TransferGroup toStandardGroup "transfer" = Just TransferGroup
toStandardGroup "backup" = Just BackupGroup toStandardGroup "backup" = Just BackupGroup
@ -77,21 +78,21 @@ specialRemoteOnly PublicGroup = True
specialRemoteOnly _ = False specialRemoteOnly _ = False
{- See doc/preferred_content.mdwn for explanations of these expressions. -} {- See doc/preferred_content.mdwn for explanations of these expressions. -}
preferredContent :: StandardGroup -> PreferredContentExpression standardPreferredContent :: StandardGroup -> PreferredContentExpression
preferredContent ClientGroup = lastResort $ standardPreferredContent ClientGroup = lastResort $
"((exclude=*/archive/* and exclude=archive/*) or (" ++ notArchived ++ ")) and not unused" "((exclude=*/archive/* and exclude=archive/*) or (" ++ notArchived ++ ")) and not unused"
preferredContent TransferGroup = lastResort $ standardPreferredContent TransferGroup = lastResort $
"not (inallgroup=client and copies=client:2) and (" ++ preferredContent ClientGroup ++ ")" "not (inallgroup=client and copies=client:2) and (" ++ standardPreferredContent ClientGroup ++ ")"
preferredContent BackupGroup = "include=* or unused" standardPreferredContent BackupGroup = "include=* or unused"
preferredContent IncrementalBackupGroup = lastResort standardPreferredContent IncrementalBackupGroup = lastResort
"(include=* or unused) and (not copies=incrementalbackup:1)" "(include=* or unused) and (not copies=incrementalbackup:1)"
preferredContent SmallArchiveGroup = lastResort $ standardPreferredContent SmallArchiveGroup = lastResort $
"(include=*/archive/* or include=archive/*) and (" ++ preferredContent FullArchiveGroup ++ ")" "(include=*/archive/* or include=archive/*) and (" ++ standardPreferredContent FullArchiveGroup ++ ")"
preferredContent FullArchiveGroup = lastResort notArchived standardPreferredContent FullArchiveGroup = lastResort notArchived
preferredContent SourceGroup = "not (copies=1)" standardPreferredContent SourceGroup = "not (copies=1)"
preferredContent ManualGroup = "present and (" ++ preferredContent ClientGroup ++ ")" standardPreferredContent ManualGroup = "present and (" ++ standardPreferredContent ClientGroup ++ ")"
preferredContent PublicGroup = "inpreferreddir" standardPreferredContent PublicGroup = "inpreferreddir"
preferredContent UnwantedGroup = "exclude=*" standardPreferredContent UnwantedGroup = "exclude=*"
notArchived :: String notArchived :: String
notArchived = "not (copies=archive:1 or copies=smallarchive:1)" notArchived = "not (copies=archive:1 or copies=smallarchive:1)"

View file

@ -99,13 +99,20 @@ noUmask :: FileMode -> IO a -> IO a
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
noUmask mode a noUmask mode a
| mode == stdFileMode = a | mode == stdFileMode = a
| otherwise = bracket setup cleanup go | otherwise = withUmask nullFileMode a
#else
noUmask _ a = a
#endif
withUmask :: FileMode -> IO a -> IO a
#ifndef mingw32_HOST_OS
withUmask umask a = bracket setup cleanup go
where where
setup = setFileCreationMask nullFileMode setup = setFileCreationMask umask
cleanup = setFileCreationMask cleanup = setFileCreationMask
go _ = a go _ = a
#else #else
noUmask _ a = a withUmask _ a = a
#endif #endif
combineModes :: [FileMode] -> FileMode combineModes :: [FileMode] -> FileMode
@ -127,14 +134,20 @@ setSticky f = modifyFileMode f $ addModes [stickyMode]
#endif #endif
{- Writes a file, ensuring that its modes do not allow it to be read {- Writes a file, ensuring that its modes do not allow it to be read
- by anyone other than the current user, before any content is written. - or written by anyone other than the current user,
- before any content is written.
-
- When possible, this is done using the umask.
- -
- On a filesystem that does not support file permissions, this is the same - On a filesystem that does not support file permissions, this is the same
- as writeFile. - as writeFile.
-} -}
writeFileProtected :: FilePath -> String -> IO () writeFileProtected :: FilePath -> String -> IO ()
writeFileProtected file content = withFile file WriteMode $ \h -> do writeFileProtected file content = withUmask 0o0077 $
void $ tryIO $ withFile file WriteMode $ \h -> do
modifyFileMode file $ void $ tryIO $ modifyFileMode file $
removeModes [groupReadMode, otherReadMode] removeModes
hPutStr h content [ groupReadMode, otherReadMode
, groupWriteMode, otherWriteMode
]
hPutStr h content

View file

@ -1,14 +1,17 @@
{- GHC File system encoding handling. {- GHC File system encoding handling.
- -
- Copyright 2012-2013 Joey Hess <joey@kitenet.net> - Copyright 2012-2014 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
{-# LANGUAGE CPP #-}
module Utility.FileSystemEncoding ( module Utility.FileSystemEncoding (
fileEncoding, fileEncoding,
withFilePath, withFilePath,
md5FilePath, md5FilePath,
decodeBS,
decodeW8, decodeW8,
encodeW8, encodeW8,
truncateFilePath, truncateFilePath,
@ -22,13 +25,24 @@ import System.IO.Unsafe
import qualified Data.Hash.MD5 as MD5 import qualified Data.Hash.MD5 as MD5
import Data.Word import Data.Word
import Data.Bits.Utils import Data.Bits.Utils
import qualified Data.ByteString.Lazy as L
#ifdef mingw32_HOST_OS
import qualified Data.ByteString.Lazy.UTF8 as L8
#endif
{- Sets a Handle to use the filesystem encoding. This causes data {- Sets a Handle to use the filesystem encoding. This causes data
- written or read from it to be encoded/decoded the same - written or read from it to be encoded/decoded the same
- as ghc 7.4 does to filenames etc. This special encoding - as ghc 7.4 does to filenames etc. This special encoding
- allows "arbitrary undecodable bytes to be round-tripped through it". -} - allows "arbitrary undecodable bytes to be round-tripped through it".
-}
fileEncoding :: Handle -> IO () fileEncoding :: Handle -> IO ()
#ifndef mingw32_HOST_OS
fileEncoding h = hSetEncoding h =<< Encoding.getFileSystemEncoding fileEncoding h = hSetEncoding h =<< Encoding.getFileSystemEncoding
#else
{- The file system encoding does not work well on Windows,
- and Windows only has utf FilePaths anyway. -}
fileEncoding h = hSetEncoding h Encoding.utf8
#endif
{- Marshal a Haskell FilePath into a NUL terminated C string using temporary {- Marshal a Haskell FilePath into a NUL terminated C string using temporary
- storage. The FilePath is encoded using the filesystem encoding, - storage. The FilePath is encoded using the filesystem encoding,
@ -60,6 +74,16 @@ _encodeFilePath fp = unsafePerformIO $ do
md5FilePath :: FilePath -> MD5.Str md5FilePath :: FilePath -> MD5.Str
md5FilePath = MD5.Str . _encodeFilePath md5FilePath = MD5.Str . _encodeFilePath
{- Decodes a ByteString into a FilePath, applying the filesystem encoding. -}
decodeBS :: L.ByteString -> FilePath
#ifndef mingw32_HOST_OS
decodeBS = encodeW8 . L.unpack
#else
{- On Windows, we assume that the ByteString is utf-8, since Windows
- only uses unicode for filenames. -}
decodeBS = L8.toString
#endif
{- Converts a [Word8] to a FilePath, encoding using the filesystem encoding. {- Converts a [Word8] to a FilePath, encoding using the filesystem encoding.
- -
- w82c produces a String, which may contain Chars that are invalid - w82c produces a String, which may contain Chars that are invalid
@ -84,6 +108,7 @@ decodeW8 = s2w8 . _encodeFilePath
- cost of efficiency when running on a large FilePath. - cost of efficiency when running on a large FilePath.
-} -}
truncateFilePath :: Int -> FilePath -> FilePath truncateFilePath :: Int -> FilePath -> FilePath
#ifndef mingw32_HOST_OS
truncateFilePath n = go . reverse truncateFilePath n = go . reverse
where where
go f = go f =
@ -91,3 +116,17 @@ truncateFilePath n = go . reverse
in if length bytes <= n in if length bytes <= n
then reverse f then reverse f
else go (drop 1 f) else go (drop 1 f)
#else
{- On Windows, count the number of bytes used by each utf8 character. -}
truncateFilePath n = reverse . go [] n . L8.fromString
where
go coll cnt bs
| cnt <= 0 = coll
| otherwise = case L8.decode bs of
Just (c, x) | c /= L8.replacement_char ->
let x' = fromIntegral x
in if cnt - x' < 0
then coll
else go (c:coll) (cnt - x') (L8.drop 1 bs)
_ -> coll
#endif

View file

@ -109,18 +109,6 @@ 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
{- Given two orderings, returns the second if the first is EQ and returns
- the first otherwise.
-
- Example use:
-
- compare lname1 lname2 `thenOrd` compare fname1 fname2
-}
thenOrd :: Ordering -> Ordering -> Ordering
thenOrd EQ x = x
thenOrd x _ = x
{-# INLINE thenOrd #-}
{- Wrapper around hGetBufSome that returns a String. {- Wrapper around hGetBufSome that returns a String.
- -
- The null string is returned on eof, otherwise returns whatever - The null string is returned on eof, otherwise returns whatever

View file

@ -28,10 +28,10 @@ instance (Arbitrary v, Eq v, Ord v) => Arbitrary (S.Set v) where
{- Times before the epoch are excluded. -} {- Times before the epoch are excluded. -}
instance Arbitrary POSIXTime where instance Arbitrary POSIXTime where
arbitrary = nonNegative arbitrarySizedIntegral arbitrary = fromInteger <$> nonNegative arbitrarySizedIntegral
instance Arbitrary EpochTime where instance Arbitrary EpochTime where
arbitrary = nonNegative arbitrarySizedIntegral arbitrary = fromInteger <$> nonNegative arbitrarySizedIntegral
{- Pids are never negative, or 0. -} {- Pids are never negative, or 0. -}
instance Arbitrary ProcessID where instance Arbitrary ProcessID where

View file

@ -1,6 +1,6 @@
{- Yesod webapp {- Yesod webapp
- -
- Copyright 2012 Joey Hess <joey@kitenet.net> - Copyright 2012-2014 Joey Hess <joey@kitenet.net>
- -
- Licensed under the GNU GPL version 3 or higher. - Licensed under the GNU GPL version 3 or higher.
-} -}
@ -36,6 +36,10 @@ import Blaze.ByteString.Builder (Builder)
import Data.Monoid import Data.Monoid
import Control.Arrow ((***)) import Control.Arrow ((***))
import Control.Concurrent import Control.Concurrent
#ifdef WITH_WEBAPP_SECURE
import Data.SecureMem
import Data.Byteable
#endif
#ifdef __ANDROID__ #ifdef __ANDROID__
import Data.Endian import Data.Endian
#endif #endif
@ -74,14 +78,14 @@ browserProc url = proc "xdg-open" [url]
runWebApp :: Maybe TLSSettings -> Maybe HostName -> Wai.Application -> (SockAddr -> IO ()) -> IO () runWebApp :: Maybe TLSSettings -> Maybe HostName -> Wai.Application -> (SockAddr -> IO ()) -> IO ()
runWebApp tlssettings h app observer = withSocketsDo $ do runWebApp tlssettings h app observer = withSocketsDo $ do
sock <- getSocket h sock <- getSocket h
void $ forkIO $ run webAppSettings sock app void $ forkIO $ go webAppSettings sock app
sockaddr <- fixSockAddr <$> getSocketName sock sockaddr <- fixSockAddr <$> getSocketName sock
observer sockaddr observer sockaddr
where where
#ifdef WITH_WEBAPP_HTTPS #ifdef WITH_WEBAPP_SECURE
run = (maybe runSettingsSocket (\ts -> runTLSSocket ts) tlssettings) go = (maybe runSettingsSocket (\ts -> runTLSSocket ts) tlssettings)
#else #else
run = runSettingsSocket go = runSettingsSocket
#endif #endif
fixSockAddr :: SockAddr -> SockAddr fixSockAddr :: SockAddr -> SockAddr
@ -208,15 +212,35 @@ webAppSessionBackend _ = do
#endif #endif
#endif #endif
{- Generates a random sha512 string, suitable to be used for an #ifdef WITH_WEBAPP_SECURE
- authentication secret. -} type AuthToken = SecureMem
genRandomToken :: IO String #else
genRandomToken = do type AuthToken = T.Text
#endif
toAuthToken :: T.Text -> AuthToken
#ifdef WITH_WEBAPP_SECURE
toAuthToken = secureMemFromByteString . TE.encodeUtf8
#else
toAuthToken = id
#endif
fromAuthToken :: AuthToken -> T.Text
#ifdef WITH_WEBAPP_SECURE
fromAuthToken = TE.decodeLatin1 . toBytes
#else
fromAuthToken = id
#endif
{- Generates a random sha512 string, encapsulated in a SecureMem,
- suitable to be used for an authentication secret. -}
genAuthToken :: IO AuthToken
genAuthToken = do
g <- newGenIO :: IO SystemRandom g <- newGenIO :: IO SystemRandom
return $ return $
case genBytes 512 g of case genBytes 512 g of
Left e -> error $ "failed to generate secret token: " ++ show e Left e -> error $ "failed to generate auth token: " ++ show e
Right (s, _) -> show $ sha512 $ L.fromChunks [s] Right (s, _) -> toAuthToken $ T.pack $ show $ sha512 $ L.fromChunks [s]
{- A Yesod isAuthorized method, which checks the auth cgi parameter {- A Yesod isAuthorized method, which checks the auth cgi parameter
- against a token extracted from the Yesod application. - against a token extracted from the Yesod application.
@ -225,15 +249,15 @@ genRandomToken = do
- possibly leaking the auth token in urls on that page! - possibly leaking the auth token in urls on that page!
-} -}
#if MIN_VERSION_yesod(1,2,0) #if MIN_VERSION_yesod(1,2,0)
checkAuthToken :: (Monad m, Yesod.MonadHandler m) => (Yesod.HandlerSite m -> T.Text) -> m Yesod.AuthResult checkAuthToken :: (Monad m, Yesod.MonadHandler m) => (Yesod.HandlerSite m -> AuthToken) -> m Yesod.AuthResult
#else #else
checkAuthToken :: forall t sub. (t -> T.Text) -> Yesod.GHandler sub t Yesod.AuthResult checkAuthToken :: forall t sub. (t -> AuthToken) -> Yesod.GHandler sub t Yesod.AuthResult
#endif #endif
checkAuthToken extractToken = do checkAuthToken extractAuthToken = do
webapp <- Yesod.getYesod webapp <- Yesod.getYesod
req <- Yesod.getRequest req <- Yesod.getRequest
let params = Yesod.reqGetParams req let params = Yesod.reqGetParams req
if lookup "auth" params == Just (extractToken webapp) if (toAuthToken <$> lookup "auth" params) == Just (extractAuthToken webapp)
then return Yesod.Authorized then return Yesod.Authorized
else Yesod.sendResponseStatus unauthorized401 () else Yesod.sendResponseStatus unauthorized401 ()
@ -243,21 +267,21 @@ checkAuthToken extractToken = do
- -
- A typical predicate would exclude files under /static. - A typical predicate would exclude files under /static.
-} -}
insertAuthToken :: forall y. (y -> T.Text) insertAuthToken :: forall y. (y -> AuthToken)
-> ([T.Text] -> Bool) -> ([T.Text] -> Bool)
-> y -> y
-> T.Text -> T.Text
-> [T.Text] -> [T.Text]
-> [(T.Text, T.Text)] -> [(T.Text, T.Text)]
-> Builder -> Builder
insertAuthToken extractToken predicate webapp root pathbits params = insertAuthToken extractAuthToken predicate webapp root pathbits params =
fromText root `mappend` encodePath pathbits' encodedparams fromText root `mappend` encodePath pathbits' encodedparams
where where
pathbits' = if null pathbits then [T.empty] else pathbits pathbits' = if null pathbits then [T.empty] else pathbits
encodedparams = map (TE.encodeUtf8 *** go) params' encodedparams = map (TE.encodeUtf8 *** go) params'
go "" = Nothing go "" = Nothing
go x = Just $ TE.encodeUtf8 x go x = Just $ TE.encodeUtf8 x
authparam = (T.pack "auth", extractToken webapp) authparam = (T.pack "auth", fromAuthToken (extractAuthToken webapp))
params' params'
| predicate pathbits = authparam:params | predicate pathbits = authparam:params
| otherwise = params | otherwise = params

40
debian/changelog vendored
View file

@ -1,3 +1,43 @@
git-annex (5.20140320) unstable; urgency=medium
* Fix zombie leak and general inneficiency when copying files to a
local git repo.
* Fix ssh connection caching stop method to work with openssh 6.5p1,
which broke the old method.
* webapp: Added a "Sync now" item to each repository's menu.
* webapp: Use securemem for constant time auth token comparisons.
* copy --fast --to remote: Avoid printing anything for files that
are already believed to be present on the remote.
* Commands that allow specifying which repository to act on using
the repository's description will now fail when multiple repositories
match, rather than picking a repository at random.
(So will --in=)
* Better workaround for problem umasks when eg, setting up ssh keys.
* "standard" can now be used as a first-class keyword in preferred content
expressions. For example "standard or (include=otherdir/*)"
* groupwanted can be used in preferred content expressions.
* vicfg: Allows editing preferred content expressions for groups.
* Improve behavior when unable to parse a preferred content expression
(thanks, ion).
* metadata: Add --get
* metadata: Support --key option (and some other ones like --all)
* For each metadata field, there's now an automatically maintained
"$field-lastchanged" that gives the date of the last change to that
field. Also the "lastchanged" field for the date of the last change
to any of a file's metadata.
* unused: In direct mode, files that are deleted from the work tree
and so have no content present are no longer incorrectly detected as
unused.
* Avoid encoding errors when using the unused log file.
* map: Fix crash when one of the remotes of a repo is a local directory
that does not exist, or is not a git repo.
* repair: Improve memory usage when git fsck finds a great many broken
objects.
* Windows: Fix some filename encoding bugs.
* rsync special remote: Fix slashes when used on Windows.
-- Joey Hess <joeyh@debian.org> Thu, 20 Mar 2014 13:21:12 -0400
git-annex (5.20140306~bpo70+1) wheezy-backports; urgency=high git-annex (5.20140306~bpo70+1) wheezy-backports; urgency=high
* Updating backport to newest release. * Updating backport to newest release.

3
debian/control vendored
View file

@ -38,6 +38,9 @@ Build-Depends:
libghc-warp-tls-dev [i386 amd64 kfreebsd-i386 kfreebsd-amd64 powerpc sparc], libghc-warp-tls-dev [i386 amd64 kfreebsd-i386 kfreebsd-amd64 powerpc sparc],
libghc-wai-dev [i386 amd64 kfreebsd-i386 kfreebsd-amd64 powerpc sparc], libghc-wai-dev [i386 amd64 kfreebsd-i386 kfreebsd-amd64 powerpc sparc],
libghc-wai-logger-dev [i386 amd64 kfreebsd-i386 kfreebsd-amd64 powerpc sparc], libghc-wai-logger-dev [i386 amd64 kfreebsd-i386 kfreebsd-amd64 powerpc sparc],
libghc-securemem-dev,
libghc-byteable-dev,
libghc-dns-dev,
libghc-case-insensitive-dev, libghc-case-insensitive-dev,
libghc-http-types-dev, libghc-http-types-dev,
libghc-blaze-builder-dev, libghc-blaze-builder-dev,

View file

@ -1,4 +1,4 @@
[[!comment format=txt [[!comment format=mdwn
username="http://yarikoptic.myopenid.com/" username="http://yarikoptic.myopenid.com/"
nickname="site-myopenid" nickname="site-myopenid"
subject="Does it require the device to be rooted?" subject="Does it require the device to be rooted?"

View file

@ -0,0 +1,14 @@
[[!comment format=mdwn
username="severo"
ip="88.182.182.135"
subject="git-assistant and transfer repository"
date="2014-03-16T17:05:43Z"
content="""
In your comment http://git-annex.branchable.com/assistant/remote_sharing_walkthrough/#comment-f97efe1d05c0101232684b4e4edc4866, you describe a way to synchronize two devices using an intermediate USB drive configured as a \"transfer repository\".
I understand that in that case, the USB drive can only be used as a \"transmitter\", in a git repository form, not as a copy of the files structure. This means the files contained by the USB drive cannot be accessed without git/git-annnex.
Is there a way to use the USB drive as a \"client repository\" in order to allow synchronization, as described earlier, but also as a simple copy of the files, in order to access them from any device (opening them with windows in a cyber coffee for example).
Thanks
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.154"
subject="comment 7"
date="2014-03-17T19:50:48Z"
content="""
@severo the web app does not support setting up that use case. However, you can make a non-bare clone of your repository onto a removable drive, and if you do the assistant will use it just the same as if you'd set up a removable drive using the webapp. Note that you will need to run `git annex sync` inside that repository in order to update the tree it displays.
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="severo"
ip="95.152.107.168"
subject="comment 8"
date="2014-03-18T10:06:50Z"
content="""
Thansk @joeyh.name for your answer. Do you think this feature could be integrated into the git-annex assistant ?
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="severo"
ip="95.152.107.168"
subject="comment 9"
date="2014-03-18T11:16:19Z"
content="""
Some explanations in French on how to do: http://seenthis.net/messages/237648#message238202
"""]]

View file

@ -1,4 +1,4 @@
[[!comment format=txt [[!comment format=mdwn
username="https://www.google.com/accounts/o8/id?id=AItOawnJTqmRu1YCKS2Hsm4vtOflLhP4fU-k98w" username="https://www.google.com/accounts/o8/id?id=AItOawnJTqmRu1YCKS2Hsm4vtOflLhP4fU-k98w"
nickname="Ahmed" nickname="Ahmed"
subject="Customise conflict resolution behaviour" subject="Customise conflict resolution behaviour"

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="https://www.google.com/accounts/o8/id?id=AItOawn3p4i4lk_zMilvjnJ9sS6g2nerpgz0Fjc"
nickname="Matthias"
subject="Use automatic merge without syncing"
date="2014-03-20T10:03:41Z"
content="""
Is there a possibility to use the automatic merge logic without using \"git annex sync\"? I don't want to have the \"synced\"-branches, but the auto-conflict-resolution is very nice.
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.102"
subject="comment 3"
date="2014-03-20T16:10:10Z"
content="""
@Matthias `git annex merge` will do what you want, as long as you have git-annex 4.20130709 or newer.
"""]]

View file

@ -11,3 +11,6 @@ In order to handle the fact that the directory where pictures are saved is not c
In the log, there are many "too many open files" errors like these : In the log, there are many "too many open files" errors like these :
git:createProcess: runInteractiveProcess: pipe: resource exhausted (Too many open files) git:createProcess: runInteractiveProcess: pipe: resource exhausted (Too many open files)
[[!tag moreinfo]]
[[!meta title="too many open files on android"]]

View file

@ -0,0 +1,13 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="108.236.230.124"
subject="comment 10"
date="2014-03-10T17:34:56Z"
content="""
I've found the 1 second delay on failure to accept in the warp source code.
It's using Network.Socket.accept, which uses accept4 with NONBLOCK by default, but can be built without `HAVE_ACCEPT4` and in that case uses `accept` with blocking.
I've put in a patch to build network without accept4 support, and am rebuilding the arm autobuilder. This will take a while..
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.154"
subject="comment 11"
date="2014-03-11T03:02:21Z"
content="""
Autobuild is now updated with the accept fix.
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.102"
subject="ping?"
date="2014-03-19T20:29:12Z"
content="""
Could either greg or Schnouki please test with the current arm autobuild and see if you can connect to the webapp?
"""]]

View file

@ -0,0 +1,10 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.146"
subject="comment 5"
date="2014-03-06T18:12:57Z"
content="""
Again the accept message does not seem to be related to dbus. A dbus client has no reason to do that; a web server does. The use of `O_NONBLOCK` with accept4 seems likely to be the culprit to me.
How frequently is dbus mentioned in the log?
"""]]

View file

@ -0,0 +1,10 @@
[[!comment format=mdwn
username="http://schnouki.net/"
nickname="Schnouki"
subject="comment 6"
date="2014-03-07T08:52:12Z"
content="""
Agreed, the dbus and accept messages are probably unrelated. I just commented here because it's the same bug I'm encountering.
The dbus message only appears once in the log (shortly after startup). The accept messages appears every second.
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.146"
subject="comment 7"
date="2014-03-07T17:03:30Z"
content="""
Are you sure that the accept message happens every second? I don't see why the webapp would continue to try to bind a socket it it failed with a 1 second delay. (It does try 100 times if it fails, per [[!commit fe3009d83b08563875856152034e7c59a0c6ecca]], before ending with \"unable to bind to local socket\".)
"""]]

View file

@ -0,0 +1,10 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.146"
subject="comment 8"
date="2014-03-07T17:16:41Z"
content="""
greg has confirmed that he can connect to the webapp, but it never replies to http requests. So, this could be the port being bound, but the accept failing.
I don't know why it would retry the accept once per second, but this could be something in warp or the network library.
"""]]

View file

@ -0,0 +1,12 @@
[[!comment format=mdwn
username="http://schnouki.net/"
nickname="Schnouki"
subject="comment 9"
date="2014-03-09T16:29:26Z"
content="""
I did some more testing today.
I have this message when using either git-annex assistant or git-annex webapp. When running the webapp, I can connect to its port, but there's no response from git-annex (either from a browser or when using telnet to send a simple \"GET / HTTP/1.0\").
The accept message comes every second, the dbus one very minute (didn't test long enough last time, sorry about that).
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.146"
subject="comment 5"
date="2014-03-06T18:14:37Z"
content="""
auto-repair is only done if git fsck detects a problem. You can run git fsck yourself to see.
"""]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="https://www.google.com/accounts/o8/id?id=AItOawnX1msQxnLoSeu7q-i-c9BWghonsN7Qmns"
nickname="Jan Ulrich"
subject="comment 6"
date="2014-03-10T14:14:06Z"
content="""
I manually ran git fsck without problems but git-annex still wants to repair something.
"""]]

View file

@ -35,3 +35,6 @@ What this tells me is that any changes that occur whilst I am not networked are
git-annex version: 5.20131130-gc25be33 git-annex version: 5.20131130-gc25be33
> This was fixed in 5.20140127; the assistant now does a daily sweep of
> unused files to move them to backup repositories when possible. [[done]]
> --[[Joey]]

View file

@ -0,0 +1,20 @@
### Please describe the problem.
Bug Report doesn't work
### What steps will reproduce the problem?
### What version of git-annex are you using? On what operating system?
### Please provide any additional information below.
[[!format sh """
# If you can, paste a complete transcript of the problem occurring here.
# If the problem is with the git-annex assistant, paste in .git/annex/daemon.log
# End of transcript or log.
"""]]
[[fixed|done]] --[[Joey]]

View file

@ -19,3 +19,4 @@ I'm using 9e57edff287ac53fc4b1cefef7271e9ed17f2285 (Fri Feb 22 15:19:25 2013 +00
Ubuntu 12.10 x86_64 Ubuntu 12.10 x86_64
[[!tag /design/assistant]] [[!tag /design/assistant]]
[[!meta title="assistant should set up non-bare repos on removable drives, and update them when syncing with them"]]

View file

@ -31,3 +31,6 @@ I noticed the problem yesterday afternoon (Thu 24 Oct).
# End of transcript or log. # End of transcript or log.
"""]] """]]
> [[moreinfo]]; either I don't have enough information to work on this,
> or it might have just been user error. --[[Joey]]

View file

@ -22,3 +22,5 @@ fatal: Could not read from remote repository.
Please make sure you have the correct access rights Please make sure you have the correct access rights
and the repository exists. and the repository exists.
"""]] """]]
[[!meta title="xmpp syncing sometimes fails"]]

View file

@ -4,3 +4,5 @@ I did a "git annex add" of a bunch of files on a storage server with low IOPS, a
failed failed
How is that even possible, when the server is doing nothing else? How is that even possible, when the server is doing nothing else?
[[!tag moreinfo]]

View file

@ -0,0 +1,28 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.102"
subject="moreinfo"
date="2014-03-19T20:49:47Z"
content="""
What I don't understand about this is, how does `open` fail due to a file being locked? This is Linux, it doesn't have mandatory locking that I know of, and git-annex certianly doesn't use such a thing.
I really need a way to reproduce this and/or a strace. As it is, I've never seen this reported by anyone else and don't understand the failure mode at all.
The relevant part of the code seems to be here:
[[!format haskell \"\"\"
setJournalFile :: JournalLocked -> FilePath -> String -> Annex ()
setJournalFile _jl file content = do
tmp <- fromRepo gitAnnexTmpMiscDir
createAnnexDirectory =<< fromRepo gitAnnexJournalDir
createAnnexDirectory tmp
-- journal file is written atomically
jfile <- fromRepo $ journalFile file
let tmpfile = tmp </> takeFileName jfile
liftIO $ do
writeBinaryFile tmpfile content
moveFile tmpfile jfile
\"\"\"]]
While there is some ctnl locking going on, it locks a special sentinal file, not the file it's writing to.
"""]]

View file

@ -19,3 +19,5 @@ This is with git-annex installed on the remote server; without it the process ge
### What version of git-annex are you using? On what operating system? ### What version of git-annex are you using? On what operating system?
Latest nightly build on ubuntu 13.10 Latest nightly build on ubuntu 13.10
[[!tag moreinfo]]

View file

@ -68,3 +68,5 @@ Mac OS X Mountain Lion. git-annex files are from within the downloadable git-ann
Thanks for your help :) Thanks for your help :)
> This is a duplicate of [[Git_annexed_files_symlink_are_wrong_when_submodule_is_not_in_the_same_path]] [[done]] --[[Joey]]

View file

@ -0,0 +1,12 @@
Latest build for Mac OS X (both autobuild and release versions) does not contain webapp.
git annex version for OS X,
git-annex version: 5.20140306-g309a73c
build flags: Assistant Pairing Testsuite S3 WebDAV FsEvents XMPP DNS Feeds Quvi TDFA CryptoHash
key/value backends: SHA256E SHA1E SHA512E SHA224E SHA384E SKEIN256E SKEIN512E SHA256 SHA1 SHA512 SHA224 SHA384 SKEIN2 56 SKEIN512 WORM URL
remote types: git gcrypt S3 bup directory rsync web webdav tahoe glacier hook external
whereas on my Linux box build flags include webapp. On os x when I run git annex webapp it does nothing, just prints the help info.
> [[fixed|done]] --[[Joey]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="http://joeyh.name/"
ip="209.250.56.146"
subject="comment 1"
date="2014-03-07T15:17:16Z"
content="""
I've fixed the missing warp-tls dep on the autobuilder and updated the builds.
"""]]

View file

@ -62,3 +62,6 @@ My .gitconfig is as follows:
> to a more recent version of git. done --[[Joey]] > to a more recent version of git. done --[[Joey]]
>> Reopened, because the Linux autobuilds have been downgraded to Debian >> Reopened, because the Linux autobuilds have been downgraded to Debian
>> stable and have this problem again. --[[Joey]] >> stable and have this problem again. --[[Joey]]
>>> Closing again! Autobuilders all run unstable and will have a current
>>> git. [[done]] --[[Joey]]

View file

@ -228,3 +228,13 @@ Everything up-to-date
"""]] """]]
Well, I see that thing about "failed to lock". I can imagine that my 'killall git-annex' to kill a leftover process that was hanging around after I'd done git-annex assistant --stop might have left stale lock files, somewhere... but of course I only got as far as doing that because I was already encountering problems, just trying to return to the webapp. Well, I see that thing about "failed to lock". I can imagine that my 'killall git-annex' to kill a leftover process that was hanging around after I'd done git-annex assistant --stop might have left stale lock files, somewhere... but of course I only got as far as doing that because I was already encountering problems, just trying to return to the webapp.
> The original bug report seems to be a case of user confusion,
> and not a bug. (Although perhaps the UI is confusing?)
>
> The "resource exhausted" that came up later is quite likely the problem
> fixed in [[!commit 4d06037fdd44ba38fcd4c118d1e6330f06e22366]],
> which affected local git remotes.
>
> [[closing|done]]; I don't see any value keeping this open, I'm afraid.
> --[[Joey]]

View file

@ -366,3 +366,5 @@ Here is the crash report osx creates
# End of transcript or log. # End of transcript or log.
"""]] """]]
> Apparently this is [[fixed|done]] in the latest release. --[[Joey]]

View file

@ -0,0 +1,8 @@
[[!comment format=mdwn
username="https://www.google.com/accounts/o8/id?id=AItOawkLdR1fuu5aEz3s9VKTBKVMize_SmeNRJM"
nickname="David"
subject="Seems to be working now"
date="2014-03-12T02:36:59Z"
content="""
Just tried again off of the most recent osx release build and it appears to be working without crashing. Not sure what else you did but thanks!
"""]]

Some files were not shown because too many files have changed in this diff Show more