1 % Copyright (C) 2002-2003,2007 David Roundy
3 % This program is free software; you can redistribute it and/or modify
4 % it under the terms of the GNU General Public License as published by
5 % the Free Software Foundation; either version 2, or (at your option)
8 % This program is distributed in the hope that it will be useful,
9 % but WITHOUT ANY WARRANTY; without even the implied warranty of
10 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 % GNU General Public License for more details.
13 % You should have received a copy of the GNU General Public License
14 % along with this program; see the file COPYING. If not, write to
15 % the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 % Boston, MA 02110-1301, USA.
19 \section{Patch relationships}
22 {-# OPTIONS_GHC -cpp -fglasgow-exts #-}
24 -- , MagicHash, TypeOperators, GADTs, PatternGuards #-}
28 module Darcs.Patch.Prim
29 ( Prim(..), IsConflictedPrim(IsC), ConflictState(..), showPrim,
30 DirPatchType(..), FilePatchType(..),
31 CommuteFunction, Perhaps(..),
32 null_patch, nullP, is_null_patch,
34 formatFileName, FileNameFormat(..),
35 adddir, addfile, binary, changepref,
36 hunk, move, rmdir, rmfile, tokreplace,
37 is_addfile, is_hunk, is_binary, is_setpref,
38 is_similar, is_adddir, is_filepatch,
39 canonize, try_to_shrink, modernizePrim,
40 subcommutes, sort_coalesceFL, join,
41 applyBinary, try_tok_internal,
42 try_shrinking_inverse,
43 FromPrim(..), FromPrims(..), ToFromPrim(..),
44 Conflict(..), Effect(..), commute_no_conflictsFL, commute_no_conflictsRL
48 import Prelude hiding ( pi )
49 import Control.Monad ( MonadPlus, msum, mzero, mplus )
50 import Data.Maybe ( isNothing )
51 #ifndef GADT_WITNESSES
52 import Data.Map ( elems, fromListWith, mapWithKey )
55 import ByteStringUtils ( substrPS, fromPS2Hex)
56 import qualified Data.ByteString as B (ByteString, length, null, head, take, concat, drop)
57 import qualified Data.ByteString.Char8 as BC (break, pack)
59 import Darcs.Patch.FileName ( FileName, fn2ps, fn2fp, fp2fn, norm_path,
60 movedirfilename, encode_white )
62 import Darcs.Sealed ( Sealed, unseal )
63 import Darcs.Patch.Patchy ( Invert(..), Commute(..) )
64 import Darcs.Patch.Permutations () -- for Invert instance of FL
65 import Darcs.SlurpDirectory ( FileContents )
67 import Darcs.Utils ( nubsort )
68 import Lcs ( getChanges )
69 import RegChars ( regChars )
70 import Printer ( Doc, vcat, packedString, Color(Cyan,Magenta), lineColor,
71 text, userchunk, invisibleText, invisiblePS, blueText,
72 ($$), (<+>), (<>), prefix, userchunkPS,
74 import GHC.Base (unsafeCoerce#)
75 #include "impossible.h"
77 data Prim C(x y) where
78 Move :: !FileName -> !FileName -> Prim C(x y)
79 DP :: !FileName -> !(DirPatchType C(x y)) -> Prim C(x y)
80 FP :: !FileName -> !(FilePatchType C(x y)) -> Prim C(x y)
81 Split :: FL Prim C(x y) -> Prim C(x y)
82 Identity :: Prim C(x x)
83 ChangePref :: !String -> !String -> !String -> Prim C(x y)
85 data FilePatchType C(x y) = RmFile | AddFile
86 | Hunk !Int [B.ByteString] [B.ByteString]
87 | TokReplace !String !String !String
88 | Binary B.ByteString B.ByteString
91 data DirPatchType C(x y) = RmDir | AddDir
94 instance MyEq FilePatchType where
95 unsafeCompare a b = a == unsafeCoerce# b
97 instance MyEq DirPatchType where
98 unsafeCompare a b = a == unsafeCoerce# b
100 null_patch :: Prim C(x x)
101 null_patch = Identity
103 is_null_patch :: Prim C(x y) -> Bool
104 is_null_patch (FP _ (Binary x y)) = B.null x && B.null y
105 is_null_patch (FP _ (Hunk _ [] [])) = True
106 is_null_patch Identity = True
107 is_null_patch _ = False
109 nullP :: Prim C(x y) -> EqCheck C(x y)
110 nullP = sloppyIdentity
112 is_identity :: Prim C(x y) -> EqCheck C(x y)
113 is_identity (FP _ (Binary old new)) | old == new = unsafeCoerce# IsEq
114 is_identity (FP _ (Hunk _ old new)) | old == new = unsafeCoerce# IsEq
115 is_identity (FP _ (TokReplace _ old new)) | old == new = unsafeCoerce# IsEq
116 is_identity (Move old new) | old == new = unsafeCoerce# IsEq
117 is_identity Identity = IsEq
118 is_identity _ = NotEq
121 %FIXME: The following code needs to be moved. It is a function
122 %``is\_similar'' which tells you if two patches are in the same category
123 %human-wise. Currently it just returns true if they are filepatches on the
127 is_similar :: Prim C(x y) -> Prim C(a b) -> Bool
128 is_similar (FP f _) (FP f' _) = f == f'
129 is_similar (DP f _) (DP f' _) = f == f'
130 is_similar _ _ = False
132 is_addfile :: Prim C(x y) -> Bool
133 is_addfile (FP _ AddFile) = True
136 is_adddir :: Prim C(x y) -> Bool
137 is_adddir (DP _ AddDir) = True
140 is_hunk :: Prim C(x y) -> Bool
141 is_hunk (FP _ (Hunk _ _ _)) = True
144 is_binary :: Prim C(x y) -> Bool
145 is_binary (FP _ (Binary _ _)) = True
148 is_setpref :: Prim C(x y) -> Bool
149 is_setpref (ChangePref _ _ _) = True
154 addfile :: FilePath -> Prim C(x y)
155 rmfile :: FilePath -> Prim C(x y)
156 adddir :: FilePath -> Prim C(x y)
157 rmdir :: FilePath -> Prim C(x y)
158 move :: FilePath -> FilePath -> Prim C(x y)
159 changepref :: String -> String -> String -> Prim C(x y)
160 hunk :: FilePath -> Int -> [B.ByteString] -> [B.ByteString] -> Prim C(x y)
161 tokreplace :: FilePath -> String -> String -> String -> Prim C(x y)
162 binary :: FilePath -> B.ByteString -> B.ByteString -> Prim C(x y)
164 evalargs :: (a -> b -> c) -> a -> b -> c
165 evalargs f x y = (f $! x) $! y
167 addfile f = FP (fp2fn $ n_fn f) AddFile
168 rmfile f = FP (fp2fn $ n_fn f) RmFile
169 adddir d = DP (fp2fn $ n_fn d) AddDir
170 rmdir d = DP (fp2fn $ n_fn d) RmDir
171 move f f' = Move (fp2fn $ n_fn f) (fp2fn $ n_fn f')
172 changepref p f t = ChangePref p f t
173 hunk f line old new = evalargs FP (fp2fn $ n_fn f) (Hunk line old new)
174 tokreplace f tokchars old new =
175 evalargs FP (fp2fn $ n_fn f) (TokReplace tokchars old new)
176 binary f old new = FP (fp2fn $! n_fn f) $ Binary old new
180 n_fn :: FilePath -> FilePath
181 n_fn f = "./"++(fn2fp $ norm_path $ fp2fn f)
184 The simplest relationship between two patches is that of ``sequential''
185 patches, which means that the context of the second patch (the one on the
186 left) consists of the first patch (on the right) plus the context of the
187 first patch. The composition of two patches (which is also a patch) refers
188 to the patch which is formed by first applying one and then the other. The
189 composition of two patches, $P_1$ and $P_2$ is represented as $P_2P_1$,
190 where $P_1$ is to be applied first, then $P_2$\footnote{This notation is
191 inspired by the notation of matrix multiplication or the application of
192 operators upon a Hilbert space. In the algebra of patches, there is
193 multiplication (i.e.\ composition), which is associative but not
194 commutative, but no addition or subtraction.}
196 There is one other very useful relationship that two patches can have,
197 which is to be parallel patches, which means that the two patches have an
198 identical context (i.e.\ their representation applies to identical trees).
199 This is represented by $P_1\parallel P_2$. Of course, two patches may also
200 have no simple relationship to one another. In that case, if you want to
201 do something with them, you'll have to manipulate them with respect to
202 other patches until they are either in sequence or in parallel.
204 The most fundamental and simple property of patches is that they must be
205 invertible. The inverse of a patch is described by: $P^{ -1}$. In the
206 darcs implementation, the inverse is required to be computable from
207 knowledge of the patch only, without knowledge of its context, but that
208 (although convenient) is not required by the theory of patches.
210 The inverse of patch $P$ is $P^{ -1}$, which is the ``simplest'' patch for
211 which the composition \( P^{ -1} P \) makes no changes to the tree.
213 Using this definition, it is trivial to prove the following theorem
214 relating to the inverse of a composition of two patches.
215 \begin{thm} The inverse of the composition of two patches is
216 \[ (P_2 P_1)^{ -1} = P_1^{ -1} P_2^{ -1}. \]
218 Moreover, it is possible to show that the right inverse of a patch is equal
219 to its left inverse. In this respect, patches continue to be analogous to
220 square matrices, and indeed the proofs relating to these properties of the
221 inverse are entirely analogous to the proofs in the case of matrix
222 multiplication. The compositions proofs can also readily be extended to
223 the composition of more than two patches.
225 instance Invert Prim where
226 invert Identity = Identity
227 invert (FP f RmFile) = FP f AddFile
228 invert (FP f AddFile) = FP f RmFile
229 invert (FP f (Hunk line old new)) = FP f $ Hunk line new old
230 invert (FP f (TokReplace t o n)) = FP f $ TokReplace t n o
231 invert (FP f (Binary o n)) = FP f $ Binary n o
232 invert (DP d RmDir) = DP d AddDir
233 invert (DP d AddDir) = DP d RmDir
234 invert (Move f f') = Move f' f
235 invert (ChangePref p f t) = ChangePref p t f
236 invert (Split ps) = Split $ invert ps
238 sloppyIdentity Identity = IsEq
239 sloppyIdentity _ = NotEq
244 instance Show (Prim C(x y)) where
245 showsPrec d (Move fn1 fn2) = showParen (d > app_prec) $ showString "Move " .
246 showsPrec (app_prec + 1) fn1 . showString " " .
247 showsPrec (app_prec + 1) fn2
248 showsPrec d (DP fn dp) = showParen (d > app_prec) $ showString "DP " .
249 showsPrec (app_prec + 1) fn . showString " " .
250 showsPrec (app_prec + 1) dp
251 showsPrec d (FP fn fp) = showParen (d > app_prec) $ showString "FP " .
252 showsPrec (app_prec + 1) fn . showString " " .
253 showsPrec (app_prec + 1) fp
254 showsPrec d (Split l) = showParen (d > app_prec) $ showString "Split " .
255 showsPrec (app_prec + 1) l
256 showsPrec _ Identity = showString "Identity"
257 showsPrec d (ChangePref p f t) = showParen (d > app_prec) $ showString "ChangePref " .
258 showsPrec (app_prec + 1) p . showString " " .
259 showsPrec (app_prec + 1) f . showString " " .
260 showsPrec (app_prec + 1) t
262 instance Show2 Prim where
263 showsPrec2 = showsPrec
265 instance Show (FilePatchType C(x y)) where
266 showsPrec _ RmFile = showString "RmFile"
267 showsPrec _ AddFile = showString "AddFile"
268 showsPrec d (Hunk line old new) | all ((==1) . B.length) old && all ((==1) . B.length) new
269 = showParen (d > app_prec) $ showString "Hunk " .
270 showsPrec (app_prec + 1) line . showString " " .
271 showsPrecC old . showString " " .
273 where showsPrecC [] = showString "[]"
274 showsPrecC ss = showParen True $ showString "packStringLetters " . showsPrec (app_prec + 1) (map B.head ss)
275 showsPrec d (Hunk line old new) = showParen (d > app_prec) $ showString "Hunk " .
276 showsPrec (app_prec + 1) line . showString " " .
277 showsPrec (app_prec + 1) old . showString " " .
278 showsPrec (app_prec + 1) new
279 showsPrec d (TokReplace t old new) = showParen (d > app_prec) $ showString "TokReplace " .
280 showsPrec (app_prec + 1) t . showString " " .
281 showsPrec (app_prec + 1) old . showString " " .
282 showsPrec (app_prec + 1) new
283 -- this case may not work usefully
284 showsPrec d (Binary old new) = showParen (d > app_prec) $ showString "Binary " .
285 showsPrec (app_prec + 1) old . showString " " .
286 showsPrec (app_prec + 1) new
288 instance Show (DirPatchType C(x y)) where
289 showsPrec _ RmDir = showString "RmDir"
290 showsPrec _ AddDir = showString "AddDir"
293 instance Show (Prim C(x y)) where
294 show p = renderString (showPrim p) ++ "\n"
297 data FileNameFormat = OldFormat | NewFormat
298 formatFileName :: FileNameFormat -> FileName -> Doc
299 formatFileName OldFormat = packedString . fn2ps
300 formatFileName NewFormat = text . encode_white . fn2fp
302 showPrim :: FileNameFormat -> Prim C(a b) -> Doc
303 showPrim x (FP f AddFile) = showAddFile x f
304 showPrim x (FP f RmFile) = showRmFile x f
305 showPrim x (FP f (Hunk line old new)) = showHunk x f line old new
306 showPrim x (FP f (TokReplace t old new)) = showTok x f t old new
307 showPrim x (FP f (Binary old new)) = showBinary x f old new
308 showPrim x (DP d AddDir) = showAddDir x d
309 showPrim x (DP d RmDir) = showRmDir x d
310 showPrim x (Move f f') = showMove x f f'
311 showPrim _ (ChangePref p f t) = showChangePref p f t
312 showPrim x (Split ps) = showSplit x ps
313 showPrim _ Identity = blueText "{}"
319 Add an empty file to the tree.
321 \verb!addfile filename!
323 showAddFile :: FileNameFormat -> FileName -> Doc
324 showAddFile x f = blueText "addfile" <+> formatFileName x f
327 \paragraph{Remove file}
328 Delete a file from the tree.
330 \verb!rmfile filename!
332 showRmFile :: FileNameFormat -> FileName -> Doc
333 showRmFile x f = blueText "rmfile" <+> formatFileName x f
337 Rename a file or directory.
339 \verb!move oldname newname!
341 showMove :: FileNameFormat -> FileName -> FileName -> Doc
342 showMove x d d' = blueText "move" <+> formatFileName x d <+> formatFileName x d'
345 \paragraph{Change Pref}
346 Change one of the preference settings. Darcs stores a number of simple
347 string settings. Among these are the name of the test script and the name
348 of the script that must be called prior to packing in a make dist.
355 showChangePref :: String -> String -> String -> Doc
356 showChangePref p f t = blueText "changepref" <+> text p
362 Add an empty directory to the tree.
364 \verb!adddir filename!
366 showAddDir :: FileNameFormat -> FileName -> Doc
367 showAddDir x d = blueText "adddir" <+> formatFileName x d
370 \paragraph{Remove dir}
371 Delete a directory from the tree.
373 \verb!rmdir filename!
375 showRmDir :: FileNameFormat -> FileName -> Doc
376 showRmDir x d = blueText "rmdir" <+> formatFileName x d
381 Replace a hunk (set of contiguous lines) of text with a new
391 showHunk :: FileNameFormat -> FileName -> Int -> [B.ByteString] -> [B.ByteString] -> Doc
392 showHunk x f line old new =
393 blueText "hunk" <+> formatFileName x f <+> text (show line)
394 $$ lineColor Magenta (prefix "-" (vcat $ map userchunkPS old))
395 $$ lineColor Cyan (prefix "+" (vcat $ map userchunkPS new))
398 \paragraph{Token replace}
400 Replace a token with a new token. Note that this format means that
401 whitespace must not be allowed within a token. If you know of a practical
402 application of whitespace within a token, let me know and I may change
405 replace FILENAME [REGEX] OLD NEW
408 showTok :: FileNameFormat -> FileName -> String -> String -> String -> Doc
409 showTok x f t o n = blueText "replace" <+> formatFileName x f
410 <+> text "[" <> userchunk t <> text "]"
415 \paragraph{Binary file modification}
428 showBinary :: FileNameFormat -> FileName -> B.ByteString -> B.ByteString -> Doc
430 blueText "binary" <+> formatFileName x f
431 $$ invisibleText "oldhex"
432 $$ (vcat $ map makeprintable $ break_every 78 $ fromPS2Hex o)
433 $$ invisibleText "newhex"
434 $$ (vcat $ map makeprintable $ break_every 78 $ fromPS2Hex n)
435 where makeprintable ps = invisibleText "*" <> invisiblePS ps
437 break_every :: Int -> B.ByteString -> [B.ByteString]
438 break_every n ps | B.length ps < n = [ps]
439 | otherwise = B.take n ps : break_every n (B.drop n ps)
442 \paragraph{Split patch [OBSOLETE!]}
443 A split patch is similar to a composite patch but rather than being
444 composed of several patches grouped together, it is created from one
445 patch that has been split apart, typically through a merge or
449 <put patches here> (indented two)
453 showSplit :: FileNameFormat -> FL Prim C(x y) -> Doc
454 showSplit x ps = blueText "("
455 $$ vcat (mapFL (showPrim x) ps)
460 \section{Commuting patches}
462 \subsection{Composite patches}
464 Composite patches are made up of a series of patches intended to be applied
465 sequentially. They are represented by a list of patches, with the first
466 patch in the list being applied first.
468 commute_split :: CommuteFunction
469 commute_split (Split patches :< patch) =
470 toPerhaps $ do (p1 :< ps) <- cs (patches :< patch)
471 case sort_coalesceFL ps of
472 p :>: NilFL -> return (p1 :< p)
473 ps' -> return (p1 :< Split ps')
474 where cs :: ((FL Prim) :< Prim) C(x y) -> Maybe ((Prim :< (FL Prim)) C(x y))
475 cs (NilFL :< p1) = return (p1 :< NilFL)
476 cs (p:>:ps :< p1) = do p1' :< p' <- commutex (p :< p1)
477 p1'' :< ps' <- cs (ps :< p1')
478 return (p1'' :< p':>:ps')
479 commute_split _ = Unknown
483 try_to_shrink :: FL Prim C(x y) -> FL Prim C(x y)
484 try_to_shrink = mapPrimFL try_harder_to_shrink
486 mapPrimFL :: (FORALL(x y) FL Prim C(x y) -> FL Prim C(x y))
487 -> FL Prim C(w z) -> FL Prim C(w z)
489 #ifdef GADT_WITNESSES
492 -- an optimisation; break the list up into independent sublists
493 -- and apply f to each of them
494 case mapM toSimple $ mapFL id x of
495 Just sx -> foldr (+>+) NilFL $ elems $
496 mapWithKey (\ k p -> f (fromSimples k (p NilFL))) $
497 fromListWith (flip (.)) $
498 map (\ (a,b) -> (a,(b:>:))) sx
501 data Simple C(x y) = SFP !(FilePatchType C(x y)) | SDP !(DirPatchType C(x y))
502 | SCP String String String
505 toSimple :: Prim C(x y) -> Maybe (FileName, Simple C(x y))
506 toSimple (FP a b) = Just (a, SFP b)
507 toSimple (DP a AddDir) = Just (a, SDP AddDir)
508 toSimple (DP _ RmDir) = Nothing -- ordering is trickier with rmdir present
509 toSimple (Move _ _) = Nothing
510 toSimple (Split _) = Nothing
511 toSimple Identity = Nothing
512 toSimple (ChangePref a b c) = Just (fp2fn "_darcs/prefs/prefs", SCP a b c)
514 fromSimple :: FileName -> Simple C(x y) -> Prim C(x y)
515 fromSimple a (SFP b) = FP a b
516 fromSimple a (SDP b) = DP a b
517 fromSimple _ (SCP a b c) = ChangePref a b c
519 fromSimples :: FileName -> FL Simple C(x y) -> FL Prim C(x y)
520 fromSimples a bs = mapFL_FL (fromSimple a) bs
523 try_harder_to_shrink :: FL Prim C(x y) -> FL Prim C(x y)
524 try_harder_to_shrink x = try_to_shrink2 $ maybe x id (try_shrinking_inverse x)
526 try_to_shrink2 :: FL Prim C(x y) -> FL Prim C(x y)
527 try_to_shrink2 psold =
528 let ps = sort_coalesceFL psold
529 ps_shrunk = shrink_a_bit ps
531 if lengthFL ps_shrunk < lengthFL ps
532 then try_to_shrink2 ps_shrunk
535 try_shrinking_inverse :: FL Prim C(x y) -> Maybe (FL Prim C(x y))
536 try_shrinking_inverse (x:>:y:>:z)
537 | IsEq <- invert x =\/= y = Just z
538 | otherwise = case try_shrinking_inverse (y:>:z) of
540 Just yz' -> Just $ case try_shrinking_inverse (x:>:yz') of
543 try_shrinking_inverse _ = Nothing
545 shrink_a_bit :: FL Prim C(x y) -> FL Prim C(x y)
546 shrink_a_bit NilFL = NilFL
547 shrink_a_bit (p:>:ps) =
548 case try_one NilRL p ps of
549 Nothing -> p :>: shrink_a_bit ps
552 try_one :: RL Prim C(w x) -> Prim C(x y) -> FL Prim C(y z)
553 -> Maybe (FL Prim C(w z))
554 try_one _ _ NilFL = Nothing
555 try_one sofar p (p1:>:ps) =
556 case coalesce (p1 :< p) of
557 Just p' -> Just (reverseRL sofar +>+ p':>:NilFL +>+ ps)
558 Nothing -> case commutex (p1 :< p) of
560 Just (p' :< p1') -> try_one (p1':<:sofar) p' ps
562 -- | 'sort_coalesceFL @ps@' coalesces as many patches in @ps@ as
563 -- possible, sorting the results according to the scheme defined
565 sort_coalesceFL :: FL Prim C(x y) -> FL Prim C(x y)
566 sort_coalesceFL = mapPrimFL sort_coalesceFL2
568 -- | The heart of 'sort_coalesceFL'
569 sort_coalesceFL2 :: FL Prim C(x y) -> FL Prim C(x y)
570 sort_coalesceFL2 NilFL = NilFL
571 sort_coalesceFL2 (x:>:xs) | IsEq <- nullP x = sort_coalesceFL2 xs
572 sort_coalesceFL2 (x:>:xs) | IsEq <- is_identity x = sort_coalesceFL2 xs
573 sort_coalesceFL2 (x:>:xs) = either id id $ push_coalesce_patch x $ sort_coalesceFL2 xs
575 -- | 'push_coalesce_patch' @new ps@ is almost like @new :>: ps@ except
576 -- as an alternative to consing, we first try to coalesce @new@ with
577 -- the head of @ps@. If this fails, we try again, using commutation
578 -- to push @new@ down the list until we find a place where either
579 -- (a) @new@ is @LT@ the next member of the list [see 'comparePrim']
580 -- (b) commutation fails or
581 -- (c) coalescing succeeds.
582 -- The basic principle is to coalesce if we can and cons otherwise.
584 -- As an additional optimization, push_coalesce_patch outputs a Left
585 -- value if it wasn't able to shrink the patch sequence at all, and
586 -- a Right value if it was indeed able to shrink the patch sequence.
587 -- This avoids the O(N) calls to lengthFL that were in the older
590 -- Also note that push_coalesce_patch is only ever used (and should
591 -- only ever be used) as an internal function in in
593 push_coalesce_patch :: Prim C(x y) -> FL Prim C(y z)
594 -> Either (FL Prim C(x z)) (FL Prim C(x z))
595 push_coalesce_patch new NilFL = Left (new:>:NilFL)
596 push_coalesce_patch new ps@(p:>:ps')
597 = case coalesce (p :< new) of
598 Just new' | IsEq <- nullP new' -> Right ps'
599 | otherwise -> Right $ either id id $ push_coalesce_patch new' ps'
600 Nothing -> if comparePrim new p == LT then Left (new:>:ps)
601 else case commutex (p :< new) of
603 case push_coalesce_patch new' ps' of
604 Right r -> Right $ either id id $
605 push_coalesce_patch p' r
606 Left r -> Left (p' :>: r)
607 Nothing -> Left (new:>:ps)
610 \newcommand{\commutex}{\longleftrightarrow}
611 \newcommand{\commutes}{\longleftrightarrow}
613 The first way (of only two) to change the context of a patch is by
614 commutation, which is the process of changing the order of two sequential
617 The commutation of patches $P_1$ and $P_2$ is represented by
618 \[ P_2 P_1 \commutes {P_1}' {P_2}'. \]
619 Here $P_1'$ is intended to describe the same change as $P_1$, with the
620 only difference being that $P_1'$ is applied after $P_2'$ rather than
623 The above definition is obviously rather vague, the reason being that what
624 is the ``same change'' has not been defined, and we simply assume (and
625 hope) that the code's view of what is the ``same change'' will match those
626 of its human users. The `$\commutes$' operator should be read as something
627 like the $==$ operator in C, indicating that the right hand side performs
628 identical changes to the left hand side, but the two patches are in
629 reversed order. When read in this manner, it is clear that commutation
630 must be a reversible process, and indeed this means that commutation
631 \emph{can} fail, and must fail in certain cases. For example, the creation
632 and deletion of the same file cannot be commuted. When two patches fail to
633 commutex, it is said that the second patch depends on the first, meaning
634 that it must have the first patch in its context (remembering that the
635 context of a patch is a set of patches, which is how we represent a tree).
636 \footnote{The fact that commutation can fail makes a huge difference in the
637 whole patch formalism. It may be possible to create a formalism in which
638 commutation always succeeds, with the result of what would otherwise be a
639 commutation that fails being something like a virtual particle (which can
640 violate conservation of energy), and it may be that such a formalism would
641 allow strict mathematical proofs (whereas those used in the current
642 formalism are mostly only hand waving ``physicist'' proofs). However, I'm
643 not sure how you'd deal with a request to delete a file that has not yet
644 been created, for example. Obviously you'd need to create some kind of
645 antifile, which would annihilate with the file when that file finally got
646 created, but I'm not entirely sure how I'd go about doing this.
647 $\ddot\frown$ So I'm sticking with my hand waving formalism.}
649 %I should add that one using the inversion relationship of sequential
650 %patches, one can avoid having to provide redundant definitions of
653 % There is another interesting property which is that a commutex's results
654 % can't be affected by commuting another thingamabopper.
657 is_in_directory :: FileName -> FileName -> Bool
658 is_in_directory d f = iid (fn2fp d) (fn2fp f)
659 where iid (cd:cds) (cf:cfs)
661 | otherwise = iid cds cfs
662 iid [] ('/':_) = True
663 iid [] [] = True -- Count directory itself as being in directory...
666 data Perhaps a = Unknown | Failed | Succeeded a
668 instance Monad Perhaps where
669 (Succeeded x) >>= k = k x
670 Failed >>= _ = Failed
671 Unknown >>= _ = Unknown
673 (Succeeded _) >> k = k
678 instance MonadPlus Perhaps where
680 Unknown `mplus` ys = ys
681 Failed `mplus` _ = Failed
682 (Succeeded x) `mplus` _ = Succeeded x
684 toMaybe :: Perhaps a -> Maybe a
685 toMaybe (Succeeded x) = Just x
688 toPerhaps :: Maybe a -> Perhaps a
689 toPerhaps (Just x) = Succeeded x
690 toPerhaps Nothing = Failed
692 clever_commute :: CommuteFunction -> CommuteFunction
693 clever_commute c (p1:<p2) =
695 Succeeded x -> Succeeded x
697 Unknown -> case c (invert p2 :< invert p1) of
698 Succeeded (p1' :< p2') -> Succeeded (invert p2' :< invert p1')
701 --clever_commute c (p1,p2) = c (p1,p2) `mplus`
702 -- (case c (invert p2,invert p1) of
703 -- Succeeded (p1', p2') -> Succeeded (invert p2', invert p1')
705 -- Unknown -> Unknown)
707 speedy_commute :: CommuteFunction
708 speedy_commute (p1 :< p2) -- Deal with common case quickly!
709 | p1_modifies /= Nothing && p2_modifies /= Nothing &&
710 p1_modifies /= p2_modifies = Succeeded (unsafeCoerce# p2 :< unsafeCoerce# p1)
711 | otherwise = Unknown
712 where p1_modifies = is_filepatch p1
713 p2_modifies = is_filepatch p2
715 everything_else_commute :: CommuteFunction
716 everything_else_commute x = eec x
718 eec :: CommuteFunction
719 eec (ChangePref p f t :<p1) = Succeeded (unsafeCoerce# p1 :< ChangePref p f t)
720 eec (p2 :<ChangePref p f t) = Succeeded (ChangePref p f t :< unsafeCoerce# p2)
721 eec (Identity :< p1) = Succeeded (p1 :< Identity)
722 eec (p2 :< Identity) = Succeeded (Identity :< p2)
725 clever_commute commute_filedir xx
726 ,clever_commute commute_split xx
730 Note that it must be true that
732 commutex (A^-1 A, P) = Just (P, A'^-1 A')
736 if commutex (A, B) == Just (B', A')
737 then commutex (B^-1, A^-1) == Just (A'^-1, B'^-1)
740 instance Commute Prim where
742 case elegant_merge (y:\/:z) of
743 Just (z' :/\: y') -> z' :/\: y'
744 Nothing -> error "Commute Prim merge"
745 commutex x = toMaybe $ msum [speedy_commute x,
746 everything_else_commute x
748 -- Recurse on everything, these are potentially spoofed patches
749 list_touched_files (Move f1 f2) = map fn2fp [f1, f2]
750 list_touched_files (Split ps) = nubsort $ concat $ mapFL list_touched_files ps
751 list_touched_files (FP f _) = [fn2fp f]
752 list_touched_files (DP d _) = [fn2fp d]
753 list_touched_files (ChangePref _ _ _) = []
754 list_touched_files Identity = []
756 is_filepatch :: Prim C(x y) -> Maybe FileName
757 is_filepatch (FP f _) = Just f
758 is_filepatch _ = Nothing
762 is_superdir :: FileName -> FileName -> Bool
763 is_superdir d1 d2 = isd (fn2fp d1) (fn2fp d2)
765 length s2 >= length s1 + 1 && take (length s1 + 1) s2 == s1 ++ "/"
767 commute_filedir :: CommuteFunction
768 commute_filedir (FP f1 p1 :< FP f2 p2) =
769 if f1 /= f2 then Succeeded ( FP f2 (unsafeCoerce# p2) :< FP f1 (unsafeCoerce# p1) )
770 else commuteFP f1 (p1 :< p2)
771 commute_filedir (DP d1 p1 :< DP d2 p2) =
772 if (not $ is_in_directory d1 d2) && (not $ is_in_directory d2 d1) &&
774 then Succeeded ( DP d2 (unsafeCoerce# p2) :< DP d1 (unsafeCoerce# p1) )
776 commute_filedir (DP d dp :< FP f fp) =
777 if not $ is_in_directory d f
778 then Succeeded (FP f (unsafeCoerce# fp) :< DP d (unsafeCoerce# dp))
781 commute_filedir (Move d d' :< FP f2 p2)
783 | (p2 == AddFile || p2 == RmFile) && d == f2 = Failed
784 | otherwise = Succeeded (FP (movedirfilename d d' f2) (unsafeCoerce# p2) :< Move d d')
785 commute_filedir (Move d d' :< DP d2 p2)
786 | is_superdir d2 d' || is_superdir d2 d = Failed
787 | (p2 == AddDir || p2 == RmDir) && d == d2 = Failed
789 | otherwise = Succeeded (DP (movedirfilename d d' d2) (unsafeCoerce# p2) :< Move d d')
790 commute_filedir (Move d d' :< Move f f')
791 | f == d' || f' == d = Failed
792 | f == d || f' == d' = Failed
793 | d `is_superdir` f && f' `is_superdir` d' = Failed
795 Succeeded (Move (movedirfilename d d' f) (movedirfilename d d' f') :<
796 Move (movedirfilename f' f d) (movedirfilename f' f d'))
798 commute_filedir _ = Unknown
802 type CommuteFunction = FORALL(x y) (Prim :< Prim) C(x y) -> Perhaps ((Prim :< Prim) C(x y))
803 subcommutes :: [(String, CommuteFunction)]
805 [("speedy_commute", speedy_commute),
806 ("commute_filedir", clever_commute commute_filedir),
807 ("commute_filepatches", clever_commute commute_filepatches),
808 ("commutex", toPerhaps . commutex)
813 \newcommand{\merge}{\Longrightarrow}
814 The second way one can change the context of a patch is by a {\bf merge}
815 operation. A merge is an operation that takes two parallel patches and
816 gives a pair of sequential patches. The merge operation is represented by
817 the arrow ``\( \merge \)''.
818 \begin{dfn}\label{merge_dfn}
819 The result of a merge of two patches, $P_1$ and $P_2$ is one of two patches,
820 $P_1'$ and $P_2'$, which satisfy the relationship:
821 \[ P_2 \parallel P_1 \merge {P_2}' P_1 \commutex {P_1}' P_2. \]
823 Note that the sequential patches resulting from a merge are \emph{required}
824 to commutex. This is an important consideration, as without it most of the
825 manipulations we would like to perform would not be possible. The other
826 important fact is that a merge \emph{cannot fail}. Naively, those two
827 requirements seem contradictory. In reality, what it means is that the
828 result of a merge may be a patch which is much more complex than any we
829 have yet considered\footnote{Alas, I don't know how to prove that the two
830 constraints even \emph{can} be satisfied. The best I have been able to do
831 is to believe that they can be satisfied, and to be unable to find an case
832 in which my implementation fails to satisfy them. These two requirements
833 are the foundation of the entire theory of patches (have you been counting
834 how many foundations it has?).}.
836 \subsection{How merges are actually performed}
838 The constraint that any two compatible patches (patches which can
839 successfully be applied to the same tree) can be merged is actually quite
840 difficult to apply. The above merge constraints also imply that the result
841 of a series of merges must be independent of the order of the merges. So
842 I'm putting a whole section here for the interested to see what algorithms
843 I use to actually perform the merges (as this is pretty close to being the
844 most difficult part of the code).
846 The first case is that in which the two merges don't actually conflict, but
847 don't trivially merge either (e.g.\ hunk patches on the same file, where the
848 line number has to be shifted as they are merged). This kind of merge can
849 actually be very elegantly dealt with using only commutation and inversion.
851 There is a handy little theorem which is immensely useful when trying to
853 \begin{thm}\label{merge_thm}
854 $ P_2' P_1 \commutex P_1' P_2 $ if and only if $ P_1'^{ -1}
855 P_2' \commutex P_2 P_1^{ -1} $, provided both commutations succeed. If
856 either commutex fails, this theorem does not apply.
858 This can easily be proven by multiplying both sides of the first
859 commutation by $P_1'^{ -1}$ on the left, and by $P_1^{ -1}$ on the right.
863 elegant_merge :: (Prim :\/: Prim) C(x y)
864 -> Maybe ((Prim :/\: Prim) C(x y))
865 elegant_merge (p1 :\/: p2) =
866 do p1':>ip2' <- commute (invert p2 :> p1)
867 -- The following should be a redundant check
868 p1o:>_ <- commute (p2 :> p1')
869 IsEq <- return $ p1o =\/= p1
870 return (invert ip2' :/\: p1')
873 It can sometimes be handy to have a canonical representation of a given
874 patch. We achieve this by defining a canonical form for each patch type,
875 and a function ``{\tt canonize}'' which takes a patch and puts it into
876 canonical form. This routine is used by the diff function to create an
877 optimal patch (based on an LCS algorithm) from a simple hunk describing the
878 old and new version of a file.
880 canonize :: Prim C(x y) -> FL Prim C(x y)
881 canonize (Split ps) = sort_coalesceFL ps
882 canonize p | IsEq <- is_identity p = NilFL
883 canonize (FP f (Hunk line old new)) = canonizeHunk f line old new
884 canonize p = p :>: NilFL
887 A simpler, faster (and more generally useful) cousin of canonize is the
888 coalescing function. This takes two sequential patches, and tries to turn
889 them into one patch. This function is used to deal with ``split'' patches,
890 which are created when the commutation of a primitive patch can only be
891 represented by a composite patch. In this case the resulting composite
892 patch must return to the original primitive patch when the commutation is
893 reversed, which a split patch accomplishes by trying to coalesce its
894 contents each time it is commuted.
897 -- | 'coalesce @p2 :< p1@' tries to combine @p1@ and @p2@ into a single
898 -- patch without intermediary changes. For example, two hunk patches
899 -- modifying adjacent lines can be coalesced into a bigger hunk patch.
900 -- Or a patch which moves file A to file B can be coalesced with a
901 -- patch that moves file B into file C, yielding a patch that moves
903 coalesce :: (Prim :< Prim) C(x y) -> Maybe (Prim C(x y))
904 coalesce (FP f1 _ :< FP f2 _) | f1 /= f2 = Nothing
905 coalesce (p2 :< p1) | IsEq <- p2 =\/= invert p1 = Just null_patch
906 coalesce (FP f1 p1 :< FP _ p2) = coalesceFilePrim f1 (p1 :< p2) -- f1 = f2
907 coalesce (Identity :< p) = Just p
908 coalesce (p :< Identity) = Just p
909 coalesce (Split NilFL :< p) = Just p
910 coalesce (p :< Split NilFL) = Just p
911 coalesce (Move a b :< Move b' a') | a == a' = Just $ Move b' b
912 coalesce (Move a b :< FP f AddFile) | f == a = Just $ FP b AddFile
913 coalesce (FP f RmFile :< Move a b) | b == f = Just $ FP a RmFile
914 coalesce (ChangePref p f1 t1 :< ChangePref p2 f2 t2) | p == p2 && t2 == f1 = Just $ ChangePref p f2 t1
917 join :: (Prim :> Prim) C(x y) -> Maybe (Prim C(x y))
918 join (x :> y) = coalesce (y :< x)
921 \subsection{File patches}
923 A file patch is a patch which only modifies a single
924 file. There are some rules which can be made about file patches in
925 general, which makes them a handy class.
926 For example, commutation of two filepatches is trivial if they modify
927 different files. If they happen to
928 modify the same file, we'll have to check whether or not they commutex.
930 commute_filepatches :: CommuteFunction
931 commute_filepatches (FP f1 p1 :< FP f2 p2) | f1 == f2 = commuteFP f1 (p1 :< p2)
932 commute_filepatches _ = Unknown
934 commuteFP :: FileName -> (FilePatchType :< FilePatchType) C(x y)
935 -> Perhaps ((Prim :< Prim) C(x y))
936 commuteFP f (Hunk line1 [] [] :< p2) =
937 seq f $ Succeeded (FP f (unsafeCoerceP p2) :< FP f (Hunk line1 [] []))
938 commuteFP f (p2 :< Hunk line1 [] []) =
939 seq f $ Succeeded (FP f (Hunk line1 [] []) :< FP f (unsafeCoerceP p2))
940 commuteFP f (Hunk line1 old1 new1 :< Hunk line2 old2 new2) = seq f $
941 toPerhaps $ commuteHunk f (Hunk line1 old1 new1 :< Hunk line2 old2 new2)
942 commuteFP f (TokReplace t o n :< Hunk line2 old2 new2) = seq f $
943 case try_tok_replace t o n old2 of
946 case try_tok_replace t o n new2 of
948 Just new2' -> Succeeded (FP f (Hunk line2 old2' new2') :<
949 FP f (TokReplace t o n))
950 commuteFP f (TokReplace t o n :< TokReplace t2 o2 n2)
951 | seq f $ t /= t2 = Failed
956 | otherwise = Succeeded (FP f (TokReplace t2 o2 n2) :<
957 FP f (TokReplace t o n))
958 commuteFP _ _ = Unknown
962 coalesceFilePrim :: FileName -> (FilePatchType :< FilePatchType) C(x y)
963 -> Maybe (Prim C(x y))
964 coalesceFilePrim f (Hunk line1 old1 new1 :< Hunk line2 old2 new2)
965 = coalesceHunk f line1 old1 new1 line2 old2 new2
966 -- Token replace patches operating right after (or before) AddFile (RmFile)
967 -- is an identity patch, as far as coalescing is concerned.
968 coalesceFilePrim f (TokReplace _ _ _ :< AddFile) = Just $ FP f AddFile
969 coalesceFilePrim f (RmFile :< TokReplace _ _ _) = Just $ FP f RmFile
970 coalesceFilePrim f (TokReplace t1 o1 n1 :< TokReplace t2 o2 n2)
971 | t1 == t2 && n2 == o1 = Just $ FP f $ TokReplace t1 o2 n1
972 coalesceFilePrim f (Binary m n :< Binary o m')
973 | m == m' = Just $ FP f $ Binary o n
974 coalesceFilePrim _ _ = Nothing
979 The hunk is the simplest patch that has a commuting pattern in which the
980 commuted patches differ from the originals (rather than simple success or
981 failure). This makes commuting or merging two hunks a tad tedious.
983 commuteHunk :: FileName -> (FilePatchType :< FilePatchType) C(x y)
984 -> Maybe ((Prim :< Prim) C(x y))
985 commuteHunk f (Hunk line2 old2 new2 :< Hunk line1 old1 new1)
986 | seq f $ line1 + lengthnew1 < line2 =
987 Just (FP f (Hunk line1 old1 new1) :<
988 FP f (Hunk (line2 - lengthnew1 + lengthold1) old2 new2))
989 | line2 + lengthold2 < line1 =
990 Just (FP f (Hunk (line1+ lengthnew2 - lengthold2) old1 new1) :<
991 FP f (Hunk line2 old2 new2))
992 | line1 + lengthnew1 == line2 &&
993 lengthold2 /= 0 && lengthold1 /= 0 && lengthnew2 /= 0 && lengthnew1 /= 0 =
994 Just (FP f (Hunk line1 old1 new1) :<
995 FP f (Hunk (line2 - lengthnew1 + lengthold1) old2 new2))
996 | line2 + lengthold2 == line1 &&
997 lengthold2 /= 0 && lengthold1 /= 0 && lengthnew2 /= 0 && lengthnew1 /= 0 =
998 Just (FP f (Hunk (line1 + lengthnew2 - lengthold2) old1 new1) :<
999 FP f (Hunk line2 old2 new2))
1000 | otherwise = seq f Nothing
1001 where lengthnew1 = length new1
1002 lengthnew2 = length new2
1003 lengthold1 = length old1
1004 lengthold2 = length old2
1005 commuteHunk _ _ = impossible
1007 Hunks, of course, can be coalesced if they have any overlap. Note that
1008 coalesce code doesn't check if the two patches are conflicting. If you are
1009 coalescing two conflicting hunks, you've already got a bug somewhere.
1012 coalesceHunk :: FileName
1013 -> Int -> [B.ByteString] -> [B.ByteString]
1014 -> Int -> [B.ByteString] -> [B.ByteString]
1015 -> Maybe (Prim C(x y))
1016 coalesceHunk f line1 old1 new1 line2 old2 new2
1017 | line1 == line2 && lengthold1 < lengthnew2 =
1018 if take lengthold1 new2 /= old1
1020 else case drop lengthold1 new2 of
1021 extranew -> Just (FP f (Hunk line1 old2 (new1 ++ extranew)))
1022 | line1 == line2 && lengthold1 > lengthnew2 =
1023 if take lengthnew2 old1 /= new2
1025 else case drop lengthnew2 old1 of
1026 extraold -> Just (FP f (Hunk line1 (old2 ++ extraold) new1))
1027 | line1 == line2 = if new2 == old1 then Just (FP f (Hunk line1 old2 new1))
1029 | line1 < line2 && lengthold1 >= line2 - line1 =
1030 case take (line2 - line1) old1 of
1031 extra-> coalesceHunk f line1 old1 new1 line1 (extra ++ old2) (extra ++ new2)
1032 | line1 > line2 && lengthnew2 >= line1 - line2 =
1033 case take (line1 - line2) new2 of
1034 extra-> coalesceHunk f line2 (extra ++ old1) (extra ++ new1) line2 old2 new2
1035 | otherwise = Nothing
1036 where lengthold1 = length old1
1037 lengthnew2 = length new2
1040 One of the most important pieces of code is the canonization of a hunk,
1041 which is where the ``diff'' algorithm is performed. This algorithm begins
1042 with chopping off the identical beginnings and endings of the old and new
1043 hunks. This isn't strictly necessary, but is a good idea, since this
1044 process is $O(n)$, while the primary diff algorithm is something
1045 considerably more painful than that\ldots\ actually the head would be dealt
1046 with all right, but with more space complexity. I think it's more
1047 efficient to just chop the head and tail off first.
1050 canonizeHunk :: FileName -> Int
1051 -> [B.ByteString] -> [B.ByteString] -> FL Prim C(x y)
1052 canonizeHunk f line old new
1053 | null old || null new
1054 = FP f (Hunk line old new) :>: NilFL
1055 canonizeHunk f line old new = make_holey f line $ getChanges old new
1057 make_holey :: FileName -> Int -> [(Int,[B.ByteString], [B.ByteString])]
1059 make_holey f line changes =
1060 unsafeMap_l2f (\ (l,o,n) -> FP f (Hunk (l+line) o n)) changes
1062 applyBinary :: B.ByteString -> B.ByteString
1063 -> FileContents -> Maybe FileContents
1064 applyBinary o n c | c == o = Just n
1065 applyBinary _ _ _ = Nothing
1069 try_tok_replace :: String -> String -> String
1070 -> [B.ByteString] -> Maybe [B.ByteString]
1071 try_tok_replace t o n mss =
1072 mapM (fmap B.concat . try_tok_internal t (BC.pack o) (BC.pack n)) mss
1075 try_tok_internal :: String -> B.ByteString -> B.ByteString
1076 -> B.ByteString -> Maybe [B.ByteString]
1077 try_tok_internal _ o n s | isNothing (substrPS o s) &&
1078 isNothing (substrPS n s) = Just [s]
1079 try_tok_internal t o n s =
1080 case BC.break (regChars t) s of
1082 case BC.break (not . regChars t) s' of
1084 case try_tok_internal t o n after of
1088 then Just $ before : n : rest
1091 else Just $ before : tok : rest
1095 modernizePrim :: Prim C(x y) -> FL Prim C(x y)
1096 modernizePrim (Split ps) = concatFL $ mapFL_FL modernizePrim ps
1097 modernizePrim p = p :>: NilFL
1101 instance MyEq Prim where
1102 unsafeCompare (Move a b) (Move c d) = a == c && b == d
1103 unsafeCompare (DP d1 p1) (DP d2 p2)
1104 = d1 == d2 && p1 `unsafeCompare` p2
1105 unsafeCompare (FP f1 fp1) (FP f2 fp2)
1106 = f1 == f2 && fp1 `unsafeCompare` fp2
1107 unsafeCompare (Split ps1) (Split ps2)
1108 = eq_FL unsafeCompare ps1 ps2
1109 unsafeCompare (ChangePref a1 b1 c1) (ChangePref a2 b2 c2)
1110 = c1 == c2 && b1 == b2 && a1 == a2
1111 unsafeCompare Identity Identity = True
1112 unsafeCompare _ _ = False
1114 merge_orders :: Ordering -> Ordering -> Ordering
1115 merge_orders EQ x = x
1116 merge_orders LT _ = LT
1117 merge_orders GT _ = GT
1119 -- | 'comparePrim' @p1 p2@ is used to provide an arbitrary ordering between
1120 -- @p1@ and @p2@. Basically, identical patches are equal and
1121 -- @Move < DP < FP < Split < Identity < ChangePref@.
1122 -- Everything else is compared in dictionary order of its arguments.
1123 comparePrim :: Prim C(x y) -> Prim C(w z) -> Ordering
1124 comparePrim (Move a b) (Move c d) = compare (a, b) (c, d)
1125 comparePrim (Move _ _) _ = LT
1126 comparePrim _ (Move _ _) = GT
1127 comparePrim (DP d1 p1) (DP d2 p2) = compare (d1, p1) $ unsafeCoerceP (d2, p2)
1128 comparePrim (DP _ _) _ = LT
1129 comparePrim _ (DP _ _) = GT
1130 comparePrim (FP f1 fp1) (FP f2 fp2) = compare (f1, fp1) $ unsafeCoerceP (f2, fp2)
1131 comparePrim (FP _ _) _ = LT
1132 comparePrim _ (FP _ _) = GT
1133 comparePrim (Split ps1) (Split ps2) = compare_FL comparePrim ps1 $ unsafeCoerceP ps2
1134 comparePrim (Split _) _ = LT
1135 comparePrim _ (Split _) = GT
1136 comparePrim Identity Identity = EQ
1137 comparePrim Identity _ = LT
1138 comparePrim _ Identity = GT
1139 comparePrim (ChangePref a1 b1 c1) (ChangePref a2 b2 c2)
1140 = compare (c1, b1, a1) (c2, b2, a2)
1142 eq_FL :: (FORALL(b c d e) a C(b c) -> a C(d e) -> Bool)
1143 -> FL a C(x y) -> FL a C(w z) -> Bool
1144 eq_FL _ NilFL NilFL = True
1145 eq_FL f (x:>:xs) (y:>:ys) = f x y && eq_FL f xs ys
1148 compare_FL :: (FORALL(b c d e) a C(b c) -> a C(d e) -> Ordering)
1149 -> FL a C(x y) -> FL a C(w z) -> Ordering
1150 compare_FL _ NilFL NilFL = EQ
1151 compare_FL _ NilFL _ = LT
1152 compare_FL _ _ NilFL = GT
1153 compare_FL f (x:>:xs) (y:>:ys) = f x y `merge_orders` compare_FL f xs ys
1159 class FromPrim p where
1160 fromPrim :: Prim C(x y) -> p C(x y)
1162 class FromPrim p => ToFromPrim p where
1163 toPrim :: p C(x y) -> Maybe (Prim C(x y))
1165 class FromPrims p where
1166 fromPrims :: FL Prim C(x y) -> p C(x y)
1167 joinPatches :: FL p C(x y) -> p C(x y)
1169 instance FromPrim Prim where
1171 instance ToFromPrim Prim where
1174 instance FromPrim p => FromPrims (FL p) where
1175 fromPrims = mapFL_FL fromPrim
1176 joinPatches = concatFL
1177 instance FromPrim p => FromPrims (RL p) where
1178 fromPrims = reverseFL . mapFL_FL fromPrim
1179 joinPatches = concatRL . reverseFL
1181 class (Invert p, Commute p, Effect p) => Conflict p where
1182 list_conflicted_files :: p C(x y) -> [FilePath]
1183 list_conflicted_files p =
1184 nubsort $ concatMap (unseal list_touched_files) $ concat $ resolve_conflicts p
1185 resolve_conflicts :: p C(x y) -> [[Sealed (FL Prim C(y))]]
1186 resolve_conflicts _ = []
1187 commute_no_conflicts :: (p :> p) C(x y) -> Maybe ((p :> p) C(x y))
1188 commute_no_conflicts (x:>y) =
1189 do y':>x' <- commute (x:>y)
1190 y'':>ix'' <- commute (invert x :> y')
1191 IsEq <- return $ y'' =\/= y
1192 IsEq <- return $ ix'' =\/= invert x'
1194 conflictedEffect :: p C(x y) -> [IsConflictedPrim]
1195 conflictedEffect x = case list_conflicted_files x of
1196 [] -> mapFL (IsC Okay) $ effect x
1197 _ -> mapFL (IsC Conflicted) $ effect x
1199 instance Conflict p => Conflict (FL p) where
1200 list_conflicted_files = nubsort . concat . mapFL list_conflicted_files
1201 resolve_conflicts NilFL = []
1202 resolve_conflicts x = resolve_conflicts $ reverseFL x
1203 commute_no_conflicts (NilFL :> x) = Just (x :> NilFL)
1204 commute_no_conflicts (x :> NilFL) = Just (NilFL :> x)
1205 commute_no_conflicts (xs :> ys) = do ys' :> rxs' <- commute_no_conflictsRLFL (reverseFL xs :> ys)
1206 return $ ys' :> reverseRL rxs'
1207 conflictedEffect = concat . mapFL conflictedEffect
1209 instance Conflict p => Conflict (RL p) where
1210 list_conflicted_files = nubsort . concat . mapRL list_conflicted_files
1211 resolve_conflicts x = rcs x NilFL
1212 where rcs :: RL p C(x y) -> FL p C(y w) -> [[Sealed (FL Prim C(w))]]
1214 rcs (p:<:ps) passedby | (_:_) <- resolve_conflicts p =
1215 case commute_no_conflictsFL (p:>passedby) of
1216 Just (_:> p') -> resolve_conflicts p' ++ rcs ps (p:>:passedby)
1217 Nothing -> rcs ps (p:>:passedby)
1218 rcs (p:<:ps) passedby = seq passedby $ rcs ps (p:>:passedby)
1219 commute_no_conflicts (NilRL :> x) = Just (x :> NilRL)
1220 commute_no_conflicts (x :> NilRL) = Just (NilRL :> x)
1221 commute_no_conflicts (xs :> ys) = do ys' :> rxs' <- commute_no_conflictsRLFL (xs :> reverseRL ys)
1222 return $ reverseFL ys' :> rxs'
1223 conflictedEffect = concat . reverse . mapRL conflictedEffect
1225 data IsConflictedPrim where
1226 IsC :: !ConflictState -> !(Prim C(x y)) -> IsConflictedPrim
1227 data ConflictState = Okay | Conflicted | Duplicated deriving ( Eq, Ord, Show, Read)
1229 class Effect p where
1230 effect :: p C(x y) -> FL Prim C(x y)
1231 effect = reverseRL . effectRL
1232 effectRL :: p C(x y) -> RL Prim C(x y)
1233 effectRL = reverseFL . effect
1234 isHunk :: p C(x y) -> Maybe (Prim C(x y))
1237 instance Effect Prim where
1238 effect p | IsEq <- sloppyIdentity p = NilFL
1239 | otherwise = p :>: NilFL
1240 effectRL p | IsEq <- sloppyIdentity p = NilRL
1241 | otherwise = p :<: NilRL
1242 isHunk p = if is_hunk p then Just p else Nothing
1244 instance Conflict Prim
1246 instance Effect p => Effect (FL p) where
1247 effect p = concatFL $ mapFL_FL effect p
1248 effectRL p = concatRL $ mapRL_RL effectRL $ reverseFL p
1250 instance Effect p => Effect (RL p) where
1251 effect p = concatFL $ mapFL_FL effect $ reverseRL p
1252 effectRL p = concatRL $ mapRL_RL effectRL p
1254 commute_no_conflictsFL :: Conflict p => (p :> FL p) C(x y) -> Maybe ((FL p :> p) C(x y))
1255 commute_no_conflictsFL (p :> NilFL) = Just (NilFL :> p)
1256 commute_no_conflictsFL (q :> p :>: ps) = do p' :> q' <- commute_no_conflicts (q :> p)
1257 ps' :> q'' <- commute_no_conflictsFL (q' :> ps)
1258 return (p' :>: ps' :> q'')
1260 commute_no_conflictsRL :: Conflict p => (RL p :> p) C(x y) -> Maybe ((p :> RL p) C(x y))
1261 commute_no_conflictsRL (NilRL :> p) = Just (p :> NilRL)
1262 commute_no_conflictsRL (p :<: ps :> q) = do q' :> p' <- commute_no_conflicts (p :> q)
1263 q'' :> ps' <- commute_no_conflictsRL (ps :> q')
1264 return (q'' :> p' :<: ps')
1266 commute_no_conflictsRLFL :: Conflict p => (RL p :> FL p) C(x y) -> Maybe ((FL p :> RL p) C(x y))
1267 commute_no_conflictsRLFL (NilRL :> ys) = Just (ys :> NilRL)
1268 commute_no_conflictsRLFL (xs :> NilFL) = Just (NilFL :> xs)
1269 commute_no_conflictsRLFL (xs :> y :>: ys) = do y' :> xs' <- commute_no_conflictsRL (xs :> y)
1270 ys' :> xs'' <- commute_no_conflictsRLFL (xs' :> ys)
1271 return (y' :>: ys' :> xs'')