{-# LANGUAGE CPP, ExistentialQuantification, ViewPatterns, RecursiveDo, QuasiQuotes #-} module Curly.Session.Commands.Repository where import Curly.Core import Curly.Core.Library import Curly.Core.Parser import Curly.Core.Security import Curly.Core.VCS import Curly.Core.VCS.Diff import Curly.Core.Documentation import Curly.Session.Commands.Common import Curly.UI import Curly.UI.Options import Definitive import IO.Filesystem import Language.Format import System.FilePath (splitFileName) import Data.IORef repositoryCmd :: Interactive Command repositoryDoc = [q_string| {title Manage Repository} {p A command to manage repositories and their contents.} {title Usage} {ul {li.p {em repository info} {p List all active repositories}} {li.p {em repository add <repository>} {p Add a repository to the list of active repositories}} {li.p {em repository browse (<library-id>|<search-pattern>)} {p Open a sub-session in the context of an external library}} {li.p {em repository list [<template> [<key-name> [<branch>]]]} {p List the branches published by <key-name>} {ln If a branch name is specified, lists that branch's libraries instead.}} {li.p {em repository commit <branch> <modifier>...} {p Push a new commit on the given branch, by applying various modifiers in order} {p The <modifier>s can be either of the following: {ul {li {em -add <path>...} Adds the libraries under <path> to the branch} {li {em -(keep|drop) (<library-id>|<search-pattern>|(maximum|minimum) <template> by <template>)} {ln Filters the branch according to a pattern.}}}}} {li.p {em repository branch <branch> ((fork|alias) <key-name> <source-branch>|rename <new-name>|delete)} {p Create a new branch that points to the same commit a another.} {p The 'alias' option creates an alias branch rather than a fork. Alias branches will always be resolved to the latest commit on their source branch.}} {li.p {em repository get (source|library) <filename> (#<library-id>|<search-pattern>)} {p Retrieve a library or its source and saves it to a file}} {li.p {em repository checkout <source-prefix> (#<library-id>|<search-pattern>)} {p Reconstruct a working source tree for the given library}}} |] repositoryCmd = withDoc repositoryDoc $ False <$ do cmd <- expected "keyword, either 'commit', 'list' or 'get-source'" (nbhspace >> dirArg) u <- lookup curlyPublisher <$> getKeyStore conn <- liftIO (readIORef libraryVCS) let withKeys k = case u of Just (_,pub,Just priv,_,_) -> k pub priv _ -> serveStrLn (format "Error: the publisher %s doesn't have a private key" curlyPublisher) >> zero modifyBranches :: (Branches -> OpParser IO Branches) -> OpParser IO () modifyBranches k = withKeys $ \pub priv -> do StampedBranches stamp bs <- getBranches conn pub x <- k bs bs' <- signValue priv (StampedBranches (1+stamp) x) vcbStore conn (BranchesKey pub) bs' getSource file lid = do x <- vcbLoad conn (AdditionalKey lid "source") case x of Just s -> liftIO $ do createFileDir file writeBytes file (snd (unsafeExtractSigned s)) Nothing -> serveStrLn $ format "Warning: cannot find source for the library %s" (show lid) getLibrary file lid = do x <- vcbLoad conn (LibraryKey lid) case x of Just s -> when (isLibData lid s) $ liftIO $ do createFileDir file writeBytes file s modifyPermissions file (set (each.executePerm) True) Nothing -> serveStrLn $ format "Warning: the library %s doesn't seem to exist" (show lid) checkout pref lid = do serveStrLn $ format "Checking out %s.cy from library %s" pref (show lid) getSource (pref+".cy") lid getLibrary (pref+".cyl") lid ctx <- liftIO $ fromMaybe zero . by (metadata.at "context") <$> readFormat (pref+".cyl") let checkoutMod suf (Pure l) = let l' = read l in if l' == by flID builtinsLib then pure [(lid,suf,l',Nothing)] else ((lid,suf,l',Just pref):) <$> checkout (pref+foldMap ("/"+) suf) l' checkoutMod suf (Join m) = do map fold $ for (m^.ascList) $ \(d,m') -> do checkoutMod (suf+[d]) m' checkoutMod [] ctx case cmd of "info" -> liftIOWarn $ do VCSB_Native repos _ _ <- readIORef libraryVCS for_ repos $ \r -> serveStrLn $ format " * %s" r "commit" -> withMountain $ do guardWarn Sev_Error "Cannot commit without admin access" (?access >= Admin) let branchFilter = do filterP <- expected "'keep' or 'drop'" (fill id (several "-keep") <+? fill not (several "-drop")) let libPred = (single '#' >> (dirArg >*> readable)) <&> \l -> warp ascList $ \ls -> [x | x@(l',_) <- ls, filterP (l==l')] tplAtom = docAtom <*= \x -> guard (has t'Join x) singlePred = do tpl <- tplAtom <+? map snd packageSearch return $ warp ascList $ \ls -> [x | x@(_,m) <- ls, filterP (nonempty (showDummyTemplate m tpl))] groupPred = do op <- fill "<=" (several "minimum") <+? fill ">=" (several "maximum") cmptpl <- nbhspace >> docAtom nbhspace >> several "by" gtpl <- nbhspace >> tplAtom let minTpl m1 m2 | nonempty (do v <- showDummyTemplate (snd m1) cmptpl showDummyTemplate (snd m2) (Join (DocTag op [] [Pure v,cmptpl]))) = m1 | otherwise = m2 return $ \ls -> let groups = c'map $ composing (\x@(_,m) -> mat (showDummyTemplate m gtpl) %~ (x:)) (ls^.ascList) zero in fromAList $ mlookup Nothing groups + fold [select (filterP . (fst (foldl1' minTpl l)==) . fst) l | (Just _,l) <- groups^.ascList] expected "filter predicate" (nbhspace >> (groupPred <+? libPred <+? singlePred)) branchAdd = do several "-add" path <- nbhspace >> absPath "" let libs = ?mountain ^?? atMs path.traverse for_ libs $ \lib -> do serveStrLn $ format "Committing library %s" (show (lib^.flID)) vcbStore conn (LibraryKey (lib^.flID)) (lib^.flBytes) for_ (lib^.flSource) $ \s -> do serveStrLn $ format "Committing source for library %s" (show (lib^.flID)) withKeys $ \_ priv -> do s' <- signValue priv ("source",stringBytes s) vcbStore conn (AdditionalKey (lib^.flID) "source") s' return $ composing (uncurry insert) [(fl^.flID,fl^.flLibrary.metadata) | fl <- libs] branch <- expected "branch name" (nbhspace >> dirArg) pred <- compose <$> many1' (nbhspace >> muteOnSuccess (branchFilter <+? branchAdd)) lookingAt (hspace >> (eol+eoi)) serveStrLn $ format "Writing new commit on the '%s' branch" branch modifyBranches $ \bs -> do mh <- ioParser (getBranch conn (lookup branch bs)) index <- ioParser (maybe (return zero) (getCommit conn) mh) let index' = pred index dff = diff index index' c = Compressed (dff,mh) commid = commitHash c vcbStore conn (CommitKey commid) c return (insert branch (Right commid) bs) "add" -> do guardWarn Sev_Error "you must have admin access to add repositories" (?access>=Admin) r <- nbsp >> many' (noneOf "\n") >*> readable liftIOWarn $ modifyIORef libraryVCS (+ r) "list" -> do template <- option' (docTag' "call" [Pure "list-default"]) ((\case Pure p -> docTag' p []; x -> x) <$> (nbhspace >> docAtom)) mkeyid <- expected "key name" (option' Nothing (nbhspace >> Just<$>dirArg)) branch <- option' Nothing (Just <$> (nbhspace >> dirArg)) lookingAt (hspace >> (eol+eoi)) case mkeyid of Nothing -> do liftIOWarn $ withStyle $ withPatterns $ do ls <- availableLibs for_ [(l,d) | (l,Just d) <- map (second (\d -> showTemplate ?terminal ?style ?patterns d template)) ls] $ \(l,d) -> do serveStrLn $ format "%s %s" (show l) d Just keyid -> do key <- lookup keyid <$> getKeyStore case (map (by l'2) key,branch) of (Nothing,_) -> serveStrLn $ format "Error: Unknown key %s" keyid (Just pub,Nothing) -> withMountain $ do StampedBranches _ m <- map (maybe (StampedBranches zero zero) unsafeExtractSigned) $ vcbLoad conn (BranchesKey pub) serveStrLn $ intercalate "\n" [format "%s: %s" n (show (Zesty c)) | (n,c) <- m^.ascList] (Just pub,Just b) -> withMountain $ withStyle $ withPatterns $ do bs <- maybe (return zero) (getCommit conn) =<< getBranch conn (Just (Left (pub,b))) forl_ (ascList.each) bs $ \(lid,m) -> do for_ (showTemplate ?terminal ?style ?patterns m template) $ \s -> serveStrLn $ format "%s %s" (show lid) s "get" -> do guardWarn Sev_Error "You must have almighty access to retrieve arbitrary files" (?access >= Almighty) getLib <- expected "'source' or 'library'" (nbhspace >> (fill True (several "library") <+? fill False (several "source"))) file <- expected "file name" (nbhspace >> dirArg) lid <- expected "library ID" (nbhspace >> libID) (if getLib then getLibrary else getSource) file lid "checkout" -> do guardWarn Sev_Error "Checkouts can only be performed with almighty access" (?access >= Almighty) (root,name) <- splitFileName <$> expected "file prefix" (nbhspace >> dirArg) let pref = root+name lid <- expected "library ID" (nbhspace >> libID) ls <- checkout pref lid liftIO $ do writeString (root+".curly") $ unlines [ "#!/usr/bin/env curly", intercalate "\n" [format "mount deps.%s%s = %s" (show l) (foldMap ("."+) suf) $ c'string $ case x of Just pref -> format "source[deps.%s] %s.cy" (show l') (drop (length root) pref+foldMap ("/"+) suf) Nothing -> format "library %s" (show l') | (l,suf,l',x) <- ls], format "mount root = source[deps.%s] %s.cy" (show lid) name, "+default - interactive" ] modifyPermissions (root+".curly") (set (each.executePerm) True) "branch" -> do guardWarn Sev_Error "Cannot modify a branch without admin access" (?access >= Admin) branch <- expected "branch name" (nbhspace >> dirArg) let branchFork = do isLink <- fill False (several "fork") <+? fill True (several "alias") user <- expected "key id" (nbhspace >> dirArg) srcBranch <- expected "branch name" (nbhspace >> dirArg) map (lookup user) getKeyStore >>= \x -> case x of Nothing -> do serveStrLn $ format "Error: unknown user %s" user zero Just (_,pub,_,_,_) -> modifyBranches $ \bs -> do if isLink then return (insert branch (Left (pub,srcBranch)) bs) else do StampedBranches _ bs' <- getBranches conn pub return (set (at branch) (bs'^.at srcBranch) bs) branchRen = do several "rename" newName <- nbhspace *> dirArg <* ack modifyBranches $ return . (join $ \bs -> set (at newName) (bs^.at branch) . delete branch) branchDel = do several "delete" <* ack modifyBranches $ return . delete branch nbhspace >> (branchFork <+? branchRen <+? branchDel) "browse" -> do guardWarn Sev_Error "you must have almighty acces to browse new libraries" (?access>=Almighty) nbsp l <- libID ?subSession [(Nothing,Mount [] (Library l))] _ -> warn Sev_Error "Expected 'commit', 'list', 'get-library' or 'get-source'" >> zero where ack = lookingAt (hspace >> (eol+eoi)) libID = (single '#' >> (dirArg >*> readable)) <+? searchID createFileDir f = createDirectoryIfMissing True (dropFileName f) ioParser m = liftIO (try (return Nothing) (Just<$>m)) >>= maybe zero return searchID = ((docAtom <*= guard . has t'Join) <+? (snd <$> packageSearch)) >>= \tpl -> do ls <- liftIO availableLibs case fold [convert (showDummyTemplate m tpl >> return l) | (l,m) <- ls] of [l] -> return l [] -> guardWarn Sev_Error (format "Error: couldn't find a library matching the search pattern '%s'" (showRawDoc tpl)) False >> zero _ -> guardWarn Sev_Error (format "Error: multiple libraries match the search pattern '%s'" (showRawDoc tpl)) False >> zero