diff options
Diffstat (limited to 'Annex')
-rw-r--r-- | Annex/Branch.hs | 316 | ||||
-rw-r--r-- | Annex/Branch/Transitions.hs | 53 | ||||
-rw-r--r-- | Annex/CatFile.hs | 75 | ||||
-rw-r--r-- | Annex/CheckIgnore.hs | 2 | ||||
-rw-r--r-- | Annex/Content.hs | 35 | ||||
-rw-r--r-- | Annex/Content/Direct.hs | 4 | ||||
-rw-r--r-- | Annex/Direct.hs | 95 | ||||
-rw-r--r-- | Annex/Environment.hs | 2 | ||||
-rw-r--r-- | Annex/Exception.hs | 7 | ||||
-rw-r--r-- | Annex/FileMatcher.hs | 1 | ||||
-rw-r--r-- | Annex/Hook.hs | 42 | ||||
-rw-r--r-- | Annex/Journal.hs | 54 | ||||
-rw-r--r-- | Annex/Link.hs | 6 | ||||
-rw-r--r-- | Annex/Quvi.hs | 20 | ||||
-rw-r--r-- | Annex/Ssh.hs | 51 | ||||
-rw-r--r-- | Annex/TaggedPush.hs | 8 | ||||
-rw-r--r-- | Annex/UUID.hs | 22 | ||||
-rw-r--r-- | Annex/Url.hs | 27 | ||||
-rw-r--r-- | Annex/Version.hs | 18 | ||||
-rw-r--r-- | Annex/Wanted.hs | 2 |
20 files changed, 684 insertions, 156 deletions
diff --git a/Annex/Branch.hs b/Annex/Branch.hs index bc3736a9a..5978260a1 100644 --- a/Annex/Branch.hs +++ b/Annex/Branch.hs @@ -1,6 +1,6 @@ {- management of the git-annex branch - - - Copyright 2011-2012 Joey Hess <joey@kitenet.net> + - Copyright 2011-2013 Joey Hess <joey@kitenet.net> - - Licensed under the GNU GPL version 3 or higher. -} @@ -20,11 +20,16 @@ module Annex.Branch ( get, change, commit, + forceCommit, files, withIndex, + performTransitions, ) where import qualified Data.ByteString.Lazy.Char8 as L +import qualified Data.Set as S +import qualified Data.Map as M +import qualified Control.Exception as E import Common.Annex import Annex.BranchState @@ -32,6 +37,7 @@ import Annex.Journal import qualified Git import qualified Git.Command import qualified Git.Ref +import qualified Git.Sha import qualified Git.Branch import qualified Git.UnionMerge import qualified Git.UpdateIndex @@ -42,6 +48,13 @@ import Annex.CatFile import Annex.Perms import qualified Annex import Utility.Env +import Logs +import Logs.Transitions +import Logs.Trust.Pure +import Annex.ReplaceFile +import qualified Annex.Queue +import Annex.Branch.Transitions +import Annex.Exception {- Name of the branch that is used to store git-annex's information. -} name :: Git.Ref @@ -110,6 +123,9 @@ forceUpdate = updateTo =<< siblingBranches - later get staged, and might overwrite changes made during the merge. - This is only done if some of the Refs do need to be merged. - + - Also handles performing any Transitions that have not yet been + - performed, in either the local branch, or the Refs. + - - Returns True if any refs were merged in, False otherwise. -} updateTo :: [(Git.Ref, Git.Branch)] -> Annex Bool @@ -117,65 +133,71 @@ updateTo pairs = do -- ensure branch exists, and get its current ref branchref <- getBranch dirty <- journalDirty - (refs, branches) <- unzip <$> filterM isnewer pairs + ignoredrefs <- getIgnoredRefs + (refs, branches) <- unzip <$> filterM (isnewer ignoredrefs) pairs if null refs {- Even when no refs need to be merged, the index - may still be updated if the branch has gotten ahead - of the index. -} - then whenM (needUpdateIndex branchref) $ lockJournal $ do - forceUpdateIndex branchref + then whenM (needUpdateIndex branchref) $ lockJournal $ \jl -> do + forceUpdateIndex jl branchref {- When there are journalled changes - as well as the branch being updated, - a commit needs to be done. -} when dirty $ - go branchref True [] [] + go branchref True [] [] jl else lockJournal $ go branchref dirty refs branches return $ not $ null refs where - isnewer (r, _) = inRepo $ Git.Branch.changed fullname r - go branchref dirty refs branches = withIndex $ do - cleanjournal <- if dirty then stageJournal else return noop + isnewer ignoredrefs (r, _) + | S.member r ignoredrefs = return False + | otherwise = inRepo $ Git.Branch.changed fullname r + go branchref dirty refs branches jl = withIndex $ do + cleanjournal <- if dirty then stageJournal jl else return noop let merge_desc = if null branches then "update" else "merging " ++ unwords (map Git.Ref.describe branches) ++ " into " ++ show name + localtransitions <- parseTransitionsStrictly "local" + <$> getLocal transitionsLog unless (null branches) $ do showSideAction merge_desc - mergeIndex refs - ff <- if dirty - then return False - else inRepo $ Git.Branch.fastForward fullname refs - if ff - then updateIndex branchref - else commitBranch branchref merge_desc - (nub $ fullname:refs) + mergeIndex jl refs + let commitrefs = nub $ fullname:refs + unlessM (handleTransitions jl localtransitions commitrefs) $ do + ff <- if dirty + then return False + else inRepo $ Git.Branch.fastForward fullname refs + if ff + then updateIndex jl branchref + else commitIndex jl branchref merge_desc commitrefs liftIO cleanjournal {- Gets the content of a file, which may be in the journal, or in the index - (and committed to the branch). - - Updates the branch if necessary, to ensure the most up-to-date available - - content is available. + - content is returned. - - Returns an empty string if the file doesn't exist yet. -} get :: FilePath -> Annex String get file = do update - get' file + getLocal file {- Like get, but does not merge the branch, so the info returned may not - reflect changes in remotes. - (Changing the value this returns, and then merging is always the - same as using get, and then changing its value.) -} -getStale :: FilePath -> Annex String -getStale = get' - -get' :: FilePath -> Annex String -get' file = go =<< getJournalFile file +getLocal :: FilePath -> Annex String +getLocal file = go =<< getJournalFileStale file where go (Just journalcontent) = return journalcontent - go Nothing = withIndex $ L.unpack <$> catFile fullname file + go Nothing = getRaw file + +getRaw :: FilePath -> Annex String +getRaw file = withIndex $ L.unpack <$> catFile fullname file {- Applies a function to modifiy the content of a file. - @@ -183,18 +205,23 @@ get' file = go =<< getJournalFile file - modifes the current content of the file on the branch. -} change :: FilePath -> (String -> String) -> Annex () -change file a = lockJournal $ a <$> getStale file >>= set file +change file a = lockJournal $ \jl -> a <$> getLocal file >>= set jl file {- Records new content of a file into the journal -} -set :: FilePath -> String -> Annex () +set :: JournalLocked -> FilePath -> String -> Annex () set = setJournalFile {- Stages the journal, and commits staged changes to the branch. -} commit :: String -> Annex () -commit message = whenM journalDirty $ lockJournal $ do - cleanjournal <- stageJournal +commit = whenM journalDirty . forceCommit + +{- Commits the current index to the branch even without any journalleda + - changes. -} +forceCommit :: String -> Annex () +forceCommit message = lockJournal $ \jl -> do + cleanjournal <- stageJournal jl ref <- getBranch - withIndex $ commitBranch ref message [fullname] + withIndex $ commitIndex jl ref message [fullname] liftIO cleanjournal {- Commits the staged changes in the index to the branch. @@ -215,17 +242,18 @@ commit message = whenM journalDirty $ lockJournal $ do - previous point, though getting it a long time ago makes the race - more likely to occur. -} -commitBranch :: Git.Ref -> String -> [Git.Ref] -> Annex () -commitBranch branchref message parents = do +commitIndex :: JournalLocked -> Git.Ref -> String -> [Git.Ref] -> Annex () +commitIndex jl branchref message parents = do showStoringStateAction - commitBranch' branchref message parents -commitBranch' :: Git.Ref -> String -> [Git.Ref] -> Annex () -commitBranch' branchref message parents = do - updateIndex branchref + commitIndex' jl branchref message parents +commitIndex' :: JournalLocked -> Git.Ref -> String -> [Git.Ref] -> Annex () +commitIndex' jl branchref message parents = do + updateIndex jl branchref committedref <- inRepo $ Git.Branch.commit message fullname parents setIndexSha committedref parentrefs <- commitparents <$> catObject committedref - when (racedetected branchref parentrefs) $ + when (racedetected branchref parentrefs) $ do + liftIO $ print ("race detected", branchref, parentrefs, "committing", (branchref, parents)) fixrace committedref parentrefs where -- look for "parent ref" lines and return the refs @@ -244,8 +272,8 @@ commitBranch' branchref message parents = do {- To recover from the race, union merge the lost refs - into the index, and recommit on top of the bad commit. -} fixrace committedref lostrefs = do - mergeIndex lostrefs - commitBranch committedref racemessage [committedref] + mergeIndex jl lostrefs + commitIndex jl committedref racemessage [committedref] racemessage = message ++ " (recovery from race)" @@ -253,13 +281,17 @@ commitBranch' branchref message parents = do files :: Annex [FilePath] files = do update - withIndex $ do - bfiles <- inRepo $ Git.Command.pipeNullSplitZombie - [ Params "ls-tree --name-only -r -z" - , Param $ show fullname - ] - jfiles <- getJournalledFiles - return $ jfiles ++ bfiles + (++) + <$> branchFiles + <*> getJournalledFilesStale + +{- Files in the branch, not including any from journalled changes, + - and without updating the branch. -} +branchFiles :: Annex [FilePath] +branchFiles = withIndex $ inRepo $ Git.Command.pipeNullSplitZombie + [ Params "ls-tree --name-only -r -z" + , Param $ show fullname + ] {- Populates the branch's index file with the current branch contents. - @@ -273,11 +305,27 @@ genIndex g = Git.UpdateIndex.streamUpdateIndex g {- Merges the specified refs into the index. - Any changes staged in the index will be preserved. -} -mergeIndex :: [Git.Ref] -> Annex () -mergeIndex branches = do +mergeIndex :: JournalLocked -> [Git.Ref] -> Annex () +mergeIndex jl branches = do + prepareModifyIndex jl h <- catFileHandle inRepo $ \g -> Git.UnionMerge.mergeIndex h g branches +{- Removes any stale git lock file, to avoid git falling over when + - updating the index. + - + - Since all modifications of the index are performed inside this module, + - and only when the journal is locked, the fact that the journal has to be + - locked when this is called ensures that no other process is currently + - modifying the index. So any index.lock file must be stale, caused + - by git running when the system crashed, or the repository's disk was + - removed, etc. + -} +prepareModifyIndex :: JournalLocked -> Annex () +prepareModifyIndex _jl = do + index <- fromRepo gitAnnexIndex + void $ liftIO $ tryIO $ removeFile $ index ++ ".lock" + {- Runs an action using the branch's index file. -} withIndex :: Annex a -> Annex a withIndex = withIndex' False @@ -299,15 +347,15 @@ withIndex' bootstrapping a = do #endif let g' = g { gitEnv = Just $ ("GIT_INDEX_FILE", f):e } - Annex.changeState $ \s -> s { Annex.repo = g' } - checkIndexOnce $ unlessM (liftIO $ doesFileExist f) $ do - unless bootstrapping create - liftIO $ createDirectoryIfMissing True $ takeDirectory f - unless bootstrapping $ inRepo genIndex - r <- a + r <- tryAnnex $ do + Annex.changeState $ \s -> s { Annex.repo = g' } + checkIndexOnce $ unlessM (liftIO $ doesFileExist f) $ do + unless bootstrapping create + liftIO $ createDirectoryIfMissing True $ takeDirectory f + unless bootstrapping $ inRepo genIndex + a Annex.changeState $ \s -> s { Annex.repo = (Annex.repo s) { gitEnv = gitEnv g} } - - return r + either E.throw return r {- Updates the branch's index to reflect the current contents of the branch. - Any changes staged in the index will be preserved. @@ -315,40 +363,48 @@ withIndex' bootstrapping a = do - Compares the ref stored in the lock file with the current - ref of the branch to see if an update is needed. -} -updateIndex :: Git.Ref -> Annex () -updateIndex branchref = whenM (needUpdateIndex branchref) $ - forceUpdateIndex branchref +updateIndex :: JournalLocked -> Git.Ref -> Annex () +updateIndex jl branchref = whenM (needUpdateIndex branchref) $ + forceUpdateIndex jl branchref -forceUpdateIndex :: Git.Ref -> Annex () -forceUpdateIndex branchref = do - withIndex $ mergeIndex [fullname] +forceUpdateIndex :: JournalLocked -> Git.Ref -> Annex () +forceUpdateIndex jl branchref = do + withIndex $ mergeIndex jl [fullname] setIndexSha branchref {- Checks if the index needs to be updated. -} needUpdateIndex :: Git.Ref -> Annex Bool needUpdateIndex branchref = do - lock <- fromRepo gitAnnexIndexLock - lockref <- Git.Ref . firstLine <$> - liftIO (catchDefaultIO "" $ readFileStrict lock) - return (lockref /= branchref) + f <- fromRepo gitAnnexIndexStatus + committedref <- Git.Ref . firstLine <$> + liftIO (catchDefaultIO "" $ readFileStrict f) + return (committedref /= branchref) {- Record that the branch's index has been updated to correspond to a - given ref of the branch. -} setIndexSha :: Git.Ref -> Annex () setIndexSha ref = do - lock <- fromRepo gitAnnexIndexLock - liftIO $ writeFile lock $ show ref ++ "\n" - setAnnexPerm lock + f <- fromRepo gitAnnexIndexStatus + liftIO $ writeFile f $ show ref ++ "\n" + setAnnexPerm f {- Stages the journal into the index and returns an action that will - clean up the staged journal files, which should only be run once - - the index has been committed to the branch. Should be run within - - lockJournal, to prevent others from modifying the journal. -} -stageJournal :: Annex (IO ()) -stageJournal = withIndex $ do + - the index has been committed to the branch. + - + - Before staging, this removes any existing git index file lock. + - This is safe to do because stageJournal is the only thing that + - modifies this index file, and only one can run at a time, because + - the journal is locked. So any existing git index file lock must be + - stale, and the journal must contain any data that was in the process + - of being written to the index file when it crashed. + -} +stageJournal :: JournalLocked -> Annex (IO ()) +stageJournal jl = withIndex $ do + prepareModifyIndex jl g <- gitRepo let dir = gitAnnexJournalDir g - fs <- getJournalFiles + fs <- getJournalFiles jl liftIO $ do h <- hashObjectStart g Git.UpdateIndex.streamUpdateIndex g @@ -361,3 +417,117 @@ stageJournal = withIndex $ do sha <- hashFile h path streamer $ Git.UpdateIndex.updateIndexLine sha FileBlob (asTopFilePath $ fileJournal file) + +{- This is run after the refs have been merged into the index, + - but before the result is committed to the branch. + - (Which is why it's passed the contents of the local branches's + - transition log before that merge took place.) + - + - When the refs contain transitions that have not yet been done locally, + - the transitions are performed on the index, and a new branch + - is created from the result. + - + - When there are transitions recorded locally that have not been done + - to the remote refs, the transitions are performed in the index, + - and committed to the existing branch. In this case, the untransitioned + - remote refs cannot be merged into the branch (since transitions + - throw away history), so they are added to the list of refs to ignore, + - to avoid re-merging content from them again. + -} +handleTransitions :: JournalLocked -> Transitions -> [Git.Ref] -> Annex Bool +handleTransitions jl localts refs = do + m <- M.fromList <$> mapM getreftransition refs + let remotets = M.elems m + if all (localts ==) remotets + then return False + else do + let allts = combineTransitions (localts:remotets) + let (transitionedrefs, untransitionedrefs) = + partition (\r -> M.lookup r m == Just allts) refs + performTransitionsLocked jl allts (localts /= allts) transitionedrefs + ignoreRefs untransitionedrefs + return True + where + getreftransition ref = do + ts <- parseTransitionsStrictly "remote" . L.unpack + <$> catFile ref transitionsLog + return (ref, ts) + +ignoreRefs :: [Git.Ref] -> Annex () +ignoreRefs rs = do + old <- getIgnoredRefs + let s = S.unions [old, S.fromList rs] + f <- fromRepo gitAnnexIgnoredRefs + replaceFile f $ \tmp -> liftIO $ writeFile tmp $ + unlines $ map show $ S.elems s + +getIgnoredRefs :: Annex (S.Set Git.Ref) +getIgnoredRefs = S.fromList . mapMaybe Git.Sha.extractSha . lines <$> content + where + content = do + f <- fromRepo gitAnnexIgnoredRefs + liftIO $ catchDefaultIO "" $ readFile f + +{- Performs the specified transitions on the contents of the index file, + - commits it to the branch, or creates a new branch. + -} +performTransitions :: Transitions -> Bool -> [Ref] -> Annex () +performTransitions ts neednewlocalbranch transitionedrefs = lockJournal $ \jl -> + performTransitionsLocked jl ts neednewlocalbranch transitionedrefs +performTransitionsLocked :: JournalLocked -> Transitions -> Bool -> [Ref] -> Annex () +performTransitionsLocked jl ts neednewlocalbranch transitionedrefs = do + -- For simplicity & speed, we're going to use the Annex.Queue to + -- update the git-annex branch, while it usually holds changes + -- for the head branch. Flush any such changes. + Annex.Queue.flush + withIndex $ do + prepareModifyIndex jl + run $ mapMaybe getTransitionCalculator $ transitionList ts + Annex.Queue.flush + if neednewlocalbranch + then do + committedref <- inRepo $ Git.Branch.commit message fullname transitionedrefs + setIndexSha committedref + else do + ref <- getBranch + commitIndex jl ref message (nub $ fullname:transitionedrefs) + where + message + | neednewlocalbranch && null transitionedrefs = "new branch for transition " ++ tdesc + | otherwise = "continuing transition " ++ tdesc + tdesc = show $ map describeTransition $ transitionList ts + + {- The changes to make to the branch are calculated and applied to + - the branch directly, rather than going through the journal, + - which would be innefficient. (And the journal is not designed + - to hold changes to every file in the branch at once.) + - + - When a file in the branch is changed by transition code, + - that value is remembered and fed into the code for subsequent + - transitions. + -} + run [] = noop + run changers = do + trustmap <- calcTrustMap <$> getRaw trustLog + fs <- branchFiles + hasher <- inRepo hashObjectStart + forM_ fs $ \f -> do + content <- getRaw f + apply changers hasher f content trustmap + liftIO $ hashObjectStop hasher + apply [] _ _ _ _ = return () + apply (changer:rest) hasher file content trustmap = + case changer file content trustmap of + RemoveFile -> do + Annex.Queue.addUpdateIndex + =<< inRepo (Git.UpdateIndex.unstageFile file) + -- File is deleted; can't run any other + -- transitions on it. + return () + ChangeFile content' -> do + sha <- inRepo $ hashObject BlobObject content' + Annex.Queue.addUpdateIndex $ Git.UpdateIndex.pureStreamer $ + Git.UpdateIndex.updateIndexLine sha FileBlob (asTopFilePath file) + apply rest hasher file content' trustmap + PreserveFile -> + apply rest hasher file content trustmap diff --git a/Annex/Branch/Transitions.hs b/Annex/Branch/Transitions.hs new file mode 100644 index 000000000..90002de62 --- /dev/null +++ b/Annex/Branch/Transitions.hs @@ -0,0 +1,53 @@ +{- git-annex branch transitions + - + - Copyright 2013 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Annex.Branch.Transitions ( + FileTransition(..), + getTransitionCalculator +) where + +import Logs +import Logs.Transitions +import Logs.UUIDBased as UUIDBased +import Logs.Presence.Pure as Presence +import Types.TrustLevel +import Types.UUID + +import qualified Data.Map as M + +data FileTransition + = ChangeFile String + | RemoveFile + | PreserveFile + +type TransitionCalculator = FilePath -> String -> TrustMap -> FileTransition + +getTransitionCalculator :: Transition -> Maybe TransitionCalculator +getTransitionCalculator ForgetGitHistory = Nothing +getTransitionCalculator ForgetDeadRemotes = Just dropDead + +dropDead :: FilePath -> String -> TrustMap -> FileTransition +dropDead f content trustmap = case getLogVariety f of + Just UUIDBasedLog -> ChangeFile $ + UUIDBased.showLog id $ dropDeadFromUUIDBasedLog trustmap $ UUIDBased.parseLog Just content + Just (PresenceLog _) -> + let newlog = Presence.compactLog $ dropDeadFromPresenceLog trustmap $ Presence.parseLog content + in if null newlog + then RemoveFile + else ChangeFile $ Presence.showLog newlog + Nothing -> PreserveFile + +dropDeadFromUUIDBasedLog :: TrustMap -> UUIDBased.Log String -> UUIDBased.Log String +dropDeadFromUUIDBasedLog trustmap = M.filterWithKey $ notDead trustmap . const + +{- Presence logs can contain UUIDs or other values. Any line that matches + - a dead uuid is dropped; any other values are passed through. -} +dropDeadFromPresenceLog :: TrustMap -> [Presence.LogLine] -> [Presence.LogLine] +dropDeadFromPresenceLog trustmap = filter $ notDead trustmap (toUUID . Presence.info) + +notDead :: TrustMap -> (v -> UUID) -> v -> Bool +notDead trustmap a v = M.findWithDefault SemiTrusted (a v) trustmap /= DeadTrusted diff --git a/Annex/CatFile.hs b/Annex/CatFile.hs index f90e74509..812d032c6 100644 --- a/Annex/CatFile.hs +++ b/Annex/CatFile.hs @@ -8,14 +8,17 @@ module Annex.CatFile ( catFile, catObject, + catTree, catObjectDetails, catFileHandle, catKey, catKeyFile, + catKeyFileHEAD, ) where import qualified Data.ByteString.Lazy as L import qualified Data.Map as M +import System.PosixCompat.Types import Common.Annex import qualified Git @@ -23,6 +26,8 @@ import qualified Git.CatFile import qualified Annex import Git.Types import Git.FilePath +import Git.FileMode +import qualified Git.Ref catFile :: Git.Branch -> FilePath -> Annex L.ByteString catFile branch file = do @@ -34,7 +39,12 @@ catObject ref = do h <- catFileHandle liftIO $ Git.CatFile.catObject h ref -catObjectDetails :: Git.Ref -> Annex (Maybe (L.ByteString, Sha)) +catTree :: Git.Ref -> Annex [(FilePath, FileMode)] +catTree ref = do + h <- catFileHandle + liftIO $ Git.CatFile.catTree h ref + +catObjectDetails :: Git.Ref -> Annex (Maybe (L.ByteString, Sha, ObjectType)) catObjectDetails ref = do h <- catFileHandle liftIO $ Git.CatFile.catObjectDetails h ref @@ -54,18 +64,51 @@ catFileHandle = do Annex.changeState $ \s -> s { Annex.catfilehandles = m' } return h -{- From the Sha or Ref of a symlink back to the key. -} -catKey :: Ref -> Annex (Maybe Key) -catKey ref = do - l <- fromInternalGitPath . encodeW8 . L.unpack <$> catObject ref - return $ if isLinkToAnnex l - then fileKey $ takeFileName l - else Nothing +{- From the Sha or Ref of a symlink back to the key. + - + - Requires a mode witness, to guarantee that the file is a symlink. + -} +catKey :: Ref -> FileMode -> Annex (Maybe Key) +catKey = catKey' True -{- From a file in the repository back to the key. +catKey' :: Bool -> Ref -> FileMode -> Annex (Maybe Key) +catKey' modeguaranteed ref mode + | isSymLink mode = do + l <- fromInternalGitPath . encodeW8 . L.unpack <$> get + return $ if isLinkToAnnex l + then fileKey $ takeFileName l + else Nothing + | otherwise = return Nothing + where + -- If the mode is not guaranteed to be correct, avoid + -- buffering the whole file content, which might be large. + -- 8192 is enough if it really is a symlink. + get + | modeguaranteed = catObject ref + | otherwise = L.take 8192 <$> catObject ref + +{- Looks up the file mode corresponding to the Ref using the running + - cat-file. - - - Prefixing the file with ./ makes this work even if in a subdirectory - - of a repo. + - Currently this always has to look in HEAD, because cat-file --batch + - does not offer a way to specify that we want to look up a tree object + - in the index. So if the index has a file staged not as a symlink, + - and it is a symlink in head, the wrong mode is gotten. + - Also, we have to assume the file is a symlink if it's not yet committed + - to HEAD. For these reasons, modeguaranteed is not set. + -} +catKeyChecked :: Bool -> Ref -> Annex (Maybe Key) +catKeyChecked needhead ref@(Ref r) = + catKey' False ref =<< findmode <$> catTree treeref + where + pathparts = split "/" r + dir = intercalate "/" $ take (length pathparts - 1) pathparts + file = fromMaybe "" $ lastMaybe pathparts + treeref = Ref $ if needhead then "HEAD" ++ dir ++ "/" else dir ++ "/" + findmode = fromMaybe symLinkMode . headMaybe . + map snd . filter (\p -> fst p == file) + +{- From a file in the repository back to the key. - - Ideally, this should reflect the key that's staged in the index, - not the key that's committed to HEAD. Unfortunately, git cat-file @@ -75,7 +118,8 @@ catKey ref = do - - For command-line git-annex use, that doesn't matter. It's perfectly - reasonable for things staged in the index after the currently running - - git-annex process to not be noticed by it. + - git-annex process to not be noticed by it. However, we do want to see + - what's in the index, since it may have uncommitted changes not in HEAD> - - For the assistant, this is much more of a problem, since it commits - files and then needs to be able to immediately look up their keys. @@ -87,6 +131,9 @@ catKey ref = do -} catKeyFile :: FilePath -> Annex (Maybe Key) catKeyFile f = ifM (Annex.getState Annex.daemon) - ( catKey $ Ref $ "HEAD:./" ++ f - , catKey $ Ref $ ":./" ++ f + ( catKeyFileHEAD f + , catKeyChecked True $ Git.Ref.fileRef f ) + +catKeyFileHEAD :: FilePath -> Annex (Maybe Key) +catKeyFileHEAD f = catKeyChecked False $ Git.Ref.fileFromRef Git.Ref.headRef f diff --git a/Annex/CheckIgnore.hs b/Annex/CheckIgnore.hs index e5626557d..d45e652bc 100644 --- a/Annex/CheckIgnore.hs +++ b/Annex/CheckIgnore.hs @@ -25,7 +25,7 @@ checkIgnoreHandle :: Annex (Maybe Git.CheckIgnoreHandle) checkIgnoreHandle = maybe startup return =<< Annex.getState Annex.checkignorehandle where startup = do - v <- inRepo $ Git.checkIgnoreStart + v <- inRepo Git.checkIgnoreStart when (isNothing v) $ warning "The installed version of git is too old for .gitignores to be honored by git-annex." Annex.changeState $ \s -> s { Annex.checkignorehandle = Just v } diff --git a/Annex/Content.hs b/Annex/Content.hs index 01ad6f96f..66ca7be18 100644 --- a/Annex/Content.hs +++ b/Annex/Content.hs @@ -30,6 +30,7 @@ module Annex.Content ( freezeContent, thawContent, cleanObjectLoc, + dirKeys, ) where import System.IO.Unsafe (unsafeInterleaveIO) @@ -43,7 +44,7 @@ import qualified Annex.Queue import qualified Annex.Branch import Utility.DiskFree import Utility.FileMode -import qualified Utility.Url as Url +import qualified Annex.Url as Url import Types.Key import Utility.DataUnits import Utility.CopyFile @@ -275,10 +276,11 @@ moveAnnex key src = withObjectLoc key storeobject storedirect thawContentDir =<< calcRepo (gitAnnexLocation key) thawContent src v <- isAnnexLink f - if (Just key == v) + if Just key == v then do updateInodeCache key src replaceFile f $ liftIO . moveFile src + chmodContent f forM_ fs $ addContentWhenNotPresent key f else ifM (goodContent key f) @@ -457,7 +459,7 @@ downloadUrl urls file = go =<< annexWebDownloadCommand <$> Annex.getGitConfig go Nothing = do opts <- map Param . annexWebOptions <$> Annex.getGitConfig headers <- getHttpHeaders - liftIO $ anyM (\u -> Url.download u headers opts file) urls + anyM (\u -> Url.withUserAgent $ Url.download u headers opts file) urls go (Just basecmd) = liftIO $ anyM (downloadcmd basecmd) urls downloadcmd basecmd url = boolSystem "sh" [Param "-c", Param $ gencmd url basecmd] @@ -500,6 +502,18 @@ freezeContent file = unlessM crippledFileSystem $ removeModes writeModes . addModes [ownerReadMode] +{- Adjusts read mode of annexed file per core.sharedRepository setting. -} +chmodContent :: FilePath -> Annex () +chmodContent file = unlessM crippledFileSystem $ + liftIO . go =<< fromRepo getSharedRepository + where + go GroupShared = modifyFileMode file $ + addModes [ownerReadMode, groupReadMode] + go AllShared = modifyFileMode file $ + addModes readModes + go _ = modifyFileMode file $ + addModes [ownerReadMode] + {- Allows writing to an annexed file that freezeContent was called on - before. -} thawContent :: FilePath -> Annex () @@ -509,3 +523,18 @@ thawContent file = unlessM crippledFileSystem $ go GroupShared = groupWriteRead file go AllShared = groupWriteRead file go _ = allowWrite file + +{- Finds files directly inside a directory like gitAnnexBadDir + - (not in subdirectories) and returns the corresponding keys. -} +dirKeys :: (Git.Repo -> FilePath) -> Annex [Key] +dirKeys dirspec = do + dir <- fromRepo dirspec + ifM (liftIO $ doesDirectoryExist dir) + ( do + contents <- liftIO $ getDirectoryContents dir + files <- liftIO $ filterM doesFileExist $ + map (dir </>) contents + return $ mapMaybe (fileKey . takeFileName) files + , return [] + ) + diff --git a/Annex/Content/Direct.hs b/Annex/Content/Direct.hs index 6da7fab52..b0b8621e9 100644 --- a/Annex/Content/Direct.hs +++ b/Annex/Content/Direct.hs @@ -101,7 +101,7 @@ addAssociatedFile key file = do else file':files {- Associated files are always stored relative to the top of the repository. - - The input FilePath is relative to the CWD. -} + - The input FilePath is relative to the CWD, or is absolute. -} normaliseAssociatedFile :: FilePath -> Annex FilePath normaliseAssociatedFile file = do top <- fromRepo Git.repoPath @@ -199,7 +199,7 @@ compareInodeCachesWith = ifM inodesChanged ( return Weakly, return Strongly ) addContentWhenNotPresent :: Key -> FilePath -> FilePath -> Annex () addContentWhenNotPresent key contentfile associatedfile = do v <- isAnnexLink associatedfile - when (Just key == v) $ do + when (Just key == v) $ replaceFile associatedfile $ liftIO . void . copyFileExternal contentfile updateInodeCache key associatedfile diff --git a/Annex/Direct.hs b/Annex/Direct.hs index d2e2cdc00..d4b73860e 100644 --- a/Annex/Direct.hs +++ b/Annex/Direct.hs @@ -8,14 +8,19 @@ module Annex.Direct where import Common.Annex +import qualified Annex import qualified Git import qualified Git.LsFiles import qualified Git.Merge import qualified Git.DiffTree as DiffTree +import qualified Git.Config +import qualified Git.Ref +import qualified Git.Branch import Git.Sha +import Git.FilePath import Git.Types +import Config import Annex.CatFile -import Utility.FileMode import qualified Annex.Queue import Logs.Location import Backend @@ -45,8 +50,8 @@ stageDirect = do {- Determine what kind of modified or deleted file this is, as - efficiently as we can, by getting any key that's associated - with it in git, as well as its stat info. -} - go (file, Just sha) = do - shakey <- catKey sha + go (file, Just sha, Just mode) = do + shakey <- catKey sha mode mstat <- liftIO $ catchMaybeIO $ getSymbolicLinkStatus file filekey <- isAnnexLink file case (shakey, filekey, mstat, toInodeCache =<< mstat) of @@ -123,6 +128,8 @@ addDirect file cache = do -} mergeDirect :: FilePath -> Git.Ref -> Git.Repo -> IO Bool mergeDirect d branch g = do + whenM (doesDirectoryExist d) $ + removeDirectoryRecursive d createDirectoryIfMissing True d let g' = g { location = Local { gitdir = Git.localGitDir g, worktree = Just d } } Git.Merge.mergeNonInteractive branch g' @@ -135,23 +142,22 @@ mergeDirect d branch g = do mergeDirectCleanup :: FilePath -> Git.Ref -> Git.Ref -> Annex () mergeDirectCleanup d oldsha newsha = do (items, cleanup) <- inRepo $ DiffTree.diffTreeRecursive oldsha newsha - forM_ items updated + makeabs <- flip fromTopFilePath <$> gitRepo + forM_ items (updated makeabs) void $ liftIO cleanup liftIO $ removeDirectoryRecursive d where - updated item = do + updated makeabs item = do + let f = makeabs (DiffTree.file item) void $ tryAnnex $ - go DiffTree.srcsha DiffTree.srcmode moveout moveout_raw + go f DiffTree.srcsha DiffTree.srcmode moveout moveout_raw void $ tryAnnex $ - go DiffTree.dstsha DiffTree.dstmode movein movein_raw + go f DiffTree.dstsha DiffTree.dstmode movein movein_raw where - go getsha getmode a araw + go f getsha getmode a araw | getsha item == nullSha = noop - | isSymLink (getmode item) = - maybe (araw f) (\k -> void $ a k f) - =<< catKey (getsha item) - | otherwise = araw f - f = DiffTree.file item + | otherwise = maybe (araw f) (\k -> void $ a k f) + =<< catKey (getsha item) (getmode item) moveout = removeDirect @@ -230,3 +236,66 @@ changedDirect oldk f = do locs <- removeAssociatedFile oldk f whenM (pure (null locs) <&&> not <$> inAnnex oldk) $ logStatus oldk InfoMissing + +{- Enable/disable direct mode. -} +setDirect :: Bool -> Annex () +setDirect wantdirect = do + if wantdirect + then do + switchHEAD + setbare + else do + setbare + switchHEADBack + setConfig (annexConfig "direct") val + Annex.changeGitConfig $ \c -> c { annexDirect = wantdirect } + where + val = Git.Config.boolConfig wantdirect + setbare = setConfig (ConfigKey Git.Config.coreBare) val + +{- Since direct mode sets core.bare=true, incoming pushes could change + - the currently checked out branch. To avoid this problem, HEAD + - is changed to a internal ref that nothing is going to push to. + - + - For refs/heads/master, use refs/heads/annex/direct/master; + - this way things that show HEAD (eg shell prompts) will + - hopefully show just "master". -} +directBranch :: Ref -> Ref +directBranch orighead = case split "/" $ show orighead of + ("refs":"heads":"annex":"direct":_) -> orighead + ("refs":"heads":rest) -> + Ref $ "refs/heads/annex/direct/" ++ intercalate "/" rest + _ -> Ref $ "refs/heads/" ++ show (Git.Ref.base orighead) + +{- Converts a directBranch back to the original branch. + - + - Any other ref is left unchanged. + -} +fromDirectBranch :: Ref -> Ref +fromDirectBranch directhead = case split "/" $ show directhead of + ("refs":"heads":"annex":"direct":rest) -> + Ref $ "refs/heads/" ++ intercalate "/" rest + _ -> directhead + +switchHEAD :: Annex () +switchHEAD = maybe noop switch =<< inRepo Git.Branch.currentUnsafe + where + switch orighead = do + let newhead = directBranch orighead + maybe noop (inRepo . Git.Branch.update newhead) + =<< inRepo (Git.Ref.sha orighead) + inRepo $ Git.Branch.checkout newhead + +switchHEADBack :: Annex () +switchHEADBack = maybe noop switch =<< inRepo Git.Branch.currentUnsafe + where + switch currhead = do + let orighead = fromDirectBranch currhead + v <- inRepo $ Git.Ref.sha currhead + case v of + Just headsha + | orighead /= currhead -> do + inRepo $ Git.Branch.update orighead headsha + inRepo $ Git.Branch.checkout orighead + inRepo $ Git.Branch.delete currhead + _ -> inRepo $ Git.Branch.checkout orighead diff --git a/Annex/Environment.hs b/Annex/Environment.hs index ae5a5646f..f22c5f2d4 100644 --- a/Annex/Environment.hs +++ b/Annex/Environment.hs @@ -32,7 +32,7 @@ import Utility.Env checkEnvironment :: Annex () checkEnvironment = do gitusername <- fromRepo $ Git.Config.getMaybe "user.name" - when (gitusername == Nothing || gitusername == Just "") $ + when (isNothing gitusername || gitusername == Just "") $ liftIO checkEnvironmentIO checkEnvironmentIO :: IO () diff --git a/Annex/Exception.hs b/Annex/Exception.hs index 99466a851..aaa6811a5 100644 --- a/Annex/Exception.hs +++ b/Annex/Exception.hs @@ -13,6 +13,7 @@ module Annex.Exception ( bracketIO, tryAnnex, + tryAnnexIO, throwAnnex, catchAnnex, ) where @@ -24,12 +25,16 @@ import Common.Annex {- Runs an Annex action, with setup and cleanup both in the IO monad. -} bracketIO :: IO v -> (v -> IO b) -> (v -> Annex a) -> Annex a -bracketIO setup cleanup go = M.bracket (liftIO setup) (liftIO . cleanup) go +bracketIO setup cleanup = M.bracket (liftIO setup) (liftIO . cleanup) {- try in the Annex monad -} tryAnnex :: Annex a -> Annex (Either SomeException a) tryAnnex = M.try +{- try in the Annex monad, but only catching IO exceptions -} +tryAnnexIO :: Annex a -> Annex (Either IOException a) +tryAnnexIO = M.try + {- throw in the Annex monad -} throwAnnex :: Exception e => e -> Annex a throwAnnex = M.throw diff --git a/Annex/FileMatcher.hs b/Annex/FileMatcher.hs index 3abba1055..cded857a2 100644 --- a/Annex/FileMatcher.hs +++ b/Annex/FileMatcher.hs @@ -13,6 +13,7 @@ import Common.Annex import Limit import Utility.Matcher import Types.Group +import Types.Limit import Logs.Group import Logs.Remote import Annex.UUID diff --git a/Annex/Hook.hs b/Annex/Hook.hs new file mode 100644 index 000000000..7301a0958 --- /dev/null +++ b/Annex/Hook.hs @@ -0,0 +1,42 @@ +{- git-annex git hooks + - + - Note that it's important that the scripts not change, otherwise + - removing old hooks using an old version of the script would fail. + - + - Copyright 2013 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Annex.Hook where + +import Common.Annex +import qualified Git.Hook as Git +import Utility.Shell +import Config + +preCommitHook :: Git.Hook +preCommitHook = Git.Hook "pre-commit" (mkHookScript "git annex pre-commit .") + +mkHookScript :: String -> String +mkHookScript s = unlines + [ shebang_local + , "# automatically configured by git-annex" + , s + ] + +hookWrite :: Git.Hook -> Annex () +hookWrite h = + -- cannot have git hooks in a crippled filesystem (no execute bit) + unlessM crippledFileSystem $ + unlessM (inRepo $ Git.hookWrite h) $ + hookWarning h "already exists, not configuring" + +hookUnWrite :: Git.Hook -> Annex () +hookUnWrite h = unlessM (inRepo $ Git.hookUnWrite h) $ + hookWarning h "contents modified; not deleting. Edit it to remove call to git annex." + +hookWarning :: Git.Hook -> String -> Annex () +hookWarning h msg = do + r <- gitRepo + warning $ Git.hookName h ++ " hook (" ++ Git.hookFile h r ++ ") " ++ msg diff --git a/Annex/Journal.hs b/Annex/Journal.hs index fff20ccc4..8b88ab2fb 100644 --- a/Annex/Journal.hs +++ b/Annex/Journal.hs @@ -1,10 +1,10 @@ {- management of the git-annex journal - - The journal is used to queue up changes before they are committed to the - - git-annex branch. Amoung other things, it ensures that if git-annex is + - git-annex branch. Among other things, it ensures that if git-annex is - interrupted, its recorded data is not lost. - - - Copyright 2011 Joey Hess <joey@kitenet.net> + - Copyright 2011-2013 Joey Hess <joey@kitenet.net> - - Licensed under the GNU GPL version 3 or higher. -} @@ -23,9 +23,14 @@ import Annex.Perms {- Records content for a file in the branch to the journal. - - Using the journal, rather than immediatly staging content to the index - - avoids git needing to rewrite the index after every change. -} -setJournalFile :: FilePath -> String -> Annex () -setJournalFile file content = do + - avoids git needing to rewrite the index after every change. + - + - The file in the journal is updated atomically, which allows + - getJournalFileStale to always return a consistent journal file + - content, although possibly not the most current one. + -} +setJournalFile :: JournalLocked -> FilePath -> String -> Annex () +setJournalFile _jl file content = do createAnnexDirectory =<< fromRepo gitAnnexJournalDir createAnnexDirectory =<< fromRepo gitAnnexTmpDir -- journal file is written atomically @@ -37,17 +42,32 @@ setJournalFile file content = do moveFile tmpfile jfile {- Gets any journalled content for a file in the branch. -} -getJournalFile :: FilePath -> Annex (Maybe String) -getJournalFile file = inRepo $ \g -> catchMaybeIO $ +getJournalFile :: JournalLocked -> FilePath -> Annex (Maybe String) +getJournalFile _jl = getJournalFileStale + +{- Without locking, this is not guaranteed to be the most recent + - version of the file in the journal, so should not be used as a basis for + - changes. -} +getJournalFileStale :: FilePath -> Annex (Maybe String) +getJournalFileStale file = inRepo $ \g -> catchMaybeIO $ readFileStrict $ journalFile file g {- List of files that have updated content in the journal. -} -getJournalledFiles :: Annex [FilePath] -getJournalledFiles = map fileJournal <$> getJournalFiles +getJournalledFiles :: JournalLocked -> Annex [FilePath] +getJournalledFiles jl = map fileJournal <$> getJournalFiles jl + +getJournalledFilesStale :: Annex [FilePath] +getJournalledFilesStale = map fileJournal <$> getJournalFilesStale {- List of existing journal files. -} -getJournalFiles :: Annex [FilePath] -getJournalFiles = do +getJournalFiles :: JournalLocked -> Annex [FilePath] +getJournalFiles _jl = getJournalFilesStale + +{- List of existing journal files, but without locking, may miss new ones + - just being added, or may have false positives if the journal is staged + - as it is run. -} +getJournalFilesStale :: Annex [FilePath] +getJournalFilesStale = do g <- gitRepo fs <- liftIO $ catchDefaultIO [] $ getDirectoryContents $ gitAnnexJournalDir g @@ -55,7 +75,7 @@ getJournalFiles = do {- Checks if there are changes in the journal. -} journalDirty :: Annex Bool -journalDirty = not . null <$> getJournalFiles +journalDirty = not . null <$> getJournalFilesStale {- Produces a filename to use in the journal for a file on the branch. - @@ -77,14 +97,19 @@ fileJournal :: FilePath -> FilePath fileJournal = replace [pathSeparator, pathSeparator] "_" . replace "_" [pathSeparator] +{- Sentinal value, only produced by lockJournal; required + - as a parameter by things that need to ensure the journal is + - locked. -} +data JournalLocked = ProduceJournalLocked + {- Runs an action that modifies the journal, using locking to avoid - contention with other git-annex processes. -} -lockJournal :: Annex a -> Annex a +lockJournal :: (JournalLocked -> Annex a) -> Annex a lockJournal a = do lockfile <- fromRepo gitAnnexJournalLock createAnnexDirectory $ takeDirectory lockfile mode <- annexFileMode - bracketIO (lock lockfile mode) unlock (const a) + bracketIO (lock lockfile mode) unlock (const $ a ProduceJournalLocked) where #ifndef mingw32_HOST_OS lock lockfile mode = do @@ -101,4 +126,3 @@ lockJournal a = do #else unlock = removeFile #endif - diff --git a/Annex/Link.hs b/Annex/Link.hs index becd7e7ec..30d8c2ae8 100644 --- a/Annex/Link.hs +++ b/Annex/Link.hs @@ -68,9 +68,9 @@ getAnnexLinkTarget file = ifM (coreSymlinks <$> Annex.getGitConfig) -- characters, or whitespace, we -- certianly don't have a link to a -- git-annex key. - if any (`elem` s) "\0\n\r \t" - then return "" - else return s + return $ if any (`elem` s) "\0\n\r \t" + then "" + else s {- Creates a link on disk. - diff --git a/Annex/Quvi.hs b/Annex/Quvi.hs new file mode 100644 index 000000000..b0725bae7 --- /dev/null +++ b/Annex/Quvi.hs @@ -0,0 +1,20 @@ +{- quvi options for git-annex + - + - Copyright 2013 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +{-# LANGUAGE Rank2Types #-} + +module Annex.Quvi where + +import Common.Annex +import qualified Annex +import Utility.Quvi +import Utility.Url + +withQuviOptions :: forall a. Query a -> [CommandParam] -> URLString -> Annex a +withQuviOptions a ps url = do + opts <- map Param . annexQuviOptions <$> Annex.getGitConfig + liftIO $ a (ps++opts) url diff --git a/Annex/Ssh.hs b/Annex/Ssh.hs index 6fd2c556c..8553ee797 100644 --- a/Annex/Ssh.hs +++ b/Annex/Ssh.hs @@ -16,6 +16,7 @@ module Annex.Ssh ( import qualified Data.Map as M import Data.Hash.MD5 +import System.Process (cwd) import Common.Annex import Annex.LockPool @@ -42,7 +43,7 @@ sshCachingOptions (host, port) opts = go =<< sshInfo (host, port) -- If the lock pool is empty, this is the first ssh of this -- run. There could be stale ssh connections hanging around -- from a previous git-annex run that was interrupted. - cleanstale = whenM (not . any isLock . M.keys <$> getPool) $ + cleanstale = whenM (not . any isLock . M.keys <$> getPool) sshCleanup {- Returns a filename to use for a ssh connection caching socket, and @@ -52,14 +53,30 @@ sshInfo (host, port) = go =<< sshCacheDir where go Nothing = return (Nothing, []) go (Just dir) = do - let socketfile = dir </> hostport2socket host port - if valid_unix_socket_path socketfile - then return (Just socketfile, sshConnectionCachingParams socketfile) - else do - socketfile' <- liftIO $ relPathCwdToFile socketfile - if valid_unix_socket_path socketfile' - then return (Just socketfile', sshConnectionCachingParams socketfile') - else return (Nothing, []) + r <- liftIO $ bestSocketPath $ dir </> hostport2socket host port + return $ case r of + Nothing -> (Nothing, []) + Just socketfile -> (Just socketfile, sshConnectionCachingParams socketfile) + +{- Given an absolute path to use for a socket file, + - returns whichever is shorter of that or the relative path to the same + - file. + - + - If no path can be constructed that is a valid socket, returns Nothing. -} +bestSocketPath :: FilePath -> IO (Maybe FilePath) +bestSocketPath abssocketfile = do + relsocketfile <- liftIO $ relPathCwdToFile abssocketfile + let socketfile = if length abssocketfile <= length relsocketfile + then abssocketfile + else relsocketfile + return $ if valid_unix_socket_path (socketfile ++ sshgarbage) + then Just socketfile + else Nothing + where + -- ssh appends a 16 char extension to the socket when setting it + -- up, which needs to be taken into account when checking + -- that a valid socket was constructed. + sshgarbage = take (1+16) $ repeat 'X' sshConnectionCachingParams :: FilePath -> [CommandParam] sshConnectionCachingParams socketfile = @@ -96,8 +113,8 @@ sshCleanup = go =<< sshCacheDir where go Nothing = noop go (Just dir) = do - sockets <- filter (not . isLock) <$> - liftIO (catchDefaultIO [] $ dirContents dir) + sockets <- liftIO $ filter (not . isLock) + <$> catchDefaultIO [] (dirContents dir) forM_ sockets cleanup cleanup socketfile = do #ifndef mingw32_HOST_OS @@ -120,13 +137,15 @@ sshCleanup = go =<< sshCacheDir stopssh socketfile #endif stopssh socketfile = do - let params = sshConnectionCachingParams socketfile + let (dir, base) = splitFileName socketfile + let params = sshConnectionCachingParams base -- "ssh -O stop" is noisy on stderr even with -q void $ liftIO $ catchMaybeIO $ withQuietOutput createProcessSuccess $ - proc "ssh" $ toCommand $ + (proc "ssh" $ toCommand $ [ Params "-O stop" - ] ++ params ++ [Param "any"] + ] ++ params ++ [Param "any"]) + { cwd = Just dir } -- Cannot remove the lock file; other processes may -- be waiting on our exclusive lock to use it. @@ -139,8 +158,10 @@ hostport2socket host Nothing = hostport2socket' host hostport2socket host (Just port) = hostport2socket' $ host ++ "!" ++ show port hostport2socket' :: String -> FilePath hostport2socket' s - | length s > 32 = md5s (Str s) + | length s > lengthofmd5s = md5s (Str s) | otherwise = s + where + lengthofmd5s = 32 socket2lock :: FilePath -> FilePath socket2lock socket = socket ++ lockExt diff --git a/Annex/TaggedPush.hs b/Annex/TaggedPush.hs index 5dac345f2..039dc0e17 100644 --- a/Annex/TaggedPush.hs +++ b/Annex/TaggedPush.hs @@ -13,13 +13,14 @@ import qualified Annex.Branch import qualified Git import qualified Git.Ref import qualified Git.Command +import qualified Git.Branch import Utility.Base64 {- Converts a git branch into a branch that is tagged with a UUID, typically - the UUID of the repo that will be pushing it, and possibly with other - information. - - - Pushing to branches on the remote that have out uuid in them is ugly, + - Pushing to branches on the remote that have our uuid in them is ugly, - but it reserves those branches for pushing by us, and so our pushes will - never conflict with other pushes. - @@ -50,7 +51,10 @@ taggedPush :: UUID -> Maybe String -> Git.Ref -> Remote -> Git.Repo -> IO Bool taggedPush u info branch remote = Git.Command.runBool [ Param "push" , Param $ Remote.name remote - , Param $ refspec Annex.Branch.name + {- Using forcePush here is safe because we "own" the tagged branch + - we're pushing; it has no other writers. Ensures it is pushed + - even if it has been rewritten by a transition. -} + , Param $ Git.Branch.forcePush $ refspec Annex.Branch.name , Param $ refspec branch ] where diff --git a/Annex/UUID.hs b/Annex/UUID.hs index c36861bbe..4e274503b 100644 --- a/Annex/UUID.hs +++ b/Annex/UUID.hs @@ -17,8 +17,11 @@ module Annex.UUID ( getUncachedUUID, prepUUID, genUUID, + genUUIDInNameSpace, + gCryptNameSpace, removeRepoUUID, storeUUID, + setUUID, ) where import Common.Annex @@ -27,7 +30,9 @@ import qualified Git.Config import Config import qualified Data.UUID as U +import qualified Data.UUID.V5 as U5 import System.Random +import Data.Bits.Utils configkey :: ConfigKey configkey = annexConfig "uuid" @@ -36,6 +41,17 @@ configkey = annexConfig "uuid" genUUID :: IO UUID genUUID = UUID . show <$> (randomIO :: IO U.UUID) +{- Generates a UUID from a given string, using a namespace. + - Given the same namespace, the same string will always result + - in the same UUID. -} +genUUIDInNameSpace :: U.UUID -> String -> UUID +genUUIDInNameSpace namespace = UUID . show . U5.generateNamed namespace . s2w8 + +{- Namespace used for UUIDs derived from git-remote-gcrypt ids. -} +gCryptNameSpace :: U.UUID +gCryptNameSpace = U5.generateNamed U5.namespaceURL $ + s2w8 "http://git-annex.branchable.com/design/gcrypt/" + {- Get current repository's UUID. -} getUUID :: Annex UUID getUUID = getRepoUUID =<< gitRepo @@ -72,3 +88,9 @@ prepUUID = whenM ((==) NoUUID <$> getUUID) $ storeUUID :: ConfigKey -> UUID -> Annex () storeUUID configfield = setConfig configfield . fromUUID + +{- Only sets the configkey in the Repo; does not change .git/config -} +setUUID :: Git.Repo -> UUID -> IO Git.Repo +setUUID r u = do + let s = show configkey ++ "=" ++ fromUUID u + Git.Config.store s r diff --git a/Annex/Url.hs b/Annex/Url.hs new file mode 100644 index 000000000..0401ffe07 --- /dev/null +++ b/Annex/Url.hs @@ -0,0 +1,27 @@ +{- Url downloading, with git-annex user agent. + - + - Copyright 2013 Joey Hess <joey@kitenet.net> + - + - Licensed under the GNU GPL version 3 or higher. + -} + +module Annex.Url ( + module U, + withUserAgent, + getUserAgent, +) where + +import Common.Annex +import qualified Annex +import Utility.Url as U +import qualified Build.SysConfig as SysConfig + +defaultUserAgent :: U.UserAgent +defaultUserAgent = "git-annex/" ++ SysConfig.packageversion + +getUserAgent :: Annex (Maybe U.UserAgent) +getUserAgent = Annex.getState $ + Just . fromMaybe defaultUserAgent . Annex.useragent + +withUserAgent :: (Maybe U.UserAgent -> IO a) -> Annex a +withUserAgent a = liftIO . a =<< getUserAgent diff --git a/Annex/Version.hs b/Annex/Version.hs index 05b3f0227..2b4a49fd2 100644 --- a/Annex/Version.hs +++ b/Annex/Version.hs @@ -19,18 +19,21 @@ defaultVersion :: Version defaultVersion = "3" directModeVersion :: Version -directModeVersion = "4" +directModeVersion = "5" supportedVersions :: [Version] supportedVersions = [defaultVersion, directModeVersion] upgradableVersions :: [Version] #ifndef mingw32_HOST_OS -upgradableVersions = ["0", "1", "2"] +upgradableVersions = ["0", "1", "2", "4"] #else -upgradableVersions = ["2"] +upgradableVersions = ["2", "4"] #endif +autoUpgradeableVersions :: [Version] +autoUpgradeableVersions = ["4"] + versionField :: ConfigKey versionField = annexConfig "version" @@ -42,12 +45,3 @@ setVersion = setConfig versionField removeVersion :: Annex () removeVersion = unsetConfig versionField - -checkVersion :: Version -> Annex () -checkVersion v - | v `elem` supportedVersions = noop - | v `elem` upgradableVersions = err "Upgrade this repository: git-annex upgrade" - | otherwise = err "Upgrade git-annex." - where - err msg = error $ "Repository version " ++ v ++ - " is not supported. " ++ msg diff --git a/Annex/Wanted.hs b/Annex/Wanted.hs index b90a1af31..04dcc1c1c 100644 --- a/Annex/Wanted.hs +++ b/Annex/Wanted.hs @@ -1,4 +1,4 @@ -{- git-annex control over whether content is wanted +{- git-annex checking whether content is wanted - - Copyright 2012 Joey Hess <joey@kitenet.net> - |