-- 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.Unrecord ( unrecord , unpull , obliterate ) where import Darcs.Prelude import Control.Monad ( unless, void, when ) import Darcs.Util.Tree ( Tree ) import Data.Maybe ( fromJust, isJust ) import System.Directory ( doesPathExist ) import System.Exit ( exitSuccess ) import Darcs.Patch ( RepoPatch, commute, effect, invert ) import Darcs.Patch.Apply ( ApplyState ) import Darcs.Patch.Bundle ( makeBundle, minContext ) import Darcs.Patch.CommuteFn ( commuterFLId ) import Darcs.Patch.Depends ( removeFromPatchSet ) import Darcs.Patch.PatchInfoAnd ( hopefully, patchDesc ) import Darcs.Patch.Permutations ( genCommuteWhatWeCanFL ) import Darcs.Patch.Set ( Origin, PatchSet ) import Darcs.Patch.Witnesses.Ordered ( FL(..), mapFL_FL, nullFL, (:>)(..), (+>+) ) import Darcs.Patch.Witnesses.Sealed ( Sealed(..) ) import Darcs.Repository ( PatchInfoAnd , RepoJob(..) , applyToWorking , finalizeRepositoryChanges , readPatches , setTentativePending , tentativelyRemovePatches , unrecordedChanges , withRepoLock ) import Darcs.Repository.Flags ( UpdatePending(..) ) import Darcs.UI.Commands ( DarcsCommand(..) , amInHashedRepository , commandAlias , nodefaults , putFinished , putInfo , putVerbose , setEnvDarcsPatches , withStdOpts ) import Darcs.UI.Commands.Util ( getUniqueDPatchName , historyEditHelp , preselectPatches , printDryRunMessageAndExit ) import Darcs.UI.Completion ( noArgs ) import Darcs.UI.Flags ( DarcsFlag , changesReverse , diffingOpts , dryRun , getOutput , isInteractive , minimize , selectDeps , umask , useCache , verbosity , xmlOutput ) import Darcs.UI.Options ( (?), (^) ) import qualified Darcs.UI.Options.All as O import Darcs.UI.PrintPatch ( printFriendly ) import Darcs.UI.SelectChanges ( WhichChanges(..), runSelection, selectionConfig ) import qualified Darcs.UI.SelectChanges as S ( PatchSelectionOptions(..) ) import Darcs.Util.English ( presentParticiple ) import Darcs.Util.Lock ( writeDocBinFile ) import Darcs.Util.Path ( AbsolutePath, toFilePath, useAbsoluteOrStd ) import Darcs.Util.Printer ( Doc, formatWords, putDoc, sentence, text, ($+$), (<+>) ) import Darcs.Util.Progress ( debugMessage ) import Darcs.Util.Prompt ( promptYorn ) import Darcs.Util.SignalHandler ( catchInterrupt, withSignalsBlocked ) unrecordDescription :: String unrecordDescription = "Remove recorded patches without changing the working tree." unrecordHelp :: Doc unrecordHelp = formatWords [ "Unrecord does the opposite of record: it deletes patches from" , "the repository without changing the working tree. The changes" , "are now again visible with `darcs whatsnew` and you can record" , "or revert them as you please." ] $+$ historyEditHelp unrecord :: DarcsCommand unrecord = DarcsCommand { commandProgramName = "darcs" , commandName = "unrecord" , commandHelp = unrecordHelp , commandDescription = unrecordDescription , commandExtraArgs = 0 , commandExtraArgHelp = [] , commandCommand = unrecordCmd , commandPrereq = amInHashedRepository , commandCompleteArgs = noArgs , commandArgdefaults = nodefaults , commandOptions = unrecordOpts } where unrecordBasicOpts = O.notInRemote ^ O.matchSeveralOrLast ^ O.selectDeps ^ O.interactive -- True ^ O.repoDir unrecordAdvancedOpts = O.umask ^ O.changesReverse unrecordOpts = unrecordBasicOpts `withStdOpts` unrecordAdvancedOpts unrecordCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO () unrecordCmd _ opts _ = withRepoLock (useCache ? opts) (umask ? opts) $ RepoJob $ \_repository -> do (_ :> removal_candidates) <- preselectPatches opts _repository let direction = if changesReverse ? opts then Last else LastReversed selection_config = selectionConfig direction "unrecord" (patchSelOpts opts) Nothing Nothing (_ :> to_unrecord) <- runSelection removal_candidates selection_config when (nullFL to_unrecord) $ do putInfo opts "No patches selected!" exitSuccess putVerbose opts $ text "About to write out (potentially) modified patches..." setEnvDarcsPatches to_unrecord _repository <- tentativelyRemovePatches _repository YesUpdatePending to_unrecord _ <- finalizeRepositoryChanges _repository (O.dryRun ? opts) putInfo opts "Finished unrecording." unpullDescription :: String unpullDescription = "Opposite of pull; unsafe if patch is not in remote repository." unpullHelp :: Doc unpullHelp = text $ "Unpull is an alias for what is nowadays called `obliterate`." unpull :: DarcsCommand unpull = (commandAlias "unpull" Nothing obliterate) { commandHelp = unpullHelp , commandDescription = unpullDescription , commandCommand = obliterateCmd "unpull" } obliterateDescription :: String obliterateDescription = "Delete selected patches from the repository." obliterateHelp :: Doc obliterateHelp = formatWords [ "Obliterate completely removes recorded patches from your local" , "repository. The changes will be undone in your working tree and the" , "patches will not be shown in your changes list anymore. Beware that" , "you can lose precious code by obliterating!" ] $+$ formatWords [ "One way to save obliterated patches is to use the -O flag. A patch" , "bundle will be created locally, that you will be able to apply" , "later to your repository with `darcs apply`. See `darcs send` for" , "a more detailed description." ] $+$ historyEditHelp obliterate :: DarcsCommand obliterate = DarcsCommand { commandProgramName = "darcs" , commandName = "obliterate" , commandHelp = obliterateHelp , commandDescription = obliterateDescription , commandExtraArgs = 0 , commandExtraArgHelp = [] , commandCommand = obliterateCmd "obliterate" , commandPrereq = amInHashedRepository , commandCompleteArgs = noArgs , commandArgdefaults = nodefaults , commandOptions = obliterateOpts } where obliterateBasicOpts = O.notInRemote ^ O.matchSeveralOrLast ^ O.selectDeps ^ O.interactive ^ O.repoDir ^ O.withSummary ^ O.output ^ O.minimize ^ O.diffAlgorithm ^ O.dryRunXml obliterateAdvancedOpts = O.umask ^ O.changesReverse obliterateOpts = obliterateBasicOpts `withStdOpts` obliterateAdvancedOpts obliterateCmd :: String -> (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO () obliterateCmd cmdname _ opts _ = do let verbOpt = verbosity ? opts withRepoLock (useCache ? opts) (umask ? opts) $ RepoJob $ \_repository -> do unrecorded <- unrecordedChanges (diffingOpts opts) _repository Nothing (_ :> removal_candidates) <- preselectPatches opts _repository let direction = if changesReverse ? opts then Last else LastReversed selection_config = selectionConfig direction cmdname (patchSelOpts opts) Nothing Nothing (_ :> removed) <- runSelection removal_candidates selection_config when (nullFL removed) $ do putInfo opts "No patches selected!" exitSuccess case genCommuteWhatWeCanFL (commuterFLId commute) (effect removed :> unrecorded) of unrecorded' :> removed_after_unrecorded :> to_revert -> do effect_removed <- case to_revert of NilFL -> return removed_after_unrecorded _ -> if isInteractive True opts then do putStrLn $ "These unrecorded changes conflict with the " ++ cmdname ++ ":" printFriendly O.Verbose O.NoSummary to_revert yes <- promptYorn "Do you want to revert these unrecorded changes?" if yes then return $ removed_after_unrecorded +>+ to_revert else do putStrLn $ "Okay, " ++ cmdname ++ " cancelled." exitSuccess else fail $ "Can't " ++ cmdname ++ " these patches without reverting some unrecorded changes." printDryRunMessageAndExit "obliterate" verbOpt (O.withSummary ? opts) (dryRun ? opts) (xmlOutput ? opts) (isInteractive True opts) removed setEnvDarcsPatches removed when (isJust $ getOutput opts (return "")) $ -- The call to preselectPatches above may have unwrapped the latest -- clean tag. If we don't want to remove it, we lost information -- about that tag being clean, so we have to access it's inventory. -- To avoid that, and thus preserve laziness, we re-read our original -- patchset and use that to create the context for the bundle. readPatches _repository >>= savetoBundle opts removed _repository <- tentativelyRemovePatches _repository NoUpdatePending removed -- rely on sifting to commute out prims not belonging in pending: setTentativePending _repository unrecorded' withSignalsBlocked $ do _repository <- finalizeRepositoryChanges _repository (O.dryRun ? opts) debugMessage "Applying patches to working tree..." unless (O.yes (O.dryRun ? opts)) $ void $ applyToWorking _repository verbOpt (invert effect_removed) putFinished opts (presentParticiple cmdname) savetoBundle :: (RepoPatch p, ApplyState p ~ Tree) => [DarcsFlag] -> FL (PatchInfoAnd p) wX wR -> PatchSet p Origin wR -> IO () savetoBundle _ NilFL _ = return () savetoBundle opts removed@(x :>: _) orig = do let kept = fromJust $ removeFromPatchSet removed orig genFullBundle = makeBundle Nothing kept (mapFL_FL hopefully removed) bundle <- if not (minimize ? opts) then genFullBundle else do putInfo opts "Minimizing context, to generate bundle with full context hit ctrl-C..." (case minContext kept removed of Sealed (kept' :> removed') -> makeBundle Nothing kept' (mapFL_FL hopefully removed')) `catchInterrupt` genFullBundle let filename = getUniqueDPatchName (patchDesc x) outname <- fromJust (getOutput opts filename) exists <- useAbsoluteOrStd (doesPathExist . toFilePath) (return False) outname when exists $ fail $ "Directory or file named '" ++ (show outname) ++ "' already exists." useAbsoluteOrStd writeDocBinFile putDoc outname bundle putInfo opts $ sentence $ useAbsoluteOrStd (("Saved patch bundle" <+>) . text . toFilePath) (text "stdout") outname patchSelOpts :: [DarcsFlag] -> S.PatchSelectionOptions patchSelOpts flags = S.PatchSelectionOptions { S.verbosity = verbosity ? flags , S.matchFlags = O.matchSeveralOrLast ? flags , S.interactive = isInteractive True flags , S.selectDeps = selectDeps ? flags , S.withSummary = O.withSummary ? flags }