diff options
-rw-r--r-- | Annex/Direct.hs | 75 | ||||
-rw-r--r-- | Command/Sync.hs | 87 | ||||
-rw-r--r-- | debian/changelog | 6 | ||||
-rw-r--r-- | doc/bugs/direct_mode_merge_can_overwrite_local__44___non-annexed_files.mdwn | 2 |
4 files changed, 92 insertions, 78 deletions
diff --git a/Annex/Direct.hs b/Annex/Direct.hs index 02766ab18..2f583fd94 100644 --- a/Annex/Direct.hs +++ b/Annex/Direct.hs @@ -170,23 +170,23 @@ mergeDirectCleanup d oldsha newsha = do makeabs <- flip fromTopFilePath <$> gitRepo let fsitems = zip (map (makeabs . DiffTree.file) items) items forM_ fsitems $ - go DiffTree.srcsha DiffTree.srcmode moveout moveout_raw + go makeabs DiffTree.srcsha DiffTree.srcmode moveout moveout_raw forM_ fsitems $ - go DiffTree.dstsha DiffTree.dstmode movein movein_raw + go makeabs DiffTree.dstsha DiffTree.dstmode movein movein_raw void $ liftIO cleanup liftIO $ removeDirectoryRecursive d where - go getsha getmode a araw (f, item) + go makeabs getsha getmode a araw (f, item) | getsha item == nullSha = noop | otherwise = void $ - tryAnnex . maybe (araw f item) (\k -> void $ a k f) + tryAnnex . maybe (araw item makeabs f) (\k -> void $ a item makeabs k f) =<< catKey (getsha item) (getmode item) - moveout = removeDirect + moveout _ _ = removeDirect {- Files deleted by the merge are removed from the work tree. - Empty work tree directories are removed, per git behavior. -} - moveout_raw f _item = liftIO $ do + moveout_raw _ _ f = liftIO $ do nukeFile f void $ tryIO $ removeDirectory $ parentDir f @@ -198,39 +198,60 @@ mergeDirectCleanup d oldsha newsha = do - - Otherwise, create the symlink and then if possible, replace it - with the content. -} - movein k f = unlessM (goodContent k f) $ do - preserveUnannexed f + movein item makeabs k f = unlessM (goodContent k f) $ do + preserveUnannexed item makeabs f oldsha l <- inRepo $ gitAnnexLink f k replaceFile f $ makeAnnexLink l 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 item = do - preserveUnannexed f + movein_raw item makeabs f = do + preserveUnannexed item makeabs f oldsha liftIO $ do createDirectoryIfMissing True $ parentDir f void $ tryIO $ rename (d </> getTopFilePath (DiffTree.file item)) f - {- If the file is present in the work tree, but did not exist in - - the oldsha branch, preserve this local, unannexed file. -} - preserveUnannexed f = whenM unannexed $ - whenM (isNothing <$> catFileDetails oldsha f) $ - liftIO $ findnewname (0 :: Int) - where - exists = isJust <$$> catchMaybeIO . getSymbolicLinkStatus +{- If the file that's being moved in is already present in the work + - tree, but did not exist in the oldsha branch, preserve this + - local, unannexed file (or directory), as "variant-local". + - + - It's also possible that the file that's being moved in + - is in a directory that collides with an exsting, non-annexed + - file (not a directory), which should be preserved. + -} +preserveUnannexed :: DiffTree.DiffTreeItem -> (TopFilePath -> FilePath) -> FilePath -> Ref -> Annex () +preserveUnannexed item makeabs absf oldsha = do + whenM (liftIO (collidingitem absf) <&&> unannexed absf) $ + liftIO $ findnewname absf 0 + checkdirs (DiffTree.file item) + where + checkdirs from = do + let p = parentDir (getTopFilePath from) + let d = asTopFilePath p + unless (null p) $ do + let absd = makeabs d + whenM (liftIO (colliding_nondir absd) <&&> unannexed absd) $ + liftIO $ findnewname absd 0 + checkdirs d + + collidingitem f = isJust + <$> catchMaybeIO (getSymbolicLinkStatus f) + colliding_nondir f = maybe False (not . isDirectory) + <$> catchMaybeIO (getSymbolicLinkStatus f) - unannexed = liftIO (exists f) - <&&> (isNothing <$> isAnnexLink f) + unannexed f = (isNothing <$> isAnnexLink f) + <&&> (isNothing <$> catFileDetails oldsha f) - findnewname n = do - let localf = mkVariant f - ("local" ++ if n > 0 then show n else "") - ifM (exists localf) - ( findnewname (n+1) - , rename f localf - `catchIO` const (findnewname (n+1)) - ) + findnewname :: FilePath -> Int -> IO () + findnewname f n = do + let localf = mkVariant f + ("local" ++ if n > 0 then show n else "") + ifM (collidingitem localf) + ( findnewname f (n+1) + , rename f localf + `catchIO` const (findnewname f (n+1)) + ) {- If possible, converts a symlink in the working tree into a direct - mode file. If the content is not available, leaves the symlink diff --git a/Command/Sync.hs b/Command/Sync.hs index 2c3e235cc..33b30df13 100644 --- a/Command/Sync.hs +++ b/Command/Sync.hs @@ -345,6 +345,12 @@ mergeFrom branch = do - It's also possible that one side has foo as an annexed file, and - the other as a directory or non-annexed file. The annexed file - is renamed to resolve the merge, and the other object is preserved as-is. + - + - In indirect mode, the merge is resolved in the work tree and files + - staged, to clean up from a conflicted merge that was run in the work + - tree. In direct mode, the work tree is not touched here; files are + - staged to the index, and written to the gitAnnexMergeDir, and later + - mergeDirectCleanup handles updating the work tree. -} resolveMerge :: Annex Bool resolveMerge = do @@ -354,10 +360,11 @@ resolveMerge = do let merged = not (null mergedfs) void $ liftIO cleanup - (deleted, cleanup2) <- inRepo (LsFiles.deleted [top]) - unless (null deleted) $ - Annex.Queue.addCommand "rm" [Params "--quiet -f --"] deleted - void $ liftIO cleanup2 + unlessM isDirect $ do + (deleted, cleanup2) <- inRepo (LsFiles.deleted [top]) + unless (null deleted) $ + Annex.Queue.addCommand "rm" [Params "--quiet -f --"] deleted + void $ liftIO cleanup2 when merged $ do unlessM isDirect $ @@ -379,7 +386,7 @@ resolveMerge' u case (kus, kthem) of -- Both sides of conflict are annexed files (Just keyUs, Just keyThem) -> do - removeoldfile keyUs + unstageoldfile if keyUs == keyThem then makelink keyUs else do @@ -388,20 +395,15 @@ resolveMerge' u return $ Just file -- Our side is annexed, other side is not. (Just keyUs, Nothing) -> do - ifM isDirect - ( do - removeoldfile keyUs - makelink keyUs - movefromdirectmerge file - , do - unstageoldfile - makelink keyUs - ) + unstageoldfile + whenM isDirect $ + stagefromdirectmergedir file + makelink keyUs return $ Just file -- Our side is not annexed, other side is. (Nothing, Just keyThem) -> do - makelink keyThem unstageoldfile + makelink keyThem return $ Just file -- Neither side is annexed; cannot resolve. (Nothing, Nothing) -> return Nothing @@ -413,45 +415,34 @@ resolveMerge' u makelink key = do let dest = variantFile file key l <- inRepo $ gitAnnexLink dest key - replaceFile dest $ makeAnnexLink l - stageSymlink dest =<< hashSymlink l - whenM isDirect $ - toDirect key dest - removeoldfile keyUs = do ifM isDirect - ( removeDirect keyUs file - , liftIO $ nukeFile file + ( do + d <- fromRepo gitAnnexMergeDir + replaceFile (d </> dest) $ makeAnnexLink l + , replaceFile dest $ makeAnnexLink l ) - Annex.Queue.addCommand "rm" [Params "--quiet -f --"] [file] - unstageoldfile = Annex.Queue.addCommand "rm" [Params "--quiet -f --cached --"] [file] + stageSymlink dest =<< hashSymlink l getKey select = case select (LsFiles.unmergedSha u) of Nothing -> return Nothing Just sha -> catKey sha symLinkMode - - {- Move something out of the direct mode merge directory and into - - the git work tree. - - - - On a filesystem not supporting symlinks, this is complicated - - because a directory may contain annex links, but just - - moving them into the work tree will not let git know they are - - symlinks. - - - - Also, if the content of the file is available, make it available - - in direct mode. - -} - movefromdirectmerge item = do + + -- removing the conflicted file from cache clears the conflict + unstageoldfile = Annex.Queue.addCommand "rm" [Params "--quiet -f --cached --"] [file] + + {- stage an item from the direct mode merge directory -} + stagefromdirectmergedir item = do d <- fromRepo gitAnnexMergeDir - liftIO $ rename (d </> item) item - mapM_ setuplink =<< liftIO (dirContentsRecursive item) - setuplink f = do - v <- getAnnexLinkTarget f - case v of - Nothing -> noop - Just target -> do - unlessM (coreSymlinks <$> Annex.getGitConfig) $ - addAnnexLink target f - maybe noop (`toDirect` f) - (fileKey (takeFileName target)) + l <- liftIO $ dirContentsRecursive (d </> item) + if null l + then go (d </> item) + else mapM_ go l + where + go f = do + v <- getAnnexLinkTarget f + case v of + Just target -> stageSymlink f + =<< hashSymlink target + Nothing -> noop {- git-merge moves conflicting files away to files - named something like f~HEAD or f~branch, but the diff --git a/debian/changelog b/debian/changelog index 0807c1c6d..73dd55ffb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,8 @@ git-annex (5.20140228) UNRELEASED; urgency=medium - * sync: Fix bug in direct mode that caused a file not checked into git - to be deleted when merging with a remote that added a file by the same - name. + * sync: Fix bug in direct mode that caused a file that was not + checked into git to be deleted when there was a conflicting + merge with a remote. * webapp: Now supports HTTPS. * webapp: No longer supports a port specified after --listen, since it was buggy, and that use case is better supported by setting up HTTPS. diff --git a/doc/bugs/direct_mode_merge_can_overwrite_local__44___non-annexed_files.mdwn b/doc/bugs/direct_mode_merge_can_overwrite_local__44___non-annexed_files.mdwn index 4b5a9d2a3..081dd414b 100644 --- a/doc/bugs/direct_mode_merge_can_overwrite_local__44___non-annexed_files.mdwn +++ b/doc/bugs/direct_mode_merge_can_overwrite_local__44___non-annexed_files.mdwn @@ -10,3 +10,5 @@ is not checked into git, the merge will overwrite it with the remote file from g New problem: If the merge pulls in a directory, and a file exists with the name of the directory, locally, not annexed, the file is left alone, but the directory is thus not checked out, and will be deleted on commit. + +> [[fixed|done]]; regression test in place. --[[Joey]] |