{-# LANGUAGE FlexibleInstances #-} import Prelude hiding ( read, filter, writeFile, readFile ) -- darcs stuff import ByteStringUtils( is_funky ) import Darcs.Repository ( Repository(..), withRepository, withRepoLock, ($-), tentativelyAddPatch, finalizeRepositoryChanges, read_repo, identifyRepositoryFor ) import Darcs.Repository.Motd( show_motd ) import Darcs.Repository.Internal ( read_pending, setTentativePending , announce_merge_conflicts , check_unrecorded_conflicts ) import Darcs.Repository.HashedRepo ( add_to_tentative_inventory ) import Darcs.Repository.InternalTypes ( extractCache ) import Darcs.Resolution ( standard_resolution ) import Darcs.External ( backupByCopying ) import Darcs.IO ( runTolerantly, runSilently ) import Darcs.ProgressPatches (progressFL) import Darcs.Patch.Depends ( get_common_and_uncommon ) import Darcs.Patch ( RepoPatch, Prim, hunk, canonize, binary, apply , sort_coalesceFL, summarize, namepatch, fromPrims , effect, list_touched_files, joinPatches, merge , anonymous, patchcontents ) import Darcs.Ordered ( FL(..), (+>+), (:>)(..), nullFL, concatRL, reverseRL, unsafeUnRL, (:\/:)(..), mapFL_FL, (:/\:)(..) ) import Darcs.PrintPatch ( printPatch ) import Darcs.RepoPath( ioAbsolute ) import Darcs.Repository.Prefs ( filetype_function, FileType(..) ) import Darcs.Patch.TouchesFiles ( choose_touching ) import Darcs.SelectChanges ( with_selected_changes_to_files', with_selected_changes ) import Darcs.Commands.Record( get_date, get_log ) import Darcs.CommandsAux ( check_paths ) import Darcs.Arguments( get_author, fixUrl ) import Darcs.Lock( world_readable_temp ) import Darcs.Utils( clarify_errors ) import Darcs.IO import Darcs.Hopefully ( n2pia, PatchInfoAnd, hopefully ) import Darcs.Sealed ( Sealed(Sealed), seal ) import Darcs.Patch.FileName ( fn2fp ) import Darcs.Flags ( Compression(..), DarcsFlag( FixFilePath ) ) import Printer ( putDocLn ) import qualified Data.ByteString.Lazy.Char8 as BL import qualified Data.ByteString.Char8 as BS import Control.Monad.State.Strict import System( getArgs ) import System.Exit( exitWith, ExitCode(..) ) import System.Directory( removeFile, getCurrentDirectory ) import Data.Maybe import Data.List( (\\) ) import Storage.Hashed import Storage.Hashed.Tree import Storage.Hashed.Index import Storage.Hashed.AnchoredPath import Storage.Hashed.Monad import Storage.Hashed.Diff import Storage.Hashed ---- FIXME this is from Storage.Hashed.Utils which is hidden ... darcsFormatSize s = BS.pack $ replicate (10 - length n) '0' ++ n where n = (show s) darcsFormatHash (Hash (Just s, h)) = BS.concat [ darcsFormatSize s , BS.singleton '-' , h ] darcsFormatHash h = error $ "Unsuitable hash for darcs-compatible hashing " ++ show h -- end FIXME floatFn = floatPath . fn2fp instance ReadableDirectory (StateT TreeState IO) where mDoesDirectoryExist d = gets (\x -> isJust $ findTree (tree x) (floatFn d)) mDoesFileExist f = gets (\x -> isJust $ findFile (tree x) (floatFn f)) mInCurrentDirectory d action = do -- TODO bracket? wd <- gets cwd modify (\x -> x { cwd = floatFn d }) x <- action modify (\x -> x { cwd = wd }) return x mGetDirectoryContents = error "get dir contents" mReadFilePS p = do x <- readFile (floatFn p) return $ BS.concat (BL.toChunks x) instance WriteableDirectory (StateT TreeState IO) where mWithCurrentDirectory = mInCurrentDirectory mSetFileExecutable _ _ = return () mWriteFilePS p ps = writeFile (floatFn p) (BL.fromChunks [ps]) mCreateDirectory p = createDirectory (floatFn p) mRename from to = rename (floatFn from) (floatFn to) mRemoveDirectory = unlink . floatFn mRemoveFile = unlink . floatFn treeDiff :: (FilePath -> FileType) -> Tree -> Tree -> IO (FL Prim) treeDiff ft t1 t2 = do (from, to) <- diffTrees t1 t2 diffs <- sequence $ zipFiles diff_files from to return $ foldr (+>+) NilFL diffs where diff_files :: AnchoredPath -> Blob -> Blob -> IO (FL Prim) diff_files p a' b' = do a <- read a' b <- read b' let path = anchorPath "." p case ft path of TextFile | no_bin a && no_bin b -> return $ text_diff path a b _ -> return $ if a /= b then binary path (strict a) (strict b) :>: NilFL else NilFL text_diff p a b = canonize (hunk p 1 lines_a lines_b) -- FIXME this is all screwed up... where lines_b | BL.null b && BL.last a == '\n' = [BS.empty] | BL.null b = [] | otherwise = lines b lines_a | BL.null a && BL.last b == '\n' = [BS.empty] | BL.null a = [] | otherwise = lines a no_bin = not . is_funky . strict . BL.take 4096 lines = map strict . BL.split '\n' strict = BS.concat . BL.toChunks readRecordedAndPending :: (RepoPatch p) => Repository p -> IO Tree readRecordedAndPending repo = do pristine <- readDarcsPristine "." Sealed pend <- read_pending repo (_, t) <- virtualTreeIO (apply [] pend) pristine return t unrecordedChanges :: (RepoPatch p) => Repository p -> (Tree -> Tree) -> IO (FL Prim) unrecordedChanges repo restrict = do pristine <- readDarcsPristine "." working <- (restrict `fmap` readIndex) >>= unfold Sealed pending_patches <- read_pending repo (_, pending') <- virtualTreeIO (apply [] pending_patches) pristine let pending = restrict pending' ft <- filetype_function diff <- treeDiff ft pending working return $ sort_coalesceFL (pending_patches +>+ diff) -- XXX both application actions below could avoid unfolding if TreeIO would be -- smart enough to unfold-as-needed... applyToTentativePristine _ patches = do pristine <- readDarcsPristine "." >>= unfold (_, tree) <- hashedTreeIO (apply [] patches) pristine "_darcs/pristine.hashed" BS.writeFile "_darcs/tentative_pristine" $ BS.concat [BS.pack "pristine:" , darcsFormatHash (fromJust $ treeHash tree)] applyToWorking :: (RepoPatch p) => Repository p -> Sealed (FL Prim) -> IO Tree applyToWorking _ (Sealed patches) = do pristine <- readDarcsPristine "." >>= unfold working <- readIndex snd `fmap` plainTreeIO (apply [] patches) working "." tentativelyMerge r cmd usi themi = do let us = mapFL_FL hopefully usi them = mapFL_FL hopefully themi (_ :/\: pc) = merge (progressFL "Merging them" them :\/: progressFL "Merging us" us) pend <- unrecordedChanges r id anonpend <- anonymous (fromPrims pend) let pend' :/\: pw = merge (pc :\/: anonpend :>: NilFL) pwprim = joinPatches $ mapFL_FL patchcontents pw Sealed standard_resolved_pw = standard_resolution pwprim mapM_ backupByCopying $ list_touched_files standard_resolved_pw have_conflicts <- announce_merge_conflicts cmd [] standard_resolved_pw have_unrecorded_conflicts <- check_unrecorded_conflicts [] pc let Sealed pw_resolution = if have_conflicts || have_unrecorded_conflicts then seal NilFL else seal standard_resolved_pw let doChanges :: FL (PatchInfoAnd p) -> IO () doChanges NilFL = applyps r themi doChanges _ = applyps r (mapFL_FL n2pia pc) doChanges usi setTentativePending r (effect pend' +>+ pw_resolution) return $ seal (effect pwprim +>+ pw_resolution) where mapAdd :: RepoPatch p => Repository p -> FL (PatchInfoAnd p) -> [IO ()] mapAdd _ NilFL = [] mapAdd r' (a:>:as) = (add_to_tentative_inventory (extractCache r') NoCompression a >> return ()) : mapAdd r' as applyps :: (RepoPatch p) => Repository p -> FL (PatchInfoAnd p) -> IO () applyps repo ps = do sequence_ $ mapAdd repo ps applyToTentativePristine repo ps filter_paths files = \p _ -> any (\x -> x `isPrefix` p || p `isPrefix` x) files restrict_paths files = if null files then id else filter $ filter_paths files cmd_wh summary repo files = do Sealed diff <- choose_touching (map (anchorPath "") files) `fmap` unrecordedChanges repo (restrict_paths files) let display | summary = putDocLn . summarize | otherwise = printPatch display diff die s = do putStrLn s exitWith $ ExitFailure 1 cmd_diff repo files = do pristine <- restrict_paths files `fmap` readRecordedAndPending repo working <- restrict_paths files `fmap` readIndex BL.unpack `fmap` unidiff pristine working >>= putStr cmd_opt repo files = do pristine <- readRecordedAndPending repo updateIndexFrom pristine >>= unfold return () cmd_rec _ files = withRepoLock [] $- \repo -> do date <- get_date [] my_author <- get_author [] let make_log = world_readable_temp "gorsvet-record" paths = map (anchorPath "") files rec_bits (chs:>_) = if nullFL chs then putStrLn "Nothing to record..." else do_rec_bits chs do_rec_bits chs = do (name, my_log, logf) <- get_log [] Nothing make_log chs patch <- namepatch date name my_author my_log $ fromPrims chs tentativelyAddPatch repo [] $ n2pia patch finalizeRepositoryChanges repo `clarify_errors` "Failed to record patch." when (isJust logf) $ removeFile (fromJust logf) diff <- unrecordedChanges repo (restrict_paths files) with_selected_changes_to_files' "rec" [] paths diff rec_bits cmd_pull :: (RepoPatch p) => Repository p -> [String] -> IO () cmd_pull local [remote'] = withRepoLock [] $- \repo -> do do here <- getCurrentDirectory here_abs <- ioAbsolute here remote <- fixUrl [FixFilePath here_abs here_abs] remote' -- WTF? when (here == remote) $ die "Can't pull from current repository..." them <- identifyRepositoryFor local remote >>= read_repo us <- read_repo local show_motd [] remote (common, ours :\/: theirs) <- return $ get_common_and_uncommon (us, them) let patches = reverseRL $ concatRL theirs pull_bits (p:>_) = if nullFL p then putStrLn "Nothing to pull..." else do_pull_bits p do_pull_bits patches = do check_paths [] patches pw <- tentativelyMerge local "pull" (reverseRL $ head $ unsafeUnRL ours) patches finalizeRepositoryChanges local applyToWorking local pw return () with_selected_changes "pull" [] patches pull_bits cmd_pull _ _ = die "Only know how to pull from a single repo." main = do withRepository [] $- \r -> do (command:args) <- getArgs case command of "diff" -> cmd_diff r (map floatPath args) "wh" -> cmd_wh ("-s" `elem` args) r $ (map floatPath $ args \\ ["-s"]) "rec" -> cmd_rec r (map floatPath args) "pull" -> cmd_pull r args "optimize" -> cmd_opt r (map floatPath args) x -> do die $ "Unknown command " ++ x ++ ", try diff, wh, rec or optimize."