-- 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. {-# LANGUAGE CPP #-} -- | 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 always 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". The patches that are first are commuted to the head -- immediately, but patches that are middle and last are mixed -- together. 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). -- -- Some patch marked "middle" may in fact be unselectable because of -- dependencies: when a patch is marked "last", its dependencies are -- not updated until patchSlot is called on them. module Darcs.Patch.Choices ( PatchChoices, patchChoices, patchChoicesLps, patchChoicesLpsSub, patchSlot, patchSlot', getChoices, refineChoices, separateFirstMiddleFromLast, separateFirstFromMiddleLast, forceFirst, forceFirsts, forceLast, forceLasts, forceMatchingFirst, forceMatchingLast, selectAllMiddles, makeUncertain, makeEverythingLater, makeEverythingSooner, LabelledPatch, Label, label, lpPatch, getLabelInt, Slot(..), substitute ) where import Prelude () import Darcs.Prelude import Control.Monad.Identity ( Identity ) import Control.Monad.State ( StateT(..) ) import Prelude hiding ( pred ) import Darcs.Patch ( Patchy, commuteRL, commute, merge, listTouchedFiles, hunkMatches , invert ) import Darcs.Patch.Merge ( Merge ) import Darcs.Patch.Permutations ( commuteWhatWeCanRL, commuteWhatWeCanFL ) import Darcs.Patch.Patchy ( Invert, Commute, PatchInspect ) import Darcs.Patch.Witnesses.Eq ( MyEq(..), EqCheck(..) ) import Darcs.Patch.Witnesses.Unsafe ( unsafeCoerceP ) import Darcs.Patch.Witnesses.Ordered ( FL(..), RL(..), (:>)(..), (:\/:)(..), (:/\:)(..), (:||:)(..), zipWithFL, mapFL_FL, concatFL, (+>+), reverseRL, anyFL ) import Darcs.Patch.Witnesses.Sealed ( Sealed2(..) ) #include "impossible.h" -- | 'Label' @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 @Label 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 @Label Nothing -- 5@, the resulting sub-patches could be identified as @Label (Label Nothing 5) -- 1@, @Label (Label Nothing 5) 2@, etc. data Label = Label (Maybe Label) Integer deriving ( Eq, Ord ) data LabelledPatch p wX wY = LP Label (p wX wY) -- | The @Bool@ parameter indicates whether the patch has been explicitely -- selected (or rejected) by the user. data PatchChoice p wX wY = PC { pcPatch :: (LabelledPatch p wX wY) , _pcChoice :: Bool} data PatchChoices p wX wY where PCs :: { pcsFirsts :: FL (LabelledPatch p) wX wM , pcsLasts :: FL (PatchChoice p) wM wY} -> PatchChoices p wX wY -- | See module documentation for 'Darcs.Patch.Choices' data Slot = InFirst | InMiddle | InLast label :: LabelledPatch p wX wY -> Label label (LP tg _) = tg getLabelInt :: Label -> Integer getLabelInt (Label _ i) = i lpPatch :: LabelledPatch p wX wY -> p wX wY lpPatch (LP _ p) = p liftLP :: (p wX wY -> p wA wB) -> (LabelledPatch p wX wY -> LabelledPatch p wA wB) liftLP f (LP t p) = LP t (f p) -- This is dangerous if two patches from different labelled series are compared -- ideally Label (and hence LabelledPatch/PatchChoices) would have a witness type -- to represent the originally labelled sequence. compareLabels :: LabelledPatch p wA wB -> LabelledPatch p wC wD -> EqCheck (wA, wB) (wC, wD) compareLabels (LP l1 _) (LP l2 _) = if l1 == l2 then unsafeCoerceP IsEq else NotEq instance MyEq p => MyEq (LabelledPatch p) where unsafeCompare (LP l1 p1) (LP l2 p2) = l1 == l2 && unsafeCompare p1 p2 instance Invert p => Invert (LabelledPatch p) where invert = liftLP invert instance Commute p => Commute (LabelledPatch p) where commute (LP l1 p1 :> LP l2 p2) = do p2' :> p1' <- commute (p1 :> p2) return (LP l2 p2' :> LP l1 p1') instance PatchInspect p => PatchInspect (LabelledPatch p) where listTouchedFiles (LP _ p) = listTouchedFiles p hunkMatches f (LP _ p) = hunkMatches f p instance Merge p => Merge (LabelledPatch p) where merge (LP l1 p1 :\/: LP l2 p2) = case merge (p1 :\/: p2) of p2' :/\: p1' -> LP l2 p2' :/\: LP l1 p1' instance Commute p => Commute (PatchChoice p) where commute (PC p1 c1 :> PC p2 c2) = do p2' :> p1' <- commute (p1 :> p2) return (PC p2' c2 :> PC p1' c1) instance PatchInspect p => PatchInspect (PatchChoice p) where listTouchedFiles (PC p _) = listTouchedFiles p hunkMatches f (PC p _) = hunkMatches f p instance Merge p => Merge (PatchChoice p) where merge (PC lp1 c1 :\/: PC lp2 c2) = case merge (lp1 :\/: lp2) of lp2' :/\: lp1' -> PC lp2' c2 :/\: PC lp1' c1 patchChoices :: Patchy p => FL p wX wY -> PatchChoices p wX wY patchChoices = fst . patchChoicesLps -- |Label a sequence of patches as subpatches of an existing label. This is intended for -- use when substituting a patch for an equivalent patch or patches. patchChoicesLpsSub :: Patchy p => Maybe Label -> FL p wX wY -> (PatchChoices p wX wY, FL (LabelledPatch p) wX wY) patchChoicesLpsSub tg ps = let lps = zipWithFL LP (map (Label tg) [1..]) ps in (PCs NilFL (mapFL_FL (\lp -> PC lp False) lps), lps) -- |Label a sequence of patches. patchChoicesLps :: Patchy p => FL p wX wY -> (PatchChoices p wX wY, FL (LabelledPatch p) wX wY) patchChoicesLps = patchChoicesLpsSub Nothing instance MyEq p => MyEq (PatchChoice p) where unsafeCompare (PC lp1 _) (PC lp2 _) = unsafeCompare lp1 lp2 separateFirstFromMiddleLast :: Patchy p => PatchChoices p wX wZ -> (FL (LabelledPatch p) :> FL (LabelledPatch p)) wX wZ separateFirstFromMiddleLast (PCs f l) = f :> mapFL_FL (\ (PC lp _) -> lp) l separateFirstMiddleFromLast :: Patchy p => PatchChoices p wX wZ -> (FL (LabelledPatch p) :> FL (LabelledPatch p)) wX wZ separateFirstMiddleFromLast (PCs f l) = case pushLasts l of (m :> l') -> f +>+ m :> l' -- | @getChoices@ evaluates a @PatchChoices@ into the first, middle and last sequences -- by doing the commutes that were needed. getChoices :: Patchy p => PatchChoices p wX wY -> (FL (LabelledPatch p) :> FL (LabelledPatch p) :> FL (LabelledPatch p)) wX wY getChoices (PCs f l) = case pushLasts l of (m :> l') -> f :> m :> l' pushLasts :: Patchy p => FL (PatchChoice p) wX wY -> (FL (LabelledPatch p) :> FL (LabelledPatch p)) wX wY pushLasts NilFL = NilFL :> NilFL pushLasts (PC lp False :>: pcs) = case pushLasts pcs of (m :> l) -> (lp :>: m) :> l pushLasts (PC lp True :>: pcs) = case pushLasts pcs of (m :> l) -> case commuteWhatWeCanFL (lp :> m) of (m' :> lp' :> deps) -> m' :> (lp' :>: deps +>+ l) -- | @refineChoices act@ performs @act@ on the middle part of a sequence -- of choices, in order to hopefully get more patches into the @first@ and -- @last@ parts of a @PatchChoices@. refineChoices :: (Patchy p, Monad m, Functor m) => (forall wU wV . FL (LabelledPatch p) wU wV -> PatchChoices p wU wV -> m (PatchChoices p wU wV)) -> PatchChoices p wX wY -> m (PatchChoices p wX wY) refineChoices act ps = case getChoices ps of (f :> m :> l) -> do let mchoices = PCs NilFL . mapFL_FL (flip PC False) $ m (PCs f' l') <- act m mchoices return . PCs (f +>+ f') $ l' +>+ mapFL_FL (flip PC True) l patchSlot :: forall p wA wB wX wY. Patchy p => LabelledPatch p wA wB -> PatchChoices p wX wY -> (Slot, PatchChoices p wX wY) patchSlot (LP t _) pc@(PCs f l) = if foundIn f then (InFirst, pc) else psLast f NilRL NilRL l where foundIn = anyFL ((== t) . label) psLast :: forall wM wC wL . FL (LabelledPatch p) wX wM -> RL (LabelledPatch p) wM wC -> RL (LabelledPatch p) wC wL -> FL (PatchChoice p) wL wY -> (Slot, PatchChoices p wX wY) psLast firsts middles bubble (PC lp True :>: ls) | label lp == t = (InLast , PCs { pcsFirsts = firsts , pcsLasts = settleM middles +>+ settleB bubble +>+ PC lp True :>: ls}) psLast firsts middles bubble (PC lp False :>: ls) | label lp == t = case commuteRL (bubble :> lp) of Just (lp' :> bubble') -> (InMiddle, PCs { pcsFirsts = firsts , pcsLasts = settleM middles +>+ PC lp' False :>: settleB bubble' +>+ ls}) Nothing -> (InLast, PCs { pcsFirsts = firsts , pcsLasts = settleM middles +>+ settleB bubble +>+ PC lp True :>: ls}) psLast firsts middles bubble (PC lp True :>: ls) = psLast firsts middles (bubble :<: lp) ls psLast firsts middles bubble (PC lp False :>: ls) = case commuteRL (bubble :> lp) of Just (lp' :> bubble') -> psLast firsts (middles :<: lp') bubble' ls Nothing -> psLast firsts middles (bubble :<: lp) ls psLast _ _ _ NilFL = impossible settleM middles = mapFL_FL (\lp -> PC lp False) $ reverseRL middles settleB bubble = mapFL_FL (\lp -> PC lp True) $ reverseRL bubble patchSlot' :: Patchy p => LabelledPatch p wA wB -> StateT (PatchChoices p wX wY) Identity Slot patchSlot' lp = StateT (return . patchSlot lp) forceMatchingFirst :: forall p wA wB. Patchy p => ( forall wX wY . LabelledPatch p wX wY -> Bool) -> PatchChoices p wA wB -> PatchChoices p wA wB forceMatchingFirst pred (PCs fn l) = fmfLasts fn NilRL l where fmfLasts :: FL (LabelledPatch p) wA wM -> RL (PatchChoice p) wM wN -> FL (PatchChoice p) wN wB -> PatchChoices p wA wB fmfLasts f l1 (a :>: l2) | pred_pc a = case commuteWhatWeCanRL (l1 :> a) of (deps :> a' :> l1') -> let f' = f +>+ mapFL_FL pcPatch (reverseRL deps) +>+ (pcPatch a' :>: NilFL) in fmfLasts f' l1' l2 fmfLasts f l1 (a :>: l2) = fmfLasts f (l1 :<: a) l2 fmfLasts f l1 NilFL = PCs { pcsFirsts = f , pcsLasts = reverseRL l1 } pred_pc :: forall wX wY . PatchChoice p wX wY -> Bool pred_pc (PC lp _) = pred lp forceFirsts :: Patchy p => [Label] -> PatchChoices p wA wB -> PatchChoices p wA wB forceFirsts ps = forceMatchingFirst ((`elem` ps) . label) forceFirst :: Patchy p => Label -> PatchChoices p wA wB -> PatchChoices p wA wB forceFirst p = forceMatchingFirst ((== p) . label) --TODO: stop after having seen the patch we want to force first selectAllMiddles :: forall p wX wY. Patchy p => Bool -> PatchChoices p wX wY -> PatchChoices p wX wY selectAllMiddles True (PCs f l) = PCs f (mapFL_FL g l) where g (PC lp _) = PC lp True selectAllMiddles False (PCs f l) = samf f NilRL NilRL l where samf :: forall wM1 wM2 wM3 . FL (LabelledPatch p) wX wM1 -> RL (LabelledPatch p) wM1 wM2 -> RL (PatchChoice p) wM2 wM3 -> FL (PatchChoice p) wM3 wY -> PatchChoices p wX wY samf f1 f2 l1 (pc@(PC lp False) :>: l2) = case commuteRL (l1 :> pc) of Nothing -> samf f1 f2 (l1 :<: PC lp True) l2 Just ((PC lp' _) :> l1') -> samf f1 (f2 :<: lp') l1' l2 samf f1 f2 l1 (PC lp True :>: l2) = samf f1 f2 (l1 :<: PC lp True) l2 samf f1 f2 l1 NilFL = PCs (f1 +>+ reverseRL f2) (reverseRL l1) forceMatchingLast :: Patchy p => (forall wX wY . LabelledPatch p wX wY -> Bool) -> PatchChoices p wA wB -> PatchChoices p wA wB forceMatchingLast pred (PCs f l) = do fmlFirst pred True NilRL f l fmlFirst :: forall p wA wB wM1 wM2 . Patchy p => (forall wX wY . LabelledPatch p wX wY -> Bool) -> Bool -> RL (LabelledPatch p) wA wM1 -> FL (LabelledPatch p) wM1 wM2 -> FL (PatchChoice p) wM2 wB -> PatchChoices p wA wB fmlFirst pred b f1 (a :>: f2) l | pred a = case commuteWhatWeCanFL (a :> f2) of (f2' :> a' :> deps) -> let l' = mapFL_FL (\lp -> PC lp b) (a' :>: deps) +>+ l in fmlFirst pred b f1 f2' l' fmlFirst pred b f1 (a :>: f2) l = fmlFirst pred b (f1 :<: a) f2 l fmlFirst pred b f1 NilFL l = PCs { pcsFirsts = reverseRL f1 , pcsLasts = mapFL_FL ch l} where ch (PC lp c) = (PC lp (if pred lp then b else c) ) forceLasts :: Patchy p => [Label] -> PatchChoices p wA wB -> PatchChoices p wA wB forceLasts ps = forceMatchingLast ((`elem` ps) . label) forceLast :: Patchy p => Label -> PatchChoices p wA wB -> PatchChoices p wA wB forceLast p = forceMatchingLast ((== p) . label) makeUncertain :: Patchy p => Label -> PatchChoices p wA wB -> PatchChoices p wA wB makeUncertain t (PCs f l) = fmlFirst ((== t) . label) False NilRL f l makeEverythingLater :: Patchy p => PatchChoices p wX wY -> PatchChoices p wX wY makeEverythingLater (PCs f l) = let m = mapFL_FL (\lp -> PC lp False) f l' = mapFL_FL (\(PC lp _) -> PC lp True) l in PCs NilFL $ m +>+ l' makeEverythingSooner :: forall p wX wY. Patchy p => PatchChoices p wX wY -> PatchChoices p wX wY makeEverythingSooner (PCs f l) = case mes NilRL NilRL l of (m :> l') -> PCs (f +>+ m) l' where mes :: forall wM1 wM2 wM3 . RL (LabelledPatch p) wM1 wM2 -> RL (LabelledPatch p) wM2 wM3 -> FL (PatchChoice p) wM3 wY -> (FL (LabelledPatch p) :> FL (PatchChoice p)) wM1 wY mes middle bubble (PC lp True :>: ls) = mes middle (bubble :<: lp) ls mes middle bubble (PC lp False :>: ls) = case commuteRL (bubble :> lp) of Nothing -> mes middle (bubble :<: lp) ls Just (lp' :> bubble') -> mes (middle :<: lp') bubble' ls mes middle bubble NilFL = (reverseRL middle) :> mapFL_FL (\lp -> PC lp False) (reverseRL bubble) -- | 'substitute' @(a :||: bs)@ @pcs@ replaces @a@ with @bs@ in @pcs@ preserving the choice -- associated with @a@ substitute :: forall p wX wY . Patchy p => Sealed2 (LabelledPatch p :||: FL (LabelledPatch p)) -> PatchChoices p wX wY -> PatchChoices p wX wY substitute (Sealed2 (lp :||: new_lps)) (PCs f l) = PCs (concatFL $ mapFL_FL substLp f) (concatFL $ mapFL_FL substPc l) where substLp :: LabelledPatch p wA wB -> FL (LabelledPatch p) wA wB substLp lp' | IsEq <- compareLabels lp lp' = new_lps | otherwise = lp' :>: NilFL substPc :: PatchChoice p wA wB -> FL (PatchChoice p) wA wB substPc (PC lp' c) | IsEq <- compareLabels lp lp' = mapFL_FL (flip PC c) new_lps | otherwise = PC lp' c :>: NilFL