% Copyright (C) 2003-2004 Jan Scheffczyk and David Roundy % % This program is free software; you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation; either version 2, or (at your option) % any later version. % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with this program; if not, write to the Free Software Foundation, % Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. \section{Population} NOTE: this documentation belongs in a ``libdarcs API'' chapter, which currently doesn't exist. \begin{code} module Population ( Population, patchChanges, adjustPopStates, applyToPop, applyPatchesPop, applyPatchSetPop, getPopFrom, popUnfold, popUnfoldDirty, initPop, cleanPop, setPopState, DirMark(..), getRepoPop, getRepoPopVersion, lookup_pop, modified_to_xml, ) where import FastPackedString ( PackedString, unpackPS, packString, splitPS, appendPS, nilPS ) import Monad ( liftM ) import List ( nub ) import Maybe ( catMaybes ) import Directory ( doesDirectoryExist, getDirectoryContents ) import DarcsUtils ( withCurrentDirectory ) import PatchInfo ( PatchInfo, patchinfo, to_xml ) import Patch ( Patch, applyToPop, patchChanges ) import Repository ( read_repo ) import PopulationData ( Population(..), PopTree(..), Info(..), DirMark(..), notModified, setPopState ) #include "impossible.h" \end{code} a dummy PatchInfo \begin{code} nullPI :: PatchInfo nullPI = patchinfo [] [] [] [] \end{code} population of an empty repository \begin{code} initPop :: Population initPop = Pop nullPI (PopDir i []) where i = Info {nameI = packString ".", modifiedByI = nullPI, modifiedHowI = DullDir, createdByI = Nothing, creationNameI = Just (packString ".")} \end{code} get the actual documents and folders (with full name) from a population tree (and their modification times) (take care for ``deleted'' files and dirs) \begin{code} popUnfold :: Population -> ([(PackedString,PatchInfo)], [(PackedString,PatchInfo)]) popUnfold (Pop _ s) = popUnfold' nilPS s where popUnfold' pre (PopDir f ss) | modifiedHowI f == RemovedDir = (ds,fs) | otherwise = (ds,f':fs) where f' = (newname, modifiedByI f) newname = if pre == nilPS then nameI f else pre `appendPS` (packString "/") `appendPS` nameI f (dss,fss) = (unzip . map (popUnfold' newname)) ss ds = concat dss fs = concat fss popUnfold' pre (PopFile d) | modifiedHowI d == RemovedFile = ([],[]) | pre == nilPS = ([(nameI d,modifiedByI d)],[]) | otherwise = ([(pre `appendPS` (packString "/") `appendPS` nameI d, modifiedByI d)],[]) \end{code} get the changed ``dirty'' documents and folders (with full name) from a population tree (and their modification times) (take care for ``deleted'' files and dirs) \begin{code} popUnfoldDirty :: Population -> ([(PackedString,PatchInfo)], [(PackedString,PatchInfo)]) popUnfoldDirty (Pop _ s) = popUnfold' nilPS s where popUnfold' pre (PopDir f ss) | notModified f || modifiedHowI f == RemovedDir = (ds,fs) | otherwise = (ds,f':fs) where f' = (newname, modifiedByI f) newname = if pre == nilPS then nameI f else pre `appendPS` (packString "/") `appendPS` nameI f (dss,fss) = (unzip . map (popUnfold' newname)) ss ds = concat dss fs = concat fss popUnfold' pre (PopFile d) | notModified d || modifiedHowI d == RemovedFile = ([],[]) | pre == nilPS = ([(nameI d,modifiedByI d)],[]) | otherwise = ([(pre `appendPS` (packString "/") `appendPS` nameI d, modifiedByI d)],[]) \end{code} read the population from a given directory ``dirname'' all folders and documents get the given time ``t'' \begin{code} getPopFrom :: FilePath -> PatchInfo -> IO Population getPopFrom the_directory pinfo = withCurrentDirectory the_directory $ do popT <- getPopFrom_helper "." return (Pop pinfo popT) where getPopFrom_helper :: FilePath -> IO PopTree getPopFrom_helper dirname = do isdir <- doesDirectoryExist dirname let n = packString dirname if isdir then do fnames <- getDirectoryContents dirname sl <- withCurrentDirectory dirname (sequence $ map getPopFrom_helper $ filter not_hidden fnames) let i = Info {nameI = n, modifiedByI = pinfo, modifiedHowI = DullDir, createdByI = Just pinfo, creationNameI = Just n} return $ PopDir i sl else do let i = Info {nameI = n, modifiedByI = pinfo, modifiedHowI = DullFile, createdByI = Just pinfo, creationNameI = Just n} return $ PopFile i not_hidden :: FilePath -> Bool not_hidden ('.':_) = False not_hidden ('_':_) = False not_hidden _ = True \end{code} apply a patchset to a population [[(PatchInfo, Maybe Patch)]] is actually a PatchSet but this provokes cycles in import hierarchy \begin{code} applyPatchSetPop :: [[(PatchInfo, Maybe Patch)]] -> Population -> Population applyPatchSetPop ps pop = applyPatchesPop (reverse $ catMaybes' $ concat ps) pop where catMaybes' [] = [] catMaybes' ((_,Nothing):xs) = catMaybes' xs catMaybes' ((a,Just x):xs) = (a,x) : catMaybes' xs \end{code} apply Patches to a population \begin{code} applyPatchesPop :: [(PatchInfo,Patch)] -> Population -> Population applyPatchesPop pips pop = foldl (\a_pop (pinfo,p) -> applyToPop pinfo p a_pop) pop pips \end{code} adjust the ``modifiedBy'' fields of a population this is necessary for backward restoring a population Patches must be in !reverse! order and already inversed! Usually they go from the populations PatchInfo back to the initial PatchInfo. Note that backward restoring invalidates (must!) the fields createdBy and creationName. This is necessary because while backward creation of a population we use inverted patches. So RmFile becomes an AddFile etc. which means that the file ``would be created after it has been changed''! \begin{code} adjustPopStates :: [(PatchInfo,Patch)] -> Population -> Population adjustPopStates pips (Pop _ tree) = Pop (fst (pips!!pips_index)) tree' where (tree',pips_index) = adjustTimesPTree tree -- snd component: greatest number of PI which changed the population (0 based) -- that is the youngest given patchinfo (relied on the order of the given list) adjustTimesPTree :: PopTree -> (PopTree,Int) adjustTimesPTree tr@(PopDir f trs) | modifiedHowI f == RemovedDir = (tr,0) -- for removed dirs there if no previous modifying patch! | otherwise = let (pinfo,dm,i) = lastChange 0 (nameI f) changes (trs',is) = unzip (map adjustTimesPTree trs) i' = max (maximum is) i in (PopDir (f {modifiedByI = pinfo, modifiedHowI = dm, createdByI = Nothing, creationNameI = Nothing}) trs' ,i') adjustTimesPTree tr@(PopFile f) | modifiedHowI f == RemovedFile = (tr,0) -- for removed files there if no previous modifying patch! | otherwise = let (pinfo,dm,i) = lastChange 0 (nameI f) changes in (PopFile (f {modifiedByI = pinfo, modifiedHowI = dm, createdByI = Nothing, creationNameI = Nothing}) ,i) lastChange :: Int -> PackedString -> [(PatchInfo,[(PackedString,DirMark)])] -> (PatchInfo,DirMark,Int) lastChange _ n [] = error ("lastChange internal error: No modifying patch for " ++ show n) lastChange i n ((pinfo,ssdm):sss) = case lookupBy (\ss -> n `elem` (splitPS '/' ss)) ssdm of Just dm -> (pinfo,dm,i) -- pinfo changes n somehow (indicated by the DirMark dm) _ -> lastChange (i+1) n sss changes = map (\ (pinfo,p) -> (pinfo,nub $ map (\ (s,d) -> (packString s,d)) $ patchChanges p)) pips lookupBy :: (a -> Bool) -> [(a,b)] -> Maybe b lookupBy f ((a,b):a_and_bs) | f a = Just b | otherwise = lookupBy f a_and_bs lookupBy _ [] = Nothing \end{code} clean up a population remove any change markers, delete ``deleted'' files and dirs \begin{code} cleanPop :: Population -> Population cleanPop (Pop t tr) = Pop t (fromJust (cleanPopTr tr)) where cleanPopTr (PopDir i trs) | modifiedHowI i == RemovedDir = Nothing | otherwise = Just $ PopDir (modInfo DullDir i) (catMaybes (map cleanPopTr trs)) cleanPopTr (PopFile i) | modifiedHowI i == RemovedFile = Nothing | otherwise = Just $ PopFile (modInfo DullFile i) modInfo s i = i {modifiedByI = nullPI, modifiedHowI = s} \end{code} get the current(!) population from a repo \begin{code} getRepoPop :: FilePath -> IO Population getRepoPop repobasedir = do pinfo <- liftM (fst . head . concat) (read_repo repobasedir) -- pinfo is the latest patchinfo getPopFrom (repobasedir ++ "/_darcs/current") pinfo \end{code} \begin{code} getRepoPopVersion :: FilePath -> PatchInfo -> IO Population getRepoPopVersion repobasedir pinfo = do pips <- concat `liftM` read_repo repobasedir return $ applyPatchSetPop [dropWhile ((/=pinfo).fst) pips] initPop \end{code} Routines for pulling data conveniently out of a Population \begin{code} lookup_pop :: FilePath -> Population -> Maybe Population lookup_pop f p@(Pop _ (PopFile i)) | unpackPS (nameI i) == f = Just p | otherwise = Nothing lookup_pop d p@(Pop pinfo (PopDir i c)) | unpackPS (nameI i) == "." = case catMaybes $ map (lookup_pop (dropDS d).(Pop pinfo)) c of [apop] -> Just apop _ -> Nothing | unpackPS (nameI i) == takeWhile (/='/') d = case dropWhile (=='/') $ dropWhile (/='/') d of "" -> Just p d' -> case catMaybes $ map (lookup_pop d'.(Pop pinfo)) c of [apop] -> Just apop _ -> Nothing | otherwise = Nothing where dropDS ('.':'/':f) = dropDS f dropDS f = f \end{code} \begin{code} modified_to_xml :: Info -> String modified_to_xml i | modifiedHowI i == DullDir = "" | modifiedHowI i == DullFile = "" modified_to_xml i = "\n" ++ "" ++ show (modifiedHowI i) ++ "\n"++ to_xml (modifiedByI i) ++ "\n" \end{code}