module Darcs.Patch.CommuteNoConflicts ( CommuteNoConflicts(..) , mergeNoConflicts ) where import Darcs.Prelude import Darcs.Patch.Commute ( Commute ) import Darcs.Patch.Invert ( Invert(..) ) import Darcs.Patch.Witnesses.Ordered ( (:/\:)(..), (:>)(..), (:\/:)(..) ) -- | It is natural to think of conflicting patches @p@ and @q@ as -- a parallel pair @p:\/:q@ because this is how conflicting patches arise. -- But then Darcs comes along and merges them anyway by converting one of -- them to a conflictor. Thus, inside a sequence of patches we may see -- them as a sequential pair @(p:>q')@. In that case, 'commute' will always -- succeed, as expressed by the 'prop_mergeCommute' law. 'commuteNoConflicts' -- is a restricted version of 'commute' that should fail in this case but -- otherwise give the same result as 'commute'. -- -- Primitive patch types have no conflictors, so for them we have -- @commute == commuteNoConflicts@. -- -- Instances should obey the following laws: -- -- * Symmetry -- -- prop> commuteNoConflicts (p:>q) == Just (q':>p') <=> commuteNoConflicts (q':>p') == Just (p':>q) -- -- * Square-Commute (if an instance @'Invert' p@ exists) -- -- prop> commuteNoConflicts (p:>q) == Just (q':>p') => commuteNoConflicts (invert p:>q') == Just (q:>invert p') -- -- * 'commuteNoConflicts' is a restriction of 'commute' -- -- prop> commuteNoConflicts (p:>q) == Just r => commute (p:>q) == Just r -- class Commute p => CommuteNoConflicts p where commuteNoConflicts :: (p :> p) wX wY -> Maybe ((p :> p) wX wY) -- ^ An alternative to 'commute' to be used if correctness of your code -- depends on the validity of the square-commute law, or to determine -- whether patches are in conflict. A parallel pair of patches @p:\/:q@ -- is conflicting if and only if @commuteNoConflicts(p^:>q)@ fails. Its -- main use is so that we can define 'mergeNoConflicts' cleanly. {- | The non-conflicting merge of @p:\/:q@ tries to commute the inverse @p^@ of @p@ with @q@. If it succeeds then the part of the result that corresponds to @p^@ is re-inverted. This is also known as a "clean merge". Note that to maintain consistency in the presence of conflictors we must use use 'commuteNoConflicts' here and not 'commute'. Otherwise we run into contradictions as explained below. Concretely, suppose we use 'commute' here and that @q@ is a conflictor that represents the primitive patch @r@ and conflicts (only) with (primitive patch) @p^@. That is, @q@ results from the conflicted @merge(r:\/:p^)=(s:/\:q)@, where @s@ is another conflictor. Now, according to 'prop_mergeCommute' we get @commute(p^:>q)=Just(r:>s)@, and thus @mergeNoConflict(p:\/:q)=Just(s^:/\:r)@ in contradiction to our assumption that @p^:\/:q@ are in conflict i.e. @mergeNoConflict(p^:\/:q)@ fails. (This argument takes for granted that the addition of conflictors to prim patches preserves their commute behavior. This is not yet stated as a law but all implementations obviously adhere to it.) As a side note, the fact that we now get an inverse conflictor @s^@ as part of the result leads to further problems. For instance, whether our repo is conflicted now depends on the order of patches: @(p:>r)@ is not conflicted, but its commute @(q:>s^)@ obviously is. In fact, @(q:>s^)@ is nothing else but the (identity-preserving) "force-commute" of @(p:>r)@, see the thread at https://lists.osuosl.org/pipermail/darcs-devel/2017-November/018403.html -} mergeNoConflicts :: (Invert p, CommuteNoConflicts p) => (p :\/: p) wX wY -> Maybe ((p :/\: p) wX wY) mergeNoConflicts (p :\/: q) = do q' :> ip' <- commuteNoConflicts (invert p :> q) return (q' :/\: invert ip')