%  Copyright (C) 2002-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.

\subsection{darcs revert}
\begin{code}
module Revert ( revert ) where
import System ( ExitCode(..), exitWith )
import Monad ( when, liftM )
import List ( sort )
import SignalHandler ( withSignalsBlocked )

import DarcsCommands ( DarcsCommand(..), nodefaults )
import DarcsArguments ( DarcsFlag( AnyOrder, All ),
                        ignoretimes, verbose, working_repo_dir,
                        all_gui_interactive,
                        fix_filepaths_wrt,
                        list_registered_files, umask_option,
                      )
import DarcsUtils ( askUser )
import Repository ( withRepoLock,
                    get_unrecorded,
                    with_new_pending, sync_repo,
                    applyToWorking,
                    amInRepository, slurp_recorded_and_unrecorded,
                  )
import Patch ( join_patches, invert, is_null_patch,
               flatten, apply_to_filepaths )
import SelectChanges ( with_selected_last_changes_to_files )
import TouchesFiles ( choose_touching )
import Unrevert ( write_unrevert )
\end{code}
\begin{code}
revert_description :: String
revert_description =
 "Revert to the recorded version (safe the first time only)."
\end{code}

\options{revert}

\haskell{revert_help} The actions of a revert may be reversed using the
unrevert command (see subsection~\ref{unrevert}).  However, if you've made
changes since the revert your mileage may vary, so please be careful.

\begin{code}
revert_help :: String
revert_help =
 "Revert is used to undo changes made to the working copy which have\n"++
 "not yet been recorded.  You will be prompted for which changes you\n"++
 "wish to undo. The last revert can be undone safely using the unrevert\n"++
 "command if the working copy was not modified in the meantime.\n"
\end{code}
\begin{code}
revert :: DarcsCommand
revert = DarcsCommand {command_name = "revert",
                       command_help = revert_help,
                       command_description = revert_description,
                       command_extra_args = -1,
                       command_extra_arg_help = ["[FILE or DIRECTORY]..."],
                       command_command = revert_cmd,
                       command_prereq = amInRepository,
                       command_get_arg_possibilities = list_registered_files,
                       command_argdefaults = nodefaults,
                       command_darcsoptions = [verbose, ignoretimes,
                                               all_gui_interactive,
                                               working_repo_dir,
                                               umask_option]}
\end{code}
You can give revert optional arguments indicating files or directories.  If
you do so it will only prompt you to revert changes in those files or in
files in those directories.
\begin{code}
revert_cmd :: [DarcsFlag] -> [String] -> IO ()
revert_cmd opts args = withRepoLock opts $ \repository -> do
  files <- sort `liftM` fix_filepaths_wrt "." opts args
  when (concat files /= "") $
       putStrLn $ "Reverting changes in "++unwords (map show files)++"..\n"
  maybe_changes <- if All `elem` opts
                   then get_unrecorded repository (AnyOrder:opts)
                   else get_unrecorded repository opts
  (rec, working_dir) <- slurp_recorded_and_unrecorded repository
  case maybe_changes of
    Nothing -> putStrLn "There are no changes to revert!"
    Just changes ->
      let pre_changed_files = apply_to_filepaths (invert changes) files in
      if is_null_patch $ choose_touching pre_changed_files changes
      then putStrLn "There are no changes to revert!"
      else with_selected_last_changes_to_files "revert" opts working_dir
               pre_changed_files (flatten changes) Nothing $ \ (p, skipped) ->
        if null p
        then putStrLn $ "If you don't want to revert after all," ++
                        " that's fine with me!"
        else do
             let seqList [] = ()
                 seqList (_:xs) = seqList xs
                 skipped' = seqList p `seq` skipped
             yorn <- if All `elem` opts
                     then return "y"
                     else askUser "Do you really want to revert these changes? "
             case yorn of ('y':_) -> return ()
                          _ -> exitWith $ ExitSuccess
             write_unrevert (join_patches skipped') p rec
             withSignalsBlocked $
                 with_new_pending repository (join_patches skipped')$
                 applyToWorking repository opts (invert $ join_patches p) `catch` \e ->
                 fail ("Unable to apply inverse patch!" ++ show e)
  sync_repo repository
  putStrLn "Finished reverting."
\end{code}

