{-# LANGUAGE OverloadedStrings #-}
module Darcs.UI.Commands.Annotate ( annotate ) where
import Prelude ()
import Darcs.Prelude
import Control.Arrow ( first )
import Control.Monad ( when )
import Darcs.UI.Commands ( DarcsCommand(..), withStdOpts, nodefaults, amInHashedRepository )
import Darcs.UI.Completion ( knownFileArgs )
import Darcs.UI.Flags ( DarcsFlag, useCache, fixSubPaths, patchIndexYes )
import Darcs.UI.Options ( (^), odesc, ocheck, onormalise
, defaultFlags, parseFlags, (?) )
import qualified Darcs.UI.Options.All as O
import Darcs.Repository.State ( readRecorded )
import Darcs.Repository
( withRepository
, withRepoLockCanFail
, RepoJob(..)
, readRepo
, repoPatchType
)
import Darcs.Repository.PatchIndex ( attemptCreatePatchIndex )
import Darcs.Patch.Set ( patchSet2RL )
import Darcs.Patch ( invertRL )
import qualified Data.ByteString.Char8 as BC ( pack, concat, intercalate )
import Data.ByteString.Lazy ( toChunks )
import Darcs.Patch.ApplyMonad( withFileNames )
import System.FilePath.Posix ( (</>) )
import Darcs.Patch.Match ( haveNonrangeMatch, getNonrangeMatchS )
import Darcs.Repository.Match ( getOnePatchset )
import Darcs.Repository.PatchIndex ( getRelevantSubsequence, canUsePatchIndex )
import Darcs.Patch.Witnesses.Sealed ( Sealed(..), seal )
import qualified Darcs.Patch.Annotate as A
import Darcs.Util.Tree( TreeItem(..), readBlob, list, expand )
import Darcs.Util.Tree.Monad( findM, virtualTreeIO )
import Darcs.Util.Path( floatPath, anchorPath, fp2fn, toFilePath
, AbsolutePath, SubPath )
import Darcs.Util.Exception ( die )
annotateDescription :: String
annotateDescription = "Annotate lines of a file with the last patch that modified it."
annotateHelp :: String
annotateHelp = unlines
[ "When `darcs annotate` is called on a file, it will find the patch that"
, "last modified each line in that file. This also works on directories."
, ""
, "The `--machine-readable` option can be used to generate output for"
, "machine postprocessing."
]
annotate :: DarcsCommand [DarcsFlag]
annotate = DarcsCommand
{ commandProgramName = "darcs"
, commandName = "annotate"
, commandHelp = annotateHelp
, commandDescription = annotateDescription
, commandExtraArgs = 1
, commandExtraArgHelp = ["[FILE or DIRECTORY]"]
, commandCommand = annotateCmd
, commandPrereq = amInHashedRepository
, commandCompleteArgs = knownFileArgs
, commandArgdefaults = nodefaults
, commandAdvancedOptions = odesc annotateAdvancedOpts
, commandBasicOptions = odesc annotateBasicOpts
, commandDefaults = defaultFlags annotateOpts
, commandCheckOptions = ocheck annotateOpts
, commandParseOptions = onormalise annotateOpts
}
where
annotateBasicOpts = O.machineReadable ^ O.matchUpToOne ^ O.repoDir
annotateAdvancedOpts = O.patchIndexYes
annotateOpts = annotateBasicOpts `withStdOpts` annotateAdvancedOpts
annotateCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO ()
annotateCmd fps opts args = do
fixed_paths <- fixSubPaths fps args
case fixed_paths of
[] -> die "Error: annotate needs a filename to work with"
(fixed_path:_) -> do
when (patchIndexYes ? opts == O.YesPatchIndex)
$ withRepoLockCanFail (useCache ? opts)
$ RepoJob (\repo -> readRepo repo >>= attemptCreatePatchIndex repo)
annotateCmd' opts fixed_path
annotateCmd' :: [DarcsFlag] -> SubPath -> IO ()
annotateCmd' opts fixed_path = withRepository (useCache ? opts) $ RepoJob $ \repository -> do
let matchFlags = parseFlags O.matchUpToOne opts
r <- readRepo repository
recorded <- readRecorded repository
(patches, initial, path') <-
if haveNonrangeMatch (repoPatchType repository) matchFlags
then do Sealed x <- getOnePatchset repository matchFlags
let fn = [fp2fn $ toFilePath fixed_path]
nonRangeMatch = getNonrangeMatchS matchFlags r
(_, [path], _) = withFileNames Nothing fn nonRangeMatch
initial <- snd `fmap` virtualTreeIO (getNonrangeMatchS matchFlags r) recorded
return (seal $ patchSet2RL x, initial, toFilePath path)
else return (seal $ patchSet2RL r, recorded, toFilePath fixed_path)
let path = "./" ++ path'
found <- findM initial (floatPath $ toFilePath path)
let fmt = if parseFlags O.machineReadable opts then A.machineFormat else A.format
usePatchIndex <- (O.yes (O.patchIndexYes ? opts) &&) <$> canUsePatchIndex repository
case found of
Nothing -> die $ "Error: no such file or directory: " ++ toFilePath path
Just (SubTree s) -> do
s' <- expand s
let subs = map (fp2fn . (path </>) . anchorPath "" . fst) $ list s'
showPath (n, File _) = BC.pack (path </> n)
showPath (n, _) = BC.concat [BC.pack (path </> n), "/"]
(Sealed ans_patches) <- do
if not usePatchIndex
then return patches
else getRelevantSubsequence patches repository r subs
putStrLn $ fmt (BC.intercalate "\n" $
map (showPath . first (anchorPath "")) $ list s') $
A.annotateDirectory (invertRL ans_patches) (fp2fn path) subs
Just (File b) -> do (Sealed ans_patches) <- do
if not usePatchIndex
then return patches
else getRelevantSubsequence patches repository r [fp2fn path]
con <- BC.concat `fmap` toChunks `fmap` readBlob b
putStrLn $ fmt con $
A.annotateFile (invertRL ans_patches) (fp2fn path) con
Just (Stub _ _) -> impossible