module Darcs.UI.Commands.WhatsNew
(
whatsnew
, status
) where
import Prelude ()
import Darcs.Prelude
import Control.Monad ( void, when )
import Control.Monad.Reader ( runReaderT )
import Control.Monad.State ( evalStateT, liftIO )
import Darcs.Util.Tree ( Tree )
import System.Exit ( ExitCode (..), exitSuccess, exitWith )
import Data.List.Ordered ( nubSort )
import Darcs.Patch
( PrimOf, PrimPatch, RepoPatch
, applyToTree, plainSummaryPrims, primIsHunk
, listTouchedFiles, IsRepoType
)
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, readRepo
, unrecordedChangesWithPatches, withRepository
)
import Darcs.Repository.Diff ( treeDiff )
import Darcs.Repository.Prefs ( filetypeFunction )
import Darcs.Repository.State ( getMovesPs, getReplaces )
import Darcs.UI.Commands
( DarcsCommand(..), withStdOpts, amInRepository
, commandAlias, nodefaults
)
import Darcs.Repository.Resolution ( patchsetConflictResolutions )
import Darcs.UI.Commands.Util ( announceFiles, filterExistingPaths )
import Darcs.UI.Flags
( DarcsFlag (Summary, LookForAdds, LookForMoves), diffAlgorithm, diffingOpts
, isUnified, useCache, fixSubPaths
, verbosity, isInteractive, isUnified, lookForAdds, lookForMoves, lookForReplaces, hasSummary
, scanKnown, useIndex
)
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, fp2fn )
import Darcs.Util.Printer
( putDocLn, renderString, RenderMode(..)
, text, vcat
)
import Darcs.Util.Prompt ( PromptConfig (..), promptChar )
whatsnewBasicOpts :: DarcsOption a
(Maybe O.Summary
-> O.WithContext
-> Bool
-> O.LookFor
-> O.DiffAlgorithm
-> Maybe String
-> Maybe Bool
-> a)
whatsnewBasicOpts
= O.summary
^ O.withContext
^ O.machineReadable
^ O.lookfor
^ O.diffAlgorithm
^ O.workingRepoDir
^ O.interactive
whatsnewAdvancedOpts :: DarcsOption a (O.UseIndex -> O.IncludeBoring -> a)
whatsnewAdvancedOpts = O.useIndex ^ O.includeBoring
whatsnewOpts :: DarcsOption a
(Maybe O.Summary
-> O.WithContext
-> Bool
-> O.LookFor
-> O.DiffAlgorithm
-> Maybe String
-> Maybe Bool
-> Maybe O.StdCmdAction
-> Bool
-> Bool
-> O.Verbosity
-> Bool
-> O.UseIndex
-> O.IncludeBoring
-> 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.interactive = isInteractive True flags
, S.selectDeps = O.PromptDeps
, S.summary = hasSummary (defaultSummary flags) flags
, S.withContext = isUnified flags
}
defaultSummary :: [DarcsFlag] -> O.Summary
defaultSummary flags
| lookForAdds flags == O.YesLookForAdds = O.YesSummary
| parseFlags O.machineReadable flags = O.YesSummary
| otherwise = 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" ++
"The `--machine-readable` option implies `--summary` while making it more\n" ++
"parsable. Modified files are only shown as `M f`, and moves are shown in\n" ++
"two lines: `F f` and `T g` (as in 'From f To g').\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 rt p wR wU wR) -> do
let scan = scanKnown (O.adds (parseFlags O.lookfor opts)) (parseFlags O.includeBoring opts)
existing_files <- do
files <- if null args then return Nothing
else Just . nubSort <$> fixSubPaths fps args
when (files == Just []) $ fail "No valid arguments were given."
files' <- traverse (filterExistingPaths repo (verbosity opts) (useIndex opts) scan) files
let files'' = fmap snd files'
when (files'' == Just []) $
fail "None of the files you specified exist."
return files''
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
isMachineReadable = parseFlags O.machineReadable opts
opts' | isLookForAdds = (Summary : filter (\o -> LookForAdds /= o &&
LookForMoves /= o ) opts)
| isMachineReadable = (Summary:opts)
| otherwise = opts
movesPs <- if isLookForMoves
then getMovesPs repo existing_files
else return NilFL
Sealed replacePs <- if isLookForReplaces
then getReplaces (diffingOpts opts) repo existing_files
else return (Sealed NilFL)
Sealed noLookChanges <- filteredUnrecordedChanges opts' repo existing_files movesPs
(unsafeCoerceP replacePs :: FL (PrimOf p) wR wR)
pristine <- readRecorded repo
Sealed unaddedNewPathsPs <- if isLookForAdds
then do
Sealed lookChanges <- filteredUnrecordedChanges opts repo existing_files movesPs (unsafeCoerceP replacePs :: FL (PrimOf p) wR wR)
noLookAddsTree <- applyAddPatchesToPristine noLookChanges pristine
lookAddsTree <- applyAddPatchesToPristine lookChanges pristine
ftf <- filetypeFunction
unFreeLeft <$> treeDiff (diffAlgorithm opts) ftf noLookAddsTree lookAddsTree
else return (Sealed NilFL)
announceFiles (verbosity opts) existing_files "What's new in"
exitOnNoChanges (unaddedNewPathsPs, noLookChanges)
if maybeIsInteractive opts
then runInteractive (interactiveHunks pristine) opts' pristine noLookChanges
else do
printChanges repo opts' pristine noLookChanges
printUnaddedPaths unaddedNewPathsPs
where
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 False []) $ ps
lowercaseAs x = vcat $ map (text . lowercaseA) $ lines x
lowercaseA ('A' : x) = 'a' : x
lowercaseA x = x
printChanges :: forall rt p wR wU wX wY. (IsRepoType rt, RepoPatch p, ApplyState p ~ Tree, ApplyState (PrimOf p) ~ Tree)
=> Repository rt p wR wU wR -> [DarcsFlag] -> Tree IO -> FL (PrimOf p) wX wY
-> IO ()
printChanges repo opts' pristine changes
| Summary `elem` opts' = do
r <- readRepo repo
Sealed res <- return $ patchsetConflictResolutions r
let conflictFns = map fp2fn $ nubSort $ listTouchedFiles res
putDocLn $ plainSummaryPrims machineReadable conflictFns changes
| isUnified opts' == O.YesContext = contextualPrintPatch pristine changes
| otherwise = printPatch changes
where machineReadable = parseFlags O.machineReadable opts
filteredUnrecordedChanges :: forall rt p wR wU wT. (RepoPatch p, ApplyState p ~ Tree,
ApplyState (PrimOf p) ~ Tree)
=> [DarcsFlag]
-> Repository rt p wR wU wT -> Maybe [SubPath]
-> FL (PrimOf p) wR wT
-> FL (PrimOf p) wT wT
-> IO (Sealed (FL (PrimOf p) wT))
filteredUnrecordedChanges opts' repo files movesPs replacesPs =
let filePaths = map toFilePath <$> files in
choosePreTouching filePaths <$> unrecordedChangesWithPatches movesPs replacesPs (diffingOpts opts') repo files
runInteractive :: (PatchListFormat p, IsHunk p, Patchy p, ShowPatch p,
PrimPatch p, PatchInspect p, PrimDetails p,
ApplyState p ~ Tree)
=> InteractiveSelectionM p wX wY ()
-> [DarcsFlag]
-> Tree IO
-> FL p wX wY
-> 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 (diffAlgorithm opts)))
Nothing (Just pristine)
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
yorn <- liftIO $ promptChar
(PromptConfig thePrompt (keysFor basic_options) (keysFor adv_options)
(Just 'n') "?h")
case yorn of
'v' -> liftIO (contextualPrintPatch pristine (lpPatch lp))
>> repeatThis lp
'x' -> liftIO (printSummary (lpPatch lp))
>> repeatThis lp
'y' -> liftIO (contextualPrintPatch pristine (lpPatch lp))
>> decide True lp >> next_hunk
'n' -> decide False lp >> next_hunk
's' -> do
currentFile >>= maybe
(return ())
(\f -> decideWholeFile f False)
next_hunk
'p' -> liftIO (printPatchPager $ lpPatch lp)
>> repeatThis lp
'j' -> next_hunk
'k' -> prev_hunk
'g' -> start_over
'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 :: 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