{-# 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 ( diffOpts, pathSetFromArgs )
import Darcs.UI.Options ( (^), oparse, odesc, ocheck, defaultFlags, (?) )
import qualified Darcs.UI.Options.All as O
import Darcs.UI.PatchHeader ( updatePatchHeader, AskAboutDeps(..)
, HijackOptions(..)
, runHijackT )
import Darcs.Repository.Flags ( UpdatePending(..), DryRun(NoDryRun) )
import Darcs.Patch ( IsRepoType, RepoPatch, description, PrimOf
, effect, invert, invertFL, sortCoalesceFL
)
import Darcs.Patch.Apply ( ApplyState )
import Darcs.Patch.Depends ( patchSetUnion, findCommonWithThem )
import Darcs.Patch.Info ( isTag )
import Darcs.Patch.Named ( fmapFL_Named )
import Darcs.Patch.PatchInfoAnd ( hopefully )
import Darcs.Patch.Set ( Origin, PatchSet, patchSet2RL )
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
, withRepoLock
, RepoJob(..)
, identifyRepositoryFor
, ReadingOrWriting(Reading)
, tentativelyRemovePatches
, tentativelyAddPatch
, withManualRebaseUpdate
, finalizeRepositoryChanges
, invalidateIndex
, readPendingAndWorking
, readRecorded
, readRepo
)
import Darcs.Repository.Pending ( tentativelyRemoveFromPW )
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(..), FlippedSeal(..) )
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
data AmendConfig = AmendConfig
{ amendUnrecord :: Bool
, notInRemote :: [O.NotInRemote]
, matchFlags :: [O.MatchFlag]
, testChanges :: O.TestChanges
, interactive :: Maybe Bool
, author :: Maybe String
, selectAuthor :: Bool
, patchname :: Maybe String
, askDeps :: Bool
, askLongComment :: Maybe O.AskLongComment
, keepDate :: Bool
, lookfor :: O.LookFor
, _workingRepoDir :: Maybe String
, withContext :: O.WithContext
, diffAlgorithm :: O.DiffAlgorithm
, verbosity :: O.Verbosity
, compress :: O.Compression
, useIndex :: O.UseIndex
, umask :: O.UMask
, sse :: O.SetScriptsExecutable
, useCache :: O.UseCache
}
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
, commandAdvancedOptions = odesc advancedOpts
, commandBasicOptions = odesc basicOpts
, commandDefaults = defaultFlags allOpts
, commandCheckOptions = ocheck 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
^ O.author
^ O.selectAuthor
^ O.patchname
^ O.askDeps
^ O.askLongComment
^ O.keepDate
^ O.lookfor
^ O.repoDir
^ O.withContext
^ O.diffAlgorithm
advancedOpts
= O.compress
^ O.useIndex
^ O.umask
^ O.setScriptsExecutable
allOpts = withStdOpts basicOpts advancedOpts
config = oparse (basicOpts ^ O.verbosity ^ advancedOpts ^ O.useCache) AmendConfig
amendCmd fps flags args = pathSetFromArgs fps args >>= doAmend (config flags)
amendrecord :: DarcsCommand
amendrecord = commandAlias "amend-record" Nothing amend
doAmend :: AmendConfig -> Maybe [AnchoredPath] -> IO ()
doAmend cfg files =
withRepoLock NoDryRun (useCache cfg) YesUpdatePending (umask cfg) $
RebaseAwareJob $ \(repository :: Repository rt p wR wU wR) -> do
patchSet <- readRepo repository
FlippedSeal patches <- filterNotInRemote cfg repository patchSet
withSelectedPatchFromList "amend" patches (patchSelOpts cfg) $ \ (_ :> oldp) -> do
announceFiles (verbosity cfg) files "Amending changes in"
pristine <- readRecorded repository
pending :> working <-
readPendingAndWorking
(diffingOpts cfg)
(O.moves (lookfor cfg))
(O.replaces (lookfor cfg))
repository
files
let go :: forall wU1 . 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 (diffAlgorithm cfg)))
files
(Just pristine)
(chosenPatches :> _) <- runInvertibleSelection ch selection_config
addChangesToPatch cfg repository oldp chosenPatches pending working
if not (isTag (info oldp))
then if amendUnrecord cfg
then do let selection_config =
selectionConfigPrim Last "unrecord"
(patchSelOpts cfg)
(Just (primSplitter (diffAlgorithm cfg)))
files
(Just pristine)
(_ :> chosenPrims) <- runInvertibleSelection (effect oldp) selection_config
let invPrims = reverseRL (invertFL chosenPrims)
addChangesToPatch cfg repository oldp invPrims pending working
else go (sortCoalesceFL (pending +>+ working))
else if hasEditMetadata cfg && isNothing files
then go NilFL
else do if hasEditMetadata cfg
then ePutDocLn "You cannot add new changes to a tag."
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 :: forall rt p wR wU wT wX wY wP
. (IsRepoType rt, RepoPatch p, ApplyState p ~ Tree)
=> AmendConfig
-> Repository rt p wR wU wT
-> PatchInfoAnd rt p wX wT
-> FL (PrimOf p) wT wY
-> FL (PrimOf p) wT wP
-> FL (PrimOf p) wP wU
-> IO ()
addChangesToPatch cfg _repository oldp chs pending working =
if nullFL chs && not (hasEditMetadata cfg)
then putInfo cfg "You don't want to record anything!"
else do
invalidateIndex _repository
(_repository, (mlogf, newp)) <-
withManualRebaseUpdate _repository $ \_repository -> do
_repository <-
tentativelyRemovePatches
_repository
(compress cfg)
NoUpdatePending
(oldp :>: NilFL)
(mlogf, newp) <-
runHijackT AlwaysRequestHijackPermission $
updatePatchHeader
"amend"
(if askDeps cfg
then AskAboutDeps _repository
else NoAskAboutDeps)
(patchSelOpts cfg)
(diffAlgorithm cfg)
(keepDate cfg)
(selectAuthor cfg)
(author cfg)
(patchname cfg)
(askLongComment 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
(compress cfg)
(verbosity cfg)
NoUpdatePending
newp
return (_repository, fixups, (mlogf, newp))
let failmsg = maybe "" (\lf -> "\nLogfile left in " ++ lf ++ ".") mlogf
testTentativeAndMaybeExit
_repository
(verbosity cfg)
(testChanges cfg)
(sse cfg)
(isInteractive cfg)
("you have a bad patch: '" ++ patchDesc newp ++ "'")
"amend it"
(Just failmsg)
tentativelyRemoveFromPW _repository chs pending working
_repository <-
finalizeRepositoryChanges _repository YesUpdatePending (compress cfg)
`clarifyErrors` failmsg
case verbosity cfg of
O.NormalVerbosity -> putDocLn "Finished amending patch."
O.Verbose -> putDocLn $ "Finished amending patch:" $$ description newp
_ -> return ()
setEnvDarcsPatches (newp :>: NilFL)
filterNotInRemote :: (IsRepoType rt, RepoPatch p)
=> AmendConfig
-> Repository rt p wR wU wT
-> PatchSet rt p Origin wR
-> IO (FlippedSeal (RL (PatchInfoAnd rt p)) wR)
filterNotInRemote cfg repository patchSet = do
nirs <- mapM getNotInRemotePath (notInRemote cfg)
if null nirs
then
return (FlippedSeal (patchSet2RL patchSet))
else do
putInfo cfg $
"Determining patches not in" <+> anyOfClause nirs $$ itemizeVertical 2 nirs
Sealed thems <- patchSetUnion `fmap` mapM readNir nirs
_ :> only_ours <- return $ findCommonWithThem patchSet thems
return (FlippedSeal (reverseFL only_ours))
where
readNir loc = do
repo <- identifyRepositoryFor Reading repository (useCache cfg) loc
rps <- readRepo 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 :: AmendConfig -> Bool
hasEditMetadata cfg = isJust (author cfg)
|| selectAuthor cfg
|| isJust (patchname cfg)
|| askLongComment cfg == Just O.YesEditLongComment
|| askLongComment cfg == Just O.PromptLongComment
|| askDeps cfg
patchSelOpts :: AmendConfig -> S.PatchSelectionOptions
patchSelOpts cfg = S.PatchSelectionOptions
{ S.verbosity = verbosity cfg
, S.matchFlags = matchFlags cfg
, S.interactive = isInteractive cfg
, S.selectDeps = O.PromptDeps
, S.withSummary = O.NoSummary
, S.withContext = withContext cfg
}
diffingOpts :: AmendConfig -> (O.UseIndex, O.ScanKnown, O.DiffAlgorithm)
diffingOpts cfg = diffOpts (useIndex cfg) (O.adds (lookfor cfg)) O.NoIncludeBoring (diffAlgorithm cfg)
isInteractive :: AmendConfig -> Bool
isInteractive = maybe True id . interactive
putInfo :: AmendConfig -> Doc -> IO ()
putInfo cfg what = unless (verbosity cfg == O.Quiet) $ putDocLn what