%  Copyright (C) 2002-2005 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.

\chapter{DarcsRepo format}
\label{repository_format}

A repository consists of a working directory, which has within it a
directory called \verb!_darcs!. There must also be a subdirectory within
\verb!_darcs! named \verb!patches!.  The \verb!patches! directory contains
the actual patches which are in the repository.  There must also be a
\emph{pristine tree}, which may either be a directory containing a cache of
the version of the tree which has been recorded, or a stub, and may be
named either ``current'' or ``pristine''.

\emph{WARNING!} Viewing files in the pristine cache is perfectly
acceptable, but if you view them with an editor (e.g.\ vi or Emacs), that
editor may create temporary files in the pristine tree
(\verb|_darcs/pristine/| or \verb|_darcs/current/|), which will temporarily
cause your repository to be inconsistent.  So \emph{don't record any
patches while viewing files in \_darcs/current with an editor!}  A better
plan would be to restrict yourself to viewing these files with a pager such
as more or less.

Also within \verb!_darcs! is the \verb!inventory! file, which lists all the
patches that are in the repository. Moreover, it also gives the order of the
representation of the patches as they are stored. Given a source of patches,
i.e.\ any other set of repositories which have between them all the patches
contained in a given repository, that repository can be reproduced based on only the
information in the \verb!inventory! file. Under those circumstances, the
order of the patches specified in the \verb!inventory! file would be
unimportant, as this order is only needed to provide context for the
interpretation of the stored patches in this repository.

\begin{code}
module DarcsRepo ( slurp_recorded,
                    slurp_recorded_and_unrecorded,
                    createPristineDirectoryTree,
                    createPartialsPristineDirectoryTree,
                    withRecorded,
                    slurp_all_but_darcs,
                    surely_slurp_Pristine,
                    read_pending, with_new_pending,
                    write_inventory, add_to_inventory, read_repo,
                    lazily_read_repo, sync_repo,
                    get_markedup_file,
                    copy_repo_patches, am_in_repo, am_not_in_repo,
                    write_patch,
                    absolute_dir,
                    get_checkpoint, get_checkpoint_by_default,
                    write_checkpoint, write_recorded_checkpoint,
                    write_checkpoint_patch,
                    remove_from_checkpoint_inventory,
                    apply_patches,
                    apply_patches_with_feedback,
                    simple_feedback,
                    seekRepo, youNeedToBeInRepo
                  ) where

import Directory ( setCurrentDirectory, doesFileExist,
                   doesDirectoryExist )
import Workaround ( getCurrentDirectory, renameFile, createDirectoryIfMissing )
import DarcsUtils ( withCurrentDirectory, bugDoc )
import System.IO ( hPutStrLn, stderr, hFlush, stdout )
import System.IO.Unsafe ( unsafeInterleaveIO )
import Monad ( liftM, when, unless )
import Maybe ( maybeToList, isNothing,
               mapMaybe,
             )
import SignalHandler ( withSignalsBlocked )
import FastPackedString ( PackedString, packString, gzReadFilePS,
                          breakOnPS, nullPS )

import Lock ( withTempDir, withDelayedDir )
import SlurpDirectory ( Slurpy, empty_slurpy,
                        slurp, mmap_slurp, co_slurp,
                        slurp_remove,
                      )
import Patch ( Patch, is_null_patch, invert, patch2patchinfo,
               apply_to_slurpy, apply, try_to_shrink,
               flatten_to_primitives, join_patches, flatten,
               is_setpref, infopatch, is_addfile, is_adddir,
               is_hunk, is_binary, merger_equivalent, commute,
               readPatch, readPatchLazily, gzReadPatchFileLazily,
               writePatch, gzWritePatch,
               really_eq_patches,
               MarkedUpFile, LineMark(..), markup_file, empty_markedup_file,
             )
import PatchInfo ( PatchInfo, make_filename, readPatchInfo,
                   human_friendly, showPatchInfo,
                 )
import Depends ( is_tag,
               )
