% Copyright (C) 2003,2005 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; see the file COPYING. If not, write to % the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, % Boston, MA 02110-1301, USA. \begin{code} module Resolution ( standard_resolution, no_resolution, external_resolution, patchset_conflict_resolutions, ) where import System.Exit ( ExitCode( ExitSuccess ) ) import System.Directory ( setCurrentDirectory, getCurrentDirectory ) import Data.List ( zip4 ) import Control.Monad ( when ) import Patch import CommandLine ( parseCmd ) import DarcsUtils ( askUser ) import SlurpDirectory ( Slurpy, slurp, list_slurpy ) import Repository ( PatchSet ) import DarcsRepo ( slurp_recorded_and_unrecorded ) import Diff ( smart_diff ) import RepoPrefs ( filetype_function ) import Exec ( exec, Redirect(..) ) import Lock ( withTempDir ) import External ( cloneTree, clonePaths ) #include "impossible.h" \end{code} \begin{code} standard_resolution :: Patch -> IO [Patch] no_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] no_resolution p = return [p] \end{code} \begin{code} merge_list :: [Patch] -> Patch merge_list patches = doml null_patch 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} \paragraph{Resolution of conflicts}\label{resolution} To resolve conflicts using an external tool, you need to specify a command to use, e.g. \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 versions. 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} To use \verb!tortoiseMerge!, you can use \begin{verbatim} --external-merge 'tortoiseMerge /base:"%a" /mine:"%1" /theirs:"%2" /merged:"%o"' \end{verbatim} (\verb!tortoiseMerge! is a nice merge tool that comes with TortoiseSVN and works well on Windows.) % Fixme: Is it actually a shell command on MS Windows? Note that the command is split into space-separated words and the first one is \verb!exec!ed with the rest as arguments---it is not a shell command. In particular, on Windows this means that the first command path should not contain spaces and you should make sure the command is in your \verb!PATH!. The substitution of the \verb!%! escapes is done everywhere. If you need to prevent substitution you can use a double percentage sign, i.e. \verb!%%a! is substituted with \verb!%a!. Here is an example script to use the Emacs' Ediff package for merging. % This is indented so that the leading #s don't confuse the preprocessor. \begin{verbatim} #! /bin/sh # External merge command for darcs, using Emacs Ediff, via server if possible. # It needs args %1 %2 %a %o, i.e. the external merge command is, say, # `emerge3 %1 %2 %a %o'. test $# -eq 4 || exit 1 form="(ediff-merge-files-with-ancestor" while test $# -gt 0; do count=$count. if [ $count = .... ]; then form=$form\ nil # Lisp STARTUP-HOOKS arg fi case $1 in # Worry about quoting -- escape " and \ *[\"\\]* ) form=$form\ \"$(echo $1 | sed -e's/["\\]/\\\0/g')\" ;; *) form=$form\ \"$1\" ;; esac shift done form=$form')' ( emacsclient --eval "$form" || # Emacs 22 server gnudoit "$form" || # XEmacs/Emacs 21 server emacs --eval "$form" || # Relatively slow to start up xemacs -eval "$form" # Horribly slow to start up ) 2>/dev/null \end{verbatim} It would be invoked like: \begin{verbatim} --external-merge 'emerge3 %1 %2 %a %o' \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 add to your defaults file (\verb!_darcs/prefs/defaults! or \verb!~/.darcs/prefs!, see \ref{defaults}) a line such as \begin{verbatim} ALL external-merge kdiff3 --output %o %a %1 %2 \end{verbatim} or \begin{verbatim} ALL external-merge tortoiseMerge /base:"%a" /mine:"%1" /theirs:"%2" /merged:"%o" \end{verbatim} Note that the defaults file does not want quotes around the command. \begin{code} external_resolution c _ p1 p2 pmerged = do (_, s) <- slurp_recorded_and_unrecorded "." former_dir <- getCurrentDirectory withTempDir "version1" $ \d1 -> do clonePaths former_dir d1 (list_slurpy s) setCurrentDirectory former_dir withTempDir "ancestor" $ \da -> do cloneTree d1 "." apply [] (invert p1) setCurrentDirectory former_dir withTempDir "cleanmerged" $ \dc -> do cloneTree d1 "." apply [] pmerged setCurrentDirectory former_dir withTempDir "merged" $ \dm -> do cloneTree dc "." setCurrentDirectory former_dir withTempDir "version2" $ \d2 -> do cloneTree da "." apply [] p2 let nms = list_conflicted_files pmerged nas = apply_to_filepaths (invert pmerged) nms n1s = apply_to_filepaths p1 nas n2s = apply_to_filepaths p2 nas ns = zip4 nas n1s n2s nms in do mapM_ (externally_resolve_file c da d1 d2 dm) ns sm <- slurp dc 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 putStrLn $ "Merging file "++fm++" by hand." 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 askUser "Hit return to move on..." return () run :: String -> [(Char,String)] -> IO ExitCode run c replacements = case parseCmd replacements c of Left err -> fail $ show err Right (c2,_) -> rr c2 where rr (command:args) = do putStrLn $ "Running command '" ++ unwords (command:args) ++ "'" exec command args (Null,Null,Null) rr [] = return ExitSuccess (///) :: 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}