packages feed

darcsden-1.1: src/DarcsDen/Handler/Repository/Changes.hs

{-# LANGUAGE GADTs, RankNTypes, FlexibleContexts, TypeFamilies, CPP #-}
module DarcsDen.Handler.Repository.Changes where

import Darcs.Patch.PatchInfoAnd (PatchInfoAnd, info)
import Darcs.Patch.Info (PatchInfo(..), piDate, makeFilename)
import Darcs.Patch.Patchy (Commute(..))
import Darcs.Patch.Permutations (commuteWhatWeCanFL)
import Darcs.Patch.Prim.V1.Core ( Prim(..), DirPatchType(..), FilePatchType(..))
-- import Darcs.Patch.Patchy ( Patchy )
-- import Darcs.Patch.Prim.Class ( FromPrim(..), PrimPatch)
import Darcs.Patch.Prim.Class (PrimPatchBase(..))
#ifdef DARCS28
import Darcs.Patch.FileName (fn2fp)
import Darcs.Witnesses.Ordered
import qualified Darcs.Witnesses.Ordered as WO
#else
import Darcs.Util.Path (fn2fp)
import Darcs.Patch.Rebase.NameHack ( NameHack(..) )
import Darcs.Patch.Witnesses.Ordered
import qualified Darcs.Patch.Witnesses.Ordered as WO
import Darcs.Repository.Flags
#endif
import Data.List (isPrefixOf, nub)
import Data.Time (UTCTime, readTime)
import System.Locale (defaultTimeLocale)
import System.Time (calendarTimeToString)
import qualified Darcs.Patch as P
import qualified Darcs.Repository as R
import qualified Data.ByteString as BS

import DarcsDen.Handler.Repository.Util
import DarcsDen.State.User
import DarcsDen.Util

--instance PrimPatch Prim
--instance Patchy Prim
--instance PrimPatchBase Prim where
--  type PrimOf Prim = Prim
--
--instance FromPrim Prim where
--  fromPrim = id


data Summary
    = Removed FilePath
    | Added FilePath
    | Replaced FilePath String String
    | Modified FilePath
    | Preference String String String
    deriving (Eq, Show)

data PatchLog =
    PatchLog
        { pID :: String
        , pDate :: UTCTime
        , pName :: String
        , pAuthor :: String
        , pIsUser :: Bool
        , pLog :: String
        , pDepends :: [String]
        }
    deriving (Eq, Show)


data PatchChange
    = Moved
        { cmFrom :: FilePath
        , cmTo :: FilePath
        }
    | DirChange
        { cdName :: FilePath
        , cdType :: DirChange
        }
    | FileChange
        { cfName :: FilePath
        , cfType :: FileChange
        }
    | PrefChange
        { cpName :: String
        , cpFrom :: String
        , cpTo :: String
        }
    deriving (Eq, Show)

data DirChange
    = DirRemoved
    | DirAdded
    deriving (Eq, Show)

data FileChange
    = FileRemoved
    | FileAdded
    | FileHunk
        { fchLine :: Int
        , fchRemove :: BS.ByteString
        , fchAdd :: BS.ByteString
        }
    | FileBinary
    | FileReplace
        { fchFind :: String
        , fchReplace :: String
        }
    deriving (Eq, Show)

data PatchChanges =
    PatchChanges
        { pPatch :: PatchLog
        , pChanges :: [PatchChange]
        }
    deriving (Eq, Show)


toLog :: (PatchInfo, [PatchInfo]) -> PatchLog
toLog (i, ds) =
    PatchLog
        (take 20 $ makeFilename i)
        (readTime defaultTimeLocale "%c" (calendarTimeToString (piDate i)))
        (fromBS $ _piName i)
        (fromBS $ _piAuthor i)
        False
        (doMarkdown . unlines . filter (not . ("Ignore-this" `isPrefixOf`)) . map fromBS $ _piLog i)
        (map (take 20 . makeFilename) ds)

findUsers :: [PatchLog] -> IO [PatchLog]
findUsers = findUsers' []
  where
    findUsers' :: [(String, Maybe String)] -> [PatchLog] -> IO [PatchLog]
    findUsers' _ [] = return []
    findUsers' checked (p:ps) =
        case lookup (pAuthor p) checked of
            Just (Just n) -> do
                rest <- findUsers' checked ps
                return (p { pAuthor = n, pIsUser = True } : rest)
            Just Nothing -> do
                rest <- findUsers' checked ps
                return (p { pAuthor = safeAuthorFrom (pAuthor p) } : rest)
            Nothing -> do
                mu <- getUserByEmail (emailFrom (pAuthor p))
                case mu of
                    Just u -> do
                        rest <- findUsers' ((pAuthor p, Just (uName u)) : checked) ps
                        return (p { pAuthor = uName u, pIsUser = True } : rest)
                    Nothing -> do
                        rest <- findUsers' ((pAuthor p, Nothing) : checked) ps
                        return (p { pAuthor = safeAuthorFrom (pAuthor p) } : rest)

toChanges :: ((PatchInfo, [PatchInfo]), [PatchChange])  -> PatchChanges
toChanges (p, changes) =
    PatchChanges (toLog p) (simplify [] changes)
  where
    simplify a [] = reverse a
    simplify a (c@(FileChange n t):cs)
        | t == FileBinary =
            simplify (c:filter (notFile n) a) (filter (notFile n) cs)
    simplify a (c@(FileChange _ (FileReplace _ _)):cs) =
        simplify (c:a) cs
    simplify a (FileChange n (FileHunk l f t):cs) =
        simplify (FileChange n (FileHunk l (highlight False n f) (highlight False n t)):a) cs
    simplify a (c@(FileChange _ _):cs) = simplify (c:a) cs
    simplify a (c@(PrefChange _ _ _):cs) = simplify (c:a) cs
    simplify a (_:cs) = simplify a cs

    notFile n (FileChange { cfName = n' })
        | n == n' = False
    notFile _ _ = True

primToChange :: Prim x y -> PatchChange
primToChange (Move f t) = Moved (drop 2 $ fn2fp f) (drop 2 $ fn2fp t)
primToChange (DP f t) = DirChange (drop 2 $ fn2fp f) (fromDP t)
primToChange (FP f t) = FileChange (drop 2 $ fn2fp f) (fromFP t)
primToChange (ChangePref n f t) = PrefChange n f t
-- primToChange a = error ("primToChange not supported for " ++ show a)

fromDP :: DirPatchType x y -> DirChange
fromDP RmDir = DirRemoved
fromDP AddDir = DirAdded

fromFP :: FilePatchType x y -> FileChange
fromFP RmFile = FileRemoved
fromFP AddFile = FileAdded
fromFP (Hunk l rs as) = FileHunk l (unlinesBS rs) (unlinesBS as)
  where
    unlinesBS = BS.concat . map (`BS.append` toBS "\n") 
fromFP (Binary _ _) = FileBinary
fromFP (TokReplace _ f r) = FileReplace f r

getChanges :: String -> Int -> IO ([PatchLog], Int)
#ifdef DARCS28
getChanges dir page = R.withRepositoryDirectory [] dir $ R.RepoJob $ \dr -> do
#else
getChanges dir page = R.withRepositoryDirectory YesUseCache dir $ R.RepoJob $ \dr -> do
#endif
    pset <- R.readRepo dr

    let ps = fromPS (\np -> (P.patch2patchinfo np, P.getdeps np)) pset
        patches = map toLog (paginate 30 page ps)

    prettyLog <- findUsers patches

    return
        ( prettyLog
        , ceiling ((fromIntegral (length ps) :: Double) / 30)
        )

getPatch :: String -> String -> IO (Maybe PatchChanges)
#ifdef DARCS28
getPatch dir patch = R.withRepositoryDirectory [] dir $ R.PrimV1Job $ \dr -> do
#else
getPatch dir patch = R.withRepositoryDirectory YesUseCache dir $ R.PrimV1Job $ \dr -> do
#endif
    pset <- R.readRepo dr

    let ps = fromPS makeList pset
        ps' = filter (\(n, _) -> take 20 n == patch) ps
    case ps' of
        [] -> return Nothing
        ((_, p):_) -> do
            let cs = toChanges p
            [l] <- findUsers [pPatch cs]
            return $ Just cs{pPatch = l}

makeList :: (P.Effect p, PrimOf p ~ Prim)
         => P.Named p x y
         -> (String, ((PatchInfo,[PatchInfo]), [PatchChange]))
makeList np =
 (P.patchname np,
    ((P.patch2patchinfo np, P.getdeps np), domap np))

domap :: (P.Effect p, PrimOf p ~ Prim)
      => P.Named p x y
      -> [PatchChange]
domap np = WO.mapFL primToChange $ (P.effect np)

--domap2 :: (P.Effect p)
--       => P.Named p x y
--       -> FL (forall a b . Prim a b) x y
--domap2 np = WO.mapFL_FL fromPrim (P.effect np)

fromPS :: P.RepoPatch p => (forall x0 y0 . P.Named p x0 y0 -> b) -> R.PatchSet p x1 y1 -> [b]
fromPS f = WO.mapRL f . WO.reverseFL . R.patchSetToPatches

summarize :: [PatchChange] -> [Summary]
summarize = nub . summarize'
  where
    summarize' [] = []
    summarize' (FileChange n FileRemoved:cs) = (Removed n) : summarize cs
    summarize' (FileChange n FileAdded:cs) = (Added n) : summarize cs
    summarize' (FileChange n (FileReplace f t):cs) = (Replaced n f t) : summarize cs
    summarize' (FileChange n _:cs) = (Modified n) : summarize cs
    summarize' (PrefChange n f t:cs) = (Preference n f t) : summarize cs
    summarize' (_:cs) = summarize cs

isModification :: PatchChange -> Bool
isModification (FileChange _ (FileHunk _ _ _)) = True
isModification _ = False

#ifdef DARCS28
findAllDeps :: Commute p
#else
findAllDeps :: (Commute p, NameHack p)
#endif
            => RL (PatchInfoAnd p) x y -> [(PatchInfo, [PatchInfo])]
findAllDeps NilRL = []
findAllDeps (p :<: ps) = (info p, findDeps ps NilFL p) : findAllDeps ps

#ifdef DARCS28
findDeps :: Commute p
#else
findDeps :: (Commute p, NameHack p)
#endif
         => RL (PatchInfoAnd p) x y
         -> FL (PatchInfoAnd p) y z
         -> PatchInfoAnd p z z1
         -> [PatchInfo]
findDeps NilRL _ _ = []
findDeps (p :<: ps) deps me =
    case commuteWhatWeCanFL (p :> deps) of
        deps' :> p' :> NilFL ->
            case commute (p' :> me) of
                Just (me' :> _) ->
                    -- not a dependency
                    findDeps ps deps' me'

                Nothing ->
                    -- a direct dependency
                    info p' : findDeps ps (p :>: deps) me
        _ ->
            -- an indirect dependency
            findDeps ps (p :>: deps) me