module Darcs.UI.Commands.MarkConflicts ( markconflicts ) where
import Prelude ()
import Darcs.Prelude
import System.Exit ( exitSuccess )
import Data.List.Ordered ( nubSort, isect )
import Control.Monad ( when, unless )
import Control.Exception ( catch, IOException )
import Darcs.Util.Prompt ( promptYorn )
import Darcs.Util.SignalHandler ( withSignalsBlocked )
import Darcs.Util.Path ( AbsolutePath, SubPath, toFilePath, simpleSubPath )
import Darcs.Util.Printer
( Doc, putDocLnWith, text, redText, debugDocLn, vsep, (<>), (<+>), ($$) )
import Darcs.Util.Printer.Color ( fancyPrinters )
import Darcs.Util.Text ( pathlist )
import Darcs.UI.Commands
( DarcsCommand(..), withStdOpts, nodefaults, amInHashedRepository, putInfo )
import Darcs.UI.Commands.Util ( filterExistingPaths )
import Darcs.UI.Completion ( knownFileArgs )
import Darcs.UI.Flags
( DarcsFlag, diffingOpts, verbosity, dryRun, umask
, useCache, fixSubPaths )
import Darcs.UI.Options ( (^), odesc, ocheck, onormalise, defaultFlags, (?) )
import qualified Darcs.UI.Options.All as O
import Darcs.Repository.Flags ( UpdateWorking (..) )
import Darcs.Repository
( withRepoLock
, RepoJob(..)
, addToPending
, applyToWorking
, readRepo
, unrecordedChanges )
import Darcs.Patch ( invert, listTouchedFiles, effectOnFilePaths )
import Darcs.Patch.Show
import Darcs.Patch.TouchesFiles ( chooseTouching )
import Darcs.Patch.Witnesses.Ordered ( FL(..), mapFL )
import Darcs.Patch.Witnesses.Sealed ( Sealed(Sealed) )
import Darcs.Repository.Resolution ( patchsetConflictResolutions )
markconflictsDescription :: String
markconflictsDescription =
"Mark unresolved conflicts in working tree, for manual resolution."
markconflictsHelp :: String
markconflictsHelp = unlines
["Darcs requires human guidance to unify changes to the same part of a"
,"source file. When a conflict first occurs, darcs will add the"
,"initial state and both choices to the working tree, delimited by the"
,"markers `v v v`, `=====`, `* * *` and `^ ^ ^`, as follows:"
,""
," v v v v v v v"
," Initial state."
," ============="
," First choice."
," *************"
," Second choice."
," ^ ^ ^ ^ ^ ^ ^"
,""
,"However, you might revert or manually delete these markers without"
,"actually resolving the conflict. In this case, `darcs mark-conflicts`"
,"is useful to show where are the unresolved conflicts. It is also"
,"useful if `darcs apply` or `darcs pull` is called with"
,"`--allow-conflicts`, where conflicts aren't marked initially."
,""
,"Unless you use the `--dry-run` flag, any unrecorded changes to the"
,"affected files WILL be lost forever when you run this command!"
,"You will be prompted for confirmation before this takes place."
]
markconflicts :: DarcsCommand [DarcsFlag]
markconflicts = DarcsCommand
{ commandProgramName = "darcs"
, commandName = "mark-conflicts"
, commandHelp = markconflictsHelp
, commandDescription = markconflictsDescription
, commandExtraArgs = 1
, commandExtraArgHelp = ["[FILE or DIRECTORY]..."]
, commandCommand = markconflictsCmd
, commandPrereq = amInHashedRepository
, commandCompleteArgs = knownFileArgs
, commandArgdefaults = nodefaults
, commandAdvancedOptions = odesc markconflictsAdvancedOpts
, commandBasicOptions = odesc markconflictsBasicOpts
, commandDefaults = defaultFlags markconflictsOpts
, commandCheckOptions = ocheck markconflictsOpts
, commandParseOptions = onormalise markconflictsOpts
}
where
markconflictsBasicOpts
= O.useIndex
^ O.repoDir
^ O.diffAlgorithm
^ O.dryRunXml
markconflictsAdvancedOpts = O.umask
markconflictsOpts = markconflictsBasicOpts `withStdOpts` markconflictsAdvancedOpts
markconflictsCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO ()
markconflictsCmd fps opts args = do
paths <- if null args then return Everything else sps2ps <$> fixSubPaths fps args
debugDocLn $ "::: paths =" <+> (text . show) paths
withRepoLock (dryRun ? opts) (useCache ? opts) YesUpdateWorking (umask ? opts) $ RepoJob $ \repository -> do
let (useidx, scan, _) = diffingOpts opts
verb = verbosity ? opts
classified_paths <-
traverse (filterExistingPaths repository verb useidx scan O.NoLookForMoves) paths
unrecorded <- unrecordedChanges (diffingOpts opts)
O.NoLookForMoves O.NoLookForReplaces
repository (fromOnly Everything)
let forward_renames = liftToPathSet (effectOnFilePaths unrecorded)
backward_renames = liftToPathSet (effectOnFilePaths (invert unrecorded))
existing_paths = fmap snd classified_paths
pre_pending_paths = backward_renames existing_paths
debugDocLn $ "::: pre_pending_paths =" <+> (text . show) pre_pending_paths
r <- readRepo repository
Sealed res <- case patchsetConflictResolutions r of
Sealed raw_res -> do
let raw_res_paths = fps2ps (listTouchedFiles raw_res)
debugDocLn $ "::: raw_res_paths =" <+> (text . show) raw_res_paths
return $ chooseTouching (ps2fps pre_pending_paths) raw_res
let res_paths = fps2ps (listTouchedFiles res)
debugDocLn $ "::: res_paths =" <+> (text . show) res_paths
let affected_paths = isectPathSet res_paths pre_pending_paths
debugDocLn $ "::: affected_paths =" <+> (text . show) affected_paths
when (affected_paths == Only []) $ do
putInfo opts "No conflicts to mark."
exitSuccess
to_revert <- unrecordedChanges (diffingOpts opts)
O.NoLookForMoves O.NoLookForReplaces
repository (fromOnly affected_paths)
let post_pending_affected_paths = forward_renames affected_paths
putInfo opts $ "Marking conflicts in:" <+> showPathSet post_pending_affected_paths <> "."
debugDocLn $ "::: to_revert =" $$ vsep (mapFL displayPatch to_revert)
debugDocLn $ "::: res = " $$ vsep (mapFL displayPatch res)
when (O.yes (dryRun ? opts)) $ do
putInfo opts $ "Conflicts will not be marked: this is a dry run."
exitSuccess
repository' <- case to_revert of
NilFL -> return repository
_ -> do
putDocLnWith fancyPrinters $
"Warning: This will revert all unrecorded changes in:"
<+> showPathSet post_pending_affected_paths <> "."
$$ redText "These changes will be LOST."
confirmed <- promptYorn "Are you sure? "
unless confirmed exitSuccess
let to_add = invert to_revert
addToPending repository YesUpdateWorking to_add
applyToWorking repository (verbosity ? opts) to_add `catch` \(e :: IOException) ->
bug ("Can't undo pending changes!" ++ show e)
withSignalsBlocked $
do addToPending repository' YesUpdateWorking res
_ <- applyToWorking repository' (verbosity ? opts) res `catch` \(e :: IOException) ->
bug ("Problem marking conflicts in mark-conflicts!" ++ show e)
return ()
putInfo opts "Finished marking conflicts."
data Only a = Everything | Only a deriving (Eq, Ord, Show)
instance Functor Only where
fmap _ Everything = Everything
fmap f (Only x) = Only (f x)
instance Foldable Only where
foldMap _ Everything = mempty
foldMap f (Only x) = f x
instance Traversable Only where
traverse _ Everything = pure Everything
traverse f (Only x) = Only <$> f x
fromOnly :: Only a -> Maybe a
fromOnly Everything = Nothing
fromOnly (Only x) = Just x
type PathSet = Only [SubPath]
isectPathSet :: PathSet -> PathSet -> PathSet
isectPathSet Everything ys = ys
isectPathSet xs Everything = xs
isectPathSet (Only xs) (Only ys) = Only (isect xs ys)
sps2ps :: [SubPath] -> PathSet
sps2ps = Only . nubSort
fps2ps :: [FilePath] -> PathSet
fps2ps = sps2ps . map fp2sp
ps2fps :: PathSet -> Maybe [FilePath]
ps2fps = fmap (map sp2fp) . fromOnly
showPathSet :: Only [SubPath] -> Doc
showPathSet Everything = text "all paths"
showPathSet (Only xs) = pathlist (map sp2fp xs)
liftToPathSet :: ([FilePath] -> [FilePath]) -> PathSet -> PathSet
liftToPathSet f = fmap (nubSort . map fp2sp . f . map sp2fp)
fp2sp :: FilePath -> SubPath
fp2sp = fromJust . simpleSubPath
sp2fp :: SubPath -> FilePath
sp2fp = toFilePath