-- Copyright (C) 2004,2007 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. -- | -- Copyright : 2004, 2007 David Roundy -- License : GPL -- Maintainer : darcs-devel@darcs.net -- Stability : experimental -- Portability : portable {-# LANGUAGE OverloadedStrings #-} module Darcs.UI.Commands.Amend ( amend , amendrecord ) where import Darcs.Prelude import Control.Monad ( unless ) import Data.Maybe ( isNothing, isJust ) import Darcs.UI.Commands ( DarcsCommand(..), withStdOpts , commandAlias , nodefaults , setEnvDarcsFiles , setEnvDarcsPatches , amInHashedRepository ) import Darcs.UI.Commands.Util ( announceFiles , historyEditHelp , testTentativeAndMaybeExit ) import Darcs.UI.Completion ( modifiedFileArgs, knownFileArgs ) import Darcs.UI.Flags ( diffingOpts, pathSetFromArgs ) import Darcs.UI.Options ( Config, (?), (^) ) import qualified Darcs.UI.Options.All as O import Darcs.UI.PatchHeader ( AskAboutDeps(..) , HijackOptions(..) , patchHeaderConfig , runHijackT , updatePatchHeader ) import Darcs.Repository.Flags ( UpdatePending(..) ) import Darcs.Patch ( RepoPatch, description, PrimOf , effect, invert, invertFL, canonizeFL ) import Darcs.Patch.Apply ( ApplyState ) import Darcs.Patch.Depends ( contextPatches, patchSetUnion, findCommonWithThem ) import Darcs.Patch.Info ( isTag ) import Darcs.Patch.Named ( fmapFL_Named ) import Darcs.Patch.PatchInfoAnd ( hopefully ) import Darcs.Patch.Set ( Origin, PatchSet ) import Darcs.Patch.Split ( primSplitter ) import Darcs.Patch.PatchInfoAnd ( PatchInfoAnd, info, patchDesc ) import Darcs.Patch.Rebase.Fixup ( RebaseFixup(..) ) import Darcs.Patch.Rebase.Name ( RebaseName(..) ) import Darcs.Util.Path ( AnchoredPath ) import Darcs.Repository ( Repository , AccessType(..) , withRepoLock , RepoJob(..) , identifyRepositoryFor , ReadingOrWriting(Reading) , tentativelyRemovePatches , tentativelyAddPatch , withManualRebaseUpdate , finalizeRepositoryChanges , readPendingAndWorking , readPristine , readPatches , tentativelyRemoveFromPW ) import Darcs.Repository.Pending ( readTentativePending, writeTentativePending ) import Darcs.Repository.Prefs ( getDefaultRepo ) import Darcs.UI.SelectChanges ( WhichChanges(..) , selectionConfigPrim , runInvertibleSelection , withSelectedPatchFromList ) import qualified Darcs.UI.SelectChanges as S ( PatchSelectionOptions(..) ) import Darcs.Util.Exception ( clarifyErrors ) import Darcs.Patch.Witnesses.Ordered ( FL(..), RL, (:>)(..), (+>+) , nullFL, reverseRL, reverseFL, mapFL_FL ) import Darcs.Patch.Witnesses.Sealed ( Sealed(..) ) import Darcs.Util.English ( anyOfClause, itemizeVertical ) import Darcs.Util.Printer ( Doc, formatWords, putDocLn, text, (<+>), ($$), ($+$) ) import Darcs.Util.Printer.Color ( ePutDocLn ) import Darcs.Util.Tree( Tree ) amendDescription :: String amendDescription = "Improve a patch before it leaves your repository." amendHelp :: Doc amendHelp = formatWords [ "Amend updates a \"draft\" patch with additions or improvements," , "resulting in a single \"finished\" patch." ] $+$ formatWords [ "By default `amend` proposes you to record additional changes." , "If instead you want to remove changes, use the flag `--unrecord`." ] $+$ formatWords [ "When recording a draft patch, it is a good idea to start the name with" , "`DRAFT:`. When done, remove it with `darcs amend --edit-long-comment`." , "Alternatively, to change the patch name without starting an editor, " , "use the `--name`/`-m` flag:" ] $+$ text " darcs amend --match 'name \"DRAFT: foo\"' --name 'foo2'" $+$ formatWords [ "Like `darcs record`, if you call amend with files as arguments," , "you will only be asked about changes to those files. So to amend a" , "patch to foo.c with improvements in bar.c, you would run:" ] $+$ text " darcs amend --match 'touch foo.c' bar.c" $+$ historyEditHelp amend :: DarcsCommand amend = DarcsCommand { commandProgramName = "darcs" , commandName = "amend" , commandHelp = amendHelp , commandDescription = amendDescription , commandExtraArgs = -1 , commandExtraArgHelp = ["[FILE or DIRECTORY]..."] , commandCommand = amendCmd , commandPrereq = amInHashedRepository , commandCompleteArgs = fileArgs , commandArgdefaults = nodefaults , commandOptions = allOpts } where fileArgs fps flags args = if (O.amendUnrecord ? flags) then knownFileArgs fps flags args else modifiedFileArgs fps flags args basicOpts = O.amendUnrecord ^ O.notInRemote ^ O.matchOneNontag ^ O.testChanges ^ O.interactive --True ^ O.author ^ O.selectAuthor ^ O.patchname ^ O.askDeps ^ O.askLongComment ^ O.keepDate ^ O.lookforadds ^ O.lookforreplaces ^ O.lookformoves ^ O.repoDir ^ O.diffAlgorithm advancedOpts = O.umask ^ O.setScriptsExecutable allOpts = withStdOpts basicOpts advancedOpts amendCmd fps flags args = pathSetFromArgs fps args >>= doAmend flags amendrecord :: DarcsCommand amendrecord = commandAlias "amend-record" Nothing amend doAmend :: Config -> Maybe [AnchoredPath] -> IO () doAmend cfg files = withRepoLock (O.useCache ? cfg) (O.umask ? cfg) $ RepoJob $ \(repository :: Repository 'RW p wU wR) -> do patchSet <- readPatches repository _ :> candidates <- filterNotInRemote cfg repository patchSet withSelectedPatchFromList "amend" candidates (patchSelOpts cfg) $ \(kept :> oldp) -> do announceFiles (O.verbosity ? cfg) files "Amending changes in" pending :> working <- readPendingAndWorking (diffingOpts cfg) repository files -- auxiliary function needed because the witness types differ for the -- isTag case let go :: FL (PrimOf p) wR wU1 -> IO () go NilFL | not (hasEditMetadata cfg) = putInfo cfg "No changes!" go ch = do let selection_config = selectionConfigPrim First "record" (patchSelOpts cfg) (Just (primSplitter (O.diffAlgorithm ? cfg))) files (chosenPatches :> _) <- runInvertibleSelection ch selection_config addChangesToPatch cfg repository kept oldp chosenPatches pending working if not (isTag (info oldp)) -- amending a normal patch then if O.amendUnrecord ? cfg then do let selection_config = selectionConfigPrim Last "unrecord" (patchSelOpts cfg) (Just (primSplitter (O.diffAlgorithm ? cfg))) files (_ :> chosenPrims) <- runInvertibleSelection (effect oldp) selection_config let invPrims = reverseRL (invertFL chosenPrims) addChangesToPatch cfg repository kept oldp invPrims pending working else go (canonizeFL (O.diffAlgorithm ? cfg) (pending +>+ working)) -- amending a tag else if hasEditMetadata cfg && isNothing files -- the user is not trying to add new changes to the tag so there is -- no reason to warn. then go NilFL -- the user is trying to add new changes to a tag. else do if hasEditMetadata cfg -- the user already knows that it is possible to edit tag metadata, -- note that s/he is providing editing options! then ePutDocLn "You cannot add new changes to a tag." -- the user may not be aware that s/he can edit tag metadata. else ePutDocLn "You cannot add new changes to a tag, but you are allowed to edit tag's metadata (see darcs help amend)." go NilFL addChangesToPatch :: (RepoPatch p, ApplyState p ~ Tree) => Config -> Repository 'RW p wU wR -> RL (PatchInfoAnd p) wC wX -- ^ candidates for --ask-deps -> PatchInfoAnd p wX wR -- ^ original patch -> FL (PrimOf p) wR wY -- ^ changes to add -> FL (PrimOf p) wR wP -- ^ pending -> FL (PrimOf p) wP wU -- ^ working -> IO () addChangesToPatch cfg _repository context oldp chs pending working = if nullFL chs && not (hasEditMetadata cfg) then putInfo cfg "You don't want to record anything!" else do -- remember the old pending for the amend --unrecord case, see below Sealed old_pending <- readTentativePending _repository -- If a rebase is in progress, we want to manually update the rebase -- state, using the amendments directly as rebase fixups. This is -- necessary because otherwise we will first remove the original patch -- then add the amended patch, -- and this can lead to more conflicts than using the amendment as a fixup -- directly. For example, if a rename operation is amended in, the rename -- can be propagated to any edits to the file in the rebase state, whereas -- a delete then add would just cause a conflict. -- -- We can also signal that any explicit dependencies of the old patch -- should be rewritten for the new patch using a 'NameFixup'. (_repository, (mlogf, newp)) <- withManualRebaseUpdate _repository $ \_repository -> do -- Note we pass NoUpdatePending here and below when re-adding the -- amended patch, and instead fix pending explicitly further below. _repository <- tentativelyRemovePatches _repository NoUpdatePending (oldp :>: NilFL) (mlogf, newp) <- runHijackT AlwaysRequestHijackPermission $ updatePatchHeader "amend" (if O.askDeps ? cfg then AskAboutDeps context else NoAskAboutDeps) (patchSelOpts cfg) (patchHeaderConfig cfg) (fmapFL_Named effect (hopefully oldp)) chs let fixups = mapFL_FL PrimFixup (invert chs) +>+ NameFixup (Rename (info newp) (info oldp)) :>: NilFL setEnvDarcsFiles newp _repository <- tentativelyAddPatch _repository NoUpdatePending newp return (_repository, fixups, (mlogf, newp)) let failmsg = maybe "" (\lf -> "\nLogfile left in " ++ lf ++ ".") mlogf tp <- readPristine _repository testTentativeAndMaybeExit tp cfg ("you have a bad patch: '" ++ patchDesc newp ++ "'") "amend it" (Just failmsg) if O.amendUnrecord ? cfg then writeTentativePending _repository $ invert chs +>+ old_pending else tentativelyRemoveFromPW _repository chs pending working _repository <- finalizeRepositoryChanges _repository (O.dryRun ? cfg) `clarifyErrors` failmsg case O.verbosity ? cfg of O.NormalVerbosity -> putDocLn "Finished amending patch." O.Verbose -> putDocLn $ "Finished amending patch:" $$ description newp _ -> return () setEnvDarcsPatches (newp :>: NilFL) filterNotInRemote :: RepoPatch p => Config -> Repository 'RW p wU wR -> PatchSet p Origin wR -> IO ((PatchSet p :> RL (PatchInfoAnd p)) Origin wR) filterNotInRemote cfg repository patchSet = do nirs <- mapM getNotInRemotePath (O.notInRemote ? cfg) if null nirs then -- We call contextPatches here because -- (a) selecting patches beyond the latest clean tag is impossible anyway -- (b) makes it easier to reconstruct a PatchSet w/o the selected patch -- (c) avoids listing the complete list of patches in the repo when user -- rejects the last selectable patch return (contextPatches patchSet) else do putInfo cfg $ "Determining patches not in" <+> anyOfClause nirs $$ itemizeVertical 2 nirs Sealed thems <- patchSetUnion `fmap` mapM readNir nirs in_remote :> only_ours <- return $ findCommonWithThem patchSet thems return (in_remote :> reverseFL only_ours) where readNir loc = do repo <- identifyRepositoryFor Reading repository (O.useCache ? cfg) loc rps <- readPatches repo return (Sealed rps) getNotInRemotePath (O.NotInRemotePath p) = return p getNotInRemotePath O.NotInDefaultRepo = do defaultRepo <- getDefaultRepo let err = fail $ "No default push/pull repo configured, please pass a " ++ "repo name to --" ++ O.notInRemoteFlagName maybe err return defaultRepo hasEditMetadata :: Config -> Bool hasEditMetadata cfg = isJust (O.author ? cfg) || O.selectAuthor ? cfg || isJust (O.patchname ? cfg) || O.askLongComment ? cfg == Just O.YesEditLongComment || O.askLongComment ? cfg == Just O.PromptLongComment || O.askDeps ? cfg patchSelOpts :: Config -> S.PatchSelectionOptions patchSelOpts cfg = S.PatchSelectionOptions { S.verbosity = O.verbosity ? cfg , S.matchFlags = O.matchOneNontag ? cfg , S.interactive = isInteractive cfg , S.selectDeps = O.PromptDeps -- option not supported, use default , S.withSummary = O.NoSummary -- option not supported, use default } isInteractive :: Config -> Bool isInteractive cfg = maybe True id (O.interactive ? cfg) putInfo :: Config -> Doc -> IO () putInfo cfg what = unless (O.verbosity ? cfg == O.Quiet) $ putDocLn what