-- 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. {-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -Wno-incomplete-record-updates #-} module Darcs.UI.Commands.Revert ( revert, clean ) where import Darcs.Prelude import Control.Monad ( unless, when, void ) import Darcs.UI.Flags ( DarcsFlag , diffAlgorithm , diffingOpts , isInteractive , pathSetFromArgs , umask , useCache ) import Darcs.UI.Options ( (^), (?) ) import qualified Darcs.UI.Options.All as O import Darcs.UI.Commands ( DarcsCommand(..) , amInHashedRepository , commandAlias , nodefaults , putInfo , putFinished , withStdOpts ) import Darcs.UI.Commands.Util ( announceFiles, filterExistingPaths ) import Darcs.Repository.Unrevert ( writeUnrevert ) import Darcs.UI.Completion ( modifiedFileArgs ) import Darcs.Util.Global ( debugMessage ) import Darcs.Util.Path ( AbsolutePath ) import Darcs.Util.Printer ( Doc, formatWords, vsep ) import Darcs.Util.SignalHandler ( withSignalsBlocked ) import Darcs.Repository ( RepoJob(..) , addToPending , finalizeRepositoryChanges , applyToWorking , readPatches , unrecordedChanges , withRepoLock ) import Darcs.Patch ( invert, commuteFL ) import Darcs.Patch.Permutations ( genCommuteWhatWeCanRL ) import Darcs.Patch.Split ( reversePrimSplitter ) import Darcs.Patch.Witnesses.Ordered ( FL(..) , (:>)(..) , nullFL , (+>>+) , reverseFL ) import Darcs.UI.SelectChanges ( WhichChanges(Last) , selectionConfigPrim , runInvertibleSelection ) import qualified Darcs.UI.SelectChanges as S ( PatchSelectionOptions(..) ) revertDescription :: String revertDescription = "Discard unrecorded changes." revertHelp :: Doc revertHelp = vsep $ map formatWords [ [ "The `darcs revert` command discards unrecorded changes in the working" , "tree. As with `darcs record`, you will be asked which hunks (changes)" , "to revert. The `--all` switch can be used to avoid such prompting. If" , "files or directories are specified, other parts of the working tree" , "are not reverted." ] , [ "In you accidentally reverted something you wanted to keep (for" , "example, typing `darcs rev -a` instead of `darcs rec -a`), you can" , "immediately run `darcs unrevert` to restore it. This is only" , "guaranteed to work if the repository has not changed since `darcs" , "revert` ran." ] ] patchSelOpts :: [DarcsFlag] -> S.PatchSelectionOptions patchSelOpts flags = S.PatchSelectionOptions { S.verbosity = O.verbosity ? flags , S.matchFlags = [] , S.interactive = isInteractive True flags , S.selectDeps = O.PromptDeps -- option not supported, use default , S.withSummary = O.NoSummary -- option not supported, use default } revert :: DarcsCommand revert = DarcsCommand { commandProgramName = "darcs" , commandName = "revert" , commandHelp = revertHelp , commandDescription = revertDescription , commandExtraArgs = -1 , commandExtraArgHelp = ["[FILE or DIRECTORY]..."] , commandCommand = revertCmd , commandPrereq = amInHashedRepository , commandCompleteArgs = modifiedFileArgs , commandArgdefaults = nodefaults , commandOptions = opts } where basicOpts = O.interactive -- True ^ O.repoDir ^ O.diffAlgorithm ^ O.maybelookforadds O.NoLookForAdds advancedOpts = O.umask opts = withStdOpts basicOpts advancedOpts revertCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO () revertCmd fps opts args = withRepoLock (useCache ? opts) (umask ? opts) $ RepoJob $ \_repository -> do existing_paths <- existingPaths _repository =<< pathSetFromArgs fps args announceFiles verbosity existing_paths "Reverting changes in" changes <- unrecordedChanges diffOpts _repository existing_paths case changes of NilFL -> putInfo opts "There are no changes to revert!" _ -> do let selection_config = selectionConfigPrim Last "revert" (patchSelOpts opts) (Just (reversePrimSplitter (diffAlgorithm ? opts))) existing_paths norevert :> torevert <- runInvertibleSelection changes selection_config if nullFL torevert then putInfo opts $ "If you don't want to revert after all, that's fine with me!" else do withSignalsBlocked $ do addToPending _repository diffOpts $ invert torevert debugMessage "About to write the unrevert file." {- The user has split unrecorded into the sequence 'norevert' then 'torevert', which is natural as the bit we keep in unrecorded should have pristine as the context. But the unrevert patch also needs to have pristine as the context, not unrecorded (which can be changed by the user at any time). So we need to commute 'torevert' with 'norevert', and if that fails then we need to keep some of 'norevert' in the actual unrevert patch so it still makes sense. The use of genCommuteWhatWeCanRL minimises the amount of 'norevert' that we need to keep. -} case genCommuteWhatWeCanRL commuteFL (reverseFL norevert :> torevert) of deps :> torevert' :> _ -> do recorded <- readPatches _repository writeUnrevert recorded (deps +>>+ torevert') _repository <- finalizeRepositoryChanges _repository (O.dryRun ? opts) debugMessage "About to apply to the working tree." unless (O.yes (O.dryRun ? opts)) $ void $ applyToWorking _repository verbosity (invert torevert) putFinished opts "reverting" where verbosity = O.verbosity ? opts diffOpts = diffingOpts opts existingPaths repo paths = do paths' <- traverse (filterExistingPaths repo verbosity diffOpts) paths let paths'' = fmap snd paths' when (paths'' == Just []) $ fail "None of the paths you specified exist." return paths'' -- | An alias for 'revert -l' i.e. remove every (non-boring) file or change -- that is not in pristine. clean :: DarcsCommand clean = alias { commandDescription = desc , commandHelp = vsep $ map formatWords [ [ "Remove unrecorded changes from the working tree." ] , [ "This is an alias for `darcs revert -l/--look-for-adds` which" , "means it works also on files that have not been added." , "You can additionally pass `--boring` to get rid of *every*" , "unrecorded file or directory." ] , [ "See description of `darcs revert` for more details." ] ] , commandOptions = opts } where alias = commandAlias "clean" Nothing revert desc = "Alias for `darcs " ++ commandName revert ++ " -l`." basicOpts = O.interactive -- True ^ O.repoDir ^ O.diffAlgorithm ^ O.maybelookforadds O.YesLookForAdds advancedOpts = O.umask opts = withStdOpts basicOpts advancedOpts