import Diff ( smart_diff )
import External ( gzFetchFilePS, fetchFilePS, copyFilesOrUrls, Cachable(..),
                  clonePartialsTree )
import Lock ( writeBinFile, writeDocBinFile, appendDocBinFile )
import DarcsFlags ( DarcsFlag(NoCompress, WorkDir, LookForAdds,
                              Partial, Complete, Verbose, Quiet) )
import PatchSet ( PatchSet )
import Depends ( slightly_optimize_patchset,
                 get_patches_beyond_tag, get_patches_in_tag )
import Pristine ( Pristine, identifyPristine, slurpPristine, syncPristine,
                  easyCreatePristineDirectoryTree,
                  easyCreatePartialsPristineDirectoryTree,
                )
import RepoPrefs ( filetype_function )
import FileName ( fp2fn )
import DarcsUtils ( catchall )
import DarcsURL (is_ssh_nopath )
import Printer ( errorDoc, text, (<+>), (<>), putDocLn, putDoc, Doc, ($$), empty )
#include "impossible.h"
\end{code}

\begin{code}
--am_in_repo is a function that is used for command_prereq, which moves in
--to the repository root directory and returns enough information to translate
--relative paths to compensate.
am_not_in_repo :: [DarcsFlag] -> IO (Either String FilePath)
am_not_in_repo f = do go_to_workdir True f
                      air <- amInDarcsRepo
                      if air then return (Left $ "You may not run this"
                                  ++ " command in a repository.")
                             else return $ Right ""

am_in_repo :: [DarcsFlag] -> IO (Either String FilePath)
am_in_repo fs = do go_to_workdir False fs
                   seekRepo amInDarcsRepo (Left youNeedToBeInRepo)

amInDarcsRepo :: IO Bool
amInDarcsRepo = do
  i <- doesFileExist "_darcs/inventory"
  if i then doesDirectoryExist "_darcs/patches" else return False

youNeedToBeInRepo :: String
youNeedToBeInRepo = 
  "You need to be in a repository directory to run this command."

-- | hunt upwards for the darcs repository
-- This keeps changing up one parent directory, testing at each
-- step if the current directory is a repository or not.  
-- WARNING this changes the current directory for good!
seekRepo :: IO Bool
            -- ^ monadic fn to determine if the current dir is a repo
         -> Either String FilePath 
            -- ^ what to return if we don't find a repository
         -> IO (Either String FilePath)
seekRepo matchFn onFail = helper "" where 
  helper dir = do
    air <- matchFn
    if air
       then return (Right dir)
       else do cd <- getCurrentDirectory
               setCurrentDirectory ".."
               cd' <- getCurrentDirectory
               if cd' /= cd
                  then helper $ reverse (takeWhile (/='/') $ reverse cd)///dir
                  else return onFail
