% 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. \begin{code} module PatchShow ( writePatch, gzWritePatch, showPatch, showNamedPrefix, showUnnamed ) where import Prelude hiding ( pi ) import PatchInfo ( PatchInfo, showPatchInfo ) import FastPackedString ( PackedString, lengthPS, dropPS, takePS, fromPS2Hex ) import FileName ( FileName ) import Lock ( writeDocBinFile, gzWriteDocFile, ) import Printer ( Doc, renderString, vcat, text, userchunk, invisibleText, invisiblePS, blueText, ($$), (<+>), (<>), prefix, userchunkPS, redText, ) import PatchCore ( Patch(..), DirPatchType(..), FilePatchType(..), fn2d, ) \end{code} \section{Patch string formatting} Of course, in order to store our patches in a file, we'll have to save them as some sort of strings. The convention is that each patch string will end with a newline, but on parsing we skip any amount of whitespace between patches. \begin{code} instance Show Patch where show p = renderString (showPatch p) ++ "\n" showPatch :: Patch -> Doc showPatch (FP f AddFile) = showAddFile f showPatch (FP f RmFile) = showRmFile f showPatch (FP f (Hunk line old new)) = showHunk f line old new showPatch (FP f (TokReplace t old new)) = showTok f t old new showPatch (FP f (Binary old new)) = showBinary f old new showPatch (DP d AddDir) = showAddDir d showPatch (DP d RmDir) = showRmDir d showPatch (Move f f') = showMove f f' showPatch (ChangePref p f t) = showChangePref p f t showPatch (ComP ps) = showComP ps showPatch (Split ps) = showSplit ps showPatch (NamedP n d p) = showNamed n d p showPatch (Merger b g _ _ p1 p2) = showMerger b g p1 p2 showPatch (Conflictor inv a b) = showConflictor inv a b writePatch :: FilePath -> Patch -> IO () writePatch f p = writeDocBinFile f $ showPatch p <> text "\n" gzWritePatch :: FilePath -> Patch -> IO () gzWritePatch f p = gzWriteDocFile f $ showPatch p <> text "\n" \end{code} \paragraph{Composite patch} A patch made up of a few other patches. \begin{verbatim} { (indented two) } \end{verbatim} \begin{code} showComP :: [Patch] -> Doc showComP ps = blueText "{" $$ vcat (map showPatch ps) $$ blueText "}" \end{code} \paragraph{Split patch} A split patch is similar to a composite patch (identical in how it's stored), but rather than being composed of several patches grouped together, it is created from one patch that has been split apart, typically through a merge or commutation. \begin{verbatim} ( (indented two) ) \end{verbatim} \begin{code} showSplit :: [Patch] -> Doc showSplit ps = blueText "(" $$ vcat (map showPatch ps) $$ blueText ")" \end{code} \paragraph{Hunk} Replace a hunk (set of contiguous lines) of text with a new hunk. \begin{verbatim} hunk FILE LINE# -LINE ... +LINE ... \end{verbatim} \begin{code} showHunk :: FileName -> Int -> [PackedString] -> [PackedString] -> Doc showHunk f line old new = blueText "hunk" <+> fn2d f <+> text (show line) $$ prefix "-" (vcat $ map userchunkPS old) $$ prefix "+" (vcat $ map userchunkPS new) \end{code} \paragraph{Token replace} Replace a token with a new token. Note that this format means that whitespace must not be allowed within a token. If you know of a practical application of whitespace within a token, let me know and I may change this. \begin{verbatim} replace FILENAME [REGEX] OLD NEW \end{verbatim} \begin{code} showTok :: FileName -> String -> String -> String -> Doc showTok f t o n = blueText "replace" <+> fn2d f <+> text "[" <> userchunk t <> text "]" <+> userchunk o <+> userchunk n \end{code} \paragraph{Binary file modification} Modify a binary file \begin{verbatim} binary FILENAME oldhex *HEXHEXHEX ... newhex *HEXHEXHEX ... \end{verbatim} \begin{code} showBinary :: FileName -> PackedString -> PackedString -> Doc showBinary f o n = blueText "binary" <+> fn2d f $$ invisibleText "oldhex" $$ (vcat $ map makeprintable $ break_every 78 $ fromPS2Hex o) $$ invisibleText "newhex" $$ (vcat $ map makeprintable $ break_every 78 $ fromPS2Hex n) where makeprintable ps = invisibleText "*" <> invisiblePS ps break_every :: Int -> PackedString -> [PackedString] break_every n ps | lengthPS ps < n = [ps] | otherwise = takePS n ps : break_every n (dropPS n ps) \end{code} \paragraph{Add file} Add an empty file to the tree. \verb!addfile filename! \begin{code} showAddFile :: FileName -> Doc showAddFile f = blueText "addfile" <+> fn2d f \end{code} \paragraph{Remove file} Delete a file from the tree. \verb!rmfile filename! \begin{code} showRmFile :: FileName -> Doc showRmFile f = blueText "rmfile" <+> fn2d f \end{code} \paragraph{Move} Rename a file or directory. \verb!move oldname newname! \begin{code} showMove :: FileName -> FileName -> Doc showMove d d' = blueText "move" <+> fn2d d <+> fn2d d' \end{code} \paragraph{Change Pref} Change one of the preference settings. Darcs stores a number of simple string settings. Among these are the name of the test script and the name of the script that must be called prior to packing in a make dist. \begin{verbatim} changepref prefname oldval newval \end{verbatim} \begin{code} showChangePref :: String -> String -> String -> Doc showChangePref p f t = blueText "changepref" <+> text p $$ userchunk f $$ userchunk t \end{code} \paragraph{Add dir} Add an empty directory to the tree. \verb!adddir filename! \begin{code} showAddDir :: FileName -> Doc showAddDir d = blueText "adddir" <+> fn2d d \end{code} \paragraph{Remove dir} Delete a directory from the tree. \verb!rmdir filename! \begin{code} showRmDir :: FileName -> Doc showRmDir d = blueText "rmdir" <+> fn2d d \end{code} \paragraph{Merger patches} Merge two patches. The MERGERVERSION is included to allow some degree of backwards compatibility if the merger algorithm needs to be changed. \begin{verbatim} merger MERGERVERSION \end{verbatim} \begin{code} showMerger :: Bool -> String -> Patch -> Patch -> Doc showMerger forwards g p1 p2 = blueText merger_name <+> text g <+> blueText "(" $$ showPatch p1 $$ showPatch p2 $$ blueText ")" where merger_name = if forwards then "merger" else "regrem" \end{code} \paragraph{Conflictor patches} The conflictor patch type is the replacement for the old merger patch type. FIXME: More explanation should be added here. \begin{verbatim} conflict with tcilfnoc \end{verbatim} \begin{code} showConflictor :: Bool -> [Patch] -> [Patch] -> Doc showConflictor inv a b = redText startconfl $$ vcat (map showPatch b) $$ redText "with" $$ vcat (map showPatch a) $$ redText endconfl where startconfl = if not inv then "conflict" else "tcilfnoc" endconfl = if not inv then "done_conflict" else "done_tcilfnoc" \end{code} \paragraph{Named patches} Named patches are displayed as a ``patch id'' which is in square brackets, followed by a patch. Optionally, after the patch id (but before the patch itself) can come a list of dependencies surrounded by angle brackets. Each dependency consists of a patch id. \begin{code} showNamedPrefix :: PatchInfo -> [PatchInfo] -> Doc showNamedPrefix n d = showPatchInfo n $$ blueText "<" $$ vcat (map showPatchInfo d) $$ blueText ">" showNamed :: PatchInfo -> [PatchInfo] -> Patch -> Doc showNamed n [] p = showPatchInfo n <> showPatch p showNamed n d p = showNamedPrefix n d <+> showPatch p showUnnamed :: Patch -> Doc showUnnamed (NamedP _ _ p) = showUnnamed p showUnnamed (ComP ps) = vcat (map showPatch ps) -- leave out braces... showUnnamed p = showPatch p \end{code}