% Copyright (C) 2002-2003 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. \section{Patch relationships} \begin{code} module PatchCore ( Patch(..), DirPatchType(..), FilePatchType(..), nubAdjBy, invert, null_patch, is_null_patch, infopatch, flatten, flatten_to_primitives, merger_undo, n_fn, fn2d, adddeps, adddir, addfile, binary, changepref, hunk, move, namepatch, rmdir, rmfile, tokreplace, join_patches, unjoin_patches, getdeps, is_addfile, is_hunk, is_binary, is_merger, is_setpref, is_similar, is_adddir, patch2patchinfo, patchname, filenames, ) where import Prelude hiding ( pi ) import FastPackedString ( PackedString, nullPS ) import FileName ( FileName, fn2ps, fn2fp, fp2fn, norm_path ) import PatchInfo ( PatchInfo, patchinfo, make_filename, invert_name ) import Printer ( Doc, packedString ) #include "impossible.h" data Patch = NamedP !PatchInfo ![PatchInfo] !Patch | Move !FileName !FileName | DP !FileName !DirPatchType | FP !FileName !FilePatchType | Split [Patch] | ComP [Patch] | Merger !Bool !String Patch [Patch] Patch Patch | Conflictor Bool [Patch] [Patch] | ChangePref !String !String !String data FilePatchType = RmFile | AddFile | Hunk !Int [PackedString] [PackedString] | TokReplace !String !String !String | Binary PackedString PackedString deriving (Eq,Ord) data DirPatchType = RmDir | AddDir deriving (Eq,Ord) nubAdjBy :: (a -> a -> Bool) -> [a] -> [a] nubAdjBy _ [] = [] nubAdjBy _ [x] = [x] nubAdjBy f (x:y:xs) = if f x y then nubAdjBy f (x:xs) else x:nubAdjBy f (y:xs) fn2d :: FileName -> Doc fn2d f = packedString $ fn2ps f null_patch :: Patch null_patch = join_patches [] is_null_patch :: Patch -> Bool is_null_patch (ComP ps) = all is_null_patch ps is_null_patch (FP _ (Binary x y)) = nullPS x && nullPS y is_null_patch (FP _ (Hunk _ [] [])) = True is_null_patch _ = False is_merger :: Patch -> Bool is_merger (Merger _ _ _ _ _ _) = True is_merger _ = False merger_undo :: Patch -> Patch merger_undo (Merger _ _ undo _ _ _) = undo merger_undo _ = impossible \end{code} %FIXME: The following code needs to be moved. It is a function %``is\_similar'' which tells you if two patches are in the same category %human-wise. Currently it just returns true if they are filepatches on the %same file. \begin{code} is_similar :: Patch -> Patch -> Bool is_similar (FP f _) (FP f' _) = f == f' is_similar (DP f _) (DP f' _) = f == f' is_similar _ _ = False is_addfile :: Patch -> Bool is_addfile (FP _ AddFile) = True is_addfile _ = False is_adddir :: Patch -> Bool is_adddir (DP _ AddDir) = True is_adddir _ = False is_hunk :: Patch -> Bool is_hunk (FP _ (Hunk _ _ _)) = True is_hunk _ = False is_binary :: Patch -> Bool is_binary (FP _ (Binary _ _)) = True is_binary _ = False is_setpref :: Patch -> Bool is_setpref (ChangePref _ _ _) = True is_setpref _ = False \end{code} %Another nice thing to be able to do with composite patches is to `flatten' %them, that is, turn them into a simple list of patches (appropriately %ordered, of course), with all nested compositeness unnested. \begin{code} {- INLINE flatten -} flatten :: Patch -> [Patch] flatten (ComP ps) = concatMap flatten ps flatten p = [p] {- INLINE flatten_to_primitives -} flatten_to_primitives :: Patch -> [Patch] flatten_to_primitives (ComP ps) = concatMap flatten_to_primitives ps flatten_to_primitives (NamedP _ _ p) = flatten_to_primitives p flatten_to_primitives p = [p] \end{code} \begin{code} addfile :: FilePath -> Patch rmfile :: FilePath -> Patch adddir :: FilePath -> Patch rmdir :: FilePath -> Patch move :: FilePath -> FilePath -> Patch changepref :: String -> String -> String -> Patch hunk :: FilePath -> Int -> [PackedString] -> [PackedString] -> Patch tokreplace :: FilePath -> String -> String -> String -> Patch binary :: FilePath -> PackedString -> PackedString -> Patch join_patches :: [Patch] -> Patch unjoin_patches :: Patch -> Maybe [Patch] namepatch :: String -> String -> String -> [String] -> Patch -> Patch infopatch :: PatchInfo -> Patch -> Patch adddeps :: Patch -> [PatchInfo] -> Patch getdeps :: Patch -> [PatchInfo] evalargs :: (a -> b -> c) -> a -> b -> c evalargs f x y = (f $! x) $! y addfile f = FP (fp2fn $ n_fn f) AddFile rmfile f = FP (fp2fn $ n_fn f) RmFile adddir d = DP (fp2fn $ n_fn d) AddDir rmdir d = DP (fp2fn $ n_fn d) RmDir move f f' = Move (fp2fn $ n_fn f) (fp2fn $ n_fn f') changepref p f t = ChangePref p f t hunk f line old new = evalargs FP (fp2fn $ n_fn f) (Hunk line old new) tokreplace f tokchars old new = evalargs FP (fp2fn $ n_fn f) (TokReplace tokchars old new) binary f old new = FP (fp2fn $! n_fn f) $ Binary old new join_patches ps = ComP $! ps unjoin_patches (ComP ps) = Just ps unjoin_patches _ = Nothing namepatch date name author desc p | '\n' `elem` name = error "Patch names cannot contain newlines." | otherwise = NamedP (patchinfo date name author desc) [] p infopatch pi p = NamedP pi [] p adddeps (NamedP pi ds p) ds' = NamedP pi (ds++ds') p adddeps _ _ = bug "can't adddeps to anything but named patch" getdeps (NamedP _ ds _) = ds getdeps _ = bug "can't getdeps on anything but named patch" patch2patchinfo :: Patch -> Maybe PatchInfo patch2patchinfo (NamedP i _ _) = Just i patch2patchinfo _ = Nothing patchname :: Patch -> Maybe String patchname (NamedP i _ _) = Just $ make_filename i patchname _ = Nothing \end{code} \begin{code} n_fn :: FilePath -> FilePath n_fn f = "./"++(fn2fp $ norm_path $ fp2fn f) \end{code} The simplest relationship between two patches is that of ``sequential'' patches, which means that the context of the second patch (the one on the left) consists of the first patch (on the right) plus the context of the first patch. The composition of two patches (which is also a patch) refers to the patch which is formed by first applying one and then the other. The composition of two patches, $P_1$ and $P_2$ is represented as $P_2P_1$, where $P_1$ is to be applied first, then $P_2$\footnote{This notation is inspired by the notation of matrix multiplication or the application of operators upon a Hilbert space. In the algebra of patches, there is multiplication (i.e.\ composition), which is associative but not commutative, but no addition or subtraction.} There is one other very useful relationship that two patches can have, which is to be parallel patches, which means that the two patches have an identical context (i.e.\ their representation applies to identical trees). This is represented by $P_1\parallel P_2$. Of course, two patches may also have no simple relationship to one another. In that case, if you want to do something with them, you'll have to manipulate them with respect to other patches until they are either in sequence or in parallel. The most fundamental and simple property of patches is that they must be invertible. The inverse of a patch is described by: $P^{ -1}$. In the darcs implementation, the inverse is required to be computable from knowledge of the patch only, without knowledge of its context, but that (although convenient) is not required by the theory of patches. \begin{dfn} The inverse of patch $P$ is $P^{ -1}$, which is the ``simplest'' patch for which the composition \( P^{ -1} P \) makes no changes to the tree. \end{dfn} Using this definition, it is trivial to prove the following theorem relating to the inverse of a composition of two patches. \begin{thm} The inverse of the composition of two patches is \[ (P_2 P_1)^{ -1} = P_1^{ -1} P_2^{ -1}. \] \end{thm} Moreover, it is possible to show that the right inverse of a patch is equal to its left inverse. In this respect, patches continue to be analogous to square matrices, and indeed the proofs relating to these properties of the inverse are entirely analogous to the proofs in the case of matrix multiplication. The compositions proofs can also readily be extended to the composition of more than two patches. \begin{code} invert :: Patch -> Patch invert (NamedP n d p) = NamedP (invert_name n) (map invert_name d) (invert p) invert (Merger b g undo unwindings p1 p2) = Merger (not b) g undo unwindings p1 p2 invert (Conflictor inv a b) = Conflictor (not inv) a b invert (FP f RmFile) = FP f AddFile invert (FP f AddFile) = FP f RmFile invert (FP f (Hunk line old new)) = FP f $ Hunk line new old invert (FP f (TokReplace t o n)) = FP f $ TokReplace t n o invert (FP f (Binary o n)) = FP f $ Binary n o invert (DP d RmDir) = DP d AddDir invert (DP d AddDir) = DP d RmDir invert (Move f f') = Move f' f invert (ChangePref p f t) = ChangePref p t f -- I need to see if there is a combined map-reverse, which I think would -- be more efficient. invert (ComP ps) = ComP (map invert (reverse ps)) invert (Split ps) = Split (map invert (reverse ps)) \end{code} \begin{code} -- Recurse on everything, these are potentially spoofed patches filenames :: Patch -> [FileName] filenames (NamedP _ _ p) = filenames p filenames (Move f1 f2) = [f1, f2] filenames (DP f _) = [f] filenames (FP f _) = [f] filenames (Split ps) = concatMap filenames ps filenames (ComP ps) = concatMap filenames ps filenames (Merger _ _ p ps p1 p2) = concatMap filenames (p:p1:p2:ps) filenames (Conflictor _ ps1 ps2) = concatMap filenames (ps1++ps2) filenames (ChangePref _ _ _) = [] \end{code}