(///) :: FilePath -> FilePath -> FilePath
""///b = b
a///"" = a
a///b = a ++ "/" ++ b

go_to_workdir :: Bool -> [DarcsFlag] -> IO () 
go_to_workdir create (WorkDir d:_) = 
  do when create $ createDirectoryIfMissing False d
     -- note that the above could always fail
     setCurrentDirectory d 
       `catchall` (fail $ "can't set directory to "++d)
go_to_workdir c (_:fs) = go_to_workdir c fs
go_to_workdir _ [] = return ()
\end{code}

There is a very special patch which may be stored in \verb!patches! which
is called `pending'.  This patch describes any changes which have not yet
been recorded, and cannot be determined by a simple diff.  For example, file
additions or renames are placed in pending until they are recorded.
Similarly, token replaces are stored in pending until they are recorded.

\begin{code}
read_pending :: String -> IO (Maybe Patch)
read_pending name = do
  pend <- gzReadFilePS name `catch`
          (\_ -> return $ packString "")
  case readPatch pend of
    Nothing -> return Nothing
    Just (p,_) -> return $ if is_null_patch p then Nothing else Just p

with_new_pending :: String -> Patch -> IO Slurpy -> IO ()
with_new_pending name origp job =
 do let p = sift_for_pending origp
        newname = name ++ ".new"
    writePatch newname p
    cur <- job
    mp <- read_pending newname
    unless (isNothing mp) $
      when (isNothing $ apply_to_slurpy (fromJust mp) cur) $ do
      let buggyname = name ++ "_buggy"
      renameFile newname buggyname
      bugDoc $ text "There was an attempt to write an invalid pending!"
            $$ text "If possible, please send the contents of"
               <+> text buggyname
            $$ text "along with a bug report."
    renameFile newname name

sift_for_pending :: Patch -> Patch
sift_for_pending patch =
  case flatten_to_primitives $ merger_equivalent patch of
  oldps ->
    if all (\p -> is_addfile p || is_adddir p) oldps
    then join_patches oldps
    else 
      case try_to_shrink $ sfp [] $ reverse oldps of
      ps | length ps < length oldps -> sift_for_pending $ join_patches ps
         | otherwise -> join_patches ps
      where sfp sofar [] = sofar
            sfp sofar (p:ps)
                | is_hunk p || is_binary p
                    = case commute (join_patches sofar, p) of
                      Just (_, sofar') -> sfp (flatten sofar') ps
                      Nothing -> sfp (p:sofar) ps
            sfp sofar (p:ps) = sfp (p:sofar) ps
\end{code}
\begin{code}
write_patch :: [DarcsFlag] -> Patch -> IO FilePath
write_patch opts p =
    case patch2patchinfo p of
    Nothing -> fail "Patch is not a named patch!"
    Just pinfo ->
       do let writeFun = if NoCompress `elem` opts
                         then writePatch
                         else gzWritePatch
              pname = "_darcs/patches/"++make_filename pinfo
          proceed <- hasChanged pname p
          when proceed $ writeFun pname p
          return pname
    where hasChanged :: FilePath -> Patch -> IO Bool
          hasChanged na pa =
             do old <- gzReadFilePS na
                       `catch` (\_ -> return $ packString "")
                case readPatch old of
                  Nothing -> return True -- new patch
                  Just (oldp,_) -> return $ not (oldp `really_eq_patches` pa)
\end{code}

\begin{code}
createPristineDirectoryTree :: Pristine -> FilePath -> IO ()
createPristineDirectoryTree pris fp
 = do done <- easyCreatePristineDirectoryTree pris fp
      unless done $ do
          patches <- get_whole_repo_patches
          createDirectoryIfMissing True fp
          withCurrentDirectory fp $
              apply_patches [] False noPut noPut patches
    where noPut _ = return ()

createPartialsPristineDirectoryTree :: [FilePath] -> Pristine -> FilePath
                                    -> IO ()
createPartialsPristineDirectoryTree prefs pris fp
 = do done <- easyCreatePartialsPristineDirectoryTree prefs pris fp
      unless done $ withRecorded (withTempDir "recorded") $ \_ -> do
          clonePartialsTree "." fp prefs

withRecorded :: ((FilePath -> IO a) -> IO a) -> (FilePath -> IO a) -> IO a
withRecorded mk_dir f
 = do dir <- getCurrentDirectory
      mk_dir $ \d -> do
          withCurrentDirectory dir $ do
              pris <- identifyPristine
              createPristineDirectoryTree pris d
          f d

get_whole_repo_patches :: IO [(PatchInfo, Maybe Patch)]
get_whole_repo_patches = do
    patches <- read_repo "."
    maybe_chk <- get_checkpoint_by_default [] "."
    return $ case maybe_chk of
             Just chk ->
                 let chtg = fromJust (patch2patchinfo chk)
                     rest = concat $ get_patches_beyond_tag chtg patches
                 in (chtg, Just chk):reverse rest
             Nothing -> reverse $ concat patches

surely_slurp_Pristine :: Pristine -> IO Slurpy
surely_slurp_Pristine pristine = do
    mc <- slurpPristine pristine
    case mc of
        (Just slurpy) -> return slurpy
        Nothing -> do
           patches <- get_whole_repo_patches
           withDelayedDir "pristine.temp" $ \cd -> do
               apply_patches [] False noPut noPut patches
               mmap_slurp cd
             where noPut _ = return ()

sync_repo :: Pristine -> IO ()
sync_repo cur = syncPristine cur

slurp_recorded :: FilePath -> IO Slurpy
slurp_recorded d = withCurrentDirectory d $
                       identifyPristine >>= surely_slurp_Pristine

slurp_all_but_darcs :: FilePath -> IO Slurpy
slurp_all_but_darcs d = do s <- slurp d
                           case slurp_remove (fp2fn "./_darcs") s of
                             Nothing -> return s
                             Just s' -> return s'
\end{code}

\begin{comment}
\end{comment}

\begin{code}
slurp_recorded_and_unrecorded :: FilePath -> IO (Slurpy, Slurpy)
slurp_recorded_and_unrecorded d = withCurrentDirectory d $ do
  cur <- identifyPristine >>= surely_slurp_Pristine
  mbpend <- read_pending "_darcs/patches/pending"
  case mbpend of
    Just pend ->
      case apply_to_slurpy pend cur of
      Nothing -> fail "Yikes, pending has conflicts!"
      Just pendslurp -> do unrec <- co_slurp pendslurp "."
                           return (cur, unrec)
    Nothing -> do unrec <- co_slurp cur "."
                  return (cur, unrec)
\end{code}

\begin{code}
--format_inventory is not exported for use outside of the DarcsRepo module
--itself.
format_inventory :: [(PatchInfo, Maybe Patch)] -> Doc
format_inventory [] = empty
format_inventory ((pinfo,_):ps) = showPatchInfo pinfo
                               $$ format_inventory ps

write_inventory :: FilePath -> PatchSet -> IO ()
-- Note that write_inventory optimizes the inventory it writes out by
-- checking on tag dependencies.
-- FIXME: There is also a problem that write_inventory always writes
-- out the entire inventory, including the parts that you haven't
-- changed...
write_inventory dir ps = withSignalsBlocked $ do
    createDirectoryIfMissing False (dir++"/_darcs/inventories")
    simply_write_inventory "inventory" dir $ slightly_optimize_patchset ps

simply_write_inventory :: String -> FilePath -> PatchSet -> IO ()
simply_write_inventory name dir [] =
    writeBinFile (dir++"/_darcs/"++name) ""
simply_write_inventory name dir [ps] = do
    writeDocBinFile (dir++"/_darcs/"++name) $ format_inventory $ reverse ps
simply_write_inventory _ _ ([]:_) =
    fail $ "Bug in simply_write_inventory, please report!"
simply_write_inventory name dir (ps:pss) = do
    tagname <- return $ make_filename $ fst $ last ps
    simply_write_inventory ("inventories/"++tagname) dir pss
    writeDocBinFile (dir++"/_darcs/"++name) $ text "Starting with tag:"
                                           $$ format_inventory (reverse ps)

add_to_inventory :: FilePath -> [PatchInfo] -> IO ()
add_to_inventory dir pinfos =
    appendDocBinFile (dir++"/_darcs/inventory") $ pidocs pinfos
    where
        pidocs [] = text ""
        pidocs (p:ps) = showPatchInfo p $$ pidocs ps
\end{code}

\begin{code}
copy_repo_patches :: [DarcsFlag] -> FilePath -> FilePath -> IO ()
copy_repo_patches opts dir out = do
  realdir <- absolute_dir dir
  patches <- read_repo "."
  mpi <- if Partial `elem` opts
         then do cps <- read_checkpoints realdir
                 case cps of
                   [] -> return Nothing
                   ((pinfo,_):_) -> return $ Just pinfo
                   -- FIXME above should get last pinfo *before* desired
                   -- tag...
         else return Nothing
  pns <- return $ map (make_filename . fst) $
         since_checkpoint mpi $ concat patches
  copyFilesOrUrls
      opts
      (realdir++"/_darcs/patches") pns (out++"/_darcs/patches") Cachable

since_checkpoint :: Maybe PatchInfo
                 -> [(PatchInfo, Maybe Patch)] -> [(PatchInfo, Maybe Patch)]
since_checkpoint Nothing ps = ps
since_checkpoint (Just ch) ((pinfo, mp):ps)
    | ch == pinfo = [(pinfo, mp)]
    | otherwise = (pinfo, mp) : since_checkpoint (Just ch) ps
since_checkpoint _ [] = []

read_repo :: String -> IO PatchSet
read_repo d = do
  realdir <- absolute_dir d
  read_repo_private False realdir "inventory" `catch`
              (\e -> do hPutStrLn stderr ("Invalid repository:  " ++ realdir)
                        ioError e)

lazily_read_repo :: String -> IO PatchSet
lazily_read_repo d = do
  realdir <- absolute_dir d
  read_repo_private True realdir "inventory" `catch`
              (\e -> do hPutStrLn stderr ("Invalid repository:  " ++ realdir)
                        ioError e)

read_repo_private :: Bool -> FilePath -> FilePath -> IO PatchSet
read_repo_private am_lazy d iname = do
    i <- fetchFilePS (d++"/_darcs/"++iname) Uncachable
    (rest,str) <- case breakOnPS '\n' i of
                  (swt,pistr) | swt == packString "Starting with tag:" ->
                    do r <- rr $ head $ read_patch_ids pistr
                       return (r,pistr)
                  _ -> return ([],i)
    pis <- return $ reverse $ read_patch_ids str
    isdir <- doesDirectoryExist d
    let parse f = let fn = d ++ "/_darcs/patches/" ++ make_filename f
                  in if isdir then parse_local fn
                              else parse_remote fn
    these <- read_patches parse pis
    return $ these : rest
    where rr pinfo = unsafeInterleaveIO $ read_repo_private am_lazy d $
                     "inventories/"++make_filename pinfo
          -- parse_remote should really download to a temporary file removed
          -- at exit
          parse_remote fn = do ps <- gzFetchFilePS fn Cachable
                               return $ liftM fst $
                                   if am_lazy
                                   then Just $ readPatchLazily ps
                                   else readPatch ps
          parse_local fn = if am_lazy
                           then liftM Just $ gzReadPatchFileLazily fn
                           else liftM (liftM fst . readPatch) $ gzReadFilePS fn
          read_patches :: (PatchInfo -> IO (Maybe Patch))
                       -> [PatchInfo] -> IO [(PatchInfo, Maybe Patch)]
          read_patches _ [] = return []
          read_patches parse (i:is) = do
              mp <- unsafeInterleaveIO $ parse i `catch` \_ -> return Nothing
              rest <- read_patches parse is
              return $ (i,mp) : rest

read_patch_ids :: PackedString -> [PatchInfo]
read_patch_ids inv | nullPS inv = []
read_patch_ids inv = case readPatchInfo inv of
                     Just (pinfo,r) -> pinfo : read_patch_ids r
                     Nothing -> []

absolute_dir :: FilePath -> IO FilePath
absolute_dir dir = do
  isdir <- doesDirectoryExist dir
  if not isdir
     then if is_ssh_nopath dir
            then return $ dir++"."
            else return $ if (take 1 $ reverse dir) == "/"
              then init dir
              else dir
             -- hope it's an URL
     else do
          realdir <- withCurrentDirectory dir getCurrentDirectory
                     -- This one is absolute!
          return realdir
\end{code}

\begin{code}
read_checkpoints :: String -> IO [(PatchInfo, Maybe Slurpy)]
read_checkpoints d = do
  realdir <- absolute_dir d
  pistr <- fetchFilePS (realdir++"/_darcs/checkpoints/inventory") Uncachable
           `catchall` (return $ packString "")
  pis <- return $ reverse $ read_patch_ids pistr
  slurpies <- sequence $ map (fetch_checkpoint realdir) pis
  return $ zip pis slurpies
      where fetch_checkpoint r pinfo =
                unsafeInterleaveIO $ do
                pstr <- gzFetchFilePS
                    (r++"/_darcs/checkpoints/"++make_filename pinfo) Cachable
                case fst `liftM` readPatch pstr of
                  Nothing -> return Nothing
                  Just p -> return $ apply_to_slurpy p empty_slurpy

get_checkpoint :: [DarcsFlag] -> String -> IO (Maybe Patch)
get_checkpoint opts r = if Partial `elem` opts
                        then get_check_internal r
                        else return Nothing

get_checkpoint_by_default :: [DarcsFlag] -> String -> IO (Maybe Patch)
get_checkpoint_by_default opts r = if Complete `elem` opts
                                   then return Nothing
                                   else get_check_internal r

get_check_internal :: String -> IO (Maybe Patch)
get_check_internal r = do
  pistr <- fetchFilePS (r++"/_darcs/checkpoints/inventory") Uncachable
           `catchall` (return $ packString "")
  case reverse $ read_patch_ids pistr of
    [] -> return Nothing
    (pinfo:_) -> ((fst `liftM`). readPatch) `liftM`
                 gzFetchFilePS
                     (r++"/_darcs/checkpoints/"++make_filename pinfo) Cachable

format_inv :: [PatchInfo] -> Doc
format_inv [] = empty
format_inv (pinfo:ps) = showPatchInfo pinfo
                     $$ format_inv ps

write_recorded_checkpoint :: PatchInfo -> IO ()
write_recorded_checkpoint pinfo = do
    ps <- (map (fromJust.snd).reverse.concat) `liftM` read_repo "."
    ftf <- filetype_function
    s <- slurp_recorded "."
    write_checkpoint_patch $ infopatch pinfo $ join_patches $ changepps ps ++
                 maybeToList (smart_diff [LookForAdds] ftf empty_slurpy s)
    where changeps p = filter is_setpref $ flatten_to_primitives p
          changepps ps = concat $ map changeps $ ps

write_checkpoint :: PatchInfo -> IO ()
write_checkpoint pinfo = do
    repodir <- getCurrentDirectory
    ps <- (reverse.map (fromJust.snd).concat.get_patches_in_tag pinfo)
          `liftM` read_repo "."
    ftf <- filetype_function
    with_tag pinfo $ do
      s <- mmap_slurp "."
      setCurrentDirectory repodir
      write_checkpoint_patch $ infopatch pinfo $ join_patches $ changepps ps ++
                   maybeToList (smart_diff [LookForAdds] ftf empty_slurpy s)
    where changeps p = filter is_setpref $ flatten_to_primitives p
          changepps ps = concat $ map changeps $ ps

write_checkpoint_patch :: Patch -> IO ()
write_checkpoint_patch p =
  case patch2patchinfo p of
  Just pinfo -> do
    createDirectoryIfMissing False "_darcs/checkpoints"
    gzWritePatch ("_darcs/checkpoints/"++make_filename pinfo) p
    cpi <- (map fst) `liftM` read_checkpoints "."
    writeDocBinFile "_darcs/checkpoints/inventory"
        $ format_inv $ reverse $ pinfo:cpi
  Nothing -> bug "bad patch in write_checkpoint_patch"

remove_from_checkpoint_inventory :: [Patch] -> IO ()
remove_from_checkpoint_inventory ps = do
    -- only tags can be checkpoints
    let pinfos = filter is_tag $ mapMaybe patch2patchinfo ps
    unless (null pinfos) $ do
        createDirectoryIfMissing False "_darcs/checkpoints"
        cpi <- (map fst) `liftM` read_checkpoints "."
        writeDocBinFile "_darcs/checkpoints/inventory" $
            format_inv $ reverse $ filter (`notElem` pinfos) cpi

with_tag :: PatchInfo -> (IO ()) -> IO ()
with_tag pinfo job = do
    ps <- read_repo "."
    case get_patches_beyond_tag pinfo ps of
        [extras] -> withRecorded (withTempDir "checkpoint") $ \_ -> do
                    apply_patches [] False noPut noPut $ map invert_it extras
                    job
        _ -> bug "with_tag"
    where noPut _ = return ()
          invert_it (pin, Just p) = (pin, Just $ invert p)
          invert_it (pin, Nothing) = errorDoc $ text "Couldn't read patch:"
                                             $$ human_friendly pin
\end{code}

The \verb!_darcs! directory also contains a directory called
``\verb!prefs!'', which is described in Chapter~\ref{configuring}.

\begin{comment}
\section{Getting interesting info on change history}

One can query the repository for the entire markup history of a file.  This
provides a data structure which contains a history of \emph{all} the
revisions ever made on a given file.

\begin{code}
get_markedup_file :: PatchInfo -> FilePath -> IO MarkedUpFile
get_markedup_file pinfo f = do
  patches <- liftM (dropWhile (\ (pi',_)-> pi' /= pinfo)
                    . reverse . concat) $ read_repo "."
  return $ snd $ do_mark_all patches (f, empty_markedup_file)
do_mark_all :: [(PatchInfo, Maybe Patch)]
            -> (FilePath, MarkedUpFile) -> (FilePath, MarkedUpFile)
do_mark_all ((n,Just p):pps) (f, mk) =
    do_mark_all pps $ markup_file n p (f, mk)
do_mark_all ((_,Nothing):_) (f, _) =
    (f, [(packString "Error reading a patch!",None)])
do_mark_all [] (f, mk) = (f, mk)
\end{code}

\begin{code}
apply_patches_with_feedback :: [DarcsFlag]
                            -> Bool -- ^ is working dir; see 'apply'
                            ->(Int -> Int -> PatchInfo -> IO())
                            -> (Doc -> IO ())
                            -> [(PatchInfo, Maybe Patch)] -> IO ()
apply_patches_with_feedback  _ _ _ _ [] = return ()
apply_patches_with_feedback opts isW feedback putInfo patches = do
    apply_cautiously patches 1
    where patch_count = length patches
          apply_cautiously ((pinfo, Nothing) : _) _ =
             do errorDoc $ text "Couldn't read patch" <+> human_friendly pinfo
          apply_cautiously ((pinfo, Just p) : more_patches) index =
             do feedback index patch_count pinfo
                apply opts isW p `catch` \e ->
                  do putInfo $ text "Unapplicable patch:"
                     putInfo $ human_friendly pinfo
                     ioError e
                apply_cautiously more_patches (index+1)
          apply_cautiously [] _ = return ()

apply_patches :: [DarcsFlag]
              -> Bool -- ^ is working dir; see 'apply'
              -> (Doc -> IO ()) -> (Doc -> IO ())
              -> [(PatchInfo, Maybe Patch)] -> IO ()
apply_patches opts isW putVerbose putInfo patches =
    apply_patches_with_feedback opts isW normalFeedback putInfo patches
    where normalFeedback _ _ pinfo = putVerbose $ text "Applying patch" <+> human_friendly pinfo  

simple_feedback :: [DarcsFlag] -> Int -> Int -> PatchInfo -> IO ()
simple_feedback opts index total pinfo =
    if am_verbose
    then putDocLn $ text "Applying patch" <+> text (show index)
                 <+> text "of" <+> text (show total) <> text ":" <+> human_friendly pinfo
    else when am_informative $ do putDoc $ text "\rApplying patch" 
                                        <+> text (show index) <+> text "of" <+> text (show total) <> text "... "
                                  hFlush stdout    
                                  when (index == total) $ putDocLn $ text "done."
    where am_verbose = Verbose `elem` opts
          am_informative = not $ Quiet `elem` opts

\end{code}

\end{comment}

