% Copyright (C) 2003-2004 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. \darcsCommand{diff} \begin{code} {-# OPTIONS_GHC -cpp #-} {-# LANGUAGE CPP #-} module Darcs.Commands.Diff ( diffCommand ) where import System.FilePath.Posix ( takeFileName ) import System.Directory ( setCurrentDirectory ) import Workaround ( getCurrentDirectory ) import Darcs.Utils ( askUser, withCurrentDirectory ) import Control.Monad ( when ) import Data.List ( (\\) ) import Darcs.External( diff_program ) import CommandLine ( parseCmd ) import Darcs.Commands ( DarcsCommand(..), nodefaults ) import Darcs.Arguments ( DarcsFlag(DiffFlags, DiffCmd, LastN, AfterPatch), matchRange, storeInMemory, diffCmdFlag, diffflags, unidiff, workingRepoDir, fixSubPaths, ) import Darcs.Flags ( isUnified ) import Darcs.Hopefully ( info ) import Darcs.RepoPath ( toFilePath, sp2fn ) import Darcs.Match ( getPartialFirstMatch, getPartialSecondMatch, firstMatch, secondMatch, matchFirstPatchset, matchSecondPatchset ) import Darcs.Repository ( PatchSet, withRepository, ($-), read_repo, amInRepository, slurp_recorded_and_unrecorded, createPristineDirectoryTree, createPartialsPristineDirectoryTree ) import Darcs.SlurpDirectory ( get_path_list, writeSlurpy ) import Darcs.Patch ( RepoPatch ) import Darcs.Witnesses.Ordered ( mapRL, concatRL ) import Darcs.Patch.Info ( PatchInfo, human_friendly ) import Darcs.External ( execPipeIgnoreError, clonePaths ) import Darcs.Lock ( withTempDir ) import Darcs.Witnesses.Sealed ( unseal ) import Printer ( Doc, putDoc, vcat, empty, ($$) ) #include "impossible.h" #include "gadts.h" diffDescription :: String diffDescription = "Create a diff between two versions of the repository." diffHelp :: String diffHelp = "Diff can be used to create a diff between two versions which are in your\n"++ "repository. Specifying just --from-patch will get you a diff against\n"++ "your working copy. If you give diff no version arguments, it gives\n"++ "you the same information as whatsnew except that the patch is\n"++ "formatted as the output of a diff command\n" diffCommand :: DarcsCommand diffCommand = DarcsCommand {commandName = "diff", commandHelp = diffHelp, commandDescription = diffDescription, commandExtraArgs = -1, commandExtraArgHelp = ["[FILE or DIRECTORY]..."], commandCommand = diffCmd, commandPrereq = amInRepository, commandGetArgPossibilities = return [], commandArgdefaults = nodefaults, commandAdvancedOptions = [], commandBasicOptions = [matchRange, diffCmdFlag, diffflags, unidiff, workingRepoDir, storeInMemory]} \end{code} \begin{options} --diff-opts \end{options} Diff calls an external ``diff'' command to do the actual work, and passes any unrecognized flags to this diff command. Thus you can call \begin{verbatim} % darcs diff -t 0.9.8 -t 0.9.10 -- -u \end{verbatim} to get a diff in the unified format. Actually, thanks to the wonders of getopt you need the ``\verb!--!'' shown above before any arguments to diff. You can also specify additional arguments to diff using the \verb!--diff-opts! flag. The above command would look like this: \begin{verbatim} % darcs diff --diff-opts -u -t 0.9.8 -t 0.9.10 \end{verbatim} This may not seem like an improvement, but it really pays off when you want to always give diff the same options. You can do this by adding \begin{verbatim} % diff diff-opts -udp \end{verbatim} to your \verb!_darcs/prefs/defaults! file. \begin{code} getDiffOpts :: [DarcsFlag] -> [String] getDiffOpts opts | isUnified opts = "-u" : get_nonU_diff_opts opts | otherwise = get_nonU_diff_opts opts where get_nonU_diff_opts (DiffFlags f:fs) = f : get_nonU_diff_opts fs get_nonU_diff_opts (_:fs) = get_nonU_diff_opts fs get_nonU_diff_opts [] = [] hasDiffCmdFlag :: [DarcsFlag] -> Bool hasDiffCmdFlag (DiffCmd _:_) = True hasDiffCmdFlag (_:t) = hasDiffCmdFlag t hasDiffCmdFlag [] = False -- | Returns the command we should use for diff as a tuple (command, arguments). -- This will either be whatever the user specified via --diff-command or the -- default 'diff_program'. Note that this potentially involves parsing the -- user's diff-command, hence the possibility for failure with an exception. getDiffCmdAndArgs :: String -> [DarcsFlag] -> String -> String -> Either String (String, [String]) getDiffCmdAndArgs cmd opts f1 f2 = helper opts where helper (DiffCmd c:_) = case parseCmd [ ('1', f1) , ('2', f2) ] c of Left err -> Left $ show err Right ([],_) -> bug $ "parseCmd should never return empty list" Right ((h:t),_) -> Right (h,t) helper [] = -- if no command specified, use 'diff' Right (cmd, ("-rN":getDiffOpts opts++[f1,f2])) helper (_:t) = helper t \end{code} If you want to view only the differences to one or more files, you can do so with a command such as \begin{verbatim} % darcs diff foo.c bar.c baz/ \end{verbatim} \begin{options} --diff-command \end{options} You can use a different program to view differences by including the flag \verb!--diff-command!, e.g. \begin{verbatim} --diff-command 'opendiff %1 %2'. \end{verbatim} The \verb!%1! and \verb!%2! are replaced with the two versions to be merged. The above example works with the FileMerge.app tool that comes with Apple's developer tools. To use xxdiff, you would use \begin{verbatim} --diff-command 'xxdiff %1 %2' \end{verbatim} To use \verb!kdiff3!, you can use \begin{verbatim} --diff-command 'kdiff3 %1 %2' \end{verbatim} 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. Also the substitution of the \verb!%! escapes is only done on complete words. See \ref{resolution} for how you might work around this fact, for example, with Emacs' Ediff package. Note also that the \verb!--diff-opts! flag is ignored if you use this option. \begin{code} diffCmd :: [DarcsFlag] -> [String] -> IO () diffCmd opts args = withRepository opts $- \repository -> do when (not (null [i | LastN i <- opts]) && not (null [p | AfterPatch p <- opts]) ) $ fail ("using --patch and --last at the same time with the 'diff' command" ++ " doesn't make sense. Use --from-patch to create a diff from this" ++ " patch to the present, or use just '--patch' to view this specific" ++ " patch.") formerdir <- getCurrentDirectory path_list <- if null args then return [] else map sp2fn `fmap` fixSubPaths opts args thename <- return $ takeFileName formerdir withTempDir ("old-"++thename) $ \odir -> do setCurrentDirectory formerdir withTempDir ("new-"++thename) $ \ndir -> do if firstMatch opts then withCurrentDirectory odir $ getPartialFirstMatch repository opts path_list else if null path_list then createPristineDirectoryTree repository (toFilePath odir) else createPartialsPristineDirectoryTree repository path_list (toFilePath odir) if secondMatch opts then withCurrentDirectory ndir $ getPartialSecondMatch repository opts path_list else do (_, s) <- slurp_recorded_and_unrecorded repository let ps = concatMap (get_path_list s . toFilePath) path_list if null path_list then withCurrentDirectory ndir $ writeSlurpy s "." else clonePaths formerdir (toFilePath ndir) ps thediff <- withCurrentDirectory (toFilePath odir ++ "/..") $ case path_list of [] -> rundiff (takeFileName $ toFilePath odir) (takeFileName $ toFilePath ndir) fs -> vcat `fmap` mapM (\f -> rundiff (takeFileName (toFilePath odir) ++ "/" ++ toFilePath f) (takeFileName (toFilePath ndir) ++ "/" ++ toFilePath f)) fs morepatches <- read_repo repository putDoc $ changelog (getDiffInfo opts morepatches) $$ thediff where rundiff :: String -> String -> IO Doc rundiff f1 f2 = do cmd <- diff_program case getDiffCmdAndArgs cmd opts f1 f2 of Left err -> fail err Right (d_cmd, d_args) -> let other_diff = hasDiffCmdFlag opts in do when other_diff $ putStrLn $ "Running command '" ++ unwords (d_cmd:d_args) ++ "'" output <- execPipeIgnoreError d_cmd d_args empty when other_diff $ do askUser "Hit return to move on..." return () return output getDiffInfo :: RepoPatch p => [DarcsFlag] -> PatchSet p C(x) -> [PatchInfo] getDiffInfo opts ps = let infos = mapRL info . concatRL handle (match_cond, do_match) | match_cond opts = unseal infos (do_match opts ps) | otherwise = infos ps in handle (secondMatch, matchSecondPatchset) \\ handle (firstMatch, matchFirstPatchset) changelog :: [PatchInfo] -> Doc changelog pis = vcat $ map human_friendly pis \end{code}