-- 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. -- | 'Named' patches group a set of changes with meta data ('PatchInfo') and -- explicit dependencies (created using `darcs tag` or using --ask-deps). -- -- While the data constructor 'NamedP' is exported for technical reasons, code -- outside this modules should (and generally does) treat it as an abstract -- data type. The only exception is the rebase implementation i.e. the modules -- under "Darcs.Patch.Rebase". {-# LANGUAGE UndecidableInstances #-} module Darcs.Patch.Named ( Named(..) -- treated as abstract data type except by Darcs.Patch.Rebase , infopatch , adddeps , setinfo , anonymous , HasDeps(..) , patch2patchinfo , patchname , patchcontents , fmapNamed , fmapFL_Named , mergerIdNamed , ShowDepsFormat(..) , ShowWhichDeps(..) , showDependencies ) where import Darcs.Prelude import Data.List.Ordered ( nubSort ) import qualified Data.Set as S import Darcs.Patch.CommuteFn ( MergeFn, commuterIdFL, mergerIdFL ) import Darcs.Patch.Conflict ( Conflict(..), findConflicting ) import Darcs.Patch.Debug ( PatchDebug(..) ) import Darcs.Patch.Effect ( Effect(effect) ) import Darcs.Patch.FileHunk ( IsHunk(..) ) import Darcs.Patch.Format ( PatchListFormat ) import Darcs.Patch.Info ( PatchInfo, readPatchInfo, showPatchInfo, patchinfo, piName, displayPatchInfo, makePatchname ) import Darcs.Patch.Merge ( CleanMerge(..), Merge(..) ) import Darcs.Patch.Object ( ObjectId ) import Darcs.Patch.Apply ( Apply(..), ObjectIdOfPatch ) import Darcs.Patch.Commute ( Commute(..) ) import Darcs.Patch.Ident ( Ident(..), PatchId ) import Darcs.Patch.Inspect ( PatchInspect(..) ) import Darcs.Patch.Permutations ( genCommuteWhatWeCanRL ) import Darcs.Patch.Read ( ReadPatch(..) ) import Darcs.Patch.FromPrim ( PrimPatchBase(..), FromPrim(..) ) import Darcs.Util.Parser ( Parser, option, lexChar, choice, skipWhile, anyChar ) import Darcs.Patch.Repair ( mapMaybeSnd, Repair(..), RepairToFL, Check(..) ) import Darcs.Patch.Show ( ShowContextPatch(..) , ShowPatch(..) , ShowPatchBasic(..) , ShowPatchFor(..) , displayPatch ) import Darcs.Patch.Summary ( Summary(..) , plainSummaryFL ) import Darcs.Patch.Unwind ( Unwind(..), squashUnwound ) import Darcs.Patch.Viewing () -- for ShowPatch FL instances import Darcs.Patch.Witnesses.Eq ( Eq2(..) ) import Darcs.Patch.Witnesses.Ordered ( (:>)(..), (:\/:)(..), (:/\:)(..) , FL(..), RL(..), mapFL, mapRL, mapFL_FL, mapRL_RL , (+<+), (+>+), concatRLFL, reverseFL , (+<<+), (+>>+), concatFL ) import Darcs.Patch.Witnesses.Sealed ( Sealed, mapSeal ) import Darcs.Patch.Witnesses.Show ( Show1, Show2 ) import Darcs.Util.IsoDate ( showIsoDateTime, theBeginning ) import Darcs.Util.Printer ( Doc, ($$), (<+>), text, vcat, cyanText, blueText, redText ) -- | The @Named@ type adds a patch info about a patch, that is a name. data Named p wX wY where NamedP :: !PatchInfo -> ![PatchInfo] -> !(FL p wX wY) -> Named p wX wY deriving Show -- ^ @NamedP info deps p@ represents patch @p@ with name -- @info@. @deps@ is a list of dependencies added at the named patch -- level, compared with the unnamed level (ie, dependencies added with -- @darcs record --ask-deps@). instance PrimPatchBase p => PrimPatchBase (Named p) where type PrimOf (Named p) = PrimOf p instance Effect p => Effect (Named p) where effect (NamedP _ _ p) = effect p type instance PatchId (Named p) = PatchInfo instance Ident (Named p) where ident = patch2patchinfo instance IsHunk (Named p) where isHunk _ = Nothing instance PatchListFormat (Named p) instance (ReadPatch p, PatchListFormat p) => ReadPatch (Named p) where readPatch' = readNamed readNamed :: (ReadPatch p, PatchListFormat p) => Parser (Sealed (Named p wX)) readNamed = do n <- readPatchInfo d <- readDepends p <- readPatch' return $ (NamedP n d) `mapSeal` p readDepends :: Parser [PatchInfo] readDepends = option [] $ do lexChar '<' readPis readPis :: Parser [PatchInfo] readPis = choice [ do pi <- readPatchInfo pis <- readPis return (pi:pis) , do skipWhile (/= '>') _ <- anyChar return [] ] instance Apply p => Apply (Named p) where type ApplyState (Named p) = ApplyState p apply (NamedP _ _ p) = apply p unapply (NamedP _ _ p) = unapply p instance RepairToFL p => Repair (Named p) where applyAndTryToFix (NamedP n d p) = mapMaybeSnd (NamedP n d) `fmap` applyAndTryToFix p anonymous :: FromPrim p => FL (PrimOf p) wX wY -> IO (Named p wX wY) anonymous ps = do info <- patchinfo (showIsoDateTime theBeginning) "anonymous" "unknown" ["anonymous"] return $ infopatch info ps infopatch :: forall p wX wY. FromPrim p => PatchInfo -> FL (PrimOf p) wX wY -> Named p wX wY infopatch pi ps = NamedP pi [] (fromPrims pi ps) where adddeps :: Named p wX wY -> [PatchInfo] -> Named p wX wY adddeps (NamedP pi _ p) ds = NamedP pi ds p setinfo :: PatchInfo -> Named p wX wY -> Named p wX wY setinfo i (NamedP _ ds ps) = NamedP i ds ps -- | This slightly ad-hoc class is here so we can call 'getdeps' with patch -- types that wrap a 'Named', such as 'RebaseChange'. class HasDeps p where getdeps :: p wX wY -> [PatchInfo] instance HasDeps (Named p) where getdeps (NamedP _ ds _) = ds patch2patchinfo :: Named p wX wY -> PatchInfo patch2patchinfo (NamedP i _ _) = i patchname :: Named p wX wY -> String patchname (NamedP i _ _) = show $ makePatchname i patchcontents :: Named p wX wY -> FL p wX wY patchcontents (NamedP _ _ p) = p patchcontentsRL :: RL (Named p) wX wY -> RL p wX wY patchcontentsRL = concatRLFL . mapRL_RL patchcontents fmapNamed :: (forall wA wB . p wA wB -> q wA wB) -> Named p wX wY -> Named q wX wY fmapNamed f (NamedP i deps p) = NamedP i deps (mapFL_FL f p) fmapFL_Named :: (FL p wA wB -> FL q wC wD) -> Named p wA wB -> Named q wC wD fmapFL_Named f (NamedP i deps p) = NamedP i deps (f p) instance (Commute p, Eq2 p) => Eq2 (Named p) where unsafeCompare (NamedP n1 ds1 ps1) (NamedP n2 ds2 ps2) = n1 == n2 && ds1 == ds2 && unsafeCompare ps1 ps2 instance Commute p => Commute (Named p) where commute (NamedP n1 d1 p1 :> NamedP n2 d2 p2) = if n2 `elem` d1 || n1 `elem` d2 then Nothing else do (p2' :> p1') <- commute (p1 :> p2) return (NamedP n2 d2 p2' :> NamedP n1 d1 p1') instance CleanMerge p => CleanMerge (Named p) where cleanMerge (NamedP n1 d1 p1 :\/: NamedP n2 d2 p2) | n1 == n2 = error "cannot cleanMerge identical Named patches" | otherwise = do p2' :/\: p1' <- cleanMerge (p1 :\/: p2) return $ NamedP n2 d2 p2' :/\: NamedP n1 d1 p1' instance Merge p => Merge (Named p) where merge (NamedP n1 d1 p1 :\/: NamedP n2 d2 p2) | n1 == n2 = error "cannot merge identical Named patches" | otherwise = case merge (p1 :\/: p2) of (p2' :/\: p1') -> NamedP n2 d2 p2' :/\: NamedP n1 d1 p1' -- Merge an unnamed patch with a named patch. -- This operation is safe even if the first patch is named, as names can -- never conflict with each other. -- This is in contrast with commuterIdNamed which is not safe and hence -- is defined closer to the code that uses it. mergerIdNamed :: MergeFn p1 p2 -> MergeFn p1 (Named p2) mergerIdNamed merger (p1 :\/: NamedP n2 d2 p2) = case mergerIdFL merger (p1 :\/: p2) of p2' :/\: p1' -> NamedP n2 d2 p2' :/\: p1' {- | This instance takes care of handling the interaction between conflict resolution and explicit dependencies. A conflict involves a set of two or more patches and the general rule is that the conflict is considered resolved if there is another (later) patch that (transitively) depends on each of the (mutually) conflicting patches. This principle extends to explicit dependencies between 'Named' patches. In particular, recording a tag has the effect of resolving any as yet unresolved conflicts in a repo. In general a 'Named' patch contains multiple changes ( a "changeset"). Consider the named patches @ Named A [] a Named B [] (b1;b2) Named C [] c Named D [A,B] _ @ where, at the RepoPatch level, @a@ conflicts with @b1@, and @c@ with @b2@. @D@ depends explicitly on both @A@ and @B@, so it fully covers the conflict between @a@ and @b1@ and thus we would be justified to consider that particular conflict as resolved. Unfortunately we cannot detect this at the Named patch level because RepoPatchV1 and V2 have no notion of patch identities. Thus, at the Named level the two underlying conflicts appear as a single large conflict between the three named patches @A@, @B@, and @C@, and this means that patch @D@ does /not/ count as a (partial) resolution (even though it arguably should). When we decide that a set of conflicting Named patches is resolved, we move the RepoPatches contained in them to the context of the resolution. For all other named patches, we must commute as much of their contents as possible past the ones marked as resolved, using commutation at the RepoPatch level (i.e. ignoring explicit dependencies). -} instance ( Commute p , Conflict p , Summary p , PrimPatchBase p , PatchListFormat p , ShowPatch p ) => Conflict (Named p) where isConflicted (NamedP _ _ ps) = or (mapFL isConflicted ps) resolveConflicts context patches = case separate S.empty [] context patches NilFL NilFL of resolved :> unresolved -> resolveConflicts (patchcontentsRL context +<<+ resolved) (reverseFL unresolved) where -- Separate the patch contents of an 'RL' of 'Named' patches into those -- we regard as resolved due to explicit dependencies and any others. -- Implicit dependencies are kept with the resolved patches. The first -- parameter accumulates the PatchInfo of patches which we consider -- resolved; the second one accumulates direct and indirect explicit -- dependencies for the patches we have traversed. The third parameter -- is the context, which is only needed as input to 'findConflicting'. separate :: S.Set PatchInfo -- names of resolved Named patches so far -> [S.Set PatchInfo] -- transitive explicit dependencies so far -> RL (Named p) w0 w1 -- context for Named patches -> RL (Named p) w1 w2 -- Named patches under consideration -> FL p w2 w3 -- result: resolved at RepoPatch layer so far -> FL p w3 w4 -- result: unresolved at RepoPatch layer so far -> (FL p :> FL p) w1 w4 separate acc_res acc_deps ctx (ps :<: p@(NamedP name deps contents)) resolved unresolved | name `S.member` acc_res || isConflicted p , _ :> _ :> conflicting <- findConflicting (ctx +<+ ps) p , let conflict_ids = S.fromList $ name : mapRL ident conflicting , any (conflict_ids `S.isSubsetOf`) acc_deps = -- Either we already determined that p is considered resolved, -- or p is conflicted and all patches involved in the conflict are -- transitively explicitly depended upon by a single patch. -- The action is to regard everything in 'contents' as resolved. separate (acc_res `S.union` conflict_ids) (extend name deps acc_deps) ctx ps (contents +>+ resolved) unresolved | otherwise = -- Commute as much as we can of our patch 'contents' past 'resolved', -- without dragging dependencies along. -- To use existing tools for commutation means we have to -- commuteWhatWeCan 'resolved' backwards through the 'contents', -- now /with/ dragging dependencies along. case genCommuteWhatWeCanRL (commuterIdFL commute) (reverseFL contents :> resolved) of dragged :> resolved' :> more_unresolved -> separate acc_res (extend name deps acc_deps) ctx ps (dragged +>>+ resolved') (more_unresolved +>>+ unresolved) separate _ _ _ NilRL resolved unresolved = resolved :> unresolved -- Extend a list of sets of dependencies by adding the new list of -- dependencies to each set that contains the given 'name'. If 'name' -- does not occur in any of the sets, we add the dependencies as a new -- set to the list. -- Since we have to track whether 'name' was found in any of the input -- sets, this is not a straight-forward fold, so we use explicit -- recursion. extend :: Ord a => a -> [a] -> [S.Set a] -> [S.Set a] extend _ [] acc_deps = acc_deps extend name deps acc_deps = go False (S.fromList deps) acc_deps where go False new [] = [new] go True _ [] = [] go found new (ds:dss) | name `S.member` ds = ds `S.union` new : go True new dss | otherwise = ds : go found new dss instance (PrimPatchBase p, Unwind p) => Unwind (Named p) where fullUnwind (NamedP _ _ ps) = squashUnwound (mapFL_FL fullUnwind ps) instance PatchInspect p => PatchInspect (Named p) where listTouchedFiles (NamedP _ _ p) = listTouchedFiles p hunkMatches f (NamedP _ _ p) = hunkMatches f p instance Summary p => Summary (Named p) where conflictedEffect = conflictedEffect . patchcontents instance Check p => Check (Named p) where isInconsistent (NamedP _ _ p) = isInconsistent p -- ForStorage: note the difference between use of <> when there are -- no explicit dependencies vs. <+> when there are showNamedPrefix :: ShowPatchFor -> PatchInfo -> [PatchInfo] -> Doc -> Doc showNamedPrefix f@ForStorage n [] p = showPatchInfo f n <> p showNamedPrefix f@ForStorage n d p = showPatchInfo f n $$ blueText "<" $$ vcat (map (showPatchInfo f) d) $$ blueText ">" <+> p showNamedPrefix f@ForDisplay n [] p = showPatchInfo f n $$ p showNamedPrefix f@ForDisplay n d p = showPatchInfo f n $$ showDependencies ShowNormalDeps ShowDepsVerbose d $$ p instance (PatchListFormat p, ShowPatchBasic p) => ShowPatchBasic (Named p) where showPatch f (NamedP n d p) = showNamedPrefix f n d $ showPatch f p instance ( Apply p , IsHunk p , PatchListFormat p , ObjectId (ObjectIdOfPatch p) , ShowContextPatch p ) => ShowContextPatch (Named p) where showPatchWithContextAndApply f (NamedP n d p) = showNamedPrefix f n d <$> showPatchWithContextAndApply f p data ShowDepsFormat = ShowDepsVerbose | ShowDepsSummary deriving (Eq) -- | Support for rebase data ShowWhichDeps = ShowNormalDeps | ShowDroppedDeps deriving (Eq) showDependencies :: ShowWhichDeps -> ShowDepsFormat -> [PatchInfo] -> Doc showDependencies which format deps = vcat (map showDependency deps) where showDependency d = case format of ShowDepsVerbose -> mark which format <+> cyanText (show (makePatchname d)) $$ text " *" <+> text (piName d) ShowDepsSummary -> mark which format <+> cyanText (take 8 (show (makePatchname d))) <+> text (piName d) mark ShowNormalDeps ShowDepsVerbose = blueText "depend" mark ShowDroppedDeps ShowDepsVerbose = redText "dropped" mark ShowNormalDeps ShowDepsSummary = text "D" mark ShowDroppedDeps ShowDepsSummary = text "D!" instance (Summary p, PatchListFormat p, PrimPatchBase p, ShowPatch p) => ShowPatch (Named p) where description (NamedP n _ _) = displayPatchInfo n summary (NamedP _ ds ps) = showDependencies ShowNormalDeps ShowDepsSummary ds $$ plainSummaryFL ps summaryFL nps = showDependencies ShowNormalDeps ShowDepsSummary ds $$ plainSummaryFL ps where ds = nubSort $ concat $ mapFL getdeps nps ps = concatFL $ mapFL_FL patchcontents nps content (NamedP _ ds ps) = showDependencies ShowNormalDeps ShowDepsVerbose ds $$ displayPatch ps instance Show2 p => Show1 (Named p wX) instance Show2 p => Show2 (Named p) instance PatchDebug p => PatchDebug (Named p)