{- | Conflict resolution for 'RepoPatchV3' -} {-# OPTIONS_GHC -fno-warn-orphans #-} module Darcs.Patch.V3.Resolution () where import qualified Data.Set as S import Darcs.Prelude import Data.List ( partition, sort ) import Darcs.Patch.Commute ( commuteFL ) import Darcs.Patch.Conflict ( Conflict(..), mangleOrFail ) import Darcs.Patch.Ident ( Ident(..), SignedId(..), StorableId(..) ) import Darcs.Patch.Prim ( PrimPatch ) import Darcs.Patch.Prim.WithName ( PrimWithName, wnPatch ) import Darcs.Patch.V3.Contexted ( Contexted, ctxDepends, ctxId, ctxToFL ) import Darcs.Patch.V3.Core ( RepoPatchV3(..), (+|), (-|) ) import Darcs.Patch.Witnesses.Ordered ( FL(..), RL(..), mapFL_FL, (:>)(..) ) import Darcs.Patch.Witnesses.Sealed ( Sealed(..), mapSeal ) -- * Conflict Resolution {- This gives an overview of the algorithm for marking conflicts. The goal is to calculate the markup for a trailing RL of patches, usually the ones we are going to add to our repo. But since in V3 we store only the direct conflicts, not the transitive set, we also require the full context of all previous patches. The markup presents each /transitive/ unresolved conflict in the form of a set of alternative changes that all apply at the end of the repo. These alternatives form the vertices of an undirected graph, where an edge exists between two vertices iff they conflict. We represent this graph as a list of connected 'Component's; thus each 'Component' represents one transitive conflict. The graph is constructed by commuting any patch that is part of a conflict to the head. If that succeeds, the resulting conflictor gives us all participents of the (direct) conflict in the form of contexted patches that apply to the end of the repo. We check if there is an overlap between this set and any already constructed components. If this is the case, we join them into a larger component, otherwise we add a new component. If commuting to the head fails, we only remember the set of conflicting patch names, and use that afterwards to connect components that might otherwise appear as unconnected. The docs for 'findComponents' explain this in greater detail. Each resulting 'Component' is then converted to a set of plain prim 'FL's (removing the prim patch IDs) and passed to the mangling function to calculate the conflict markup as a single prim patch. The result differs from that for RepoPatchV1 in that we do not merge the maximal independent (i.e. non-conflicting) sets for each component. While the latter gives a theoretically valid and more compact presentation, typically with fewer alternatives, it has some disadvantages in practice: * Merging means that a single alternative no longer corresponds to a single named patch in our repo. Thus, even if we annotate alternatives with patch names or hashes (as planned for V3), identifying which part of an alternative belongs to which named patch requires additional mental effort during manual resolution. * The same original prim is now contained in more than one alternative, making it harder to manually resolve the conflict in a systematic way by applying difference between alternatives and the baseline step by step. -} instance (SignedId name, StorableId name, PrimPatch prim) => Conflict (RepoPatchV3 name prim) where isConflicted Conflictor{} = True isConflicted Prim{} = False resolveConflicts context = map resolveOne . conflictingAlternatives context where resolveOne = mangleOrFail . map (mapSeal (mapFL_FL wnPatch)) conflictingAlternatives :: (SignedId name, StorableId name, PrimPatch prim) => RL (RepoPatchV3 name prim) wO wX -> RL (RepoPatchV3 name prim) wX wY -> [[Sealed (FL (PrimWithName name prim) wY)]] conflictingAlternatives context = map (map ctxToFL . S.toList) . findComponents context -- | A connected component of the conflict graph. type Component name prim wY = S.Set (Contexted (PrimWithName name prim) wY) {- | Construct the conflict graph by searching the history for unresolved conflicts. The history is split into an initial 'RL' of patches (the context) and a trailing 'RL' of patches we are interested in. We examine patches starting with the head and going backwards, maintaining the following state: @done@ A list of 'Component's, initially empty, which will become the resulting conflict graph. @todo@ A set of @name@s, initially empty, that are candidates for inspection, in addition to conflicted patches in the trailing 'RL'. We maintain the invariant that this set never contains the @name@ of any patch we have already traversed. @res@ A list of sets of @name@s, initially empty, with the @name@s of patches involved in conflicts that are (partially) resolved. Used to post process the result (see below). We inspect any conflictor in the trailing 'RL', as well as any patch whose @name@ is in @todo@ throughout the history, terminating early if the trailing 'RL' and @todo@ are both exhausted. For each such candidate we first try to commute it to the head. If that succeeds, then its commuted version must be a conflictor. (Either it was a conflictor to begin with, in which case it remains one; or it is a patch that a later conflictor conflicted with, and that means it must itself become conflicted when commuted to the head.) The contexted patch that the (commuted) conflictor represents, together with its set of conflicts, is either added as a new component to @done@, or else is joined with some already found component. If the commute does not succeed, then this indicates that some later patch has resolved (parts of) the conflict. So this patch makes no direct contribution to the confict graph. However, it may still be part of a larger transitive conflict and not all patches involved may have been fully resolved. (Remember that the commute rules for V3 are such that a patch depends on a conflictor if it depends on /any/ of the patches involved in the conflict.) To make sure that the result is independent of the order of patches, we need to remember the set of directly conflicting patches (by adding it to @res@). When the traversal terminates, we use this information to join any components connected by these sets into larger components. See the discussion below for details. In both cases, if the patch is conflicted, we insert any patch that the candidate conflicts with into @todo@ (and remove the patch itself). Note that in order to maintain our invariant, we must extract the set of conflicts from the patch /in its uncommuted form/. (If we took them from the commuted version, then we might mark patches that we already traversed.) The necessity to remember information about all direct conflicts until the end, regardless of partial resolutions, can be seen with the following example. Suppose we have patches A;B;C;D;E where E (a partial resolution) depends (only) on C, and we have direct conflicts A/C, C/B, B/D. So D commutes past E but conflicts only indirectly with C. Thus when we encounter C and fail to remember the fact that it conflicts with A, we end up with two components [{C,B},{A,D}] instead of a single transitive conflict [{A,B,C,D}] that we would get when examining the patches in the order A;C;B;D;E. On the other hand, suppose we have A;B;C;D;E;F, where A;B;C;D;E form a transitive conflict chain (i.e. we have direct conflicts A/B, B/C, C/D, D/E), and the partial resolution F depends on {B,C,D}. Note that /all/ direct conflicts involving C are resolved by F, so we expect to get the two components {A,B} and {D,E}. Indeed, suppose the order were B;D;C;F;A;E, then at the point after F is added we have a repo with all conflicts resolved. Thus after adding A and E we should only see the direct conflicts of A and E, i.e. A, B, D, and E. The slightly subtle implication is that when we finally join components, we must do so for one conflict set at a time; it would be wrong to first join conflict sets and then use those to join components, since that would join {A,B} and {D,E}, even though there is no conflict between the two sets. -} findComponents :: forall name prim wO wX wY . (SignedId name, StorableId name, PrimPatch prim) => RL (RepoPatchV3 name prim) wO wX -> RL (RepoPatchV3 name prim) wX wY -> [Component name prim wY] findComponents context patches = go S.empty [] [] context patches NilFL where go :: S.Set name -> [Component name prim wY] -> [S.Set name] -> RL (RepoPatchV3 name prim) wO wA -> RL (RepoPatchV3 name prim) wA wB -> FL (RepoPatchV3 name prim) wB wY -> [Component name prim wY] go todo done res cs (ps :<: p) passedby | isConflicted p || ident p `S.member` todo , Just (_ :> p') <- commuteFL (p :> passedby) = go (updTodo p todo) (updDone p' done) res cs ps (p :>: passedby) | otherwise = go (updTodo p todo) done (updRes p res) cs ps (p :>: passedby) go todo done res _ NilRL _ | S.null todo = sort $ map purgeDeps $ foldr joinOverlapping done res go todo done res (cs :<: p) NilRL passedby | ident p `S.member` todo , Just (_ :> p') <- commuteFL (p :> passedby) = go (updTodo p todo) (updDone p' done) res cs NilRL (p :>: passedby) | otherwise = go (updTodo p todo) done (updRes p res) cs NilRL (p :>: passedby) go _ _ _ NilRL NilRL _ = error "autsch, hit the bottom" updTodo p todo = S.map ctxId (conflicts p) <> (ident p -| todo) updDone p' done = joinOrAddNew (allConflicts p') done updRes p res = S.map ctxId (allConflicts p) : res conflicts (Conflictor _ x _) = x conflicts _ = S.empty allConflicts (Conflictor _ x cp) = cp +| x allConflicts _ = S.empty -- Join all components which overlap with the given set of IDs joinOverlapping ids cs = case partition (not . S.disjoint ids . S.map ctxId) cs of ([], to_keep) -> to_keep -- avoid adding empty components (to_join, to_keep) -> S.unions to_join : to_keep -- remove vertices that others depend on purgeDeps :: Component name prim wY -> Component name prim wY purgeDeps c = S.filter (\a -> not $ any (a `ctxDepends`) (a -| c)) c -- | Add a set to a disjoint list of sets, such that we maintain the invariant -- that the resulting list of sets is disjoint, and such that their unions are -- equal to the unions of the inputs. -- -- The tricky point here is that the new set may overlap with any number of -- list elements; we must ensure they are all joined into a single set. joinOrAddNew :: Ord a => S.Set a -> [S.Set a] -> [S.Set a] joinOrAddNew c [] = [c] joinOrAddNew c (d:ds) | not $ all (S.disjoint d) ds = error "precondition: sets are not disjoint" | c `S.disjoint` d = d : joinOrAddNew c ds | otherwise = joinOrAddNew (c `S.union` d) ds