-- Copyright (C) 2002-2004 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. {-# OPTIONS_GHC -cpp -fglasgow-exts #-} {-# LANGUAGE CPP #-} -- , TypeOperators, GeneralizedNewtypeDeriving, ScopedTypeVariables #-} #include "gadts.h" -- | PatchChoices divides a sequence of patches into three sets: "first", -- "middle" and "last", such that all patches can be applied, if you first -- apply the first ones then the middle ones and then the last ones. -- Obviously if there are dependencies between the patches that will put a -- constraint on how you can choose to divide them up. The PatchChoices data -- type and associated functions are here to deal with many of the common -- cases that come up when choosing a subset of a group of patches. -- -- 'forceLast' tells PatchChoices that a particular patch is required to be in -- the "last" group, which also means that any patches that depend on it -- must be in the "last" group. -- -- Internally, a PatchChoices doesn't actually reorder the patches until it is -- asked for the final output (e.g. by 'get_first_choice'). Instead, each -- patch is placed in a state of definitely first, definitely last and -- undecided; undecided leans towards "middle". In case you're wondering -- about the first-middle-last language, it's because in some cases the -- "yes" answers will be last (as is the case for the revert command), and -- in others first (as in record, pull and push). module Darcs.Patch.Choices ( PatchChoices, patchChoices, patchChoicesTps, patchChoicesTpsSub, patchSlot, getChoices, separateFirstMiddleFromLast, separateFirstFromMiddleLast, forceFirst, forceFirsts, forceLast, forceLasts, forceMatchingFirst, forceMatchingLast, selectAllMiddles, makeUncertain, makeEverythingLater, TaggedPatch, Tag, tag, tpPatch, Slot(..), substitute, ) where import System.IO.Unsafe ( unsafePerformIO ) import Data.IORef ( newIORef, writeIORef, readIORef ) import Darcs.Patch import Darcs.Patch.Permutations ( commuteWhatWeCanRL ) import Darcs.Patch.Patchy ( Invert, Commute ) import Darcs.Witnesses.Ordered ( FL(..), RL(..), MyEq, unsafeCompare, EqCheck(..), (:>)(..), (:\/:)(..), (:/\:)(..), (:||:)(..), zipWithFL, mapFL_FL, mapFL, concatFL, (+>+), reverseRL, unsafeCoerceP ) import Darcs.Witnesses.Sealed ( Sealed2(..) ) -- | 'TG' @mp i@ acts as a temporary identifier to help us keep track of patches -- during the selection process. These are useful for finding patches that -- may have moved around during patch selection (being pushed forwards or -- backwards as dependencies arise). -- -- The identifier is implemented as a tuple @TG mp i@. The @i@ is just some -- arbitrary label, expected to be unique within the patches being -- scrutinised. The @mp@ is motivated by patch splitting; it -- provides a convenient way to generate a new identifier from the patch -- being split. For example, if we split a patch identified as @TG Nothing -- 5@, the resulting sub-patches could be identified as @TG (TG Nothing 5) -- 1@, @TG (TG Nothing 5) 2@, etc. data Tag = TG (Maybe Tag) Integer deriving ( Eq, Ord ) data TaggedPatch p C(x y) = TP Tag (p C(x y)) data PatchChoice p C(x y) = PC (TaggedPatch p C(x y)) Slot newtype PatchChoices p C(x y) = PCs (FL (PatchChoice p) C(x y)) -- | See module documentation for 'Darcs.Patch.Choices' data Slot = InFirst | InMiddle | InLast negTag :: Tag -> Tag negTag (TG k n) = TG k (-n) invertTag :: Slot -> Slot invertTag InFirst = InLast invertTag InLast = InFirst invertTag t = t tag :: TaggedPatch p C(x y) -> Tag tag (TP tg _) = tg tpPatch :: TaggedPatch p C(x y) -> p C(x y) tpPatch (TP _ p) = p liftTP :: (p C(x y) -> p C(a b)) -> (TaggedPatch p C(x y) -> TaggedPatch p C(a b)) liftTP f (TP t p) = TP t (f p) -- This is dangerous if two patches from different tagged series are compared -- ideally Tag (and hence TaggedPatch/PatchChoices) would have a witness type -- to represent the originally tagged sequence. compareTags :: TaggedPatch p C(a b) -> TaggedPatch p C(c d) -> EqCheck C((a, b) (c, d)) compareTags (TP t1 _) (TP t2 _) = if t1 == t2 then unsafeCoerceP IsEq else NotEq instance MyEq p => MyEq (TaggedPatch p) where unsafeCompare (TP t1 p1) (TP t2 p2) = t1 == t2 && unsafeCompare p1 p2 instance Invert p => Invert (TaggedPatch p) where invert = liftTP invert identity = TP (TG Nothing (-1)) identity instance Commute p => Commute (TaggedPatch p) where commute (TP t1 p1 :> TP t2 p2) = do p2' :> p1' <- commute (p1 :> p2) return (TP t2 p2' :> TP t1 p1') listTouchedFiles (TP _ p) = listTouchedFiles p hunkMatches f (TP _ p) = hunkMatches f p merge (TP t1 p1 :\/: TP t2 p2) = case merge (p1 :\/: p2) of p2' :/\: p1' -> TP t2 p2' :/\: TP t1 p1' patchChoices :: Patchy p => FL p C(x y) -> PatchChoices p C(x y) patchChoices = fst . patchChoicesTps -- |Tag a sequence of patches as subpatches of an existing tag. This is intended for -- use when substituting a patch for an equivalent patch or patches. patchChoicesTpsSub :: Patchy p => Maybe Tag -> FL p C(x y) -> (PatchChoices p C(x y), FL (TaggedPatch p) C(x y)) patchChoicesTpsSub tg ps = let tps = zipWithFL TP (map (TG tg) [1..]) ps in (PCs $ zipWithFL (flip PC) (repeat InMiddle) tps, tps) -- |Tag a sequence of patches. patchChoicesTps :: Patchy p => FL p C(x y) -> (PatchChoices p C(x y), FL (TaggedPatch p) C(x y)) patchChoicesTps = patchChoicesTpsSub Nothing instance MyEq p => MyEq (PatchChoice p) where unsafeCompare (PC tp1 _) (PC tp2 _) = unsafeCompare tp1 tp2 instance Invert p => Invert (PatchChoice p) where invert (PC tp mf) = PC (invert tp) (invertTag mf) identity = PC identity InMiddle instance Commute p => Commute (PatchChoice p) where commute (PC t1 x1 :> PC t2 x2) = do t2' :> t1' <- commute (t1 :> t2) return (PC t2' x2 :> PC t1' x1) merge (PC t1 x1 :\/: PC t2 x2) = case merge (t1 :\/: t2) of t2' :/\: t1' -> PC t2' x2 :/\: PC t1' x1 listTouchedFiles (PC t _) = listTouchedFiles t hunkMatches f (PC t _) = hunkMatches f t invertSeq :: (Invert p, Invert q) => (p :> q) C(x y) -> (q :> p) C(y x) invertSeq (x :> y) = (invert y :> invert x) separateFirstFromMiddleLast :: Patchy p => PatchChoices p C(x z) -> (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x z) separateFirstFromMiddleLast (PCs e) = pull_only_firsts e separateFirstMiddleFromLast :: Patchy p => PatchChoices p C(x z) -> (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x z) separateFirstMiddleFromLast (PCs e) = pull_firsts_middles e getChoices :: Patchy p => PatchChoices p C(x y) -> (FL (TaggedPatch p) :> FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x y) getChoices (PCs e) = case pull_firsts e of f :> ml -> case pull_firsts (invert ml) of l :> m -> f :> mapFL_FL pc2tp (invert m) :> invert l where pc2tp (PC tp _) = tp {- This unsafePerformIO hack was reported by Igloo as being necessary for constant space performance when working with a very large set of changes (e.g. from an initial import) where the second element of the returned tuple is expected to be small, and will only be accessed after the entire first element has been forced. On a quick scan on 20080729 it seemed like only revert/unrevert actually make use of both elements of the tuple. We should (a) add a test case that checks on constant space usage and (b) clean up this interface and code, perhaps by replacing the FL :> FL with a custom structure that forces traversal of the first element to get at the second (but then how would we commute/pattern-match? messy...) -} pull_firsts_middles :: Patchy p => FL (PatchChoice p) C(x z) -> (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x z) pull_firsts_middles easyPC = let r = unsafePerformIO $ newIORef (error "pull_firsts_middles called badly") f :: Patchy p => RL (TaggedPatch p) C(a x) -> FL (PatchChoice p) C(x z) -> FL (TaggedPatch p) C(a d) f acc NilFL = unsafePerformIO (writeIORef r (reverseRL acc)) `seq` (unsafeCoerceP NilFL) f acc (PC tp InLast:>:e) = f (tp:<:acc) e f acc (PC tp _:>:e) = case commuteWhatWeCanRL (acc :> tp) of more :> tp' :> acc' -> reverseRL more+>+tp':>:f acc' e xs = f NilRL easyPC in (xs :> unsafePerformIO (readIORef r)) pull_only_firsts :: Patchy p => FL (PatchChoice p) C(x z) -> (FL (TaggedPatch p) :> FL (TaggedPatch p)) C(x z) pull_only_firsts easyPC = let r = unsafePerformIO $ newIORef (error "pull_only_firsts called badly") f :: Patchy p => RL (TaggedPatch p) C(a x) -> FL (PatchChoice p) C(x z) -> FL (TaggedPatch p) C(a d) f acc NilFL = unsafePerformIO (writeIORef r (reverseRL acc)) `seq` (unsafeCoerceP NilFL) f acc (PC tp InFirst:>:e) = case commuteWhatWeCanRL (acc :> tp) of more :> tp' :> acc' -> reverseRL more+>+tp':>:f acc' e f acc (PC tp _:>:e) = f (tp:<:acc) e xs = f NilRL easyPC in (xs :> unsafePerformIO (readIORef r)) {- pull_middles_lasts :: EasyPC p -> ([TaggedPatch p], [TaggedPatch p]) pull_middles_lasts easyPC = let r = unsafePerformIO $ newIORef (error "pull_middles_lasts called badly") f acc [] = unsafePerformIO (writeIORef r (reverse acc)) `seq` [] f acc (PC tp (Just True):e) = f (tp:acc) e f acc (PC (TP t p) _:e) = case commute_up_list p acc of (acc', p') -> TP t p':f acc' e xs = f [] easyPC in (xs, unsafePerformIO (readIORef r)) -} --pull_only_lasts :: EasyPC p -> ([TaggedPatch p], [TaggedPatch p]) --pull_only_lasts easyPC = -- let r = unsafePerformIO -- $ newIORef (error "pull_only_lasts called badly") -- f acc [] = unsafePerformIO (writeIORef r (reverse acc)) `seq` [] -- f acc (PC (TP t p) (Just False):e) = case commute_up_list p acc of -- (acc', p') -> TP t p':f acc' e -- f acc (PC tp _:e) = f (tp:acc) e -- xs = f [] easyPC -- in (xs, unsafePerformIO (readIORef r)) pull_firsts :: Patchy p => FL (PatchChoice p) C(x z) -> (FL (TaggedPatch p) :> FL (PatchChoice p)) C(x z) pull_firsts e = case pull_first e of Nothing -> (NilFL :> e) Just (p:>e') -> case pull_firsts e' of (ps:>e'') -> (p:>:ps :> e'') pull_lasts :: Patchy p => FL (PatchChoice p) C(x y) -> (FL (PatchChoice p) :> FL (TaggedPatch p)) C(x y) pull_lasts e = invertSeq $ pull_firsts $ invert e pull_first :: Patchy p => FL (PatchChoice p) C(x z) -> Maybe ((TaggedPatch p :> FL (PatchChoice p)) C(x z)) pull_first NilFL = Nothing pull_first (PC tp InFirst:>:e) = Just (tp :> e) pull_first (PC (TP t p) InLast:>:e) = case pull_first e of Just (TP t2 p2 :> e') -> case commute (p:>p2) of Just (p2':>p') -> Just (TP t2 p2' :> PC (TP t p') InLast:>:e') Nothing -> error "Aaack fixme!" Nothing -> Nothing pull_first (PC tp@(TP t p) InMiddle:>:e) = case pull_first e of Just (TP t2 p2 :> e') -> case commute (p:>p2) of Just (p2':>p') -> Just (TP t2 p2' :> (PC (TP t p') InMiddle:>:e')) Nothing -> Just (tp :> PC (TP (negTag t2) p2) InFirst:>:e') Nothing -> Nothing patchSlot :: forall p C(a b x y). TaggedPatch p C(a b) -> PatchChoices p C(x y) -> Slot patchSlot tp (PCs e) = ipf e where ipf :: FL (PatchChoice p) C(u v) -> Slot ipf (PC a mb:>:e') | tag a == tag tp = mb | otherwise = ipf e' -- actually, the following should be impossible, but this is a reasonable answer ipf NilFL = InLast -- | 'setSimplys' @ts s ps@ assigns all patches in @ps@ with a tag in @ts@ to slot @s@ -- (and any other patch to slot 'InMiddle') setSimplys :: [Tag] -> Slot -> FL (PatchChoice p) C(x y) -> FL (PatchChoice p) C(x y) setSimplys ts s e = mapFL_FL ch e where ch (PC tp@(TP t _) _) | t `elem` ts = PC tp s | otherwise = PC tp InMiddle m2ids :: (FORALL(x y) TaggedPatch p C(x y) -> Bool) -> FL (PatchChoice p) C(a b) -> [Tag] m2ids m (PC tp@(TP t _) _:>:e) | m tp = t:m2ids m e | otherwise = m2ids m e m2ids _ NilFL = [] forceMatchingFirst :: Patchy p => (FORALL(x y) TaggedPatch p C(x y) -> Bool) -> PatchChoices p C(a b) -> PatchChoices p C(a b) forceMatchingFirst m (PCs e) = let thd (PC (TP t _) _) = t xs = m2ids m e not_needed = case pull_firsts $ setSimplys xs InFirst e of _ :> rest -> mapFL thd rest ch pc@(PC tp@(TP t _) _) | t `elem` not_needed = pc | otherwise = PC tp InFirst in PCs $ mapFL_FL ch e forceFirsts :: Patchy p => [Tag] -> PatchChoices p C(x y) -> PatchChoices p C(x y) forceFirsts ps pc = forceMatchingFirst ((`elem` ps) . tag) pc forceFirst :: Patchy p => Tag -> PatchChoices p C(x y) -> PatchChoices p C(x y) forceFirst p pc = forceMatchingFirst ((== p) . tag) pc selectAllMiddles :: Patchy p => Bool -> PatchChoices p C(x y) -> PatchChoices p C(x y) selectAllMiddles b (PCs e) = PCs (mapFL_FL f e) where f (PC tp InMiddle) = PC tp (if b then InLast else InFirst) f pc = pc reverse_pc :: Patchy p => PatchChoices p C(x y) -> PatchChoices p C(y x) reverse_pc (PCs e) = PCs $ invert e forceMatchingLast :: Patchy p => (FORALL(x y) TaggedPatch p C(x y) -> Bool) -> PatchChoices p C(a b) -> PatchChoices p C(a b) forceMatchingLast m (PCs e) = let thd (PC (TP t _) _) = t xs = m2ids m e not_needed = case pull_lasts $ setSimplys xs InLast e of rest :> _ -> mapFL thd rest ch pc@(PC tp@(TP t _) _) | t `elem` not_needed = pc | otherwise = PC tp InLast in PCs $ mapFL_FL ch e forceLast :: Patchy p => Tag -> PatchChoices p C(x y) -> PatchChoices p C(x y) forceLast p pc = reverse_pc $ forceFirst p $ reverse_pc pc forceLasts :: Patchy p => [Tag] -> PatchChoices p C(x y) -> PatchChoices p C(x y) forceLasts ps pc = reverse_pc $ forceFirsts ps $ reverse_pc pc makeUncertain :: Patchy p => Tag -> PatchChoices p C(x y) -> PatchChoices p C(x y) makeUncertain t (PCs e) = PCs $ mapFL_FL ch e where ch pc@(PC x _) = if t == tag x then PC x InMiddle else pc makeEverythingLater :: Patchy p => PatchChoices p C(x y) -> PatchChoices p C(x y) makeEverythingLater (PCs e) = PCs $ mapFL_FL ch e where ch (PC tp InMiddle) = PC tp InLast ch (PC tp InFirst) = PC tp InMiddle ch x = x -- | 'substitute' @(a :||: bs)@ @pcs@ replaces @a@ with @bs@ in @pcs@ preserving the choice -- associated with @a@ substitute :: forall p C(x y) . Patchy p => Sealed2 (TaggedPatch p :||: FL (TaggedPatch p)) -> PatchChoices p C(x y) -> PatchChoices p C(x y) substitute (Sealed2 (tp :||: new_tps)) (PCs pcs) = PCs (concatFL (mapFL_FL translate pcs)) where translate :: PatchChoice p C(a b) -> FL (PatchChoice p) C(a b) translate (PC tp' c) | IsEq <- compareTags tp tp' = mapFL_FL (flip PC c) new_tps | otherwise = PC tp' c :>: NilFL