darcs-2.10.2: src/Darcs/UI/Commands/WhatsNew.hs
-- Copyright (C) 2002-2004 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 CPP #-}
module Darcs.UI.Commands.WhatsNew
(
whatsnew
, status
) where
import Prelude hiding ( (^), catch )
import Control.Applicative ( (<$>) )
import Control.Monad ( void )
import Control.Monad.Reader ( runReaderT )
import Control.Monad.State ( evalStateT, liftIO )
import Storage.Hashed.Tree ( Tree )
import System.Exit ( ExitCode (..), exitSuccess, exitWith )
import Darcs.Patch
( PrimOf, PrimPatch, RepoPatch
, applyToTree, plainSummaryPrims, primIsHunk
)
import Darcs.Patch.Apply ( ApplyState )
import Darcs.Patch.Choices ( patchChoicesLps, lpPatch )
import Darcs.Patch.FileHunk ( IsHunk (..) )
import Darcs.Patch.Format ( PatchListFormat (..) )
import Darcs.Patch.Inspect ( PatchInspect (..) )
import Darcs.Patch.Patchy ( Patchy )
import Darcs.Patch.Permutations ( partitionRL )
import Darcs.Patch.Prim.Class ( PrimDetails (..) )
import Darcs.Patch.Show ( ShowPatch )
import Darcs.Patch.Split ( primSplitter )
import Darcs.Patch.TouchesFiles ( choosePreTouching )
import Darcs.Patch.Witnesses.Ordered
( (:>) (..), FL (..), RL (..)
, lengthFL, reverseFL, reverseRL
)
import Darcs.Patch.Witnesses.Sealed
( Sealed (..), Sealed2 (..)
, unFreeLeft
)
import Darcs.Patch.Witnesses.Unsafe ( unsafeCoerceP )
import Darcs.Patch.Witnesses.WZipper ( FZipper (..) )
import Darcs.Repository
( RepoJob (..), Repository
, listRegisteredFiles, readRecorded
, unrecordedChangesWithPatches, withRepository
)
import Darcs.Repository.Diff ( treeDiff )
import Darcs.Repository.Prefs ( filetypeFunction )
import Darcs.Repository.Util ( getMovesPs, getReplaces )
import Darcs.UI.Commands
( DarcsCommand(..), withStdOpts, amInRepository
, commandAlias, nodefaults
)
import Darcs.UI.Commands.Util ( announceFiles )
import Darcs.UI.Flags
( DarcsFlag (Summary, LookForAdds, LookForMoves), diffAlgorithm, diffingOpts
, isUnified, useCache, fixSubPaths
, verbosity, isInteractive, isUnified, lookForAdds, lookForMoves, lookForReplaces, hasSummary
)
import Darcs.UI.Options ( DarcsOption, (^), odesc, ocheck, onormalise, defaultFlags, parseFlags )
import qualified Darcs.UI.Options.All as O
import Darcs.UI.PrintPatch
( contextualPrintPatch, printPatch
, printPatchPager
)
import Darcs.UI.SelectChanges
( InteractiveSelectionContext (..)
, InteractiveSelectionM, KeyPress (..)
, WhichChanges (..), backAll
, backOne, currentFile
, currentPatch, decide
, decideWholeFile, helpFor
, keysFor, prompt
, selectionContextPrim, skipMundane
, skipOne, printSummary
)
import qualified Darcs.UI.SelectChanges as S ( PatchSelectionOptions (..) )
import Darcs.Util.Path ( AbsolutePath, SubPath, toFilePath )
import Darcs.Util.Printer
( putDocLn, renderString, RenderMode(..)
, text, vcat
)
import Darcs.Util.Prompt ( PromptConfig (..), promptChar )
whatsnewBasicOpts :: DarcsOption a
(Maybe O.Summary
-> O.WithContext
-> O.LookFor
-> O.DiffAlgorithm
-> Maybe String
-> Maybe Bool
-> a)
whatsnewBasicOpts
= O.summary
^ O.withContext
^ O.lookfor
^ O.diffAlgorithm
^ O.workingRepoDir
^ O.interactive -- False
whatsnewAdvancedOpts :: DarcsOption a (O.UseIndex -> Bool -> a)
whatsnewAdvancedOpts = O.useIndex ^ O.includeBoring
whatsnewOpts :: DarcsOption a
(Maybe O.Summary
-> O.WithContext
-> O.LookFor
-> O.DiffAlgorithm
-> Maybe String
-> Maybe Bool
-> Maybe O.StdCmdAction
-> Bool
-> Bool
-> O.Verbosity
-> Bool
-> O.UseIndex
-> Bool
-> O.UseCache
-> Maybe String
-> Bool
-> Maybe String
-> Bool
-> a)
whatsnewOpts = whatsnewBasicOpts `withStdOpts` whatsnewAdvancedOpts
patchSelOpts :: [DarcsFlag] -> S.PatchSelectionOptions
patchSelOpts flags = S.PatchSelectionOptions
{ S.verbosity = verbosity flags
, S.matchFlags = []
, S.diffAlgorithm = diffAlgorithm flags
, S.interactive = isInteractive True flags
, S.selectDeps = O.PromptDeps -- option not supported, use default
, S.summary = hasSummary (defaultSummary flags) flags
, S.withContext = isUnified flags
}
defaultSummary :: [DarcsFlag] -> O.Summary
defaultSummary flags = if lookForAdds flags == O.YesLookForAdds then O.YesSummary else O.NoSummary
whatsnew :: DarcsCommand [DarcsFlag]
whatsnew = DarcsCommand
{ commandProgramName = "darcs"
, commandName = "whatsnew"
, commandHelp = whatsnewHelp
, commandDescription = whatsnewDescription
, commandExtraArgs = -1
, commandExtraArgHelp = ["[FILE or DIRECTORY]..."]
, commandCommand = whatsnewCmd
, commandPrereq = amInRepository
, commandGetArgPossibilities = listRegisteredFiles
, commandArgdefaults = nodefaults
, commandAdvancedOptions = odesc whatsnewAdvancedOpts
, commandBasicOptions = odesc whatsnewBasicOpts
, commandDefaults = defaultFlags whatsnewOpts
, commandCheckOptions = ocheck whatsnewOpts
, commandParseOptions = onormalise whatsnewOpts
}
whatsnewDescription :: String
whatsnewDescription = "List unrecorded changes in the working tree."
whatsnewHelp :: String
whatsnewHelp =
"The `darcs whatsnew` command lists unrecorded changes to the working\n" ++
"tree. If you specify a set of files and directories, only unrecorded\n" ++
"changes to those files and directories are listed.\n" ++
"\n" ++
"With the `--summary` option, the changes are condensed to one line per\n" ++
"file, with mnemonics to indicate the nature and extent of the change.\n" ++
"The `--look-for-adds` option causes candidates for `darcs add` to be\n" ++
"included in the summary output. Summary mnemonics are as follows:\n" ++
"\n" ++
"* `A f` and `A d/` respectively mean an added file or directory.\n" ++
"* `R f` and `R d/` respectively mean a removed file or directory.\n" ++
"* `M f -N +M rP` means a modified file, with `N` lines deleted, `M`\n" ++
" lines added, and `P` lexical replacements.\n" ++
"* `f -> g` means a moved file or directory.\n" ++
"* `a f` and `a d/` respectively mean a new, but unadded, file or\n" ++
" directory, when using `--look-for-adds`.\n" ++
"\n" ++
" An exclamation mark (!) as in `R! foo.c`, means the hunk is known to\n" ++
" conflict with a hunk in another patch. The phrase `duplicated`\n" ++
" means the hunk is known to be identical to a hunk in another patch.\n" ++
"\n" ++
"By default, `darcs whatsnew` uses Darcs' internal format for changes.\n" ++
"To see some context (unchanged lines) around each change, use the\n" ++
"`--unified` option. To view changes in conventional `diff` format, use\n" ++
"the `darcs diff` command; but note that `darcs whatsnew` is faster.\n" ++
"\n" ++
"This command exits unsuccessfully (returns a non-zero exit status) if\n" ++
"there are no unrecorded changes.\n"
whatsnewCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO ()
whatsnewCmd fps opts args =
withRepository (useCache opts) $ RepoJob $ \(repo :: Repository p wR wU wR) -> do
files <- if null args
then return Nothing
else Just <$> fixSubPaths fps args
let isLookForMoves = lookForMoves opts == O.YesLookForMoves && parseFlags O.summary opts /= Just O.NoSummary
isLookForAdds = lookForAdds opts == O.YesLookForAdds && parseFlags O.summary opts /= Just O.NoSummary
isLookForReplaces = lookForReplaces opts == O.YesLookForReplaces
-- LookForAdds and LookForMoves implies Summary, unless it's explcitly disabled.
optsModifier = if isLookForAdds
then (Summary :) . filter (\o -> LookForAdds /= o &&
LookForMoves /= o )
else id
opts' = optsModifier opts
movesPs <- if isLookForMoves
then getMovesPs repo files
else return NilFL
Sealed replacePs <- if isLookForReplaces
then getReplaces (diffingOpts opts) repo files
else return (Sealed NilFL)
Sealed noLookChanges <- filteredUnrecordedChanges opts' repo files movesPs
(unsafeCoerceP replacePs :: FL (PrimOf p) wR wR)
pristine <- readRecorded repo
-- If we are looking for moves, return the corresponding FL of changes.
-- If we are looking for adds, return the corresponding FL of changes.
Sealed unaddedNewPathsPs <- if isLookForAdds
then do
-- Use opts not opts', here, since we *do* want to look for adds.
Sealed lookChanges <- filteredUnrecordedChanges opts repo files movesPs (unsafeCoerceP replacePs :: FL (PrimOf p) wR wR)
noLookAddsTree <- applyAddPatchesToPristine noLookChanges pristine
lookAddsTree <- applyAddPatchesToPristine lookChanges pristine
ftf <- filetypeFunction
-- Return the patches that create files/dirs that aren't yet added.
unFreeLeft <$> treeDiff (diffAlgorithm opts) ftf noLookAddsTree lookAddsTree
else return (Sealed NilFL)
announceFiles files "What's new in"
exitOnNoChanges (unaddedNewPathsPs, noLookChanges)
if maybeIsInteractive opts
then runInteractive (interactiveHunks pristine) opts' pristine noLookChanges
else do
printChanges opts' pristine noLookChanges
printUnaddedPaths unaddedNewPathsPs
where
-- |Filter out hunk patches (leaving add patches) and return the tree
-- resulting from applying the filtered patches to the pristine tree.
applyAddPatchesToPristine ps pristine = do
adds :> _ <- return $ partitionRL primIsHunk $ reverseFL ps
applyToTree (reverseRL adds) pristine
exitOnNoChanges :: (FL p wX wY, FL p wU wV) -> IO ()
exitOnNoChanges (NilFL, NilFL) = do putStrLn "No changes!"
exitWith $ ExitFailure 1
exitOnNoChanges _ = return ()
printUnaddedPaths :: PrimPatch p => FL p wX wY -> IO ()
printUnaddedPaths NilFL = return ()
printUnaddedPaths ps =
putDocLn . lowercaseAs . renderString Encode . plainSummaryPrims $ ps
-- Make any add markers lowercase, to distinguish new-but-unadded files
-- from those that are unrecorded, but added.
lowercaseAs x = vcat $ map (text . lowercaseA) $ lines x
lowercaseA ('A' : x) = 'a' : x
lowercaseA x = x
-- |Appropriately print changes, according to the passed flags.
printChanges :: (PatchListFormat p, IsHunk p, Patchy p, ShowPatch p, PrimDetails p,
ApplyState p ~ Tree) => [DarcsFlag] -> Tree IO -> FL p wX wY
-> IO ()
printChanges opts' pristine changes
| Summary `elem` opts' = putDocLn $ plainSummaryPrims changes
| isUnified opts' == O.YesContext = contextualPrintPatch pristine changes
| otherwise = printPatch changes
-- |return the unrecorded changes that affect an optional list of paths.
filteredUnrecordedChanges :: forall p wR wU wT. (RepoPatch p, ApplyState p ~ Tree,
ApplyState (PrimOf p) ~ Tree) => [DarcsFlag]
-> Repository p wR wU wT -> Maybe [SubPath]
-> FL (PrimOf p) wR wT -- look-for-moves patches
-> FL (PrimOf p) wT wT -- look-for-replaces patches
-> IO (Sealed (FL (PrimOf p) wT))
filteredUnrecordedChanges opts' repo files movesPs replacesPs =
let filePaths = map toFilePath <$> files in
choosePreTouching filePaths <$> unrecordedChangesWithPatches (diffingOpts opts') repo files movesPs replacesPs
-- | Runs the 'InteractiveSelectionM' code
runInteractive :: (PatchListFormat p, IsHunk p, Patchy p, ShowPatch p,
PrimPatch p, PatchInspect p, PrimDetails p,
ApplyState p ~ Tree)
=> InteractiveSelectionM p wX wY () -- Selection to run
-> [DarcsFlag] -- Command-line options
-> Tree IO -- Pristine
-> FL p wX wY -- A list of patches
-> IO ()
runInteractive i opts pristine ps' = do
let (choices',lps') = patchChoicesLps ps'
let ps = evalStateT i $
ISC { total = lengthFL lps'
, current = 0
, lps = FZipper NilRL lps'
, choices = choices'
}
void $ runReaderT ps $
selectionContextPrim First "view" (patchSelOpts opts) (Just primSplitter)
Nothing (Just pristine)
-- | The interactive part of @darcs whatsnew@
interactiveHunks :: (PatchListFormat p, IsHunk p, Patchy p, ShowPatch p,
PatchInspect p, PrimDetails p, ApplyState p ~ Tree)
=> Tree IO -> InteractiveSelectionM p wX wY ()
interactiveHunks pristine = do
c <- currentPatch
case c of
Nothing -> liftIO $ putStrLn "No more changes!"
Just (Sealed2 lp) -> do
liftIO $ printPatch (lpPatch lp)
repeatThis lp
where
repeatThis lp = do
thePrompt <- prompt -- "Shall I view this change? (n/m)"
yorn <- liftIO $ promptChar
(PromptConfig thePrompt (keysFor basic_options) (keysFor adv_options)
(Just 'n') "?h")
case yorn of
-- View hunk in context
'v' -> liftIO (contextualPrintPatch pristine (lpPatch lp))
>> repeatThis lp
-- View summary of the change
'x' -> liftIO (printSummary (lpPatch lp))
>> repeatThis lp
-- View hunk and move on
'y' -> liftIO (contextualPrintPatch pristine (lpPatch lp))
>> decide True lp >> next_hunk
-- Go to the next patch
'n' -> decide False lp >> next_hunk
-- Skip the whole file
's' -> do
currentFile >>= maybe
(return ())
(\f -> decideWholeFile f False)
next_hunk
-- View hunk in a pager
'p' -> liftIO (printPatchPager $ lpPatch lp)
>> repeatThis lp
-- Next hunk
'j' -> next_hunk
-- Previous hunk
'k' -> prev_hunk
-- Start from the first change
'g' -> start_over
-- Quit whatsnew
'q' -> liftIO $ exitSuccess
_ -> do liftIO . putStrLn $
helpFor "whatsnew" basic_options adv_options
repeatThis lp
start_over = backAll >> interactiveHunks pristine
next_hunk = skipOne >> skipMundane >> interactiveHunks pristine
prev_hunk = backOne >> interactiveHunks pristine
options_yn =
[ KeyPress 'v' "view this hunk in a context"
, KeyPress 'y'
"view this hunk in a context and go to the next one"
, KeyPress 'n' "go to the next hunk" ]
optionsView =
[ KeyPress 'p' "view this hunk in context wih pager "
, KeyPress 'x' "view a summary of this patch"
]
optionsNav =
[ KeyPress 'q' "quit whatsnew"
, KeyPress 's' "skip the rest of the changes to this file"
, KeyPress 'j' "skip to the next hunk"
, KeyPress 'k' "back up to previous hunk"
, KeyPress 'g' "start over from the first hunk"
]
basic_options = [ options_yn ]
adv_options = [ optionsView, optionsNav ]
-- |status is an alias for whatsnew, with implicit Summary and LookForAdds
-- flags. We override the default description, to include the implicit flags.
status :: DarcsCommand [DarcsFlag]
status = statusAlias { commandCommand = statusCmd
, commandDescription = statusDesc
}
where
statusAlias = commandAlias "status" Nothing whatsnew
statusCmd fps fs = commandCommand whatsnew fps (Summary : LookForAdds : fs)
statusDesc = "Alias for `darcs " ++ commandName whatsnew ++ " -ls '."
maybeIsInteractive :: [DarcsFlag] -> Bool
maybeIsInteractive = maybe False id . parseFlags O.interactive