{-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Darcs.Patch.Prim.V1.Mangle () where import Darcs.Prelude import qualified Data.ByteString.Char8 as BC (pack, last) import qualified Data.ByteString as B (null, ByteString) import Data.Maybe ( isJust, listToMaybe ) import Data.List ( sort, intercalate, nub ) import Darcs.Patch.FileHunk ( FileHunk(..), IsHunk(..) ) import Darcs.Patch.Inspect ( PatchInspect(listTouchedFiles) ) import Darcs.Patch.Invert ( Invert(..) ) import Darcs.Patch.Prim.Class ( PrimConstruct(primFromHunk) , PrimMangleUnravelled(..) ) import Darcs.Patch.Prim.V1.Core ( Prim ) import Darcs.Patch.Witnesses.Ordered ( FL(..), (+>+), mapFL_FL_M ) import Darcs.Patch.Witnesses.Sealed ( Sealed(..), mapSeal, unseal ) import Darcs.Util.Path ( AnchoredPath ) -- | The state of a single file as far as we know it. 'Nothing' -- means we don't know the content of a particular line. newtype FileState wX = FileState { content :: [Maybe B.ByteString] } -- | An infinite list of undefined lines. unknownFileState :: FileState wX unknownFileState = FileState (repeat Nothing) -- | Note that @applyHunk p . applyHunk (invert p) /= id@: it converts -- undefined lines ('Nothing') to defined ones ('Just' the old content of @p@). applyHunk :: FileHunk wX wY -> FileState wX -> FileState wY applyHunk (FileHunk _ line old new) = FileState . go . content where go mls = case splitAt (line - 1) mls of (before, rest) -> concat [before, map Just new, drop (length old) rest] -- | Iterate 'applyHunk'. applyHunks :: FL FileHunk wX wY -> FileState wX -> FileState wY applyHunks NilFL = id applyHunks (p:>:ps) = applyHunks ps . applyHunk p instance PrimMangleUnravelled Prim where mangleUnravelled pss = do hunks <- onlyHunks pss filename <- listToMaybe (filenames pss) return $ mapSeal ((:>: NilFL) . primFromHunk) $ mangleHunks filename hunks where -- | The names of all touched files. filenames = nub . concatMap (unseal listTouchedFiles) -- | Convert every prim in the input to a 'FileHunk', or fail. onlyHunks :: forall prim wX. IsHunk prim => [Sealed (FL prim wX)] -> Maybe [Sealed (FL FileHunk wX)] onlyHunks = mapM toHunk where toHunk :: Sealed (FL prim wA) -> Maybe (Sealed (FL FileHunk wA)) toHunk (Sealed ps) = fmap Sealed $ mapFL_FL_M isHunk ps -- | Mangle a list of hunks, returning a single hunk. -- Note: the input list consists of 'FL's because when commuting conflicts -- to the head we may accumulate dependencies. In fact, the patches in all -- of the given (mutually conflicting) 'FL's should coalesce to a single hunk. mangleHunks :: AnchoredPath -> [Sealed (FL FileHunk wX)] -> Sealed (FileHunk wX) mangleHunks _ [] = error "mangleHunks called with empty list of alternatives" mangleHunks path ps = Sealed (FileHunk path l old new) where oldf = foldl oldFileState unknownFileState ps newfs = map (newFileState oldf) ps l = getHunkline (Sealed oldf : newfs) nchs = sort (map (makeChunk l) newfs) old = makeChunk l (Sealed oldf) new = [top] ++ old ++ [initial] ++ intercalate [middle] nchs ++ [bottom] top = BC.pack ("v v v v v v v" ++ eol_c) initial = BC.pack ("=============" ++ eol_c) middle = BC.pack ("*************" ++ eol_c) bottom = BC.pack ("^ ^ ^ ^ ^ ^ ^" ++ eol_c) -- simple heuristic to infer the line ending convention from patch contents eol_c = if any (\line -> not (B.null line) && BC.last line == '\r') old then "\r" else "" -- | Apply the patches and their inverse. This turns all lines touched -- by the 'FL' of patches into defined lines with their "old" values. oldFileState :: FileState wX -> Sealed (FL FileHunk wX) -> FileState wX oldFileState mls (Sealed ps) = applyHunks (ps +>+ invert ps) mls -- | This is @flip 'applyHunks'@ under 'Sealed'. newFileState :: FileState wX -> Sealed (FL FileHunk wX) -> Sealed FileState newFileState mls (Sealed ps) = Sealed (applyHunks ps mls) -- Index of the first line touched by any of the FileStates (1-based). getHunkline :: [Sealed FileState] -> Int getHunkline = go 1 . map (unseal content) where -- head and tail are safe here because all inner lists are infinite go n pps = if any (isJust . head) pps then n else go (n + 1) $ map tail pps -- | The chunk of defined lines starting at the given position (1-based). makeChunk :: Int -> Sealed FileState -> [B.ByteString] makeChunk n = takeWhileJust . drop (n - 1) . unseal content where -- stolen from utility-ht, thanks Henning! takeWhileJust = foldr (\x acc -> maybe [] (:acc) x) []