1 % Copyright (C) 2003-2004 David Roundy
3 % This program is free software; you can redistribute it and/or modify
4 % it under the terms of the GNU General Public License as published by
5 % the Free Software Foundation; either version 2, or (at your option)
8 % This program is distributed in the hope that it will be useful,
9 % but WITHOUT ANY WARRANTY; without even the implied warranty of
10 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 % GNU General Public License for more details.
13 % You should have received a copy of the GNU General Public License
14 % along with this program; see the file COPYING. If not, write to
15 % the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 % Boston, MA 02110-1301, USA.
18 \subsection{darcs diff}
20 {-# OPTIONS_GHC -cpp #-}
23 module Darcs.Commands.Diff ( diff_command ) where
25 import System.FilePath ( takeFileName )
26 import System.Directory ( setCurrentDirectory )
27 import Workaround ( getCurrentDirectory )
28 import Darcs.Utils ( askUser, withCurrentDirectory )
29 import Control.Monad ( when )
30 import Data.List ( (\\) )
32 import Autoconf ( diff_program )
33 import CommandLine ( parseCmd )
34 import Darcs.Commands ( DarcsCommand(..), nodefaults )
35 import Darcs.Arguments ( DarcsFlag(DiffFlags, Unified, DiffCmd,
37 match_range, store_in_memory,
38 diff_cmd_flag, diffflags, unidiff,
39 working_repo_dir, fixSubPaths,
41 import Darcs.Hopefully ( info )
42 import Darcs.RepoPath ( toFilePath, sp2fn )
43 import Darcs.Match ( get_partial_first_match, get_partial_second_match,
44 first_match, second_match,
45 match_first_patchset, match_second_patchset )
46 import Darcs.Repository ( PatchSet, withRepository, ($-), read_repo,
47 amInRepository, slurp_recorded_and_unrecorded,
48 createPristineDirectoryTree,
49 createPartialsPristineDirectoryTree )
50 import Darcs.SlurpDirectory ( get_path_list, writeSlurpy )
51 import Darcs.Patch ( RepoPatch )
52 import Darcs.Ordered ( mapRL, concatRL )
53 import Darcs.Patch.Info ( PatchInfo, human_friendly )
54 import Darcs.External ( execPipeIgnoreError, clonePaths )
55 import Darcs.Lock ( withTempDir )
56 import Darcs.Sealed ( unsafeUnseal )
57 import Printer ( Doc, putDocLn, vcat, empty, ($$) )
58 #include "impossible.h"
63 diff_description :: String
64 diff_description = "Create a diff between two versions of the repository."
70 "Diff can be used to create a diff between two versions which are in your\n"++
71 "repository. Specifying just --from-patch will get you a diff against\n"++
72 "your working copy. If you give diff no version arguments, it gives\n"++
73 "you the same information as whatsnew except that the patch is\n"++
74 "formatted as the output of a diff command\n"
76 diff_command :: DarcsCommand
77 diff_command = DarcsCommand {command_name = "diff",
78 command_help = diff_help,
79 command_description = diff_description,
80 command_extra_args = -1,
81 command_extra_arg_help
82 = ["[FILE or DIRECTORY]..."],
83 command_command = diff_cmd,
84 command_prereq = amInRepository,
85 command_get_arg_possibilities = return [],
86 command_argdefaults = nodefaults,
87 command_advanced_options = [],
88 command_basic_options = [match_range,
91 working_repo_dir, store_in_memory]}
98 Diff calls an external ``diff'' command to do the actual work, and passes
99 any unrecognized flags to this diff command. Thus you can call
101 % darcs diff -t 0.9.8 -t 0.9.10 -- -u
103 to get a diff in the unified format. Actually, thanks to the wonders of
104 getopt you need the ``\verb!--!'' shown above before any arguments to diff.
105 You can also specify additional arguments to diff using the
106 \verb!--diff-opts! flag. The above command would look like this:
108 % darcs diff --diff-opts -u -t 0.9.8 -t 0.9.10
110 This may not seem like an improvement, but it really pays off when you want
111 to always give diff the same options. You can do this by adding
113 % diff diff-opts -udp
115 to your \verb!_darcs/prefs/defaults! file.
118 get_diff_opts :: [DarcsFlag] -> [String]
119 get_diff_opts [] = []
120 get_diff_opts (Unified:fs) = "-u" : get_diff_opts fs
121 get_diff_opts (DiffFlags f:fs) = f : get_diff_opts fs
122 get_diff_opts (_:fs) = get_diff_opts fs
124 has_diff_cmd_flag :: [DarcsFlag] -> Bool
125 has_diff_cmd_flag (DiffCmd _:_) = True
126 has_diff_cmd_flag (_:t) = has_diff_cmd_flag t
127 has_diff_cmd_flag [] = False
129 -- | Returns the command we should use for diff as a tuple (command, arguments).
130 -- This will either be whatever the user specified via --diff-command or the
131 -- default 'diff_program'. Note that this potentially involves parsing the
132 -- user's diff-command, hence the possibility for failure with an exception.
133 get_diff_cmd_and_args :: [DarcsFlag] -> String -> String
134 -> Either String (String, [String])
135 get_diff_cmd_and_args opts f1 f2 = helper opts where
136 helper (DiffCmd c:_) =
137 case parseCmd [ ('1', f1) , ('2', f2) ] c of
138 Left err -> Left $ show err
139 Right ([],_) -> bug $ "parseCmd should never return empty list"
140 Right ((h:t),_) -> Right (h,t)
141 helper [] = -- if no command specified, use 'diff'
142 Right (diff_program, ("-rN":get_diff_opts opts++[f1,f2]))
143 helper (_:t) = helper t
146 If you want to view only the differences to one or more files, you can do
147 so with a command such as
149 % darcs diff foo.c bar.c baz/
156 You can use a different program to view differences by including
157 the flag \verb!--diff-command!, e.g.
159 --diff-command 'opendiff %1 %2'.
161 The \verb!%1! and \verb!%2! are replaced with the two versions to be
162 merged. The above example works with the FileMerge.app tool that comes with
163 Apple's developer tools. To use xxdiff, you would use
165 --diff-command 'xxdiff %1 %2'
167 To use \verb!kdiff3!, you can use
169 --diff-command 'kdiff3 %1 %2'
172 Note that the command is split into space-separated words and the first one is
173 \verb!exec!ed with the rest as arguments---it is not a shell command. Also
174 the substitution of the \verb!%! escapes is only done on complete words.
175 See \ref{resolution} for how you might work around this fact, for example,
176 with Emacs' Ediff package.
178 Note also that the \verb!--diff-opts! flag is ignored if you use this option.
181 diff_cmd :: [DarcsFlag] -> [String] -> IO ()
182 diff_cmd opts args = withRepository opts $- \repository -> do
183 when (not (null [i | LastN i <- opts])
184 && not (null [p | AfterPatch p <- opts])
186 fail ("using --patch and --last at the same time with the 'diff' command"
187 ++ " doesn't make sense. Use --from-patch to create a diff from this"
188 ++ " patch to the present, or use just '--patch' to view this specific"
190 formerdir <- getCurrentDirectory
191 path_list <- if null args
193 else map sp2fn `fmap` fixSubPaths opts args
194 thename <- return $ takeFileName formerdir
195 withTempDir ("old-"++thename) $ \odir -> do
196 setCurrentDirectory formerdir
197 withTempDir ("new-"++thename) $ \ndir -> do
199 then withCurrentDirectory odir $
200 get_partial_first_match repository opts path_list
201 else if null path_list
202 then createPristineDirectoryTree repository (toFilePath odir)
203 else createPartialsPristineDirectoryTree repository path_list (toFilePath odir)
205 then withCurrentDirectory ndir $
206 get_partial_second_match repository opts path_list
207 else do (_, s) <- slurp_recorded_and_unrecorded repository
208 let ps = concatMap (get_path_list s . toFilePath) path_list
210 then withCurrentDirectory ndir $ writeSlurpy s "."
211 else clonePaths formerdir (toFilePath ndir) ps
212 thediff <- withCurrentDirectory (toFilePath odir ++ "/..") $
214 [] -> rundiff (takeFileName $ toFilePath odir) (takeFileName $ toFilePath ndir)
217 (takeFileName (toFilePath odir) ++ "/" ++ toFilePath f)
218 (takeFileName (toFilePath ndir) ++ "/" ++ toFilePath f)) fs
219 morepatches <- read_repo repository
220 putDocLn $ changelog (get_diff_info opts morepatches)
222 where rundiff :: String -> String -> IO Doc
224 case get_diff_cmd_and_args opts f1 f2 of
226 Right (d_cmd, d_args) ->
227 let other_diff = has_diff_cmd_flag opts in
228 do when other_diff $ putStrLn $
229 "Running command '" ++ unwords (d_cmd:d_args) ++ "'"
230 output <- execPipeIgnoreError d_cmd d_args empty
232 askUser "Hit return to move on..."
238 get_diff_info :: RepoPatch p => [DarcsFlag] -> PatchSet p -> [PatchInfo]
239 get_diff_info opts ps =
240 let pi1s = mapRL info $ concatRL $ if first_match opts
241 then unsafeUnseal $ match_first_patchset opts ps
243 pi2s = mapRL info $ concatRL $ if second_match opts
244 then unsafeUnseal $ match_second_patchset opts ps
248 changelog :: [PatchInfo] -> Doc
249 changelog pis = vcat $ map human_friendly pis