diff options
author | Joey Hess <joey@kitenet.net> | 2012-12-18 15:04:44 -0400 |
---|---|---|
committer | Joey Hess <joey@kitenet.net> | 2012-12-18 15:04:44 -0400 |
commit | 10d77d95f454a2fb2806c031a14344dd7cdea006 (patch) | |
tree | b9ba63e2f844031d4fd1d8248e5b01e7b5be7902 | |
parent | 19e46a374225bc37131454774f20da4c6a7779d9 (diff) |
direct mode merging works!
Automatic merge resoltion code needs to be fixed to preserve objects from
direct mode files.
-rw-r--r-- | Annex/Content/Direct.hs | 22 | ||||
-rw-r--r-- | Annex/Direct.hs | 96 | ||||
-rw-r--r-- | Command/Direct.hs | 25 | ||||
-rw-r--r-- | Command/Sync.hs | 34 | ||||
-rw-r--r-- | Git/DiffTree.hs | 15 | ||||
-rw-r--r-- | Locations.hs | 5 |
6 files changed, 135 insertions, 62 deletions
diff --git a/Annex/Content/Direct.hs b/Annex/Content/Direct.hs index 66aa2e9d5..5e33a8951 100644 --- a/Annex/Content/Direct.hs +++ b/Annex/Content/Direct.hs @@ -9,7 +9,6 @@ module Annex.Content.Direct ( associatedFiles, removeAssociatedFile, addAssociatedFile, - updateAssociatedFiles, goodContent, updateCache, recordedCache, @@ -23,11 +22,7 @@ module Annex.Content.Direct ( import Common.Annex import qualified Git -import qualified Git.DiffTree as DiffTree -import Git.Sha -import Annex.CatFile import Utility.TempFile -import Utility.FileMode import Logs.Location import System.Posix.Types @@ -70,23 +65,6 @@ addAssociatedFile key file = changeAssociatedFiles key $ \files -> then files else file:files -{- Uses git diff-tree to find files changed between two tree Shas, and - - updates the associated file mappings, efficiently. -} -updateAssociatedFiles :: Git.Sha -> Git.Sha -> Annex () -updateAssociatedFiles oldsha newsha = do - (items, cleanup) <- inRepo $ DiffTree.diffTree oldsha newsha - forM_ items update - void $ liftIO $ cleanup - where - update item = do - go DiffTree.dstsha DiffTree.dstmode addAssociatedFile - go DiffTree.srcsha DiffTree.srcmode removeAssociatedFile - where - go getsha getmode a = - when (getsha item /= nullSha && isSymLink (getmode item)) $ do - key <- catKey (getsha item) - maybe noop (\k -> void $ a k $ DiffTree.file item) key - {- Checks if a file in the tree, associated with a key, has not been modified. - - To avoid needing to fsck the file's content, which can involve an diff --git a/Annex/Direct.hs b/Annex/Direct.hs index 12984687e..ad67ee990 100644 --- a/Annex/Direct.hs +++ b/Annex/Direct.hs @@ -12,9 +12,13 @@ import qualified Git import qualified Git.LsFiles import qualified Git.UpdateIndex import qualified Git.HashObject -import qualified Annex.Queue +import qualified Git.Merge +import qualified Git.DiffTree as DiffTree +import Git.Sha import Git.Types import Annex.CatFile +import Utility.FileMode +import qualified Annex.Queue import Logs.Location import Backend import Types.KeySource @@ -103,3 +107,93 @@ addDirect file cache = do showEndFail return False ) + +{- In direct mode, git merge would usually refuse to do anything, since it + - sees present direct mode files as type changed files. To avoid this, + - merge is run with the work tree set to a temp directory. + - + - This should only be used once any changes to the real working tree have + - already been committed, because it overwrites files in the working tree. + -} +mergeDirect :: FilePath -> Git.Ref -> Git.Repo -> IO Bool +mergeDirect d branch g = do + createDirectoryIfMissing True d + let g' = g { location = Local { gitdir = Git.localGitDir g, worktree = Just d } } + Git.Merge.mergeNonInteractive branch g' + +{- Cleans up after a direct mode merge. The merge must have been committed, + - and the commit sha passed in, along with the old sha of the tree + - before the merge. Uses git diff-tree to find files that changed between + - the two shas, and applies those changes to the work tree. + -} +mergeDirectCleanup :: FilePath -> Git.Ref -> Git.Ref -> Annex () +mergeDirectCleanup d oldsha newsha = do + (items, cleanup) <- inRepo $ DiffTree.diffTreeRecursive oldsha newsha + forM_ items updated + void $ liftIO $ cleanup + liftIO $ removeDirectoryRecursive d + where + updated item = do + go DiffTree.srcsha DiffTree.srcmode moveout moveout_raw + go DiffTree.dstsha DiffTree.dstmode movein movein_raw + where + go 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 + + {- Any content that was present in direct mode and whose file is to + - be modified or deleted by the merge is first moved to + - .git/annex/objects, unless there are other associated files for + - the content. No content is ever lost due to a direct mode merge. -} + moveout k f = do + locs <- removeAssociatedFile k f + when (null locs) $ do + r <- liftIO $ catchMaybeIO $ getSymbolicLinkStatus f + case r of + Just s + | not (isSymbolicLink s) -> + moveAnnex k f + _ -> noop + moveout_raw f + + {- Files deleted by the merge are removed from the work tree. + - Empty work tree directories are removed, per git behavior. -} + moveout_raw f = liftIO $ do + nukeFile f + void $ catchMaybeIO $ removeDirectory $ parentDir f + + {- Key symlinks are replaced with their content, if it's available. -} + movein k f = do + movein_raw f + maybe noop id =<< toDirect k f + + {- Any new, modified, or renamed files were written to the temp + - directory by the merge, and are moved to the real work tree. -} + movein_raw f = liftIO $ do + createDirectoryIfMissing True $ parentDir f + rename (d </> f) f + +{- If possible, returns an action that will convert a symlink in the + - working tree into a direct mode file. -} +toDirect :: Key -> FilePath -> Annex (Maybe (Annex ())) +toDirect k f = do + loc <- inRepo $ gitAnnexLocation k + createContentDir loc -- thaws directory too + locs <- filter (/= f) <$> addAssociatedFile k f + case locs of + [] -> ifM (liftIO $ doesFileExist loc) + ( return $ Just $ do + {- Move content from annex to direct file. -} + updateCache k loc + thawContent loc + liftIO $ replaceFile f $ moveFile loc + , return Nothing + ) + (loc':_) -> return $ Just $ do + {- Another direct file has the content, so + - hard link to it. -} + liftIO $ replaceFile f $ createLink loc' diff --git a/Command/Direct.hs b/Command/Direct.hs index 598a7b4b3..991930c38 100644 --- a/Command/Direct.hs +++ b/Command/Direct.hs @@ -13,8 +13,7 @@ import qualified Git import qualified Git.Command import qualified Git.LsFiles import Config -import Annex.Content -import Annex.Content.Direct +import Annex.Direct def :: [Command] def = [command "direct" paramNothing seek "switch repository to direct mode"] @@ -41,25 +40,13 @@ perform = do void $ liftIO clean next cleanup where - {- Walk tree from top and move all present objects to the - - files that link to them, while updating direct mode mappings. -} go = whenAnnexed $ \f (k, _) -> do - loc <- inRepo $ gitAnnexLocation k - createContentDir loc -- thaws directory too - locs <- filter (/= f) <$> addAssociatedFile k f - case locs of - [] -> whenM (liftIO $ doesFileExist loc) $ do - {- Move content from annex to direct file. -} + r <- toDirect k f + case r of + Nothing -> noop + Just a -> do showStart "direct" f - updateCache k loc - thawContent loc - liftIO $ replaceFile f $ moveFile loc - showEndOk - (loc':_) -> do - {- Another direct file has the content, so - - hard link to it. -} - showStart "direct" f - liftIO $ replaceFile f $ createLink loc' + a showEndOk return Nothing diff --git a/Command/Sync.hs b/Command/Sync.hs index 7a034bfa4..2d1b2fb9c 100644 --- a/Command/Sync.hs +++ b/Command/Sync.hs @@ -15,7 +15,6 @@ import qualified Annex import qualified Annex.Branch import qualified Annex.Queue import Annex.Content -import Annex.Content.Direct import Annex.Direct import Annex.CatFile import qualified Git.Command @@ -179,31 +178,30 @@ mergeAnnex = do void $ Annex.Branch.forceUpdate stop -{- Merges from a branch into the current branch. - - - - In direct mode, updates associated files mappings for the files that - - were changed by the merge. -} +{- Merges from a branch into the current branch. -} mergeFrom :: Git.Ref -> Annex Bool -mergeFrom branch = ifM isDirect - ( maybe go godirect =<< inRepo Git.Branch.current - , go - ) +mergeFrom branch = do + showOutput + ifM isDirect + ( maybe go godirect =<< inRepo Git.Branch.current + , go + ) where - go = do - showOutput - ok <- inRepo $ Git.Merge.mergeNonInteractive branch - if ok - then return ok - else resolveMerge + go = runmerge $ inRepo $ Git.Merge.mergeNonInteractive branch godirect currbranch = do old <- inRepo $ Git.Ref.sha currbranch - r <- go + d <- fromRepo gitAnnexMergeDir + r <- runmerge $ inRepo $ mergeDirect d branch new <- inRepo $ Git.Ref.sha currbranch case (old, new) of - (Just oldsha, Just newsha) -> do - updateAssociatedFiles oldsha newsha + (Just oldsha, Just newsha) -> + mergeDirectCleanup d oldsha newsha _ -> noop return r + runmerge a = ifM (a) + ( return True + , resolveMerge + ) {- Resolves a conflicted merge. It's important that any conflicts be - resolved in a way that itself avoids later merge conflicts, since diff --git a/Git/DiffTree.hs b/Git/DiffTree.hs index 7281255f5..af230b495 100644 --- a/Git/DiffTree.hs +++ b/Git/DiffTree.hs @@ -8,6 +8,7 @@ module Git.DiffTree ( DiffTreeItem(..), diffTree, + diffTreeRecursive, parseDiffTree ) where @@ -31,9 +32,19 @@ data DiffTreeItem = DiffTreeItem {- Diffs two tree Refs. -} diffTree :: Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool) -diffTree src dst repo = do - (diff, cleanup) <- pipeNullSplit [Params "diff-tree -z --raw --no-renames -l0", Param (show src), Param (show dst)] repo +diffTree = diffTree' [] + +{- Diffs two tree Refs, recursing into sub-trees -} +diffTreeRecursive :: Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool) +diffTreeRecursive = diffTree' [Param "-r"] + +diffTree' :: [CommandParam] -> Ref -> Ref -> Repo -> IO ([DiffTreeItem], IO Bool) +diffTree' params src dst repo = do + (diff, cleanup) <- pipeNullSplit ps repo return (parseDiffTree diff, cleanup) + where + ps = Params "diff-tree -z --raw --no-renames -l0" : params ++ + [Param (show src), Param (show dst)] {- Parses diff-tree output. -} parseDiffTree :: [String] -> [DiffTreeItem] diff --git a/Locations.hs b/Locations.hs index cfe9bd27d..ce867fac5 100644 --- a/Locations.hs +++ b/Locations.hs @@ -25,6 +25,7 @@ module Locations ( gitAnnexFsckState, gitAnnexTransferDir, gitAnnexCredsDir, + gitAnnexMergeDir, gitAnnexJournalDir, gitAnnexJournalLock, gitAnnexIndex, @@ -161,6 +162,10 @@ gitAnnexFsckState r = gitAnnexDir r </> "fsckstate" gitAnnexCredsDir :: Git.Repo -> FilePath gitAnnexCredsDir r = addTrailingPathSeparator $ gitAnnexDir r </> "creds" +{- .git/annex/merge/ is used for direct mode merges. -} +gitAnnexMergeDir :: Git.Repo -> FilePath +gitAnnexMergeDir r = addTrailingPathSeparator $ gitAnnexDir r </> "merge" + {- .git/annex/transfer/ is used to record keys currently - being transferred, and other transfer bookkeeping info. -} gitAnnexTransferDir :: Git.Repo -> FilePath |