% Copyright (C) 2003 David Roundy % % This program is free software; you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation; either version 2, or (at your option) % any later version. % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with this program; if not, write to the Free Software Foundation, % Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. \begin{code} module Resolution ( standard_resolution, hand_resolution, no_resolution, external_resolution, patchset_conflict_resolutions, ) where import System.IO ( hFlush, stdout ) import System.Exit ( ExitCode( ExitSuccess ) ) import System.Directory ( setCurrentDirectory, getCurrentDirectory ) import Data.List ( zip4 ) import Control.Monad ( when ) import Patch import SlurpDirectory ( Slurpy, slurp, slurp_write ) import Repository ( PatchSet ) import Diff ( smart_diff ) import RepoPrefs ( filetype_function ) import Exec ( exec ) import Lock ( withTempDir ) #include "impossible.h" \end{code} \begin{code} standard_resolution :: Patch -> IO [Patch] no_resolution :: Patch -> IO [Patch] hand_resolution :: Patch -> IO [Patch] external_resolution :: String -> Slurpy -> Patch -> Patch -> Patch -> IO [Patch] patchset_conflict_resolutions :: PatchSet -> [[Patch]] \end{code} \begin{code} standard_resolution p = return [p,merge_list $ map head $ resolve_conflicts p] hand_resolution = standard_resolution no_resolution p = return [p] \end{code} \begin{code} merge_list :: [Patch] -> Patch merge_list patches = doml (join_patches []) patches where doml mp (p:ps) = case merge (mp,p) of Just (mp',_) -> doml (join_patches $ p : (flatten mp')) ps Nothing -> impossible doml mp [] = mp \end{code} \subsection{Resolution of conflicts}\label{resolution} To resolve conflicts using an external tool, you need to specify a command to use via something like \begin{verbatim} --external-merge 'opendiff %1 %2 -ancestor %a -merge %o'. \end{verbatim} The \verb!%1! and \verb!%2! are replaced with the two versions to be merged, \verb!%a! is replaced with the common ancestor of the two version. Most importantly, \verb!%o! is replaced with the name of the output file that darcs will require to be created holding the merged version. The above example works with the FileMerge.app tool that comes with Apple's developer tools. To use xxdiff, you would use \begin{verbatim} --external-merge 'xxdiff -m -O -M %o %1 %a %2' \end{verbatim} To use \verb!kdiff3!, you can use \begin{verbatim} --external-merge 'kdiff3 --output %o %a %1 %2' \end{verbatim} If you figure out how to use darcs with another merge tool, please let me know what flags you used so I can mention it here. Note that if you do use an external merge tool, most likely you will want to your defaults file \verb!_darcs/prefs/defaults! (see \ref{defaults}) a line such as \begin{verbatim} ALL external-merge kdiff3 --output %o %a %1 %2 \end{verbatim} Note that the defaults file does not want quotes around the command. \begin{code} external_resolution c s1 p1 p2 pmerged = case apply_to_slurpy (invert p1) s1 of Nothing -> bug "problem 1 in external_resolution" Just sa -> case apply_to_slurpy pmerged s1 of Nothing -> bug "problem 2 in external_resolution" Just sm -> case apply_to_slurpy p2 sa of Nothing -> bug "problem 3 in external_resolution" Just s2 -> do former_dir <- getCurrentDirectory withTempDir "ancestor" $ \da -> do setCurrentDirectory former_dir withTempDir "version1" $ \d1 -> do setCurrentDirectory former_dir withTempDir "merged" $ \dm -> do setCurrentDirectory former_dir withTempDir "version2" $ \d2 -> do setCurrentDirectory d1 slurp_write s1 setCurrentDirectory da slurp_write sa setCurrentDirectory d2 slurp_write s2 setCurrentDirectory dm slurp_write sm let nms = list_conflicted_files pmerged nas = map (apply_to_filepath $ invert pmerged) nms n1s = map (apply_to_filepath p1) nas n2s = map (apply_to_filepath p2) nas ns = zip4 nas n1s n2s nms in do sequence_ $ map (externally_resolve_file c da d1 d2 dm) ns sfixed <- slurp dm ftf <- filetype_function case smart_diff [] ftf sm sfixed of Nothing -> return [pmerged] Just di -> length (show di) `seq` return [pmerged,di] -- The `seq` above forces the two slurpies to be read before -- we delete their directories. externally_resolve_file :: String -> String -> String -> String -> String -> (FilePath, FilePath, FilePath, FilePath) -> IO () externally_resolve_file c da d1 d2 dm (fa, f1, f2, fm) = do putStr $ "Merging file "++fm++" by hand.\n" ec <- run c [("%1", d1///f1), ("%2", d2///f2), ("%a", da///fa),("%o", dm///fm)] when (ec /= ExitSuccess) $ putStrLn $ "External merge command exited with " ++ show ec putStr "Hit return to move on..." hFlush stdout getLine return () run :: String -> [(String,String)] -> IO ExitCode run c replacements = rr $ doreplace replacements $ words c where rr (command:args) = do putStr $ "Running command '" ++ unwords (command:args) ++ "'\n" exec command args "/dev/null" "/dev/null" rr [] = return ExitSuccess doreplace :: [(String,String)] -> [String] -> [String] doreplace (r:rs) as = doreplace rs $ map (rep r) as doreplace [] as = as rep (o,n) a = if o == a then n else a (///) :: FilePath -> FilePath -> FilePath d /// f = d ++ "/" ++ f \end{code} \begin{code} patchset_conflict_resolutions ([]:_) = [] patchset_conflict_resolutions [] = [] patchset_conflict_resolutions (xs:_) = resolve_conflicts $ join_patches $ map (fromJust . snd) $ reverse xs \end{code}